A fast, resilient HTTP client for Elixir with built-in connection pooling, HTTP/2 multiplexing, and streaming support.
- HTTP/1.1 and HTTP/2 -- automatic protocol handling with TLS+ALPN
- Connection pooling -- NimblePool for HTTP/1, GenStateMachine coordinator for HTTP/2
- Streaming responses -- lazy body streams for large payloads and SSE
- Origin-based routing -- exact, wildcard, and default pool rules per origin
- Structured errors -- three error classes (transient, invalid, unrecoverable)
- Telemetry -- request spans, connection lifecycle, and pool queue depth events
- Supervised -- pools start lazily and live under your application's supervision tree
Add quiver to your dependencies in mix.exs:
def deps do
[
{:quiver, "~> 0.1.0"}
]
endAdd Quiver to your supervision tree:
children = [
{Quiver.Supervisor, pools: %{default: [size: 10]}}
]
Supervisor.start_link(children, strategy: :one_for_one)Make requests -- no need to pass a name, Quiver uses Quiver.Pool by default:
# GET request
{:ok, %Quiver.Response{status: 200, body: body}} =
Quiver.new(:get, "https://httpbin.org/get")
|> Quiver.request()
# POST with headers and body
{:ok, %Quiver.Response{status: 200}} =
Quiver.new(:post, "https://httpbin.org/post")
|> Quiver.header("content-type", "application/json")
|> Quiver.body(~s({"key": "value"}))
|> Quiver.request()Stream large responses:
{:ok, %Quiver.StreamResponse{status: 200, body: body_stream}} =
Quiver.new(:get, "https://httpbin.org/stream/100")
|> Quiver.stream_request()
body_stream
|> Stream.each(&IO.write/1)
|> Stream.run()If you need multiple Quiver instances, pass a :name option:
# Supervision tree
children = [
{Quiver.Supervisor, name: :internal_client, pools: %{default: [size: 20]}},
{Quiver.Supervisor, name: :external_client, pools: %{default: [size: 5]}}
]
# Requests
Quiver.new(:get, "https://internal.api/data")
|> Quiver.request(name: :internal_client)Route origins to pools with different settings:
pools = %{
"https://api.example.com" => [size: 50],
"https://*.internal.io" => [size: 20],
default: [size: 5]
}
{Quiver.Supervisor, pools: pools}Rules match by specificity: exact > wildcard > default.
By default, Quiver auto-detects the HTTP protocol via TLS ALPN negotiation. To force a specific protocol per origin:
pools = %{
"https://http2-only.example.com" => [size: 10, protocol: :http2],
"https://legacy.example.com" => [size: 10, protocol: :http1],
default: [size: 5]
}Quiver ships with an optional Tesla adapter.
Add tesla to your dependencies and configure your client:
defmodule MyClient do
use Tesla
plug Tesla.Middleware.BaseUrl, "https://api.example.com"
plug Tesla.Middleware.JSON
adapter Tesla.Adapter.Quiver
endAll requests go through the default Quiver.Pool supervisor. To target a
custom supervisor, pass it as an adapter option:
adapter Tesla.Adapter.Quiver, name: :my_quiverAdapter-level options like streaming and timeouts can also be passed per-request:
MyClient.get("/large-file", opts: [adapter: [response: :stream]])
MyClient.get("/slow", opts: [adapter: [receive_timeout: 60_000]])See Tesla.Adapter.Quiver docs for all available options.
Full API documentation is available on HexDocs.
MIT -- see LICENSE for details.