katipo (katipo v2.0.0-rc.2)
View SourceAn HTTP/HTTP2/HTTP3 client library for Erlang built around libcurl-multi and libevent.
Quick Start
{ok, _} = application:ensure_all_started(katipo).
{ok, _} = katipo_pool:start(my_pool, 2, [{pipelining, multiplex}]).
{ok, #{status := 200, body := Body}} = katipo:get(my_pool, <<"https://example.com">>).Request Options
Options can be passed as the third argument to HTTP method functions, or included
directly in the request map passed to req/2.
See opts/0 for all available options and request/0 for the full request map type.
Responses
Synchronous request functions return response/0:
{ok, #{status := pos_integer(), headers := headers(), cookiejar := cookiejar(), body := body()}}
{error, #{code := error_code(), message := error_msg()}}Async Requests
Async functions (async_get/2,3, async_req/2, etc.) return {ok, Ref} immediately
and deliver the response as a message to the calling process (or the pid specified
by the reply_to option):
{katipo_response, Ref, #{status := pos_integer(), headers := headers(), ...}}
{katipo_error, Ref, #{code := error_code(), message := error_msg()}}Use await/1,2 to block until the response arrives, or cancel/2 to abort an
in-flight request (no response is then delivered).
If the pool worker handling an in-flight async request dies (e.g. its port
crashes), a {katipo_error, Ref, #{code => worker_died}} message is delivered
so the caller fails fast instead of blocking until the request timeout. If the
request cannot be handed to a worker at all (the worker is dead or being
restarted), the async function returns {error, #{code => worker_died}}
instead of {ok, Ref} -- an accepted request always produces exactly one
terminal message. Handing an async request to a worker is itself bounded
(5s): a wedged or deeply backed-up worker yields
{error, #{code => admission_timeout}} rather than blocking the caller.
(Sync requests are bounded by the request timeout instead -- their single
call carries the whole response.)
With stream => true the body is delivered incrementally as
{katipo_headers, Ref, _}, {katipo_chunk, Ref, _}* and a terminal
{katipo_done, Ref, _} instead of one buffered response; see async_req/2.
Async requests emit the same OTel span (HTTP <METHOD>, parented to the
caller's context) and metrics as their synchronous counterparts; the span
covers the full request and is finished when the response, a timeout, or a
worker failure arrives.
Summary
Functions
Equivalent to async_delete/3.
Performs an async HTTP DELETE request. Returns {ok, Ref} immediately.
Equivalent to async_get/3.
Performs an async HTTP GET request. Returns {ok, Ref} immediately. The
response is delivered as a {katipo_response, Ref, Response} message.
Equivalent to async_head/3.
Performs an async HTTP HEAD request. Returns {ok, Ref} immediately.
Equivalent to async_options/3.
Performs an async HTTP OPTIONS request. Returns {ok, Ref} immediately.
Equivalent to async_patch/3.
Performs an async HTTP PATCH request. Returns {ok, Ref} immediately.
Equivalent to async_post/3.
Performs an async HTTP POST request. Returns {ok, Ref} immediately.
Equivalent to async_put/3.
Performs an async HTTP PUT request. Returns {ok, Ref} immediately.
Performs an async HTTP request using the full request map.
Equivalent to await/2.
Blocks until an async response for Ref arrives or the timeout expires.
Only for buffered async requests: a streamed response (stream => true)
arrives as a message sequence, which await does not understand -- receive
those messages directly.
Cancels the async request identified by Ref (returned by async_get/2,3,
async_req/2, etc.). The Ref routes directly to the worker holding the
request; the pool argument is retained for API compatibility and not used.
Validates request options without performing the request. Cross-field rules
are checked under the async API's rules (the superset): stream => true
passes here but is additionally rejected by req/2 and the synchronous
wrappers.
Equivalent to delete/3.
Performs an HTTP DELETE request.
Returns whether DNS-over-HTTPS is available (curl >= 7.62.0).
Equivalent to get/3.
Performs an HTTP GET request.
Equivalent to head/3.
Performs an HTTP HEAD request.
Returns whether HTTP/3 is available (curl >= 7.66.0).
Equivalent to options/3.
Performs an HTTP OPTIONS request.
Equivalent to patch/3.
Performs an HTTP PATCH request.
Equivalent to post/3.
Performs an HTTP POST request.
Equivalent to put/3.
Performs an HTTP PUT request.
Performs an HTTP request using the full request map.
Returns whether SSL key blob is available (curl >= 7.71.0).
Returns whether TCP Fast Open is available (curl >= 7.49.0).
Returns whether Unix socket paths are available (curl >= 7.40.0).
Grants N more chunk-message credits to the streaming request Ref, which
must have been started with a bounded stream_window. Routed like
cancel/2 (directly to the owning worker; the pool argument is unused) and
equally best-effort: granting credits to an unknown, completed, or
unbounded-window request is a harmless no-op. Granting in batches (e.g.
half the window at a time) amortizes the per-grant messaging.
Types
-type async_response() :: {ok, reference()} | {error, #{code := error_code(), message := error_msg()}}.
-type body() :: binary().
-type connecttimeout_ms() :: pos_integer().
-opaque cookiejar()
-type curlmopts() :: [{pipelining, pipelining()} | {max_total_connections, non_neg_integer()} | {max_concurrent_streams, non_neg_integer()}].
-type curlopt_http_version() ::
curl_http_version_none | curl_http_version_1_0 | curl_http_version_1_1 |
curl_http_version_2_0 | curl_http_version_2tls | curl_http_version_2_prior_knowledge |
curl_http_version_3.
-type curlopt_sslversion() ::
sslversion_default | sslversion_tlsv1 | sslversion_tlsv1_0 | sslversion_tlsv1_1 |
sslversion_tlsv1_2 | sslversion_tlsv1_3.
-type doh_url() :: binary().
-type error_code() ::
ok | unsupported_protocol | failed_init | url_malformat | not_built_in |
couldnt_resolve_proxy | couldnt_resolve_host | couldnt_connect | ftp_weird_server_reply |
remote_access_denied | ftp_accept_failed | ftp_weird_pass_reply | ftp_accept_timeout |
ftp_weird_pasv_reply | ftp_weird_227_format | ftp_cant_get_host | http2 |
ftp_couldnt_set_type | partial_file | ftp_couldnt_retr_file | obsolete20 | quote_error |
http_returned_error | write_error | obsolete24 | upload_failed | read_error | out_of_memory |
operation_timedout | obsolete29 | ftp_port_failed | ftp_couldnt_use_rest | obsolete32 |
range_error | http_post_error | ssl_connect_error | bad_download_resume |
file_couldnt_read_file | ldap_cannot_bind | ldap_search_failed | obsolete40 |
function_not_found | aborted_by_callback | bad_function_argument | obsolete44 |
interface_failed | obsolete46 | too_many_redirects | unknown_option | telnet_option_syntax |
obsolete50 | got_nothing | ssl_engine_notfound | ssl_engine_setfailed | send_error |
recv_error | obsolete57 | ssl_certproblem | ssl_cipher | peer_failed_verification |
bad_content_encoding | ldap_invalid_url | filesize_exceeded | use_ssl_failed |
send_fail_rewind | ssl_engine_initfailed | login_denied | tftp_notfound | tftp_perm |
remote_disk_full | tftp_illegal | tftp_unknownid | remote_file_exists | tftp_nosuchuser |
conv_failed | conv_reqd | ssl_cacert_badfile | remote_file_not_found | ssh |
ssl_shutdown_failed | again | ssl_crl_badfile | ssl_issuer_error | ftp_pret_failed |
rtsp_cseq_error | rtsp_session_error | ftp_bad_file_list | chunk_failed |
no_connection_available | obsolete16 | ssl_pinnedpubkeynotmatch | ssl_invalidcertstatus |
http2_stream | recursive_api_call | auth_error | http3 | proxy | ssl_clientcert |
quic_connect_error | unrecoverable_poll | too_large | ech_required | curl_last | bad_opts |
await_timeout | worker_died | overload | admission_timeout.
-type error_msg() :: binary().
-type headers() :: [header()].
-type http_auth() :: basic | digest | ntlm | negotiate.
-type interface() :: binary().
-type method() :: get | post | put | head | options | patch | delete.
-type metrics() :: proplists:proplist().
-type opts() :: #{reply_to => pid(), headers => headers(), cookiejar => cookiejar(), body => req_body(), connecttimeout_ms => connecttimeout_ms(), followlocation => boolean(), ssl_verifyhost => ssl_verifyhost(), ssl_verifypeer => ssl_verifypeer(), capath => binary(), cacert => binary(), timeout_ms => pos_integer(), maxredirs => -1 | non_neg_integer(), http_auth => http_auth(), username => binary(), password => binary(), proxy => proxy(), tcp_fastopen => tcp_fastopen(), interface => interface(), unix_socket_path => unix_socket_path(), doh_url => doh_url(), http_version => curlopt_http_version(), sslversion => curlopt_sslversion(), verbose => boolean(), sslcert => sslcert(), sslkey => sslkey(), sslkey_blob => sslkey_blob(), keypasswd => binary(), userpwd => userpwd(), dns_cache_timeout => integer(), ca_cache_timeout => integer(), pipewait => boolean(), stream => boolean(), stream_window => pos_integer() | infinity}.
-type pipelining() :: nothing | http1 | multiplex.
-type pool_opts() :: [{pipelining, pipelining()} | {max_total_connections, non_neg_integer()} | {max_concurrent_streams, non_neg_integer()} | {max_in_flight, pos_integer() | infinity}].
-type proxy() :: binary().
-type qs_vals() :: [{unicode:chardata(), unicode:chardata() | true}].
-type request() :: #{url := binary(), method := method(), reply_to => pid(), headers => headers(), cookiejar => cookiejar(), body => req_body(), connecttimeout_ms => connecttimeout_ms(), followlocation => boolean(), ssl_verifyhost => ssl_verifyhost(), ssl_verifypeer => ssl_verifypeer(), capath => binary(), cacert => binary(), timeout_ms => pos_integer(), maxredirs => -1 | non_neg_integer(), http_auth => http_auth(), username => binary(), password => binary(), proxy => proxy(), tcp_fastopen => tcp_fastopen(), interface => interface(), unix_socket_path => unix_socket_path(), doh_url => doh_url(), http_version => curlopt_http_version(), sslversion => curlopt_sslversion(), verbose => boolean(), sslcert => sslcert(), sslkey => sslkey(), sslkey_blob => sslkey_blob(), keypasswd => binary(), userpwd => userpwd(), dns_cache_timeout => integer(), ca_cache_timeout => integer(), pipewait => boolean(), stream => boolean(), stream_window => pos_integer() | infinity}.
-type ssl_verifyhost() :: boolean().
-type ssl_verifypeer() :: boolean().
-type sslcert() :: binary().
-type sslkey() :: binary().
-type sslkey_blob() :: binary().
-type status() :: pos_integer().
-type tcp_fastopen() :: boolean().
-type unix_socket_path() :: binary().
-type url() :: binary().
-type userpwd() :: binary().
Functions
-spec async_delete(katipo_pool:name(), url()) -> async_response().
Equivalent to async_delete/3.
-spec async_delete(katipo_pool:name(), url(), opts()) -> async_response().
Performs an async HTTP DELETE request. Returns {ok, Ref} immediately.
-spec async_get(katipo_pool:name(), url()) -> async_response().
Equivalent to async_get/3.
-spec async_get(katipo_pool:name(), url(), opts()) -> async_response().
Performs an async HTTP GET request. Returns {ok, Ref} immediately. The
response is delivered as a {katipo_response, Ref, Response} message.
-spec async_head(katipo_pool:name(), url()) -> async_response().
Equivalent to async_head/3.
-spec async_head(katipo_pool:name(), url(), opts()) -> async_response().
Performs an async HTTP HEAD request. Returns {ok, Ref} immediately.
-spec async_options(katipo_pool:name(), url()) -> async_response().
Equivalent to async_options/3.
-spec async_options(katipo_pool:name(), url(), opts()) -> async_response().
Performs an async HTTP OPTIONS request. Returns {ok, Ref} immediately.
-spec async_patch(katipo_pool:name(), url()) -> async_response().
Equivalent to async_patch/3.
-spec async_patch(katipo_pool:name(), url(), opts()) -> async_response().
Performs an async HTTP PATCH request. Returns {ok, Ref} immediately.
-spec async_post(katipo_pool:name(), url()) -> async_response().
Equivalent to async_post/3.
-spec async_post(katipo_pool:name(), url(), opts()) -> async_response().
Performs an async HTTP POST request. Returns {ok, Ref} immediately.
-spec async_put(katipo_pool:name(), url()) -> async_response().
Equivalent to async_put/3.
-spec async_put(katipo_pool:name(), url(), opts()) -> async_response().
Performs an async HTTP PUT request. Returns {ok, Ref} immediately.
-spec async_req(katipo_pool:name(), request()) -> async_response().
Performs an async HTTP request using the full request map.
Returns {ok, Ref} once a pool worker has accepted the request. The response
is delivered as a {katipo_response, Ref, ResponseMap} or
{katipo_error, Ref, ErrorMap} message to the process specified by the
reply_to option (defaults to self()). If no worker could accept the
request, returns {error, #{code => worker_died | overload | admission_timeout}} (dead/restarting worker, pool at max_in_flight
capacity, or a wedged worker not answering the bounded admission call) and
no message is delivered.
Use await/1,2 to block until the response arrives.
With stream => true the response body is delivered incrementally instead of
as one buffered binary. The message flow is:
{katipo_headers, Ref, #{status := pos_integer(), headers := headers()}}
{katipo_chunk, Ref, binary()} %% zero or more, in order
{katipo_done, Ref, #{status := pos_integer(), cookiejar := cookiejar()}}A {katipo_error, Ref, ErrorMap} message is terminal and can arrive at any
point, including after headers and chunks (e.g. a request timeout mid-body).
cancel/2 works as for buffered async requests. await/1,2 does not apply
to streamed requests; receive the messages directly. Streaming is only
available through the async API -- req/2 and the synchronous wrappers
reject stream => true.
By default chunks are delivered as fast as the transfer produces them. Pass
stream_window => N to bound that: the transfer pauses (propagating
backpressure to the server via TCP or the HTTP/2/3 stream window) once N
chunk messages are outstanding, and update_flow/3 grants more. The request
timer keeps running while a transfer is paused, so a consumer that stops
granting credits eventually receives operation_timedout.
Caveat shared with buffered mode: when following redirects
(followlocation => true), a redirect response that itself carries a body
surfaces that body through the write path -- in streaming form the
katipo_headers message may then describe the redirect response rather
than the final one. Real-world redirect responses rarely carry bodies.
Equivalent to await/2.
Blocks until an async response for Ref arrives or the timeout expires.
Only for buffered async requests: a streamed response (stream => true)
arrives as a message sequence, which await does not understand -- receive
those messages directly.
-spec cancel(katipo_pool:name(), reference()) -> ok.
Cancels the async request identified by Ref (returned by async_get/2,3,
async_req/2, etc.). The Ref routes directly to the worker holding the
request; the pool argument is retained for API compatibility and not used.
Best-effort: once the cancel takes effect no {katipo_response, Ref, _} or
{katipo_error, Ref, _} message is delivered. A message that was already
delivered before the cancel raced in may still be in the receiver's mailbox, so
callers should be prepared to flush a late one. Cancelling an unknown or
already-completed Ref is a harmless no-op.
Validates request options without performing the request. Cross-field rules
are checked under the async API's rules (the superset): stream => true
passes here but is additionally rejected by req/2 and the synchronous
wrappers.
-spec delete(katipo_pool:name(), url()) -> response().
Equivalent to delete/3.
-spec delete(katipo_pool:name(), url(), opts()) -> response().
Performs an HTTP DELETE request.
Returns whether DNS-over-HTTPS is available (curl >= 7.62.0).
-spec get(katipo_pool:name(), url()) -> response().
Equivalent to get/3.
-spec get(katipo_pool:name(), url(), opts()) -> response().
Performs an HTTP GET request.
-spec head(katipo_pool:name(), url()) -> response().
Equivalent to head/3.
-spec head(katipo_pool:name(), url(), opts()) -> response().
Performs an HTTP HEAD request.
Returns whether HTTP/3 is available (curl >= 7.66.0).
-spec options(katipo_pool:name(), url()) -> response().
Equivalent to options/3.
-spec options(katipo_pool:name(), url(), opts()) -> response().
Performs an HTTP OPTIONS request.
-spec patch(katipo_pool:name(), url()) -> response().
Equivalent to patch/3.
-spec patch(katipo_pool:name(), url(), opts()) -> response().
Performs an HTTP PATCH request.
-spec post(katipo_pool:name(), url()) -> response().
Equivalent to post/3.
-spec post(katipo_pool:name(), url(), opts()) -> response().
Performs an HTTP POST request.
-spec put(katipo_pool:name(), url()) -> response().
Equivalent to put/3.
-spec put(katipo_pool:name(), url(), opts()) -> response().
Performs an HTTP PUT request.
-spec req(katipo_pool:name(), request()) -> response().
Performs an HTTP request using the full request map.
Returns whether SSL key blob is available (curl >= 7.71.0).
Returns whether TCP Fast Open is available (curl >= 7.49.0).
Returns whether Unix socket paths are available (curl >= 7.40.0).
-spec update_flow(katipo_pool:name(), reference(), pos_integer()) -> ok.
Grants N more chunk-message credits to the streaming request Ref, which
must have been started with a bounded stream_window. Routed like
cancel/2 (directly to the owning worker; the pool argument is unused) and
equally best-effort: granting credits to an unknown, completed, or
unbounded-window request is a harmless no-op. Granting in batches (e.g.
half the window at a time) amortizes the per-grant messaging.