Packages
req
0.6.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Req is a batteries-included HTTP client for Elixir.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/req.ex
defmodule Req do
@moduledoc ~S"""
The high-level API.
Req is composed of:
* `Req` - the high-level API (you're here!)
* `Req.Request` - the low-level API and the request struct
* `Req.Steps` - the collection of built-in steps
* `Req.Test` - the testing conveniences
The high-level API is what most users of Req will use most of the time.
## Examples
Making a GET request with `Req.get!/1`:
iex> Req.get!("https://api.github.com/repos/wojtekmach/req").body["description"]
"Req is a batteries-included HTTP client for Elixir."
Same, but by explicitly building request struct first:
iex> req = Req.new(base_url: "https://api.github.com")
iex> Req.get!(req, url: "/repos/wojtekmach/req").body["description"]
"Req is a batteries-included HTTP client for Elixir."
Return the request that was sent using `Req.run!/2`:
iex> {req, resp} = Req.run!("https://httpbin.org/basic-auth/foo/bar", auth: {:basic, "foo:bar"})
iex> req.headers["authorization"]
["Basic Zm9vOmJhcg=="]
iex> resp.status
200
Making a POST request with `Req.post!/2`:
iex> Req.post!("https://httpbin.org/post", form: [comments: "hello!"]).body["form"]
%{"comments" => "hello!"}
Set connection timeout:
iex> resp = Req.get!("https://httpbin.org", connect_options: [timeout: 100])
iex> resp.status
200
See [`run_finch`](`Req.Steps.run_finch/1`) for more connection related options and usage examples.
Stream request body:
iex> stream = Stream.duplicate("foo", 3)
iex> Req.post!("https://httpbin.org/post", body: stream).body["data"]
"foofoofoo"
Stream response body using a callback:
iex> resp =
...> Req.get!("http://httpbin.org/stream/2", into: fn {:data, data}, {req, resp} ->
...> IO.puts(data)
...> {:cont, {req, resp}}
...> end)
# output: {"url": "http://httpbin.org/stream/2", ...}
# output: {"url": "http://httpbin.org/stream/2", ...}
iex> resp.status
200
iex> resp.body
""
Stream response body into a `Collectable`:
iex> resp = Req.get!("http://httpbin.org/stream/2", into: IO.stream())
# output: {"url": "http://httpbin.org/stream/2", ...}
# output: {"url": "http://httpbin.org/stream/2", ...}
iex> resp.status
200
iex> resp.body
%IO.Stream{}
Stream response body to the current process and parse incoming messages using `Req.parse_message/2`.
iex> resp = Req.get!("http://httpbin.org/stream/2", into: :self)
iex> Req.parse_message(resp, receive do message -> message end)
{:ok, [data: "{\"url\": \"http://httpbin.org/stream/2\", ..., \"id\": 0}\n"]}
iex> Req.parse_message(resp, receive do message -> message end)
{:ok, [data: "{\"url\": \"http://httpbin.org/stream/2\", ..., \"id\": 1}\n"]}
iex> Req.parse_message(resp, receive do message -> message end)
{:ok, [:done]}
""
Same as above, using enumerable API:
iex> resp = Req.get!("http://httpbin.org/stream/2", into: :self)
iex> resp.body
#Req.Response.Async<...>
iex> Enum.each(resp.body, &IO.puts/1)
# {"url": "http://httpbin.org/stream/2", ..., "id": 0}
# {"url": "http://httpbin.org/stream/2", ..., "id": 1}
:ok
See `:into` option in `Req.new/1` documentation for more information on response body streaming.
## Headers
The HTTP specification requires that header names should be case-insensitive.
Req allows two ways to access the headers; using functions and by accessing
the data directly:
iex> Req.Response.get_header(response, "content-type")
["text/html"]
iex> response.headers["content-type"]
["text/html"]
While we can ensure case-insensitive handling in the former case, we can't
in the latter. For this reason, Req made the following design choices:
* header names are stored as downcased
* functions like `Req.Request.get_header/2`, `Req.Request.put_header/3`,
`Req.Response.get_header/2`, `Req.Response.put_header/3`, etc
automatically downcase the given header name.
> #### Note {: .tip}
>
> Most Elixir/Erlang HTTP clients represent headers as lists of tuples like:
>
> ```elixir
> [{"content-type", "text/plain"}]`
> ```
>
> For interopability with those, use
> `Req.get_headers_list/1`.
"""
# Response streaming to caller:
#
# iex> {req, resp} = Req.async_request!("http://httpbin.org/stream/2")
# iex> resp.status
# 200
# iex> resp.body
# ""
# iex> Req.parse_message(req, receive do message -> message end)
# [{:data, "{\"url\": \"http://httpbin.org/stream/2\"" <> ...}]
# iex> Req.parse_message(req, receive do message -> message end)
# [{:data, "{\"url\": \"http://httpbin.org/stream/2\"" <> ...}]
# iex> Req.parse_message(req, receive do message -> message end)
# [:done]
# ""
@type url() :: URI.t() | String.t()
@req Req.Request.new()
|> Req.Steps.attach()
@default_finch_options Req.Finch.pool_options(%{})
@doc """
Returns a new request struct with built-in steps.
See `request/2`, `run/2`, as well as `get/2`, `post/2`, and similar functions for
making requests.
Also see `Req.Request` module documentation for more information on the underlying request
struct.
## Options
Basic request options:
* `:method` - the request method, defaults to `:get`.
* `:url` - the request URL.
* `:headers` - the request headers as a `{key, value}` enumerable (e.g. map, keyword list).
The header names should be downcased.
The headers are automatically encoded using these rules:
* atom header names are turned into strings, replacing `_` with `-`. For example,
`:user_agent` becomes `"user-agent"`.
* string header names are downcased.
* `%DateTime{}` header values are encoded as "HTTP date".
If you set `:headers` options both in `Req.new/1` and `request/2`, the header lists are merged.
See also "Headers" section in the module documentation.
* `:body` - the request body.
Can be one of:
* `iodata` - send request body eagerly
* `enumerable` - stream `enumerable` as request body
Additional URL options:
* `:base_url` - if set, the request URL is prepended with this base URL (via
[`put_base_url`](`Req.Steps.put_base_url/1`) step.)
* `:params` - if set, appends parameters to the request query string (via
[`put_params`](`Req.Steps.put_params/1`) step.)
* `:path_params` - if set, uses a templated request path (via
[`put_path_params`](`Req.Steps.put_path_params/1`) step.)
* `:path_params_style` (*available since v0.5.1*) - how path params are expressed (via
[`put_path_params`](`Req.Steps.put_path_params/1`) step). Can be one of:
* `:colon` - (default) for Plug-style parameters, such as `:code` in
`https://httpbin.org/status/:code`.
* `:curly` - for [OpenAPI](https://swagger.io/specification/)-style parameters, such as
`{code}` in `https://httpbin.org/status/{code}`.
Authentication options:
* `:auth` - sets request authentication (via [`auth`](`Req.Steps.auth/1`) step.)
Can be one of:
* `{:basic, userinfo}` - uses Basic HTTP authentication.
* `{:digest, userinfo}` - uses Digest HTTP authentication.
* `{:bearer, token}` - uses Bearer HTTP authentication.
* `:netrc` - load credentials from the default .netrc file.
* `{:netrc, path}` - load credentials from `path`.
* `string` - sets to this value.
* `&fun/0` - a function that returns one of the above (such as a `{:bearer, token}`).
* `{mod, fun, args}` - an MFArgs tuple that returns one of the above (such as a `{:bearer, token}`).
Request body encoding options ([`encode_body`](`Req.Steps.encode_body/1`)):
* `:form` - if set, encodes the request body as `application/x-www-form-urlencoded`
* `:form_multipart` - if set, encodes the request body as `multipart/form-data`.
* `:json` - if set, encodes the request body as JSON
Other request body options:
* `:compress_body` - if set to `true`, compresses the request body using gzip (via [`compress_body`](`Req.Steps.compress_body/1`) step.)
Defaults to `false`.
AWS Signature Version 4 options ([`put_aws_sigv4`](`Req.Steps.put_aws_sigv4/1`) step):
* `:aws_sigv4` - if set, the AWS options to sign request:
* `:access_key_id` - the AWS access key id.
* `:secret_access_key` - the AWS secret access key.
* `:service` - the AWS service.
* `:region` - if set, AWS region. Defaults to `"us-east-1"`.
* `:datetime` - the request datetime, defaults to `DateTime.utc_now(:second)`.
Response body options:
* `:compressed` - if set to `true`, asks the server to return compressed response.
(via [`compressed`](`Req.Steps.compressed/1`) step.) Defaults to `true`.
* `:raw` - if set to `true`, disables automatic body decompression
([`decompress_body`](`Req.Steps.decompress_body/1`) step) and decoding
([`decode_body`](`Req.Steps.decode_body/1`) step.) Defaults to `false`.
* `:decode_body` - if set to `false`, disables automatic response body decoding.
Defaults to `true`.
* `:decoders` - the list of decoders to use for automatic response body decoding.
Defaults to `[:json, :json_api]`. See [`decode_body`](`Req.Steps.decode_body/1`) for
the supported formats and how to add custom decoders.
* `:decode_json` - options to pass to `Jason.decode!/2`, defaults to `[]`.
* `:into` - where to send the response body. It can be one of:
* `nil` - (default) read the whole response body and store it in the `response.body`
field.
* `fun` - stream response body using a function. The first argument is a `{:data, data}`
tuple containing the chunk of the response body. The second argument is a
`{request, response}` tuple. To continue streaming chunks, return `{:cont, {req, resp}}`.
To cancel, return `{:halt, {req, resp}}`. For example:
into: fn {:data, data}, {req, resp} ->
IO.puts(data)
{:cont, {req, resp}}
end
* `collectable` - stream response body into a `t:Collectable.t/0`. For example:
into: File.stream!("path")
Note that the collectable is only used, if the response status is 200. In other cases,
the body is accumulated and processed as usual.
* `:self` - stream response body into the current process mailbox.
Received messages should be parsed with `Req.parse_message/2`.
`response.body` is set to opaque data structure `Req.Response.Async` which implements
`Enumerable` that receives and automatically parses messages. See module documentation
for example usage.
If the request is sent using HTTP/1, an extra process is spawned to consume messages
from the underlying socket. On both HTTP/1 and HTTP/2 the messages are sent to the
current process as soon as they arrive, as a firehose. If you wish to maximize request
rate or have more control over how messages are streamed, use `into: fun` or
`into: collectable` instead.
Response redirect options ([`redirect`](`Req.Steps.redirect/1`) step):
* `:redirect` - if set to `false`, disables automatic response redirects. Defaults to `true`.
* `:redirect_trusted` - by default, authorization credentials are only sent on redirects
with the same host, scheme and port. If `:redirect_trusted` is set to `true`, credentials
will be sent to any host.
* `:max_redirects` - the maximum number of redirects, defaults to `10`.
Other response options:
* `:http_errors` - how to handle HTTP 4xx/5xx error responses (via
[`handle_http_errors`](`Req.Steps.handle_http_errors/1`) step).
Can be one of the following:
* `:return` (default) - return the response
* `:raise` - raise an error
Retry options ([`retry`](`Req.Steps.retry/1`) step):
* `:retry` - can be one of the following:
* `:safe_transient` (default) - retry safe (GET/HEAD) requests on one of:
* HTTP 408/429/500/502/503/504 responses
* `Req.TransportError` with `reason: :timeout | :econnrefused | :closed`
* `Req.HTTPError` with `protocol: :http2, reason: :unprocessed`
* `:transient` - same as `:safe_transient` except retries all HTTP methods (POST, DELETE, etc.)
* `fun` - a 2-arity function that accepts a `Req.Request` and either a `Req.Response` or an exception struct
and returns one of the following:
* `true` - retry with the default delay controller by default delay option described below.
* `{:delay, milliseconds}` - retry with the given delay.
* `false/nil` - don't retry.
* `false` - don't retry.
* `:retry_delay` - if not set, which is the default, the retry delay is determined by
the value of the `Retry-After` header on HTTP 429/503 responses. If the header is not set,
the default delay follows a simple exponential backoff: 1s, 2s, 4s, 8s, ...
`:retry_delay` can be set to a function that receives the retry count (starting at 0)
and returns the delay, the number of milliseconds to sleep before making another attempt.
* `:retry_log_level` - the log level to emit retry logs at. Can also be set to `false` to disable
logging these messages. Defaults to `:warning`.
* `:max_retries` - maximum number of retry attempts, defaults to `3` (for a total of `4`
requests to the server, including the initial one.)
Caching options ([`cache`](`Req.Steps.cache/1`) step):
* `:cache` - if `true`, performs HTTP caching. Defaults to `false`.
* `:cache_dir` - the directory to store the cache, defaults to `<user_cache_dir>/req`
(see: `:filename.basedir/3`)
Request adapters:
* `:adapter` - adapter to use to make the actual HTTP request. See `:adapter` field description
in the `Req.Request` module documentation for more information.
The default is [`run_finch`](`Req.Steps.run_finch/1`).
* `:plug` - if set, calls the given plug instead of making an HTTP request over the network (via [`run_plug`](`Req.Steps.run_plug/1`) step).
The plug can be one of:
* A _function_ plug: a `fun(conn)` or `fun(conn, options)` function that takes a
`Plug.Conn` and returns a `Plug.Conn`.
* A _module_ plug: a `module` name or a `{module, options}` tuple.
Finch options ([`run_finch`](`Req.Steps.run_finch/1`) step), see `Finch.start_link/1` for options:
* `:finch` - the Finch pool to use. Defaults to pool automatically started by `Req`.
* `:connect_options` - dynamically starts (or re-uses already started) Finch pool with
the given connection options (see `Mint.HTTP.connect/4` for options):
* `:timeout` - socket connect timeout in milliseconds, defaults to `30_000`.
* `:protocols` - the HTTP protocols to use, defaults to
`#{inspect(Keyword.fetch!(@default_finch_options, :protocols))}`.
* `:hostname` - Mint explicit hostname.
* `:transport_opts` - Mint transport options.
* `:proxy_headers` - Mint proxy headers.
* `:proxy` - Mint HTTP/1 proxy settings, a `{scheme, address, port, options}` tuple.
* `:client_settings` - Mint HTTP/2 client settings.
* `:inet6` - if set to true, uses IPv6. Defaults to `false`.
* `:pool_timeout` - pool checkout timeout in milliseconds, defaults to `5000`.
* `:receive_timeout` - socket receive timeout in milliseconds, defaults to `15_000`.
* `:unix_socket` - if set, connect through the given UNIX domain socket.
* `:pool_max_idle_time` - the maximum number of milliseconds that a pool can be
idle before being terminated, used only by HTTP1 pools. Default to `:infinity`.
* `:finch_private` - a map or keyword list of private metadata to add to the Finch request. May be useful
for adding custom data when handling telemetry with `Finch.Telemetry`.
* `:finch_request` - a function that executes the Finch request, defaults to using `Finch.request/3`.
## Examples
iex> req = Req.new(url: "https://elixir-lang.org")
iex> req.method
:get
iex> URI.to_string(req.url)
"https://elixir-lang.org"
Fake adapter:
iex> fake = fn request ->
...> {request, Req.Response.new(status: 200, body: "it works!")}
...> end
iex>
iex> req = Req.new(adapter: fake)
iex> Req.get!(req).body
"it works!"
"""
@spec new(options :: keyword()) :: Req.Request.t()
def new(options \\ []) do
options = Keyword.merge(default_options(), options)
{plugins, options} = Keyword.pop(options, :plugins, [])
@req
|> run_plugins(plugins)
|> merge(options)
end
defp new(%Req.Request{} = request, options) when is_list(options) do
Req.merge(request, options)
end
defp new(options1, options2) when is_list(options1) and is_list(options2) do
new(options1 ++ options2)
end
defp new(url, options) when (is_binary(url) or is_struct(url, URI)) and is_list(options) do
new([url: url] ++ options)
end
defp new(request, options) when is_list(options) do
raise ArgumentError,
"expected 1st argument to be a request, got: #{inspect(request)}"
end
defp new(_request, options) do
raise ArgumentError,
"expected 2nd argument to be an options keywords list, got: #{inspect(options)}"
end
@doc false
@deprecated "Use Req.merge/2 instead"
def update(request, options) do
Req.merge(request, options)
end
@doc """
Updates a request struct.
See `new/1` for a list of available options. Also see `Req.Request` module documentation
for more information on the underlying request struct.
## Examples
iex> req = Req.new(base_url: "https://httpbin.org")
iex> req = Req.merge(req, auth: {:basic, "alice:secret"})
iex> req.options[:base_url]
"https://httpbin.org"
iex> req.options[:auth]
{:basic, "alice:secret"}
Passing `:headers` will automatically encode and merge them:
iex> req = Req.new(headers: %{point_x: 1})
iex> req = Req.merge(req, headers: %{point_y: 2})
iex> req.headers
%{"point-x" => ["1"], "point-y" => ["2"]}
The same header names are overwritten however:
iex> req = Req.new(headers: %{authorization: "bearer foo"})
iex> req = Req.merge(req, headers: %{authorization: "bearer bar"})
iex> req.headers
%{"authorization" => ["bearer bar"]}
Similarly to headers, `:params` are merged too:
req = Req.new(url: "https://httpbin.org/anything", params: [a: 1, b: 1])
req = Req.merge(req, params: [a: 2])
Req.get!(req).body["args"]
#=> %{"a" => "2", "b" => "1"}
"""
@spec merge(Req.Request.t(), options :: keyword()) :: Req.Request.t()
def merge(%Req.Request{} = request, options) when is_list(options) do
# TODO: Remove on Req 1.0
if Keyword.has_key?(options, :redact_auth) do
IO.warn("Setting :redact_auth is deprecated and has no effect")
end
request_option_names = [:method, :url, :headers, :body, :adapter, :into]
{request_options, options} = Keyword.split(options, request_option_names)
if options[:output] && unquote(!System.get_env("REQ_NOWARN_OUTPUT")) do
IO.warn("setting `output: path` is deprecated in favour of `into: File.stream!(path)`")
end
registered =
MapSet.union(
request.registered_options,
MapSet.new(request_option_names)
)
Req.Request.validate_options(options, registered)
request =
Enum.reduce(request_options, request, fn
{:url, url}, acc ->
put_in(acc.url, URI.parse(url))
{:headers, new_headers}, acc ->
update_in(acc.headers, &Req.Fields.merge(&1, new_headers))
{name, value}, acc ->
%{acc | name => value}
end)
update_in(
request.options,
&Map.merge(&1, Map.new(options), fn
:params, old, new ->
Keyword.merge(old, new)
_, _, new ->
new
end)
)
end
@doc """
Makes a GET request and returns a response or an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> {:ok, resp} = Req.get("https://api.github.com/repos/wojtekmach/req")
iex> resp.body["description"]
"Req is a batteries-included HTTP client for Elixir."
With options:
iex> {:ok, resp} = Req.get(url: "https://api.github.com/repos/wojtekmach/req")
iex> resp.status
200
With request struct:
iex> req = Req.new(base_url: "https://api.github.com")
iex> {:ok, resp} = Req.get(req, url: "/repos/elixir-lang/elixir")
iex> resp.status
200
"""
@doc type: :request
@spec get(url() | keyword() | Req.Request.t(), options :: keyword()) ::
{:ok, Req.Response.t()} | {:error, Exception.t()}
def get(request, options \\ []) do
request(%{new(request, options) | method: :get})
end
@doc """
Makes a GET request and returns a response or raises an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> Req.get!("https://api.github.com/repos/wojtekmach/req").body["description"]
"Req is a batteries-included HTTP client for Elixir."
With options:
iex> Req.get!(url: "https://api.github.com/repos/wojtekmach/req").status
200
With request struct:
iex> req = Req.new(base_url: "https://api.github.com")
iex> Req.get!(req, url: "/repos/elixir-lang/elixir").status
200
"""
@doc type: :request
@spec get!(url() | keyword() | Req.Request.t(), options :: keyword()) :: Req.Response.t()
def get!(request, options \\ []) do
request!(%{new(request, options) | method: :get})
end
@doc """
Makes a HEAD request and returns a response or an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> {:ok, resp} = Req.head("https://httpbin.org/status/201")
iex> resp.status
201
With options:
iex> {:ok, resp} = Req.head(url: "https://httpbin.org/status/201")
iex> resp.status
201
With request struct:
iex> req = Req.new(base_url: "https://httpbin.org")
iex> {:ok, resp} = Req.head(req, url: "/status/201")
iex> resp.status
201
"""
@doc type: :request
@spec head(url() | keyword() | Req.Request.t(), options :: keyword()) ::
{:ok, Req.Response.t()} | {:error, Exception.t()}
def head(request, options \\ []) do
request(%{new(request, options) | method: :head})
end
@doc """
Makes a HEAD request and returns a response or raises an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> Req.head!("https://httpbin.org/status/201").status
201
With options:
iex> Req.head!(url: "https://httpbin.org/status/201").status
201
With request struct:
iex> req = Req.new(base_url: "https://httpbin.org")
iex> Req.head!(req, url: "/status/201").status
201
"""
@doc type: :request
@spec head!(url() | keyword() | Req.Request.t(), options :: keyword()) :: Req.Response.t()
def head!(request, options \\ []) do
request!(%{new(request, options) | method: :head})
end
@doc """
Makes a POST request and returns a response or an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> {:ok, resp} = Req.post("https://httpbin.org/anything", body: "hello!")
iex> resp.body["data"]
"hello!"
iex> {:ok, resp} = Req.post("https://httpbin.org/anything", form: [x: 1])
iex> resp.body["form"]
%{"x" => "1"}
iex> {:ok, resp} = Req.post("https://httpbin.org/anything", json: %{x: 2})
iex> resp.body["json"]
%{"x" => 2}
With options:
iex> {:ok, resp} = Req.post(url: "https://httpbin.org/anything", body: "hello!")
iex> resp.body["data"]
"hello!"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> {:ok, resp} = Req.post(req, body: "hello!")
iex> resp.body["data"]
"hello!"
"""
@doc type: :request
@spec post(url() | keyword() | Req.Request.t(), options :: keyword()) ::
{:ok, Req.Response.t()} | {:error, Exception.t()}
def post(request, options \\ []) do
request(%{new(request, options) | method: :post})
end
@doc """
Makes a POST request and returns a response or raises an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> Req.post!("https://httpbin.org/anything", body: "hello!").body["data"]
"hello!"
iex> Req.post!("https://httpbin.org/anything", form: [x: 1]).body["form"]
%{"x" => "1"}
iex> Req.post!("https://httpbin.org/anything", json: %{x: 2}).body["json"]
%{"x" => 2}
With options:
iex> Req.post!(url: "https://httpbin.org/anything", body: "hello!").body["data"]
"hello!"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> Req.post!(req, body: "hello!").body["data"]
"hello!"
"""
@doc type: :request
@spec post!(url() | keyword() | Req.Request.t(), options :: keyword()) :: Req.Response.t()
def post!(request, options \\ []) do
request!(%{new(request, options) | method: :post})
end
@doc """
Makes a PUT request and returns a response or an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> {:ok, resp} = Req.put("https://httpbin.org/anything", body: "hello!")
iex> resp.body["data"]
"hello!"
With options:
iex> {:ok, resp} = Req.put(url: "https://httpbin.org/anything", body: "hello!")
iex> resp.body["data"]
"hello!"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> {:ok, resp} = Req.put(req, body: "hello!")
iex> resp.body["data"]
"hello!"
"""
@doc type: :request
@spec put(url() | keyword() | Req.Request.t(), options :: keyword()) ::
{:ok, Req.Response.t()} | {:error, Exception.t()}
def put(request, options \\ []) do
request(%{new(request, options) | method: :put})
end
@doc """
Makes a PUT request and returns a response or raises an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> Req.put!("https://httpbin.org/anything", body: "hello!").body["data"]
"hello!"
With options:
iex> Req.put!(url: "https://httpbin.org/anything", body: "hello!").body["data"]
"hello!"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> Req.put!(req, body: "hello!").body["data"]
"hello!"
"""
@doc type: :request
@spec put!(url() | keyword() | Req.Request.t(), options :: keyword()) :: Req.Response.t()
def put!(request, options \\ []) do
request!(%{new(request, options) | method: :put})
end
@doc """
Makes a PATCH request and returns a response or an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> {:ok, resp} = Req.patch("https://httpbin.org/anything", body: "hello!")
iex> resp.body["data"]
"hello!"
With options:
iex> {:ok, resp} = Req.patch(url: "https://httpbin.org/anything", body: "hello!")
iex> resp.body["data"]
"hello!"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> {:ok, resp} = Req.patch(req, body: "hello!")
iex> resp.body["data"]
"hello!"
"""
@doc type: :request
@spec patch(url() | keyword() | Req.Request.t(), options :: keyword()) ::
{:ok, Req.Response.t()} | {:error, Exception.t()}
def patch(request, options \\ []) do
request(%{new(request, options) | method: :patch})
end
@doc """
Makes a PATCH request and returns a response or raises an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> Req.patch!("https://httpbin.org/anything", body: "hello!").body["data"]
"hello!"
With options:
iex> Req.patch!(url: "https://httpbin.org/anything", body: "hello!").body["data"]
"hello!"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> Req.patch!(req, body: "hello!").body["data"]
"hello!"
"""
@doc type: :request
@spec patch!(url() | keyword() | Req.Request.t(), options :: keyword()) :: Req.Response.t()
def patch!(request, options \\ []) do
request!(%{new(request, options) | method: :patch})
end
@doc """
Makes a DELETE request and returns a response or an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> {:ok, resp} = Req.delete("https://httpbin.org/anything")
iex> resp.body["method"]
"DELETE"
With options:
iex> {:ok, resp} = Req.delete(url: "https://httpbin.org/anything")
iex> resp.body["method"]
"DELETE"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> {:ok, resp} = Req.delete(req)
iex> resp.body["method"]
"DELETE"
"""
@doc type: :request
@spec delete(url() | keyword() | Req.Request.t(), options :: keyword()) ::
{:ok, Req.Response.t()} | {:error, Exception.t()}
def delete(request, options \\ []) do
request(%{new(request, options) | method: :delete})
end
@doc """
Makes a DELETE request and returns a response or raises an error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
## Examples
With URL:
iex> Req.delete!("https://httpbin.org/anything").body["method"]
"DELETE"
With options:
iex> Req.delete!(url: "https://httpbin.org/anything").body["method"]
"DELETE"
With request struct:
iex> req = Req.new(url: "https://httpbin.org/anything")
iex> Req.delete!(req).body["method"]
"DELETE"
"""
@doc type: :request
@spec delete!(url() | keyword() | Req.Request.t(), options :: keyword()) :: Req.Response.t()
def delete!(request, options \\ []) do
request!(%{new(request, options) | method: :delete})
end
@doc """
Makes an HTTP request and returns a response or an error.
`request` can be one of:
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
Also see `run/2` for a similar function that returns the request and the response or error.
## Examples
With options keywords list:
iex> {:ok, response} = Req.request(url: "https://api.github.com/repos/wojtekmach/req")
iex> response.status
200
iex> response.body["description"]
"Req is a batteries-included HTTP client for Elixir."
With request struct:
iex> req = Req.new(url: "https://api.github.com/repos/elixir-lang/elixir")
iex> {:ok, response} = Req.request(req)
iex> response.status
200
"""
@doc type: :request
@spec request(request :: Req.Request.t() | keyword(), options :: keyword()) ::
{:ok, Req.Response.t()} | {:error, Exception.t()}
def request(request, options \\ []) do
Req.Request.run(new(request, options))
end
@doc """
Makes an HTTP request and returns a response or raises an error.
See `new/1` for a list of available options.
Also see `run!/2` for a similar function that returns the request and the response or error.
## Examples
With options keywords list:
iex> Req.request!(url: "https://api.github.com/repos/elixir-lang/elixir").status
200
With request struct:
iex> req = Req.new(url: "https://api.github.com/repos/elixir-lang/elixir")
iex> Req.request!(req).status
200
"""
@doc type: :request
@spec request!(request :: Req.Request.t() | keyword(), options :: keyword()) :: Req.Response.t()
def request!(request, options \\ []) do
case request(request, options) do
{:ok, response} -> response
{:error, exception} -> raise exception
end
end
@doc """
Makes an HTTP request and returns the request and response or error.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
Also see `request/2` for a similar function that returns the response or error
(without the request).
## Examples
With options keywords list:
iex> {req, resp} = Req.run(url: "https://api.github.com/repos/elixir-lang/elixir")
iex> req.url.host
"api.github.com"
iex> resp.status
200
With request struct and options:
iex> req = Req.new(base_url: "https://api.github.com")
iex> {req, resp} = Req.run(req, url: "/repos/elixir-lang/elixir")
iex> req.url.host
"api.github.com"
iex> resp.status
200
Returns an error:
iex> {_req, exception} = Req.run("http://localhost:9999", retry: false)
iex> exception
%Req.TransportError{reason: :econnrefused}
"""
@doc type: :request
@spec run(request :: url() | keyword() | Req.Request.t(), options :: keyword()) ::
{Req.Request.t(), Req.Response.t() | Exception.t()}
def run(request, options \\ [])
def run(request, options) when is_list(options) do
Req.Request.run_request(new(request, options))
end
def run(_request, options) do
raise ArgumentError,
"expected 2nd argument to be an options keywords list, got: #{inspect(options)}"
end
@doc """
Makes an HTTP request and returns the request and response or raises on errors.
`request` can be one of:
* an url (`String` or `URI`);
* a `Keyword` options;
* a `Req.Request` struct
See `new/1` for a list of available options.
Also see `request!/2` for a similar function that returns the response (without the request).
## Examples
With options keywords list:
iex> {req, resp} = Req.run!(url: "https://api.github.com/repos/elixir-lang/elixir")
iex> req.url.host
"api.github.com"
iex> resp.status
200
With request struct and options:
iex> req = Req.new(base_url: "https://api.github.com")
iex> {req, resp} = Req.run!(req, url: "/repos/elixir-lang/elixir")
iex> req.url.host
"api.github.com"
iex> resp.status
200
Raises an error:
iex> Req.run!("http://localhost:9999", retry: false)
** (Req.TransportError) connection refused
"""
@doc type: :request
@spec run!(request :: url() | keyword() | Req.Request.t(), options :: keyword()) ::
{Req.Request.t(), Req.Response.t()}
def run!(request, options \\ []) do
case run(request, options) do
{req, %Req.Response{} = resp} ->
{req, resp}
{_req, exception} ->
raise exception
end
end
@doc false
@deprecated "use Req.request(into: self()) instead"
def async_request(request, options \\ []) do
Req.Request.run_request(%{new(request, options) | into: :legacy_self})
end
@deprecated "use Req.request!(into: self()) instead"
@doc false
def async_request!(request, options \\ []) do
case async_request(request, options) do
{request, %Req.Response{} = response} ->
{request, response}
{_request, exception} ->
raise exception
end
end
@doc """
Parses asynchronous response body message.
A request with option `:into` set to `:self` returns response with asynchronous body.
In that case, Req sends chunks to the calling process as messages. You'd typically
get them using `receive/1` or [`handle_info/2`](`c:GenServer.handle_info/2`) in a GenServer.
These messages should be parsed using this function. The possible return values are:
* `{:ok, chunks}` - where a chunk can be `{:data, binary}`, `{:trailers, trailers}`, or
`:done`.
* `{:error, reason}` - an error occured
* `:unknown` - the message was not meant for this response.
See also `Req.Response.Async`.
## Examples
iex> resp = Req.get!("http://httpbin.org/stream/2", into: :self)
iex> Req.parse_message(resp, receive do message -> message end)
{:ok, [data: "{\"url\": \"http://httpbin.org/stream/2\", ..., \"id\": 0}\\n"]}
iex> Req.parse_message(resp, receive do message -> message end)
{:ok, [data: "{\"url\": \"http://httpbin.org/stream/2\", ..., \"id\": 1}\\n"]}
iex> Req.parse_message(resp, receive do message -> message end)
{:ok, [:done]}
iex> Req.parse_message(resp, :other)
:unknown
"""
@doc type: :async
def parse_message(response, message)
def parse_message(%Req.Response{body: %Req.Response.Async{stream_fun: fun, ref: ref}}, message) do
fun.(ref, message)
end
def parse_message(%Req.Request{} = request, message) do
IO.warn(
"passing %Req.Request{} to parse_message/2 is deprecated. Pass %Req.Response{} instead"
)
request.async.stream_fun.(request.async.ref, message)
end
@doc """
Cancels an asynchronous response.
An asynchronous response is a result of request with `into: :self`.
See also `Req.Response.Async`.
## Examples
iex> resp = Req.get!("http://httpbin.org/stream/2", into: :self)
iex> Req.cancel_async_response(resp)
:ok
"""
@doc type: :async
def cancel_async_response(%Req.Response{body: %Req.Response.Async{cancel_fun: fun, ref: ref}}) do
fun.(ref)
end
@deprecated "use Req.cancel_async_response(resp)) instead"
@doc false
def cancel_async_request(%Req.Request{} = request) do
request.async.cancel_fun.(request.async.ref)
end
@doc """
Returns default options.
See `default_options/1` for more information.
"""
@spec default_options() :: keyword()
def default_options() do
Application.get_env(:req, :default_options, [])
end
@doc """
Sets default options for `Req.new/1`.
Avoid setting default options in libraries as they are global.
## Examples
iex> Req.default_options(base_url: "https://httpbin.org")
iex> Req.get!("/statuses/201").status
201
iex> Req.new() |> Req.get!(url: "/statuses/201").status
201
"""
@spec default_options(keyword()) :: :ok
def default_options(options) do
Application.put_env(:req, :default_options, options)
end
@doc """
Returns request/response headers as list.
## Examples
iex> req = Req.Request.new(headers: %{"accept" => ["application/json"]})
iex> Req.get_headers_list(req)
[{"accept", "application/json"}]
iex> resp = Req.Response.new(headers: %{"content-type" => ["application/json"]})
iex> Req.get_headers_list(resp)
[{"content-type", "application/json"}]
"""
@doc since: "0.5.10"
@spec get_headers_list(Req.Request.t() | Req.Response.t()) :: [{binary(), binary()}]
def get_headers_list(%struct{headers: headers}) when struct in [Req.Request, Req.Response] do
Req.Fields.get_list(headers)
end
# Plugins support is experimental and undocumented.
defp run_plugins(request, [plugin | rest]) when is_atom(plugin) do
run_plugins(plugin.attach(request), rest)
end
defp run_plugins(request, [plugin | rest]) when is_function(plugin, 1) do
run_plugins(plugin.(request), rest)
end
defp run_plugins(request, []) do
request
end
@doc false
@deprecated "Manually build Req.Request struct instead"
def build(method, url, options \\ []) do
%Req.Request{
method: method,
url: URI.parse(url),
headers: Keyword.get(options, :headers, []),
body: Keyword.get(options, :body, "")
}
end
end