Tesla
Tesla is an HTTP client loosely based on Faraday. It embraces the concept of middleware when processing the request/response cycle.
Note that this README refers to the
master
branch of Tesla, not the latest released version on Hex. See the documentation for the documentation of the version you’re using.
For the list of changes, checkout the latest release notes.
HTTP Client example
Define module with use Tesla
and choose from a variety of middleware.
defmodule GitHub do
use Tesla
plug Tesla.Middleware.BaseUrl, "https://api.github.com"
plug Tesla.Middleware.Headers, [{"authorization", "token xyz"}]
plug Tesla.Middleware.JSON
def user_repos(login) do
get("/users/" <> login <> "/repos")
end
end
Then use it like this:
{:ok, response} = GitHub.user_repos("teamon")
response.status
# => 200
response.body
# => [%{…}, …]
response.headers
# => [{"content-type", "application/json"}, ...]
See below for documentation.
config/config.exs
config :tesla, adapter: Tesla.Adapter.Hackney
> The default adapter is erlang's built-in `httpc`, but it is not recommended
> to use it in production environment as it does not validate SSL certificates
> [among other issues](https://github.com/teamon/tesla/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Ahttpc+).
## Middleware
Tesla is built around the concept of composable middlewares.
This is very similar to how [Plug Router](https://github.com/elixir-plug/plug#plugrouter) works.
### Basic
- [`Tesla.Middleware.BaseUrl`](https://hexdocs.pm/tesla/Tesla.Middleware.BaseUrl.html) - set base url
- [`Tesla.Middleware.Headers`](https://hexdocs.pm/tesla/Tesla.Middleware.Headers.html) - set request headers
- [`Tesla.Middleware.Query`](https://hexdocs.pm/tesla/Tesla.Middleware.Query.html) - set query parameters
- [`Tesla.Middleware.Opts`](https://hexdocs.pm/tesla/Tesla.Middleware.Opts.html) - set request options
- [`Tesla.Middleware.FollowRedirects`](https://hexdocs.pm/tesla/Tesla.Middleware.FollowRedirects.html) - follow HTTP 3xx redirects
- [`Tesla.Middleware.MethodOverride`](https://hexdocs.pm/tesla/Tesla.Middleware.MethodOverride.html) - set `X-Http-Method-Override` header
- [`Tesla.Middleware.Logger`](https://hexdocs.pm/tesla/Tesla.Middleware.Logger.html) - log requests (method, url, status, and time)
- [`Tesla.Middleware.KeepRequest`](https://hexdocs.pm/tesla/Tesla.Middleware.KeepRequest.html) - keep request `body` and `headers`
- [`Tesla.Middleware.PathParams`](https://hexdocs.pm/tesla/Tesla.Middleware.PathParams.html) - use templated URLs
### Formats
- [`Tesla.Middleware.FormUrlencoded`](https://hexdocs.pm/tesla/Tesla.Middleware.FormUrlencoded.html) - urlencode POST body, useful for POSTing a map/keyword list
- [`Tesla.Middleware.JSON`](https://hexdocs.pm/tesla/Tesla.Middleware.JSON.html) - JSON request/response body
- [`Tesla.Middleware.Compression`](https://hexdocs.pm/tesla/Tesla.Middleware.Compression.html) - `gzip` and `deflate`
- [`Tesla.Middleware.DecodeRels`](https://hexdocs.pm/tesla/Tesla.Middleware.DecodeRels.html) - decode `Link` header into `opts[:rels]` field in response
### Auth
- [`Tesla.Middleware.BasicAuth`](https://hexdocs.pm/tesla/Tesla.Middleware.BasicAuth.html) - HTTP Basic Auth
- [`Tesla.Middleware.BearerAuth`](https://hexdocs.pm/tesla/Tesla.Middleware.BearerAuth.html) - HTTP Bearer Auth
- [`Tesla.Middleware.DigestAuth`](https://hexdocs.pm/tesla/Tesla.Middleware.DigestAuth.html) - Digest access authentication
### Error handling
- [`Tesla.Middleware.Timeout`](https://hexdocs.pm/tesla/Tesla.Middleware.Timeout.html) - timeout request after X milliseconds despite of server response
- [`Tesla.Middleware.Retry`](https://hexdocs.pm/tesla/Tesla.Middleware.Retry.html) - retry few times in case of connection refused
- [`Tesla.Middleware.Fuse`](https://hexdocs.pm/tesla/Tesla.Middleware.Fuse.html) - fuse circuit breaker integration
## Runtime middleware
All HTTP functions, such as `Tesla.get/3` and `Tesla.post/4`, can take a dynamic client as the first argument.
This allows to use convenient syntax for modifying the behaviour in runtime.
Consider the following case: GitHub API can be accessed using OAuth token authorization.
We can't use `plug Tesla.Middleware.Headers, [{"authorization", "token here"}]`
since this would be compiled only once and there is no way to insert dynamic user token.
Instead, we can use `Tesla.client` to create a client with dynamic middleware:
```elixir
defmodule GitHub do
# notice there is no `use Tesla`
def user_repos(client, login) do
# pass `client` argument to `Tesla.get` function
Tesla.get(client, "/user/" <> login <> "/repos")
end
def issues(client) do
Tesla.get(client, "/issues")
end
# build dynamic client based on runtime arguments
def client(token) do
middleware = [
{Tesla.Middleware.BaseUrl, "https://api.github.com"},
Tesla.Middleware.JSON,
{Tesla.Middleware.Headers, [{"authorization", "token: " <> token }]}
]
Tesla.client(middleware)
end
end
and then:
client = GitHub.client(user_token)
client |> GitHub.user_repos("teamon")
client |> GitHub.get("/me")
Adapter options
In case there is a need to pass specific adapter options you can do it in one of four ways:
Supplying them as a keyword list in a tuple via config:
config :tesla, adapter: {Tesla.Adapter.Hackney, [recv_timeout: 30_000]}
Using adapter
macro:
defmodule GitHub do
use Tesla
adapter Tesla.Adapter.Hackney, recv_timeout: 30_000, ssl_options: [certfile: "certs/client.crt"]
end
Using Tesla.client/2
:
def new(...) do
middleware = [...]
adapter = {Tesla.Adapter.Hackney, [recv_timeout: 30_000]}
Tesla.client(middleware, adapter)
end
Passing directly to request functions such as MyClient.get/3
or Tesla.get/3
.
MyClient.get("/", opts: [adapter: [recv_timeout: 30_000]])
Tesla.get(client, "/", opts: [adapter: [recv_timeout: 30_000]])
Streaming
Multipart
You can pass a Tesla.Multipart
struct as the body:
alias Tesla.Multipart
mp =
Multipart.new()
|> Multipart.add_content_type_param("charset=utf-8")
|> Multipart.add_field("field1", "foo")
|> Multipart.add_field("field2", "bar",
headers: [{"content-id", "1"}, {"content-type", "text/plain"}]
)
|> Multipart.add_file("test/tesla/multipart_test_file.sh")
|> Multipart.add_file("test/tesla/multipart_test_file.sh", name: "foobar")
|> Multipart.add_file_content("sample file content", "sample.txt")
{:ok, response} = MyApiClient.post("https://httpbin.org/post", mp)
Testing
You can set the adapter to Tesla.Mock
in tests:
# config/test.exs
# Use mock adapter for all clients
config :tesla, adapter: Tesla.Mock
# or only for one
config :tesla, MyApi, adapter: Tesla.Mock
Then, mock requests before using your client:
defmodule MyAppTest do
use ExUnit.Case
import Tesla.Mock
setup do
mock(fn
%{method: :get, url: "https://example.com/hello"} ->
%Tesla.Env{status: 200, body: "hello"}
%{method: :post, url: "https://example.com/world"} ->
json(%{"my" => "data"})
end)
:ok
end
test "list things" do
assert {:ok, %Tesla.Env{} = env} = MyApi.get("https://example.com/hello")
assert env.status == 200
assert env.body == "hello"
end
end
Writing middleware
A Tesla middleware is a module with c:Tesla.Middleware.call/3
function, that at some point calls Tesla.run/2
with env
and next
to process the rest of stack.
defmodule MyMiddleware do
@behaviour Tesla.Middleware
def call(env, next, options) do
env
|> do_something_with_request()
|> Tesla.run(next)
|> do_something_with_response()
end
end
The arguments are:
env
-Tesla.Env
instancenext
- middleware continuation stack; to be executed withTesla.run/2
withenv
andnext
options
- arguments passed during middleware configuration (plug MyMiddleware, options
)
There is no distinction between request and response middleware, it’s all about executing Tesla.run/2
function at the correct time.
For example, a request logger middleware could be implemented like this:
defmodule Tesla.Middleware.RequestLogger do
@behaviour Tesla.Middleware
def call(env, next, _) do
env
|> IO.inspect()
|> Tesla.run(next)
end
end
and response logger middleware like this:
defmodule Tesla.Middleware.ResponseLogger do
@behaviour Tesla.Middleware
def call(env, next, _) do
env
|> Tesla.run(next)
|> IO.inspect()
end
end
See built-in middlewares for more examples.
Middleware should have documentation following this template:
defmodule Tesla.Middleware.SomeMiddleware do
@moduledoc """
Short description what it does
Longer description, including e.g. additional dependencies.
### Examples
```elixir
defmodule MyClient do
use Tesla
plug Tesla.Middleware.SomeMiddleware, most: :common, options: "here"
end
```
### Options
- `:list` - all possible options
- `:with` - their default values
"""
@behaviour Tesla.Middleware
end
Example get request
{:ok, response} = Tesla.get(”https://httpbin.org/ip”)
response.status
=> 200
response.body
=> “{\n “origin”: “87.205.72.203”\n}\n”
response.headers
=> [{“content-type”, “application/json” …}]
{:ok, response} = Tesla.get(”https://httpbin.org/get”, query: [a: 1, b: “foo”])
Example post request
{:ok, response} = Tesla.post(”https://httpbin.org/post”, “data”, headers: [{“content-type”, “application/json”}])
## Cheatsheet
### Making requests 101
```elixir
# GET /path
get("/path")
# GET /path?a=hi&b[]=1&b[]=2&b[]=3
get("/path", query: [a: "hi", b: [1, 2, 3]])
# GET with dynamic client
get(client, "/path")
get(client, "/path", query: [page: 3])
# arguments are the same for GET, HEAD, OPTIONS & TRACE
head("/path")
options("/path")
trace("/path")
# POST, PUT, PATCH
post("/path", "some-body-i-used-to-know")
put("/path", "some-body-i-used-to-know", query: [a: "0"])
patch("/path", multipart)
Configuring HTTP functions visibility
# generate only get and post function
use Tesla, only: ~w(get post)a
# generate only delete function
use Tesla, only: [:delete]
# generate all functions except delete and options
use Tesla, except: [:delete, :options]
Disable docs for HTTP functions
use Tesla, docs: false
Encode only JSON request (do not decode response)
plug Tesla.Middleware.EncodeJson
Decode only JSON response (do not encode request)
plug Tesla.Middleware.DecodeJson
Use other JSON library
# use JSX
plug Tesla.Middleware.JSON, engine: JSX, engine_opts: [strict: [:comments]]
# use custom functions
plug Tesla.Middleware.JSON, decode: &JSX.decode/1, encode: &JSX.encode/1
Custom middleware
defmodule Tesla.Middleware.MyCustomMiddleware do
def call(env, next, options) do
env
|> do_something_with_request()
|> Tesla.run(next)
|> do_something_with_response()
end
end
0.x
to 1.0
Migration Guide
Sponsors
This project is sponsored by ubots - Useful bots for Slack