Skip to content

edlontech/quiver

Repository files navigation

Quiver

A fast, resilient HTTP client for Elixir with built-in connection pooling, HTTP/2 multiplexing, and streaming support.

Features

  • 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

Installation

Add quiver to your dependencies in mix.exs:

def deps do
  [
    {:quiver, "~> 0.1.0"}
  ]
end

Quick Start

Add 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()

Custom supervisor name

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)

Pool Configuration

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

Tesla Integration

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
end

All 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_quiver

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

Documentation

Full API documentation is available on HexDocs.

License

MIT -- see LICENSE for details.

About

A pretty neat Elixir HTTP Client

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages