Tesla
Tesla is an HTTP client losely based on Faraday. It embraces the concept of middleware when processing the request/response cycle.
WARNING: Tesla is currently under heavy development, so please don’t use it in your production application just yet.
Nevertheless all comments/issues/suggestions are more than welcome - please submit them using GitHub issues, thanks!
Basic usage
# Example get request
response = Tesla.get("http://httpbin.org/ip")
response.status # => 200
response.body # => '{\n "origin": "87.205.72.203"\n}\n'
response.headers # => %{'Content-Type' => 'application/json' ...}
response = Tesla.get({"http://httpbin.org/get", [a: 1, b: "foo"]})
response.url # => "http://httpbin.org/get?a=1&b=foo"
# Example post request
response = Tesla.post("http://httpbin.org/post", "data")Installation
Add tesla as dependency in mix.exs
defp deps do
[{:tesla, "~> 0.1.0"},
{:exjsx, "~> 3.1.0"}] # for JSON middleware
end
When using ibrowse or hackney adapters remember to alter applications list in mix.exs
def application do
[applications: [:ibrowse, ...], ...] # or :hackney
endCreating API clients
Use Tesla.Builder module to create API wrappers.
For example
defmodule GitHub do
use Tesla.Builder
plug Tesla.Middleware.BaseUrl, "https://api.github.com"
plug Tesla.Middleware.Headers, %{'Authorization' => 'xyz'}
plug Tesla.Middleware.EncodeJson
plug Tesla.Middleware.DecodeJson
adapter Tesla.Adapter.Ibrowse
def user_repos(login) do
get("/user/" <> login <> "/repos")
end
endThen use it like this:
GitHub.get("/user/teamon/repos")
GitHub.user_repos("teamon")Adapters
Tesla has support for different adapters that do the actual HTTP request processing.
httpc
The default adapter, available in all erlang installations
ibrowse
Tesla has built-in support for ibrowse Erlang HTTP client.
To use it simply include adapter :ibrowse line in your API client definition.
NOTE: Remember to include ibrowse in applications list.
hackney
This adapter supports real streaming body.
To use it simply include adapter :hackney line in your API client definition.
NOTE: Remember to include hackney in applications list.
Test / Mock
When testing it might be useful to use simple function as adapter:
defmodule MyApi do
use Tesla
adapter fn (env) ->
case env.url do
"/" -> {200, %{}, "home"}
"/about" -> {200, %{}, "about us"}
end
end
endMiddleware
Basic
Tesla.Middleware.BaseUrl- set base url for all requestTesla.Middleware.Headers- set request headersTesla.Middleware.QueryParams- set query parametersTesla.Middleware.AdapterOptions- set default adapter options (like ssl etc.)Tesla.Middleware.DecodeRels- decodeLinkheader intorelsfield in response
JSON
NOTE: requires poison (or other engine) as dependency
Tesla.Middleware.DecodeJson- decode response body as JSONTesla.Middleware.EncodeJson- endode request body as JSON
If you are using different json library it can be easily configured:
plug Tesla.Middleware.DecodeJson, engine: JSX, opts: [labels: :atom]
# or
plug Tesla.Middleware.DecodeJson, decode: &JSX.decode/1
See json.ex for implementation details.
Logging
Tesla.Middleware.Logger- log each request in single line including method, path, status and execution time (colored)Tesla.Middleware.DebugLogger- log full request and response (incl. headers and body)
Dynamic middleware
All methods can take a middleware function as the first parameter. This allow to use convinient syntax for modyfiyng 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.build_client to create a dynamic middleware function:
defmodule GitHub do
# same as above
def client(token) do
Tesla.build_client [
{Tesla.Middleware.Headers, %{'Authorization' => "token: " <> token }}
]
end
endand then:
client = GitHub.client(user_token)
client |> GitHub.user_repos("teamon")
client |> GitHub.get("/me")Writing your own middleware
A Tesla middleware is a module with call/3 function:
defmodule MyMiddleware do
def call(env, run, options) do
# ...
end
endThe arguments are:
env-Tesla.Envinstancerun- continuation function for the rest of middleware/adapter stackoptions- arguments passed during middleware configuration (plug MyMiddleware, options)
There is no distinction between request and response middleware, it’s all about executing run function at the correct time.
For example, z request logger middleware could be implemented like this:
defmodule Tesla.Middleware.RequestLogger do
def call(env, run, _) do
IO.inspect env # print request env
run.(env)
end
endand response logger middleware like this:
defmodule Tesla.Middleware.ResponseLogger do
def call(env, run, _) do
res = run.(env)
IO.inspect res # print response env
res
end
end
See core.ex and json.ex for more examples.
Asynchronous requests
If adapter supports it, you can make asynchronous requests by passing respond_to: pid option:
Tesla.get("http://example.org", respond_to: self)
receive do
{:tesla_response, res} -> res.status # => 200
endStreaming body
If adapter supports it, you can pass a Stream as body, e.g.:
defmodule ES do
use Tesla.Builder
plug Tesla.Middleware.BaseUrl, "http://localhost:9200"
plug Tesla.Middleware.DecodeJson
plug Tesla.Middleware.EncodeJson
def index(records) do
stream = records |> Stream.map(fn record -> %{index: [some, data]})
post("/_bulk", stream)
end
endEach piece of stream will be encoded as json and sent as a new line (conforming to json stream format)