Fetch.Conn (Fetch v0.1.0)

View Source

One HTTP/1.1 connection to one origin, reused for many requests (keep-alive).

{:ok, conn} = Fetch.Conn.new("https://example.com")
{:ok, conn, first} = Fetch.Conn.request(conn, :get, "/")
{:ok, conn, second} = Fetch.Conn.request(conn, :get, "/other")
conn = Fetch.Conn.close(conn)

A connection is a value, not a process. Every request returns the updated connection, because a request can change it: the server may ask to close it, or a failure may leave it unusable. Always continue with the returned one.

Lifecycle

new ──► request ──► request ──► ... ──► close
           │
           ├─ not connected, or the idle socket was closed by the server
           │    → connect: DNS → TCP → TLS
           ├─ send the request, read the response
           └─ keep the socket if the response allows it, close it otherwise

An HTTP/1.1 connection stays open after a response unless (RFC 9112 §9.3):

  • the response says connection: close
  • the response is HTTP/1.0
  • the body was delimited by closing the connection
  • the server sent more bytes than the response contains
  • anything went wrong while sending or receiving

Servers close idle connections whenever they like. Before reusing one, the client checks, without waiting, whether the server closed it (or sent something on its own, like 408 Request Timeout) and reconnects if so. That is safe because nothing has been sent yet. A connection that breaks after the request was sent is an error, not a retry: the server may already have acted on the request.

The socket belongs to the process that connected it and is closed when that process exits. Use a connection from one process.

Options

new/2 takes the connection options of Fetch: :connect_timeout, :receive_timeout, :max_body_size and :ssl.

request/4 takes:

  • :headers — list of {name, value} tuples. Default [].
  • :body — request body as iodata. Default nil.
  • :keep_alive — false sends connection: close and closes the connection after the response. Default true.

Redirects are not followed: a connection belongs to one origin.

Summary

Functions

Closes the socket, if any. The connection can still be used: the next request reconnects.

Creates a connection to the origin (scheme, host, port) of url; the path is ignored. Nothing happens on the network until the first request.

Sends a request for path (like "/users?page=2") and reads the response.

Types

t()

@type t() :: %Fetch.Conn{
  opts: keyword(),
  transport: Fetch.Transport.t() | nil,
  url: Fetch.URL.t()
}

Functions

close(conn)

@spec close(t()) :: t()

Closes the socket, if any. The connection can still be used: the next request reconnects.

new(url, opts \\ [])

@spec new(
  String.t() | Fetch.URL.t(),
  keyword()
) :: {:ok, t()} | {:error, {:url, term()}}

Creates a connection to the origin (scheme, host, port) of url; the path is ignored. Nothing happens on the network until the first request.

request(conn, method, path, opts \\ [])

@spec request(t(), Fetch.Request.method(), String.t(), keyword()) ::
  {:ok, t(), Fetch.Response.t()} | {:error, t(), {atom(), term()}}

Sends a request for path (like "/users?page=2") and reads the response.

Returns the connection to use next together with the response or the error. Errors are the same {stage, reason} as in Fetch, plus {:url, {:invalid_path, path}}.