HTTP Guide
View SourceThis guide covers hackney's HTTP features in depth.
Request Anatomy
hackney:request(Method, URL, Headers, Body, Options) ->
{ok, StatusCode, RespHeaders, Body} | {error, Reason}Body is always returned directly in the response for consistent behavior across HTTP/1.1, HTTP/2, and HTTP/3.
Request Bodies
Binary Body
hackney:post(URL,
[{<<"content-type">>, <<"application/json">>}],
<<"{\"key\": \"value\"}">>
).Form-Encoded Body
hackney:post(URL, [], {form, [{<<"key">>, <<"value">>}]}).Multipart Body
Multipart requests are used to upload files and send form data together.
Basic File Upload
hackney:post(URL, [], {multipart, [
{<<"field">>, <<"value">>},
{file, <<"/path/to/file.txt">>}
]}).File Upload with Custom Field Name
Use {file, Path, FieldName, ExtraHeaders} to specify the form field name:
%% Upload file to "attachment" field instead of default "file"
hackney:post(URL, [], {multipart, [
{file, <<"/path/to/document.pdf">>, <<"attachment">>, []}
]}).File Upload with Full Control
For complete control over the Content-Disposition header:
Path = <<"/path/to/photo.jpg">>,
FName = hackney_bstr:to_binary(filename:basename(Path)),
Disposition = {<<"form-data">>,
[{<<"name">>, <<"photo">>},
{<<"filename">>, FName}]},
hackney:post(URL, [], {multipart, [
{file, Path, Disposition, []}
]}).Mixed File and Text Fields
Combine file uploads with text fields:
hackney:post(URL, [], {multipart, [
{file, <<"/path/to/image.jpg">>, <<"image">>, []},
{<<"title">>, <<"My Photo">>},
{<<"description">>, <<"A nice picture">>}
]}).Text Fields with Explicit Content-Type
Some servers require explicit content-type for text fields:
hackney:post(URL, [], {multipart, [
{file, <<"/path/to/doc.pdf">>, <<"document">>, []},
{<<"name">>, <<"Report">>, [{<<"content-type">>, <<"text/plain">>}]}
]}).Supported Part Formats
| Format | Description |
|---|---|
{file, Path} | File with auto-generated field name |
{file, Path, ExtraHeaders} | File with extra headers |
{file, Path, FieldName, ExtraHeaders} | File with custom field name |
{file, Path, {Disposition, Params}, ExtraHeaders} | Full control |
{Name, Data} | Text field (Data must be binary) |
{Name, Data, ExtraHeaders} | Text field with headers |
{Name, Data, Disposition, ExtraHeaders} | Text field with full control |
Streaming Body
{ok, Ref} = hackney:post(URL, Headers, stream),
ok = hackney:send_body(Ref, <<"chunk1">>),
ok = hackney:send_body(Ref, <<"chunk2">>),
ok = hackney:finish_send_body(Ref),
{ok, Status, RespHeaders, Ref} = hackney:start_response(Ref).Response Handling
Read Full Body
%% Body is returned directly
{ok, 200, Headers, Body} = hackney:get(URL).Automatic Decompression
Hackney can automatically decompress gzip and deflate encoded responses:
{ok, 200, Headers, Body} = hackney:get(URL, [], <<>>, [
{auto_decompress, true}
]).When auto_decompress is enabled:
- Adds
Accept-Encoding: gzip, deflateheader to requests - Automatically decompresses the response body based on
Content-Encoding - Supports gzip, deflate, and x-gzip encodings
- Non-compressed responses are returned unchanged
Stream Response Body (Async Mode)
For incremental body streaming, use async mode:
{ok, Ref} = hackney:get(URL, [], <<>>, [async]),
stream_loop(Ref).
stream_loop(Ref) ->
receive
{hackney_response, Ref, {status, Status, _}} ->
io:format("Status: ~p~n", [Status]),
stream_loop(Ref);
{hackney_response, Ref, {headers, Headers}} ->
io:format("Headers: ~p~n", [Headers]),
stream_loop(Ref);
{hackney_response, Ref, done} ->
ok;
{hackney_response, Ref, Chunk} when is_binary(Chunk) ->
process_chunk(Chunk),
stream_loop(Ref)
end.HTTP/2 Support
Hackney automatically negotiates HTTP/2 for HTTPS connections via ALPN.
Response format is consistent across all protocols - body is always returned directly.
Automatic HTTP/2
%% HTTP/2 used automatically when server supports it
{ok, 200, Headers, Body} = hackney:get(<<"https://nghttp2.org/">>).Force Protocol
%% HTTP/2 only
hackney:get(URL, [], <<>>, [{protocols, [http2]}]).
%% HTTP/1.1 only
hackney:get(URL, [], <<>>, [{protocols, [http1]}]).Detect Protocol
HTTP/2 responses have lowercase header names:
case hd(Headers) of
{<<"date">>, _} -> http2;
{<<"Date">>, _} -> http1
end.For details on multiplexing, server push, and architecture, see the HTTP/2 Guide.
Async Responses
{ok, Ref} = hackney:get(URL, [], <<>>, [async]),
receive
{hackney_response, Ref, {status, Status, _}} -> ok
end,
receive
{hackney_response, Ref, {headers, Headers}} -> ok
end,
receive
{hackney_response, Ref, done} -> ok;
{hackney_response, Ref, Bin} -> ok
end.Async Once
{ok, Ref} = hackney:get(URL, [], <<>>, [{async, once}]),
receive {hackney_response, Ref, Msg} -> ok end,
hackney:stream_next(Ref). %% Request next messageStream to Another Process
Use stream_to to send async messages to a different process:
Receiver = spawn(fun() -> receive_loop() end),
{ok, Ref} = hackney:get(URL, [], <<>>, [
async,
{stream_to, Receiver}
]).When stream_to is specified:
- The connection is owned by the
stream_toprocess, not the caller - If
stream_todies, the connection terminates - If the original caller dies, the connection continues as long as
stream_tois alive - This ensures proper cleanup when the message recipient terminates
Connection Pooling
Default Pool
hackney:get(URL). %% Uses default poolNamed Pools
hackney_pool:start_pool(my_api, [
{max_connections, 100},
{timeout, 150000}
]),
hackney:get(URL, [], <<>>, [{pool, my_api}]).Manual Connection Management
For fine-grained control, you can create a connection and reuse it for multiple requests. This works for both HTTP/1.1 and HTTP/2.
Get a Connection
%% Connect to a host (returns a connection PID)
{ok, ConnPid} = hackney:connect(hackney_ssl, "example.com", 443, []).
%% Or from a URL
{ok, ConnPid} = hackney:connect(<<"https://example.com">>).Check the Protocol
%% See which protocol was negotiated
Protocol = hackney_conn:get_protocol(ConnPid). %% http1 | http2 | http3Send Requests on the Connection
%% Send multiple requests on the same connection
{ok, 200, Headers1, Body1} = hackney:send_request(ConnPid, {get, <<"/api/users">>, [], <<>>}).
{ok, 201, Headers2, Body2} = hackney:send_request(ConnPid, {post, <<"/api/users">>,
[{<<"content-type">>, <<"application/json">>}],
<<"{\"name\": \"Alice\"}">>}).
{ok, 200, Headers3, Body3} = hackney:send_request(ConnPid, {get, <<"/api/users/1">>, [], <<>>}).Close the Connection
hackney:close(ConnPid).Complete Example
%% Reuse a connection for multiple API calls
{ok, Conn} = hackney:connect(hackney_ssl, "api.example.com", 443, []),
%% Check protocol (optional)
case hackney_conn:get_protocol(Conn) of
http2 -> io:format("Using HTTP/2 multiplexing~n");
http1 -> io:format("Using HTTP/1.1 keep-alive~n")
end,
%% Make requests
{ok, 200, _, Token} = hackney:send_request(Conn, {post, <<"/auth">>, [], Credentials}),
{ok, 200, _, Users} = hackney:send_request(Conn, {get, <<"/users">>, AuthHeaders, <<>>}),
{ok, 200, _, Data} = hackney:send_request(Conn, {get, <<"/data">>, AuthHeaders, <<>>}),
%% Clean up
hackney:close(Conn).HTTP/1.1 vs HTTP/2 Behavior
| Aspect | HTTP/1.1 | HTTP/2 |
|---|---|---|
| Requests | Sequential (one at a time) | Multiplexed (concurrent) |
| Connection | Keep-alive between requests | Single connection, multiple streams |
| Use case | Simple sequential calls | High-throughput parallel calls |
For HTTP/2 multiplexing (parallel requests on one connection), see the HTTP/2 Guide.
Redirects
{ok, 200, Headers, Body} = hackney:get(URL, [], <<>>, [
{follow_redirect, true},
{max_redirect, 5}
]).Proxies
HTTP Proxy
hackney:get(URL, [], <<>>, [
{proxy, <<"http://proxy:8080">>}
]).Environment Variables
hackney reads HTTP_PROXY, HTTPS_PROXY, NO_PROXY automatically.
SSL/TLS
Custom CA Certificate
hackney:get(URL, [], <<>>, [
{ssl_options, [{cacertfile, "/path/to/ca.crt"}]}
]).Skip Verification
hackney:get(URL, [], <<>>, [insecure]).Timeouts
hackney:get(URL, [], <<>>, [
{connect_timeout, 5000},
{recv_timeout, 30000}
]).Cookies
hackney:get(URL, [], <<>>, [{cookie, <<"session=abc">>}]).
%% Parse response cookies
{ok, 200, Headers, _} = hackney:get(URL),
Cookies = hackney:cookies(Headers).Basic Authentication
hackney:get(URL, [], <<>>, [
{basic_auth, {<<"user">>, <<"pass">>}}
]).Next Steps
- HTTP/2 Guide - Multiplexing, server push, architecture
- WebSocket Guide
- Migration Guide