Skip to content

Repository files navigation

Tesla

Build StatusHex.pmHex.pmHex.pmcodecovInline docs

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.


Documentation for 0.x branch


HTTP Client example

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")endend

Then use it like this:

{:ok,response}=GitHub.user_repos("teamon")response.status# => 200response.body# => [%{…}, …]response.headers# => [{"content-type", "application/json"}, ...]

See below for documentation.

Installation

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"}]end

Configure default adapter in config/config.exs (optional).

# config/config.exsconfig: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.

Documentation

Middleware

Tesla is built around the concept of composable middlewares. This is very similar to how Plug Router works.

Basic

Formats

Auth

Error handling

Runtime middleware

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)endend

and then:

client=GitHub.client(user_token)client|>GitHub.user_repos("teamon")client|>GitHub.get("/me")

Adapters

Tesla supports multiple HTTP adapter that do the actual HTTP request processing.

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.end

Adapter options

In 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"]end

Using Tesla.client/2:

defnew(...)domiddleware=[...]adapter={Tesla.Adapter.Hackney,[recv_timeout: 30_000]}Tesla.client(middleware,adapter)end

Passing directly to get/post/etc.

MyClient.get("/",opts: [adapter: [recv_timeout: 30_000]])Tesla.get(client,"/",opts: [adapter: [recv_timeout: 30_000]])

Streaming

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)endend

Each piece of stream will be encoded as JSON and sent as a new line (conforming to JSON stream format)

Multipart

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)

Testing

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.Mock

Then, 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"endend

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.

defmoduleMyMiddlewaredo@behaviourTesla.Middlewaredefcall(env,next,options)doenv|>do_something_with_request()|>Tesla.run(next)|>do_something_with_response()endend

The arguments are:

  • env - Tesla.Env instance
  • next - middleware continuation stack; to be executed with Tesla.run/2 with env and 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 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)endend

and response logger middleware like this:

defmoduleTesla.Middleware.ResponseLoggerdo@behaviourTesla.Middlewaredefcall(env,next,_)doenv|>Tesla.run(next)|>IO.inspect()endend

See 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.Middlewareend

Direct usage

You 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"}])

Cheatsheet

Making requests 101

# 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)

Configuring HTTP functions visibility

# 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]

Disable docs for HTTP functions

useTesla,docs: false

Decode only JSON response (do not encode request)

plugTesla.Middleware.DecodeJson

Use other JSON library

# use JSXplugTesla.Middleware.JSON,engine: JSX,engine_opts: [strict: [:comments]]# use custom functionsplugTesla.Middleware.JSON,decode: &JSX.decode/1,encode: &JSX.encode/1

Custom middleware

defmoduleTesla.Middleware.MyCustomMiddlewaredodefcall(env,next,options)doenv|>do_something_with_request()|>Tesla.run(next)|>do_something_with_response()endend

Contributing

  1. Fork it (https://github.com/teamon/tesla/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details

Copyright (c) 2015-2020 Tymon Tobolski


Sponsors

This project is sponsored by ubots - Useful bots for Slack

About

The flexible HTTP client library for Elixir, with support for middleware and multiple adapters.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages