HTTP

This chapter describes how to use the Gun client for communicating with an HTTP/1.1 or HTTP/2 server.

Streams

Every time a request is initiated, Gun creates a stream. A stream reference uniquely identifies a set of request and response and must be used to perform additional operations with a stream or to identify its messages.

Stream references use the Erlang reference data type and are therefore unique.

Streams can be canceled at any time. This will stop any further messages from being sent to the owner process. Depending on its capabilities, the server will also be instructed to cancel the request.

Canceling a stream may result in Gun dropping the connection temporarily, to avoid uploading or downloading data that will not be used.

Cancelling a stream
gun:cancel(ConnPid, StreamRef).

Sending requests

Gun provides many convenient functions for performing common operations, like GET, POST or DELETE. It also provides a general purpose function in case you need other methods.

The availability of these methods on the server can vary depending on the software used but also on a per-resource basis.

Gun will automatically set a few headers depending on the method used. For all methods however it will set the host header if it has not been provided in the request arguments.

This section focuses on the act of sending a request. The handling of responses will be explained further on.

GET and HEAD

Use gun:get/2,3,4 to request a resource.

GET "/organizations/ninenines"
StreamRef = gun:get(ConnPid, "/organizations/ninenines").
GET "/organizations/ninenines" with custom headers
StreamRef = gun:get(ConnPid, "/organizations/ninenines", [
    {<<"accept">>, "application/json"},
    {<<"user-agent">>, "revolver/1.0"}
]).

Note that the list of headers has the field name as a binary. The field value is iodata, which is either a binary or an iolist.

Use gun:head/2,3,4 if you don't need the response body.

HEAD "/organizations/ninenines"
StreamRef = gun:head(ConnPid, "/organizations/ninenines").
HEAD "/organizations/ninenines" with custom headers
StreamRef = gun:head(ConnPid, "/organizations/ninenines", [
    {<<"accept">>, "application/json"},
    {<<"user-agent">>, "revolver/1.0"}
]).

It is not possible to send a request body with a GET or HEAD request.

POST, PUT and PATCH

HTTP defines three methods to create or update a resource.

POST is generally used when the resource identifier (URI) isn't known in advance when creating a resource. POST can also be used to replace an existing resource, although PUT is more appropriate in that situation.

PUT creates or replaces a resource identified by the URI.

PATCH provides instructions on how to modify the resource.

Both POST and PUT send the entire resource representation in their request body. The PATCH method can be used when this is not desirable. The request body of a PATCH method may be a partial representation or a list of instructions on how to update the resource.

The gun:post/4,5, gun:put/4,5 and gun:patch/4,5 functions take a body as their fourth argument. These functions do not require any body-specific header to be set, although it is always recommended to set the content-type header. Gun will set the other headers automatically.

In this and the following examples in this section, gun:post can be replaced by gun:put or gun:patch for performing a PUT or PATCH request, respectively.

POST "/organizations/ninenines"
Body = "{\"msg\": \"Hello world!\"}",
StreamRef = gun:post(ConnPid, "/organizations/ninenines", [
    {<<"content-type">>, "application/json"}
], Body).

The gun:post/3, gun:put/3 and gun:patch/3 functions do not take a body in their arguments. If a body is to be provided later on, using the gun:data/4 function, then the request headers must indicate this. This can be done by setting the content-length or content-type request headers. If these headers are not set then Gun will assume the request has no body.

It is recommended to send the content-length header if you know it in advance, although this is not required. If it is not set, HTTP/1.1 will use the chunked transfer-encoding, and HTTP/2 will continue normally as it is chunked by design.

POST "/organizations/ninenines" with delayed body
Body = "{\"msg\": \"Hello world!\"}",
StreamRef = gun:post(ConnPid, "/organizations/ninenines", [
    {<<"content-length">>, integer_to_binary(length(Body))},
    {<<"content-type">>, "application/json"}
]),
gun:data(ConnPid, StreamRef, fin, Body).

The atom fin indicates this is the last chunk of data to be sent. You can call the gun:data/4 function as many times as needed until you have sent the entire body. The last call must use fin and all the previous calls must use nofin. The last chunk may be empty.

Streaming the request body
sendfile(ConnPid, StreamRef, Filepath) ->
    {ok, IoDevice} = file:open(Filepath, [read, binary, raw]),
    do_sendfile(ConnPid, StreamRef, IoDevice).

do_sendfile(ConnPid, StreamRef, IoDevice) ->
    case file:read(IoDevice, 8000) of
        eof ->
            gun:data(ConnPid, StreamRef, fin, <<>>),
            file:close(IoDevice);
        {ok, Bin} ->
            gun:data(ConnPid, StreamRef, nofin, Bin),
            do_sendfile(ConnPid, StreamRef, IoDevice)
    end.

DELETE

Use gun:delete/2,3,4 to delete a resource.

DELETE "/organizations/ninenines"
StreamRef = gun:delete(ConnPid, "/organizations/ninenines").
DELETE "/organizations/ninenines" with custom headers
StreamRef = gun:delete(ConnPid, "/organizations/ninenines", [
    {<<"user-agent">>, "revolver/1.0"}
]).

OPTIONS

Use gun:options/2,3 to request information about a resource.

OPTIONS "/organizations/ninenines"
StreamRef = gun:options(ConnPid, "/organizations/ninenines").
OPTIONS "/organizations/ninenines" with custom headers
StreamRef = gun:options(ConnPid, "/organizations/ninenines", [
    {<<"user-agent">>, "revolver/1.0"}
]).

You can also use this function to request information about the server itself.

OPTIONS "*"
StreamRef = gun:options(ConnPid, "*").

Requests with an arbitrary method

The gun:request/4,5,6 function can be used to send requests with a configurable method name. It is mostly useful when you need a method that Gun does not understand natively.

Example of a TRACE request
gun:request(ConnPid, "TRACE", "/", [
    {<<"max-forwards">>, "30"}
]).

Processing responses

All data received from the server is sent to the owner process as a message. First a gun_response message is sent, followed by zero or more gun_data messages. If something goes wrong, a gun_error message is sent instead.

The response message will inform you whether there will be data messages following. If it contains fin there will be no data messages. If it contains nofin then one or more data messages will follow.

When using HTTP/2 this value is sent with the frame and simply passed on in the message. When using HTTP/1.1 however Gun must guess whether data will follow by looking at the response headers.

You can receive messages directly, or you can use the await functions to let Gun receive them for you.

Receiving a response using receive
print_body(ConnPid, MRef) ->
    StreamRef = gun:get(ConnPid, "/"),
    receive
        {gun_response, ConnPid, StreamRef, fin, Status, Headers} ->
            no_data;
        {gun_response, ConnPid, StreamRef, nofin, Status, Headers} ->
            receive_data(ConnPid, MRef, StreamRef);
        {'DOWN', MRef, process, ConnPid, Reason} ->
            error_logger:error_msg("Oops!"),
            exit(Reason)
    after 1000 ->
        exit(timeout)
    end.

receive_data(ConnPid, MRef, StreamRef) ->
    receive
        {gun_data, ConnPid, StreamRef, nofin, Data} ->
            io:format("~s~n", [Data]),
            receive_data(ConnPid, MRef, StreamRef);
        {gun_data, ConnPid, StreamRef, fin, Data} ->
            io:format("~s~n", [Data]);
        {'DOWN', MRef, process, ConnPid, Reason} ->
            error_logger:error_msg("Oops!"),
            exit(Reason)
    after 1000 ->
        exit(timeout)
    end.

While it may seem verbose, using messages like this has the advantage of never locking your process, allowing you to easily debug your code. It also allows you to start more than one connection and concurrently perform queries on all of them at the same time.

You can also use Gun in a synchronous manner by using the await functions.

The gun:await/2,3,4 function will wait until it receives a response to, a pushed resource related to, or data from the given stream.

When calling gun:await/2,3 and not passing a monitor reference, one is automatically created for you for the duration of the call.

The gun:await_body/2,3,4 works similarly, but returns the body received. Both functions can be combined to receive the response and its body sequentially.

Receiving a response using await
StreamRef = gun:get(ConnPid, "/"),
case gun:await(ConnPid, StreamRef) of
    {response, fin, Status, Headers} ->
        no_data;
    {response, nofin, Status, Headers} ->
        {ok, Body} = gun:await_body(ConnPid, StreamRef),
        io:format("~s~n", [Body])
end.

Handling streams pushed by the server

The HTTP/2 protocol allows the server to push more than one resource for every request. It will start sending those extra resources before it starts sending the response itself, so Gun will send you gun_push messages before gun_response when that happens.

You can safely choose to ignore gun_push messages, or you can handle them. If you do, you can either receive the messages directly or use await functions.

The gun_push message contains both the new stream reference and the stream reference of the original request.

Receiving a pushed response using receive
receive
    {gun_push, ConnPid, OriginalStreamRef, PushedStreamRef,
            Method, Host, Path, Headers} ->
        enjoy()
end.

If you use the gun:await/2,3,4 function, however, Gun will use the original reference to identify the message but will return a tuple that doesn't contain it.

Receiving a pushed response using await
{push, PushedStreamRef, Method, URI, Headers}
    = gun:await(ConnPid, OriginalStreamRef).

The PushedStreamRef variable can then be used with gun:await/2,3,4 and gun:await_body/2,3,4.

Flushing unwanted messages

Gun provides the function gun:flush/1 to quickly get rid of unwanted messages sitting in the process mailbox. You can use it to get rid of all messages related to a connection, or just the messages related to a stream.

Flush all messages from a Gun connection
gun:flush(ConnPid).
Flush all messages from a specific stream
gun:flush(StreamRef).

Redirecting responses to a different process

Gun allows you to specify which process will handle responses to a request via the reply_to request option.

GET "/organizations/ninenines" to a different process
StreamRef = gun:get(ConnPid, "/organizations/ninenines", [],
    #{reply_to => Pid}).

Gun 1.3 User Guide

Navigation

Version select

Like my work? Donate!

Donate to Loïc Hoguin because his work on Cowboy, Ranch, Gun and Erlang.mk is fantastic:

Recurring payment options are also available via GitHub Sponsors. These funds are used to cover the recurring expenses like food, dedicated servers or domain names.