From 83d8b63b8abb46b374439c8c8571091968af6260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 25 Mar 2015 13:44:08 +0100 Subject: Update the guide A number of @todo remain in it and will be worked on shortly. The guide has been converted to Asciidoc and 'make asciidoc' will generate a PDF and a chunked HTML version. --- doc/src/guide/http.asciidoc | 363 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 doc/src/guide/http.asciidoc (limited to 'doc/src/guide/http.asciidoc') diff --git a/doc/src/guide/http.asciidoc b/doc/src/guide/http.asciidoc new file mode 100644 index 0000000..929860a --- /dev/null +++ b/doc/src/guide/http.asciidoc @@ -0,0 +1,363 @@ +== HTTP + +This chapter describes how to use the Gun client for +communicating with an HTTP/1.1 or SPDY server. + +=== Streams + +Every time a request is initiated, Gun creates a _stream_. +A _stream reference_ uniquely identifies a set of request and +response(s) 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 +[source,erlang] +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}` to request a resource. + +.GET "/organizations/ninenines" + +[source,erlang] +StreamRef = gun:get(ConnPid, "/organizations/ninenines"). + +.GET "/organizations/ninenines" with custom headers + +[source,erlang] +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}` if you don't need the response body. + +.HEAD "/organizations/ninenines" + +[source,erlang] +StreamRef = gun:head(ConnPid, "/organizations/ninenines"). + +.HEAD "/organizations/ninenines" with custom headers + +[source,erlang] +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 the 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`, `gun:put/4` and `gun:patch/4` 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" + +[source,erlang] +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 SPDY will continue normally as it is chunked by design. + +@todo Stop relying on transfer encoding header in Gun + +@todo SPDY needs to remove invalid headers, and to detect +a body if content-length is set + +.POST "/organizations/ninenines" with delayed body + +[source,erlang] +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. + +@todo what to do about empty chunk, ignore? + +.Streaming the request body + +[source,erlang] +---- +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}` to delete a resource. + +.DELETE "/organizations/ninenines" + +[source,erlang] +StreamRef = gun:delete(ConnPid, "/organizations/ninenines"). + +.DELETE "/organizations/ninenines" with custom headers + +[source,erlang] +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" + +[source,erlang] +StreamRef = gun:options(ConnPid, "/organizations/ninenines"). + +.OPTIONS "/organizations/ninenines" with custom headers + +[source,erlang] +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 "*" + +[source,erlang] +StreamRef = gun:options(ConnPid, "*"). + +==== Requests with an arbitrary method + +The `gun:request/{4,5}` 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 + +[source,erlang] +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 SPDY 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 + +[source,erlang] +---- +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. The `gun:await/2` call will automatically monitor +the process and use a timeout of 5000. The monitor and the +timeout can be passed by using `gun:await/3` or `gun:await/4`. + +The `gun:await_body/{2,3,4}` works similarly, but returns the +body received. + +.Receiving a response using await + +[source,erlang] +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 SPDY 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 + +[source,erlang] +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 + +[source,erlang] +{push, PushedStreamRef, Method, Host, Path, Headers} + = gun:await(ConnPid, OriginalStreamRef). + +The `PushedStreamRef` variable can then be used with `gun:await_body/{2,3,4}` +if needed. + +=== 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 + +[source,erlang] +gun:flush(ConnPid). + +.Flush all messages from a specific stream + +[source,erlang] +gun:flush(StreamRef). -- cgit v1.2.3