- flake.nix: Elixir 1.18.4 / OTP 27 / Node 22 dev shell - .envrc: use flake - config/config.exs: move fetch_env! to runtime.exs (compile-time safe) - config/runtime.exs: all secrets loaded at runtime via env vars - mix.lock: generated after mix deps.get - All 3 apps compile cleanly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1392 lines
40 KiB
Elixir
1392 lines
40 KiB
Elixir
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`.
|
|
|
|
* `: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
|