Dowser.Client.HTTP (Dowser.Client v0.3.0)

View Source

The HTTP transport: OTP's :httpc, and nothing else.

Given a method, an absolute URL, headers and an already-encoded body, it performs the request and returns a normalized Dowser.Client.Response (or an error). It never encodes or decodes bodies — that is the JSON/codec layer's job.

:httpc ships with OTP, so dowser_client has no HTTP dependency at all. Every request is HTTP/1.1 with keep-alive, sent through a dedicated :httpc profile (see Dowser.Client.HTTP.Profile) rather than the shared :default one.

Options

:http_opts, merged global → context → request, is passed here:

  • :connect_timeout — TCP connect timeout in milliseconds; default 2_000.
  • :timeout — whole-request timeout in milliseconds; default 30_000.
  • :ssl — TLS options for an https:// endpoint; see Dowser.Client.HTTP.SSL. Verification is on by default.
  • :profile / :profile_opts — the :httpc profile to send through and its :httpc.set_options/2 settings (max_sessions, max_keep_alive_length, keep_alive_timeout, pipeline_timeout, cookies, ...); see Dowser.Client.HTTP.Profile.
  • :autoredirect, :proxy_auth, :relaxed — passed to :httpc as HTTPOptions.
  • :full_result, :headers_as_is, :socket_opts, :ipv6_host_with_brackets — passed to :httpc as Options.
  • :http_options / :options — escape hatches merged last into :httpc's two option lists, for anything not named above.

An unrecognized key is an error ({:unknown_http_opts, keys}) rather than a silently ignored setting.

version: ~c"HTTP/1.1" and body_format: :binary are always set: :httpc never speaks HTTP/2, and the response body always comes back as a binary.

GET with a body

:httpc's request tuple has no body slot for :get, :head or :trace. Since search backends do expect a body on GET (Elasticsearch's GET /_search), a :get carrying a non-empty body is sent as a POST — which every such backend accepts on the same path — and logged at debug level. A body on :head/:trace is meaningless, so it returns {:error, {:unsupported_body, method}} rather than being dropped silently.

Testing

Dowser.Client.HTTP.Stub.stub/1 intercepts requests in the calling process, so tests exercise the whole pipeline without a server. Checked first on every request, which costs one process-dictionary read in production.

Summary

Functions

Performs method against url, returning {:ok, %Dowser.Client.Response{}} for any completed exchange (whatever the status), or {:error, reason} when the transport fails.

Types

body()

@type body() :: iodata() | nil

headers()

@type headers() :: [{String.t(), String.t()}]

method()

@type method() :: :get | :post | :put | :patch | :delete | :head | :options | :trace

opts()

@type opts() :: keyword()

url()

@type url() :: String.t()

Functions

request(method, url, headers, body, opts \\ [])

@spec request(method(), url(), headers(), body(), opts()) ::
  {:ok, Dowser.Client.Response.t()} | {:error, term()}

Performs method against url, returning {:ok, %Dowser.Client.Response{}} for any completed exchange (whatever the status), or {:error, reason} when the transport fails.

reason is :httpc's own term (:timeout, a {:failed_connect, _} tuple, ...) or one of this layer's: {:unsupported_body, method}, {:unknown_http_opts, keys}, {:no_cacerts, _}, {:profile_down, _}. Dowser.Client wraps it in a Dowser.Client.HTTP.Error.