View Source Finch (Finch v0.19.0)
An HTTP client with a focus on performance, built on top of Mint and NimblePool.
We attempt to achieve this goal by providing efficient connection pooling strategies and avoiding copying of memory wherever possible.
Most developers will most likely prefer to use the fabulous HTTP client Req which takes advantage of Finch's pooling and provides an extremely friendly and pleasant to use API.
Usage
In order to use Finch, you must start it and provide a :name
. Often in your
supervision tree:
children = [
{Finch, name: MyFinch}
]
Or, in rare cases, dynamically:
Finch.start_link(name: MyFinch)
Once you have started your instance of Finch, you are ready to start making requests:
Finch.build(:get, "https://hex.pm") |> Finch.request(MyFinch)
When using HTTP/1, Finch will parse the passed in URL into a {scheme, host, port}
tuple, and maintain one or more connection pools for each {scheme, host, port}
you
interact with.
You can also configure a pool size and count to be used for specific URLs that are
known before starting Finch. The passed URLs will be parsed into {scheme, host, port}
,
and the corresponding pools will be started. See Finch.start_link/1
for configuration
options.
children = [
{Finch,
name: MyConfiguredFinch,
pools: %{
:default => [size: 10],
"https://hex.pm" => [size: 32, count: 8]
}}
]
Pools will be started for each configured {scheme, host, port}
when Finch is started.
For any unconfigured {scheme, host, port}
, the pool will be started the first time
it is requested. Note pools are not automatically terminated by default, if you need to
terminate them after some idle time, use the pool_max_idle_time
option (available only for HTTP1 pools).
Telemetry
Finch uses Telemetry to provide instrumentation. See the Finch.Telemetry
module for details on specific events.
Logging TLS Secrets
Finch supports logging TLS secrets to a file. These can be later used in a tool such as
Wireshark to decrypt HTTPS sessions. To use this feature you must specify the file to
which the secrets should be written. If you are using TLSv1.3 you must also add
keep_secrets: true
to your pool :transport_opts
. For example:
{Finch,
name: MyFinch,
pools: %{
default: [conn_opts: [transport_opts: [keep_secrets: true]]]
}}
There are two different ways to specify this file:
- The
:ssl_key_log_file
connection option in your pool configuration. For example:
{Finch,
name: MyFinch,
pools: %{
default: [
conn_opts: [
ssl_key_log_file: "/writable/path/to/the/sslkey.log"
]
]
}}
- Alternatively, you could also set the
SSLKEYLOGFILE
environment variable.
Summary
Types
The :name
provided to Finch in start_link/1
.
Options used by request functions.
The reference used to identify a request sent using async_request/3
.
The stream function given to stream/5
.
The stream function given to stream_while/5
.
Functions
Sends an HTTP request asynchronously, returning a request reference.
Cancels a request sent with async_request/3
.
Returns a specification to start this module under a supervisor.
Get pool metrics list.
Sends an HTTP request and returns a Finch.Response
struct.
Sends an HTTP request and returns a Finch.Response
struct
or raises an exception in case of failure.
Start an instance of Finch.
Streams an HTTP request and returns the accumulator.
Streams an HTTP request until it finishes or fun
returns {:halt, acc}
.
Types
@type name() :: atom()
The :name
provided to Finch in start_link/1
.
@type request_opts() :: [request_opt()]
Options used by request functions.
@opaque request_ref()
The reference used to identify a request sent using async_request/3
.
@type scheme() :: :http | :https
@type scheme_host_port() :: {scheme(), host :: String.t(), port :: :inet.port_number()}
@type stream(acc) :: ({:status, integer()} | {:headers, Mint.Types.headers()} | {:data, binary()} | {:trailers, Mint.Types.headers()}, acc -> acc)
The stream function given to stream/5
.
@type stream_while(acc) :: ({:status, integer()} | {:headers, Mint.Types.headers()} | {:data, binary()} | {:trailers, Mint.Types.headers()}, acc -> {:cont, acc} | {:halt, acc})
The stream function given to stream_while/5
.
Functions
@spec async_request(Finch.Request.t(), name(), request_opts()) :: request_ref()
Sends an HTTP request asynchronously, returning a request reference.
If the request is sent using HTTP1, an extra process is spawned to
consume messages from the underlying socket. 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, a strategy using request/3
or stream/5
should be used instead.
Receiving the response
Response information is sent to the calling process as it is received
in {ref, response}
tuples.
If the calling process exits before the request has completed, the request will be canceled.
Responses include:
{:status, status}
- HTTP response status{:headers, headers}
- HTTP response headers{:data, data}
- section of the HTTP response body{:error, exception}
- an error occurred during the request:done
- request has completed successfully
On a successful request, a single :status
message will be followed
by a single :headers
message, after which more than one :data
messages may be sent. If trailing headers are present, a final
:headers
message may be sent. Any :done
or :error
message
indicates that the request has succeeded or failed and no further
messages are expected.
Example
iex> req = Finch.build(:get, "https://httpbin.org/stream/5")
iex> ref = Finch.async_request(req, MyFinch)
iex> flush()
{ref, {:status, 200}}
{ref, {:headers, [...]}}
{ref, {:data, "..."}}
{ref, :done}
Options
Shares options with request/3
.
@spec build( Finch.Request.method(), Finch.Request.url(), Finch.Request.headers(), Finch.Request.body(), Keyword.t() ) :: Finch.Request.t()
Builds an HTTP request to be sent with request/3
or stream/4
.
It is possible to send the request body in a streaming fashion. In order to do so, the
body
parameter needs to take form of a tuple {:stream, body_stream}
, where body_stream
is a Stream
.
@spec cancel_async_request(request_ref()) :: :ok
Cancels a request sent with async_request/3
.
Returns a specification to start this module under a supervisor.
See Supervisor
.
@spec get_pool_status(name(), url :: String.t() | scheme_host_port()) :: {:ok, [Finch.HTTP1.PoolMetrics.t()]} | {:ok, [Finch.HTTP2.PoolMetrics.t()]} | {:error, :not_found}
Get pool metrics list.
The number of items present on the metrics list depends on the :count
option
each metric will have a pool_index
going from 1 to :count
.
The metrics struct depends on the pool scheme defined on the :protocols
option
Finch.HTTP1.PoolMetrics
for :http1
and Finch.HTTP2.PoolMetrics
for :http2
.
See the Finch.HTTP1.PoolMetrics
and Finch.HTTP2.PoolMetrics
for more details.
{:error, :not_found}
may return on 2 scenarios:
- There is no pool registered for the given pair finch instance and url
- The pool is configured with
start_pool_metrics?
option false (default)
Example
iex> Finch.get_pool_status(MyFinch, "https://httpbin.org")
{:ok, [
%Finch.HTTP1.PoolMetrics{
pool_index: 1,
pool_size: 50,
available_connections: 43,
in_use_connections: 7
},
%Finch.HTTP1.PoolMetrics{
pool_index: 2,
pool_size: 50,
available_connections: 37,
in_use_connections: 13
}]
}
@spec request(Finch.Request.t(), name(), request_opts()) :: {:ok, Finch.Response.t()} | {:error, Exception.t()}
Sends an HTTP request and returns a Finch.Response
struct.
Options
:pool_timeout
- This timeout is applied when we check out a connection from the pool. Default value is5_000
.:receive_timeout
- The maximum time to wait for each chunk to be received before returning an error. Default value is15_000
.:request_timeout
- The amount of time to wait for a complete response before returning an error. This timeout only applies to HTTP/1, and its current implementation is a best effort timeout, it does not guarantee the call will return precisely when the time has elapsed. Default value is:infinity
.
@spec request!(Finch.Request.t(), name(), request_opts()) :: Finch.Response.t()
Sends an HTTP request and returns a Finch.Response
struct
or raises an exception in case of failure.
See request/3
for more detailed information.
Start an instance of Finch.
Options
:name
- The name of your Finch instance. This field is required.:pools
- A map specifying the configuration for your pools. The keys should be URLs provided as binaries, a tuple{scheme, {:local, unix_socket}}
whereunix_socket
is the path for the socket, or the atom:default
to provide a catch-all configuration to be used for any unspecified URLs. See "Pool Configuration Options" below for details on the possible map values. Default value is%{default: [size: 50, count: 1]}
.
Pool Configuration Options
:protocol
:protocols
- The type of connections to support.If using
:http1
only, an HTTP1 pool without multiplexing is used. If using:http2
only, an HTTP2 pool with multiplexing is used. If both are listed, then both HTTP1/HTTP2 connections are supported (via ALPN), but there is no multiplexing.The default value is
[:http1]
.:size
(pos_integer/0
) - Number of connections to maintain in each pool. Used only by HTTP1 pools since HTTP2 is able to multiplex requests through a single connection. In other words, for HTTP2, the size is always 1 and the:count
should be configured in order to increase capacity. The default value is50
.:count
(pos_integer/0
) - Number of pools to start. HTTP1 pools are able to re-use connections in the same pool and establish new ones only when necessary. However, if there is a high pool count and few requests are made, these requests will be scattered across pools, reducing connection reuse. It is recommended to increase the pool count for HTTP1 only if you are experiencing high checkout times. The default value is1
.:max_idle_time
(timeout/0
) - The maximum number of milliseconds an HTTP1 connection is allowed to be idle before being closed during a checkout attempt.:conn_opts
(keyword/0
) - These options are passed toMint.HTTP.connect/4
whenever a new connection is established.:mode
is not configurable as Finch must control this setting. Typically these options are used to configure proxying, https settings, or connect timeouts. The default value is[]
.:pool_max_idle_time
(timeout/0
) - The maximum number of milliseconds that a pool can be idle before being terminated, used only by HTTP1 pools. This options is forwarded to NimblePool and it starts and idle verification cycle that may impact performance if misused. For instance setting a very low timeout may lead to pool restarts. For more information see NimblePool'shandle_ping/2
documentation. The default value is:infinity
.:conn_max_idle_time
(timeout/0
) - The maximum number of milliseconds an HTTP1 connection is allowed to be idle before being closed during a checkout attempt. The default value is:infinity
.:start_pool_metrics?
(boolean/0
) - When true, pool metrics will be collected and available through Finch.pool_status/2 The default value isfalse
.
@spec stream(Finch.Request.t(), name(), acc, stream(acc), request_opts()) :: {:ok, acc} | {:error, Exception.t()} when acc: term()
Streams an HTTP request and returns the accumulator.
A function of arity 2 is expected as argument. The first argument is a tuple, as listed below, and the second argument is the accumulator. The function must return a potentially updated accumulator.
See also stream_while/5
.
HTTP2 streaming and back-pressure
At the moment, streaming over HTTP2 connections do not provide any back-pressure mechanism: this means the response will be sent to the client as quickly as possible. Therefore, you must not use streaming over HTTP2 for non-terminating responses or when streaming large responses which you do not intend to keep in memory.
Stream commands
{:status, status}
- the http response status{:headers, headers}
- the http response headers{:data, data}
- a streaming section of the http response body{:trailers, trailers}
- the http response trailers
Options
Shares options with request/3
.
Examples
path = "/tmp/archive.zip"
file = File.open!(path, [:write, :exclusive])
url = "https://example.com/archive.zip"
request = Finch.build(:get, url)
Finch.stream(request, MyFinch, nil, fn
{:status, status}, _acc ->
IO.inspect(status)
{:headers, headers}, _acc ->
IO.inspect(headers)
{:data, data}, _acc ->
IO.binwrite(file, data)
end)
File.close(file)
@spec stream_while(Finch.Request.t(), name(), acc, stream_while(acc), request_opts()) :: {:ok, acc} | {:error, Exception.t()} when acc: term()
Streams an HTTP request until it finishes or fun
returns {:halt, acc}
.
A function of arity 2 is expected as argument. The first argument is a tuple, as listed below, and the second argument is the accumulator.
The function must return:
{:cont, acc}
to continue streaming{:halt, acc}
to halt streaming
See also stream/5
.
HTTP2 streaming and back-pressure
At the moment, streaming over HTTP2 connections do not provide any back-pressure mechanism: this means the response will be sent to the client as quickly as possible. Therefore, you must not use streaming over HTTP2 for non-terminating responses or when streaming large responses which you do not intend to keep in memory.
Stream commands
{:status, status}
- the http response status{:headers, headers}
- the http response headers{:data, data}
- a streaming section of the http response body{:trailers, trailers}
- the http response trailers
Options
Shares options with request/3
.
Examples
path = "/tmp/archive.zip"
file = File.open!(path, [:write, :exclusive])
url = "https://example.com/archive.zip"
request = Finch.build(:get, url)
Finch.stream_while(request, MyFinch, nil, fn
{:status, status}, acc ->
IO.inspect(status)
{:cont, acc}
{:headers, headers}, acc ->
IO.inspect(headers)
{:cont, acc}
{:data, data}, acc ->
IO.binwrite(file, data)
{:cont, acc}
end)
File.close(file)