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
masterbranch of Tesla, not the latest released version on Hex. See the documentation for the documentation of the version you're using.
Define module with use Tesla and choose from a variety of middleware.
defmoduleGitHubdouseTeslaplugTesla.Middleware.BaseUrl,"https://api.github.com"plugTesla.Middleware.Headers,[{"authorization","token xyz"}]plugTesla.Middleware.JSONdefuser_repos(login)doget("/users/"<>login<>"/repos")endendThen use it like this:
{:ok,response}=GitHub.user_repos("teamon")response.status# => 200response.body# => [%{…}, …]response.headers# => [{"content-type", "application/json"}, ...]See below for documentation.
Add tesla as dependency in mix.exs:
defpdepsdo[{:tesla,"~> 1.4.0"},# optional, but recommended adapter{:hackney,"~> 1.16.0"},# optional, required by JSON middleware{:jason,">= 1.0.0"}]endConfigure default adapter in config/config.exs (optional).
# config/config.exsconfig:tesla,adapter: Tesla.Adapter.HackneyThe 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.
- Middleware
- Runtime middleware
- Adapters
- Streaming
- Multipart
- Testing
- Writing middleware
- Direct usage
- Cheatsheet
- Cookbook
- Changelog
Tesla is built around the concept of composable middlewares. This is very similar to how Plug Router works.
Tesla.Middleware.BaseUrl- set base urlTesla.Middleware.Headers- set request headersTesla.Middleware.Query- set query parametersTesla.Middleware.Opts- set request optionsTesla.Middleware.FollowRedirects- follow 3xx redirectsTesla.Middleware.MethodOverride- set X-Http-Method-OverrideTesla.Middleware.Logger- log requests (method, url, status, time)Tesla.Middleware.KeepRequest- keep request body & headersTesla.Middleware.PathParams- use templated URLs
Tesla.Middleware.FormUrlencoded- urlencode POST body, useful for POSTing a map/keyword listTesla.Middleware.JSON- JSON request/response bodyTesla.Middleware.Compression- gzip & deflateTesla.Middleware.DecodeRels- decodeLinkheader intoopts[:rels]field in response
Tesla.Middleware.BasicAuth- HTTP Basic AuthTesla.Middleware.DigestAuth- Digest access authentication
Tesla.Middleware.Timeout- timeout request after X milliseconds despite of server responseTesla.Middleware.Retry- retry few times in case of connection refusedTesla.Middleware.Fuse- fuse circuit breaker integration
All HTTP functions (get, post, etc.) can take a dynamic client as the first argument.
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.client to create a client with dynamic middleware:
defmoduleGitHubdo# notice there is no `use Tesla`defuser_repos(client,login)do# pass `client` argument to `Tesla.get` functionTesla.get(client,"/user/"<>login<>"/repos")enddefissues(client)doTesla.get(client,"/issues")end# build dynamic client based on runtime argumentsdefclient(token)domiddleware=[{Tesla.Middleware.BaseUrl,"https://api.github.com"},Tesla.Middleware.JSON,{Tesla.Middleware.Headers,[{"authorization","token: "<>token}]}]Tesla.client(middleware)endendand then:
client=GitHub.client(user_token)client|>GitHub.user_repos("teamon")client|>GitHub.get("/me")Tesla supports multiple HTTP adapter that do the actual HTTP request processing.
Tesla.Adapter.Httpc- the default, built-in erlang httpc adapterTesla.Adapter.Hackney- hackney, "simple HTTP client in Erlang"Tesla.Adapter.Ibrowse- ibrowse, "Erlang HTTP client"Tesla.Adapter.Gun- gun, "HTTP/1.1, HTTP/2 and Websocket client for Erlang/OTP"Tesla.Adapter.Mint- mint, "Functional HTTP client for Elixir with support for HTTP/1 and HTTP/2"Tesla.Adapter.Finch- finch, "An HTTP client with a focus on performance, built on top of Mint and NimblePool."
When using adapter other than httpc remember to add it to the dependencies list in mix.exs
defpdepsdo[{:tesla,"~> 1.4.0"},{:jason,">= 1.0.0"},# optional, required by JSON middleware{:hackney,"~> 1.10"}]# or :gun etc.endIn case there is a need to pass specific adapter options you can do it in one of three ways:
Using adapter macro:
defmoduleGitHubdouseTeslaadapterTesla.Adapter.Hackney,recv_timeout: 30_000,ssl_options: [certfile: "certs/client.crt"]endUsing Tesla.client/2:
defnew(...)domiddleware=[...]adapter={Tesla.Adapter.Hackney,[recv_timeout: 30_000]}Tesla.client(middleware,adapter)endPassing directly to get/post/etc.
MyClient.get("/",opts: [adapter: [recv_timeout: 30_000]])Tesla.get(client,"/",opts: [adapter: [recv_timeout: 30_000]])If adapter supports it, you can pass a Stream as body, e.g.:
defmoduleElasticSearchdouseTeslaplugTesla.Middleware.BaseUrl,"http://localhost:9200"plugTesla.Middleware.JSONdefindex(records_stream)dostream=records_stream|>Stream.map(fnrecord->%{index: [some,data]}end)post("/_bulk",stream)endendEach piece of stream will be encoded as JSON and sent as a new line (conforming to JSON stream format)
You can pass a Tesla.Multipart struct as the body.
aliasTesla.Multipartmp=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("http://httpbin.org/post",mp)You can set the adapter to Tesla.Mock in tests.
# config/test.exs# Use mock adapter for all clientsconfig:tesla,adapter: Tesla.Mock# or only for oneconfig:tesla,MyApi,adapter: Tesla.MockThen, mock requests before using your client:
defmoduleMyAppTestdouseExUnit.CaseimportTesla.Mocksetupdomock(fn%{method: :get,url: "http://example.com/hello"}->%Tesla.Env{status: 200,body: "hello"}%{method: :post,url: "http://example.com/world"}->json(%{"my"=>"data"})end):okendtest"list things"doassert{:ok,%Tesla.Env{}=env}=MyApp.get("/hello")assertenv.status==200assertenv.body=="hello"endendA 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.
defmoduleMyMiddlewaredo@behaviourTesla.Middlewaredefcall(env,next,options)doenv|>do_something_with_request()|>Tesla.run(next)|>do_something_with_response()endendThe arguments are:
env-Tesla.Envinstancenext- middleware continuation stack; to be executed withTesla.run/2withenvandnextoptions- 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:
defmoduleTesla.Middleware.RequestLoggerdo@behaviourTesla.Middlewaredefcall(env,next,_)doenv|>IO.inspect()|>Tesla.run(next)endendand response logger middleware like this:
defmoduleTesla.Middleware.ResponseLoggerdo@behaviourTesla.Middlewaredefcall(env,next,_)doenv|>Tesla.run(next)|>IO.inspect()endendSee built-in middlewares for more examples.
Middleware should have documentation following this template:
defmoduleTesla.Middleware.SomeMiddlewaredo@moduledoc""" Short description what it does Longer description, including e.g. additional dependencies. ### Example usage ``` defmodule MyClient do use Tesla plug Tesla.Middleware.SomeMiddleware, most: :common, options: "here" end ``` ### Options - `:list` - all possible options - `:with` - their default values """@behaviourTesla.MiddlewareendYou can also use Tesla directly, without creating a client module. This however won’t include any middleware.
# Example get request{:ok,response}=Tesla.get("http://httpbin.org/ip")response.status# => 200response.body# => "{\n "origin": "87.205.72.203"\n}\n"response.headers# => [{"content-type", "application/json" ...}]{:ok,response}=Tesla.get("http://httpbin.org/get",query: [a: 1,b: "foo"])# Example post request{:ok,response}=Tesla.post("http://httpbin.org/post","data",headers: [{"content-type","application/json"}])# GET /pathget("/path")# GET /path?a=hi&b[]=1&b[]=2&b[]=3get("/path",query: [a: "hi",b: [1,2,3]])# GET with dynamic clientget(client,"/path")get(client,"/path",query: [page: 3])# arguments are the same for GET, HEAD, OPTIONS & TRACEhead("/path")options("/path")trace("/path")# POST, PUT, PATCHpost("/path","some-body-i-used-to-know")put("/path","some-body-i-used-to-know",query: [a: "0"])patch("/path",multipart)# generate only get and post functionuseTesla,only: ~w(get post)a# generate only delete functionuseTesla,only: [:delete]# generate all functions except delete and optionsuseTesla,except: [:delete,:options]useTesla,docs: falseplugTesla.Middleware.DecodeJson# use JSXplugTesla.Middleware.JSON,engine: JSX,engine_opts: [strict: [:comments]]# use custom functionsplugTesla.Middleware.JSON,decode: &JSX.decode/1,encode: &JSX.encode/1defmoduleTesla.Middleware.MyCustomMiddlewaredodefcall(env,next,options)doenv|>do_something_with_request()|>Tesla.run(next)|>do_something_with_response()endend- Fork it (https://github.com/teamon/tesla/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request
This project is licensed under the MIT License - see the LICENSE file for details
Copyright (c) 2015-2020 Tymon Tobolski
This project is sponsored by ubots - Useful bots for Slack