Tesla
Tesla is an HTTP client losely based on Faraday. It embraces the concept of middleware when processing the request/response cycle.
NOTE: 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!
Direct 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", query: [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", headers: %{"Content-Type" => "application/json"})
Installation
Add tesla
as dependency in mix.exs
defp deps do
[{:tesla, "~> 0.1.0"},
{:poison, ">= 1.0.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
end
Creating API clients
Use Tesla
module to create API wrappers.
For example
defmodule GitHub do
use Tesla
plug Tesla.Middleware.BaseUrl, "https://api.github.com"
plug Tesla.Middleware.Headers, %{'Authorization' => 'xyz'}
plug Tesla.Middleware.JSON
adapter Tesla.Adapter.Hackney
def user_repos(login) do
get("/user/" <> login <> "/repos")
end
end
Then 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
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.
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.
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
"/" -> %{env | status: 200, body: "home"}
"/about" -> %{env | status: 200, body: "about us"}
end
end
end
Middleware
Basic
Tesla.Middleware.BaseUrl
- set base url for all requestTesla.Middleware.Headers
- set request headersTesla.Middleware.Query
- set query parametersTesla.Middleware.DecodeRels
- decodeLink
header intoopts[:rels]
field in response
JSON
NOTE: requires poison (or other engine) as dependency
Tesla.Middleware.JSON
- encode/decode request/response bodies as JSON If you are using different json library it can be easily configured: ```ex plug Tesla.Middleware.JSON, engine: JSX, engine_opts: [labels: :atom] # or plug Tesla.Middleware.JSON, decode: &JSX.decode/1, encode: &JSX.encode/1 ``` See [
json.ex](https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/json.ex) for implementation details. ### Logging - [
Tesla.Middleware.Logger](Tesla.Middleware.Logger.html) - log each request in single line including method, path, status and execution time (colored) - [
Tesla.Middleware.DebugLogger](Tesla.Middleware.DebugLogger.html) - log full request and response (incl. headers and body) <h2 id="dynamic-middleware" class="section-heading"> <a href="#dynamic-middleware" class="hover-link"><i class="icon-link"></i></a> Dynamic middleware </h2> All methods can take a middleware function as the first parameter. This allow 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.buildclientto create a dynamic middleware function: ```ex defmodule GitHub do # same as above def client(token) do Tesla.build_client [ {Tesla.Middleware.Headers, %{"Authorization" => "token: " <> token }} ] end end ``` and then: ```ex client = GitHub.client(user_token) client |> GitHub.user_repos("teamon") client |> GitHub.get("/me") ``` <h2 id="writing-your-own-middleware" class="section-heading"> <a href="#writing-your-own-middleware" class="hover-link"><i class="icon-link"></i></a> Writing your own middleware </h2> A Tesla middleware is a module with
call/3function, that at some point calls
Tesla.run(env, next)to process the rest of stack ```ex defmodule MyMiddleware do 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](Tesla.Env.html) instance -
next- middleware continuation stack; to be executed with
Tesla.run(env, next)-
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`](Tesla.html#run/2) function at the correct time. For example, z request logger middleware could be implemented like this: ```ex defmodule Tesla.Middleware.RequestLogger do def call(env, next, ) do IO.inspect env # print request env Tesla.run(env, next) end endand response logger middleware like this:
ex defmodule Tesla.Middleware.ResponseLogger do def call(env, next, _) do res = Tesla.run(env, next) IO.inspect res # print response env res end endSee [`core.ex`](https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/core.ex) and [`json.ex`](https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/json.ex) for more examples. <h2 id="streaming-body" class="section-heading"> <a href="#streaming-body" class="hover-link"><i class="icon-link"></i></a> Streaming body </h2> If adapter supports it, you can pass a [Stream](http://elixir-lang.org/docs/stable/elixir/Stream.html) as body, e.g.:
ex 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 end ``` Each piece of stream will be encoded as json and sent as a new line (conforming to json stream format)