diff options
author | Loïc Hoguin <[email protected]> | 2015-03-25 13:44:08 +0100 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2015-03-25 13:44:08 +0100 |
commit | 83d8b63b8abb46b374439c8c8571091968af6260 (patch) | |
tree | 11a7bb370d43a7c25e4734eb7a6f7649ce9cdc61 | |
parent | eab45765497e6eb3e031cba5dad66a7a20ec3651 (diff) | |
download | gun-83d8b63b8abb46b374439c8c8571091968af6260.tar.gz gun-83d8b63b8abb46b374439c8c8571091968af6260.tar.bz2 gun-83d8b63b8abb46b374439c8c8571091968af6260.zip |
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.
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | doc/src/guide/book.asciidoc | 16 | ||||
-rw-r--r-- | doc/src/guide/connect.asciidoc | 119 | ||||
-rw-r--r-- | doc/src/guide/http.asciidoc | 363 | ||||
-rw-r--r-- | doc/src/guide/introduction.asciidoc (renamed from guide/introduction.md) | 25 | ||||
-rw-r--r-- | doc/src/guide/protocols.asciidoc | 119 | ||||
-rw-r--r-- | doc/src/guide/start.asciidoc | 67 | ||||
-rw-r--r-- | doc/src/guide/websocket.asciidoc | 100 | ||||
-rw-r--r-- | guide/connect.md | 96 | ||||
-rw-r--r-- | guide/http.md | 227 | ||||
-rw-r--r-- | guide/protocols.md | 79 | ||||
-rw-r--r-- | guide/toc.md | 11 | ||||
-rw-r--r-- | guide/websocket.md | 85 |
13 files changed, 803 insertions, 516 deletions
@@ -4,9 +4,7 @@ PROJECT = gun # Options. -CT_SUITES = twitter ws CT_OPTS += -pa test -ct_hooks gun_ct_hook [] -boot start_sasl - PLT_APPS = ssl # Dependencies. @@ -20,3 +18,13 @@ dep_ct_helper = git https://github.com/extend/ct_helper.git master # Standard targets. include erlang.mk + +# AsciiDoc. + +asciidoc: + a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf + a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/* doc/ + rmdir doc/src/guide/book.chunked + +clean:: + $(gen_verbose) rm doc/guide.pdf doc/*.html doc/*.css diff --git a/doc/src/guide/book.asciidoc b/doc/src/guide/book.asciidoc new file mode 100644 index 0000000..7e6a441 --- /dev/null +++ b/doc/src/guide/book.asciidoc @@ -0,0 +1,16 @@ +// a2x: --dblatex-opts "-P latex.output.revhistory=0 -P doc.publisher.show=0 -P index.numbered=0" +// a2x: -d book --attribute tabsize=4 + += Gun User Guide + +include::introduction.asciidoc[Introduction] + +include::start.asciidoc[Starting and stopping] + +include::protocols.asciidoc[Supported protocols] + +include::connect.asciidoc[Connection] + +include::http.asciidoc[Using HTTP] + +include::websocket.asciidoc[Using Websocket] diff --git a/doc/src/guide/connect.asciidoc b/doc/src/guide/connect.asciidoc new file mode 100644 index 0000000..e1ad56e --- /dev/null +++ b/doc/src/guide/connect.asciidoc @@ -0,0 +1,119 @@ +== Connection + +This chapter describes how to open, monitor and close +a connection using the Gun client. + +=== Gun connections + +Gun is designed with the SPDY and Websocket protocols in mind. +They are built for long-running connections that allow concurrent +exchange of data, either in the form of request/responses for +SPDY or in the form of messages for Websocket. + +A Gun connection is an Erlang process that manages a socket to +a remote endpoint. This Gun connection is owned by a user +process that is called the _owner_ of the connection, and is +managed by the supervision tree of the `gun` application. + +The owner process communicates with the Gun connection +by calling functions from the module `gun`. All functions +perform their respective operations asynchronously. The Gun +connection will send Erlang messages to the owner process +whenever needed. + +When the remote endpoint closes the connection, Gun attempts +to reconnect automatically. + +=== Opening a new connection + +The `gun:open/{2,3}` function must be used to open a connection. + +.Opening a connection to example.org on port 443 + +[source,erlang] +{ok, ConnPid} = gun:open("example.org", 443). + +@todo open/3 +@todo make opts a map + +If the port given is 80, Gun will attempt to connect using +TCP and use the HTTP/1.1 protocol. For any other port, TLS +will be used. The NPN extension for TLS allows Gun to select +SPDY automatically if the server supports it. Otherwise, +HTTP/1.1 will be used. + +@todo more about defaults + +=== Monitoring the connection process + +@todo Gun should detect the owner process being killed + +Because software errors are unavoidable, it is important to +detect when the Gun process crashes. It is also important +to detect when it exits normally. Erlang provides two ways +to do that: links and monitors. + +Gun leaves you the choice as to which one will be used. +However, if you use the `gun:await/{2,3}` or `gun:await_body/{2,3}` +functions, a monitor may be used for you to avoid getting +stuck waiting for a message that will never come. + +If you choose to monitor yourself you can do it on a permanent +basis rather than on every message you will receive, saving +resources. Indeed, the `gun:await/{3,4}` and `gun:await_body/{3,4}` +functions both accept a monitor argument if you have one already. + +.Monitoring the connection process + +[source,erlang] +{ok, ConnPid} = gun:open("example.org", 443). +MRef = monitor(process, ConnPid). + +This monitor reference can be kept and used until the connection +process exits. + +.Handling `DOWN` messages + +[source,erlang] +receive + %% Receive Gun messages here... + {DOWN', Mref, process, ConnPid, Reason} -> + error_logger:error_msg("Oops!"), + exit(Reason); +end. + +What to do when you receive a `DOWN` message is entirely up to you. + +=== Closing the connection abruptly + +The connection can be stopped abruptly at any time by calling +the `gun:close/1` function. + +.Immediate closing of the connection + +[source,erlang] +gun:close(ConnPid). + +The process is stopped immediately without having a chance to +perform the protocol's closing handshake, if any. + +=== Closing the connection gracefully + +The connection can also be stopped gracefully by calling the +`gun:shutdown/1` function. + +.Graceful shutdown of the connection + +[source,erlang] +gun:shutdown(ConnPid). + +Gun will refuse any new requests or messages after you call +this function. It will however continue to send you messages +for existing streams until they are all completed. + +For example if you performed a GET request just before calling +`gun:shutdown/1`, you will still receive the response before +Gun closes the connection. + +If you set a monitor beforehand, you will receive a message +when the connection has been closed. 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). diff --git a/guide/introduction.md b/doc/src/guide/introduction.asciidoc index ca417ec..ade3d80 100644 --- a/guide/introduction.md +++ b/doc/src/guide/introduction.asciidoc @@ -1,35 +1,28 @@ -Introduction -============ +== Introduction -Purpose -------- +Gun is an Erlang HTTP client with support for HTTP/1.1, SPDY and Websocket. -Gun is an asynchronous SPDY, HTTP and Websocket client. +=== Prerequisites -Prerequisites -------------- - -Knowledge of Erlang, but also of the HTTP, SPDY and Websocket +Knowledge of Erlang, but also of the HTTP/1.1, SPDY and Websocket protocols is required in order to read this guide. -Supported platforms -------------------- +=== Supported platforms Gun is tested and supported on Linux. -Gun is developed for Erlang R16B+. +Gun is developed for Erlang 17+. Gun may be compiled on earlier Erlang versions with small source code -modifications but there is no guarantee that it will work as expected. +modifications but there is no guarantee that it will work as intended. -Conventions ------------ +=== Conventions In the HTTP protocol, the method name is case sensitive. All standard method names are uppercase. Header names are case insensitive. Gun converts all the header names to lowercase, and expects your application to provide lowercase header -names also. +names. The same applies to any other case insensitive value. diff --git a/doc/src/guide/protocols.asciidoc b/doc/src/guide/protocols.asciidoc new file mode 100644 index 0000000..2180c5b --- /dev/null +++ b/doc/src/guide/protocols.asciidoc @@ -0,0 +1,119 @@ +== Supported protocols + +This chapter describes the protocols supported and the +operations available to them. + +=== HTTP/1.1 + +HTTP/1.1 is a text request-response protocol. The client +sends a request, the server sends back a response. + +Gun provides convenience functions for performing GET, HEAD, +OPTIONS, POST, PATCH, PUT, and DELETE requests. All these +functions are aliases of `gun:request/{4,5}` for each respective +methods. Gun also provides a `gun:data/4` function for streaming +the request body. + +Gun will send a `gun_response` message for every response +received, followed by zero or more `gun_data` messages for +the response body. If something goes wrong, a `gun_error` +will be sent instead. + +Gun provides convenience functions for dealing with messages. +The `gun:await/{2,3,4}` function waits for a response to the given +request, and the `gun:await_body/{2,3,4}` function for the +response's body. The `gun:flush/1` function can be used to clear all +messages related to a request or a connection from the mailbox +of the process. + +The function `gun:cancel/2` can be used to silence the +response to a request previously sent if it is no longer +needed. When using HTTP/1.1 there is no multiplexing so +Gun will have to receive the response fully before any +other response can be received. + +Finally, Gun can upgrade an HTTP/1.1 connection to Websocket. +It provides the `gun:ws_upgrade/{2,3,4}` function for that +purpose. A `gun_ws_upgrade` message will be sent on success; +a `gun_response` message otherwise. + +=== SPDY + +SPDY is a binary protocol based on HTTP, compatible with +the HTTP semantics, that reduces the complexity of parsing +requests and responses, compresses the HTTP headers and +allows the server to push multiple responses to a single +request. + +The SPDY interface is very similar to HTTP/1.1, so this +section instead focuses on the differences in the interface +for the two protocols. + +Because a SPDY server can push multiple responses to a +single request, Gun might send `gun_push` messages for +every push received. They can be ignored safely if they +are not needed. + +The `gun:cancel/2` function will use the SPDY stream +cancellation mechanism which allows Gun to inform the +server to stop sending a response for this particular +request, saving resources. + +It is not possible to upgrade a SPDY connection to Websocket +due to protocol limitations. + +=== Websocket + +Websocket is a binary protocol built on top of HTTP that +allows asynchronous concurrent communication between the +client and the server. A Websocket server can push data to +the client at any time. + +Websocket is only available as a connection upgrade over +an HTTP/1.1 connection. + +Once the Websocket connection is established, the only +operation available on this connection is sending Websocket +frames using `gun:ws_send/2`. + +Gun will send a `gun_ws` message for every frame received. + +=== Summary + +The two following tables summarize the supported operations +and the messages Gun sends depending on the connection's +current protocol. + +.Supported operations per protocol +[cols="<,3*^",options="header"] +|=== +| Operation | HTTP/1.1 | SPDY | Websocket +| delete | yes | yes | no +| get | yes | yes | no +| head | yes | yes | no +| options | yes | yes | no +| patch | yes | yes | no +| post | yes | yes | no +| put | yes | yes | no +| request | yes | yes | no +| data | yes | yes | no +| await | yes | yes | no +| await_body | yes | yes | no +| flush | yes | yes | no +| cancel | yes | yes | no +| ws_upgrade | yes | no | no +| ws_send | no | no | yes +|=== + +.Messages sent per protocol +[cols="<,3*^",options="header"] +|=== +| Message | HTTP/1.1 | SPDY | Websocket +| gun_push | no | yes | no +| gun_response | yes | yes | no +| gun_data | yes | yes | no +| gun_error (StreamRef) | yes | yes | no +| gun_error | yes | yes | yes +| gun_ws_upgrade | yes | no | no +| gun_ws | no | no | yes +|=== diff --git a/doc/src/guide/start.asciidoc b/doc/src/guide/start.asciidoc new file mode 100644 index 0000000..6d93e2e --- /dev/null +++ b/doc/src/guide/start.asciidoc @@ -0,0 +1,67 @@ +== Starting and stopping + +This chapter describes how to start and stop the Gun application. + +=== Setting up + +Before Gun can be used it needs to be in Erlang's `ERL_LIBS` path variable. +If you use `erlang.mk` or a similar build tool, you only need to specify +Gun as a dependency to your application and the tool will take care +of downloading Gun and setting up paths. + +With `erlang.mk` this is done by adding `gun` to the `DEPS` variable +in your Makefile. + +.Adding Gun as an erlang.mk dependency + +[source,make] +DEPS = gun + +=== Starting + +Gun is an _OTP application_. It needs to be started before you can +use it. + +.Starting Gun in an Erlang shell + +[source,erlang] +---- +1> application:ensure_all_started(gun). +{ok,[ranch,crypto,cowlib,asn1,public_key,ssl,gun]} +---- + +=== Stopping + +You can stop Gun using the `application:stop/1` function, however +only Gun will be stopped. This is the equivalent of `application:start/1`. +The `application_ensure_all_started/1` function has no equivalent for +stopping all applications. + +.Stopping Gun + +[source,erlang] +application:stop(gun). + +=== Using Gun with releases + +An _OTP release_ starts applications automatically. All you need +to do is to set up your application resource file so that Gun can +be included in the release. The application resource file can be +found in `ebin/your_application.app`, or in `src/your_application.app.src` +if you are using a build tool like `erlang.mk`. + +The key you need to change is the `applications` key. By default +it only includes `kernel` and `stdlib`. You need to add `gun` to +that list. + +.Adding Gun to the application resource file + +[source,erlang] +{applications, [ + kernel, + stdlib, + gun +]} + +Do not put an extra comma at the end, the comma is a separator +between the elements of the list. diff --git a/doc/src/guide/websocket.asciidoc b/doc/src/guide/websocket.asciidoc new file mode 100644 index 0000000..011e457 --- /dev/null +++ b/doc/src/guide/websocket.asciidoc @@ -0,0 +1,100 @@ +== Websocket + +This chapter describes how to use the Gun client for +communicating with a Websocket server. + +@todo recovering from connection failure +reconnecting to Websocket etc. + +=== HTTP upgrade + +Websocket is a protocol built on top of HTTP. To use Websocket, +you must first request for the connection to be upgraded. Only +HTTP/1.1 connections can be upgraded to Websocket, so you might +need to restrict the protocol to HTTP/1.1 if you are planning +to use Websocket over TLS. + +@todo add option to disable specific protocols + +You must use the `gun_ws:upgrade/{2,3}` function to upgrade +to Websocket. This function can be called anytime after connection, +so you can send HTTP requests before upgrading to Websocket. + +.Upgrade to Websocket + +[source,erlang] +gun:ws_upgrade(ConnPid, "/websocket"). + +Gun will set all the necessary headers for performing the +Websocket upgrade, but you can specify additional headers +if needed. For example you can request a custom sub-protocol. + +.Upgrade to Websocket and request a protocol + +[source,erlang] +gun:ws_upgrade(ConnPid, "/websocket", [ + {<<"sec-websocket-protocol">>, "mychat"} +]). + +The success or failure of this operation will be sent as a +message. + +@todo hmm we want the headers to be sent in the gun_ws_upgrade ok message too + +[source,erlang] +receive + {gun_ws_upgrade, ConnPid, ok} -> + upgrade_success(ConnPid); + {gun_ws_upgrade, ConnPid, error, IsFin, Status, Headers} -> + exit({ws_upgrade_failed, Status, Headers}); + %% More clauses here as needed. +after 1000 -> + exit(timeout); +end. + +=== Sending data + +Once the Websocket upgrade has completed successfully, you no +longer have access to functions for performing requests. You +can only send and receive Websocket messages. + +Use `gun:ws_send/2` to send one or more messages to the server. + +@todo Implement sending of N frames + +.Send a text frame + +[source,erlang] +gun:ws_send(ConnPid, {text, "Hello!"}). + +.Send a text frame, a binary frame and then close the connection + +[source,erlang] +gun:ws_send(ConnPid, [ + {text, "Hello!"}, + {binary, BinaryValue}, + close +]). + +Note that if you send a close frame, Gun will close the connection +cleanly and will not attempt to reconnect afterwards, similar to +calling `gun:shutdown/1`. + +=== Receiving data + +Gun sends an Erlang message to the owner process for every +Websocket message it receives. + +[source,erlang] +receive + {gun_ws, ConnPid, Frame} -> + handle_frame(ConnPid, Frame) +end. + +@todo auto ping has not been implemented yet + +Gun will automatically send ping messages to the server to keep +the connection alive, however if the connection dies and Gun has +to reconnect it will not upgrade to Websocket automatically, you +need to perform the operation when you receive the `gun_error` +message. diff --git a/guide/connect.md b/guide/connect.md deleted file mode 100644 index 5655d66..0000000 --- a/guide/connect.md +++ /dev/null @@ -1,96 +0,0 @@ -Connection -========== - -This chapter describes how to open, monitor and close -a connection using the Gun client. - -Opening a new connection ------------------------- - -Gun is designed with the SPDY and Websocket protocols in mind, -and as such establishes a permanent connection to the remote -server. Because of this, the connection must be initiated -before being able to send any request. - -The process that creates the connection is also known as the -owner of the connection, or the controlling process.. Only -this process can perform operations on the connection, and -only this process will receive messages from the connection. - -To open a new connection, the `gun:open/{2,3}` function can be used. - -``` erlang -{ok, Pid} = gun:open("twitter.com", 443). -``` - -Gun will by default assume that SSL should be used. - -The connection is managed by a separate process and is supervised -by the Gun supervisor directly. - -The connection can later be stopped either gracefully or abruptly -by the client. If an unexpected disconnection occurs, the client -will retry connecting every few seconds until it succeeds and -can resume normal operations. - -Monitoring the connection process ---------------------------------- - -The connection is managed by a separate process. Because -software errors are a reality, it is important to monitor -this process for failure. Thankfully, due to the asynchronous -nature of Gun, we only need to create a monitor once when -the connection is established. - -``` erlang -{ok, Pid} = gun:open("twitter.com", 443). -MRef = monitor(process, Pid). -``` - -There is no need to monitor again after that regardless of -the number of requests sent or messages received. - -You can detect the process failure when receiving messages. - -``` erlang -receive - {'DOWN', Tag, _, _, Reason} -> - error_logger:error_msg("Oops!"), - exit(Reason); - %% Receive Gun messages here... -end. -``` - -You will probably want to reopen the connection when that -happens. - -Closing the connection abruptly -------------------------------- - -The connection can be stopped abruptly at any time by calling -the `gun:close/1` function. - -``` erlang -gun:close(Pid). -``` - -The process is stopped immediately. - -Closing the connection gracefully ---------------------------------- - -The connection can also be stopped gracefully by calling the -`gun:shutdown/1` function. - -``` erlang -gun:shutdown(Pid). -``` - -Gun will refuse any new requests from both the Erlang side and -the server and will attempt to finish the currently opened -streams. For example if you performed a GET request just before -calling `gun:shutdown/1`, you will still receive the response -before Gun closes the connection. - -If you set a monitor beforehand, it will inform you when the -connection has finally been shutdown. diff --git a/guide/http.md b/guide/http.md deleted file mode 100644 index 2b16f1a..0000000 --- a/guide/http.md +++ /dev/null @@ -1,227 +0,0 @@ -Using HTTP -========== - -This chapter describes how to use the Gun client for -communicating with an HTTP or SPDY server. - -Streams -------- - -Every time a request is initiated, either by the client or the -server, Gun creates a "stream". The stream controls whether -the endpoints are still sending any data, and allows you to -identify incoming messages. - -Streams are references in Gun, and are therefore always unique. - -Streams can be canceled at any time. This will stop any further -messages being sent to the controlling process. Depending on -its capabilities, the server will also be instructed to drop -the request. - -Canceling a stream may result in Gun dropping the connection -temporarily, to avoid uploading or downloading data that will -not be used. This situation can only occur with HTTP, as SPDY -features stream canceling as part of its protocol. - -To cancel a stream, the `gun:cancel/2` function can be used. - -``` erlang -gun:cancel(Pid, 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. - -To retrieve a resource, `gun:get/{2,3}` can be used. If you -don't need the response body, `gun:head/{2,3}` is available. -As this type of requests can't have a request body, only the -path and optionally the headers can be specified. - -``` erlang -%% Without headers. -StreamRef = gun:get(Pid, "/organizations/extend"). -%% With headers. -StreamRef = gun:get(Pid, "/organizations/extend", [ - {"accept", "application/json"}, - {"user-agent", "revolver/1.0"}]). -``` - -To create or update a resource, the functions `gun:patch/{3,4}`, -`gun:post/{3,4}` and `gun:put/{3,4}` can be used. As this type -of request is meant to come with a body, headers are not optional, -because you must specify at least the content-type of the body, -and if possible also the content-length. The body is however -optional, because there might not be any at all, or because it -will be subsequently streamed. If a body is set here it is assumed -to be the full body. - -``` erlang -%% Without body. -StreamRef = gun:put(Pid, "/organizations/extend", [ - {"content-length", 23}, - {"content-type", "application/json"}]). -%% With body. -StreamRef = gun:put(Pid, "/organizations/extend", [ - {"content-length", 23}, - {"content-type", "application/json"}], - "{\"msg\": \"Hello world!\"}"). -``` - -To delete a resource, the `gun:delete/{2,3}` function can be -used. It works similarly to the GET and HEAD functions. - -``` erlang -%% Without headers. -StreamRef = gun:delete(Pid, "/organizations/extend"). -%% With headers. -StreamRef = gun:delete(Pid, "/organizations/extend", [ - {"accept", "application/json"}, - {"user-agent", "revolver/1.0"}]). -``` - -To obtain the functionality available for a given resource, -the `gun:options/{2,3}` can be used. It also works like the -GET and HEAD functions. - -``` erlang -%% Without headers. -StreamRef = gun:options(Pid, "/organizations/extend"). -%% With headers. -StreamRef = gun:options(Pid, "/organizations/extend", [ - {"accept", "application/json"}, - {"user-agent", "revolver/1.0"}]). -``` - -You can obtain information about the server as a whole by -using the special path `"*"`. - -``` erlang -StreamRef = gun:options(Pid, "*"). -``` - -Streaming data --------------- - -When a PATCH, POST or PUT operation is performed, and a -content-type is specified but no body is given, Gun will -expect data to be streamed to the connection using the -`gun:data/4` function. - -This function can be called as many times as needed until -all data is sent. The third argument needs to be `nofin` -when there is remaining data to be sent, and `fin` for the -last chunk. The last chunk may be empty if needed. - -For example, with an `IoDevice` opened like follow: - -``` erlang -{ok, IoDevice} = file:open(Filepath, [read, binary, raw]). -``` - -The following function will stream all data until the end -of the file: - -``` erlang -sendfile(Pid, StreamRef, IoDevice) -> - case file:read(IoDevice, 8000) of - eof -> - gun:data(Pid, StreamRef, fin, <<>>), - file:close(IoDevice); - {ok, Bin} -> - gun:data(Pid, StreamRef, nofin, Bin), - sendfile(Pid, StreamRef, IoDevice) - end. -``` - -Receiving responses -------------------- - -All data received from the server is sent to the controlling -process as a message. First a response message is sent, then -zero or more data messages. If something goes wrong, error -messages are sent instead. - -The response message will inform you whether there will be -data messages following. If it contains `fin` then no data -will follow. If it contains `nofin` then one or more data -messages will arrive. - -When using SPDY this value is sent along the frame and simply -passed on in the message. When using HTTP however Gun must -guess whether data will follow by looking at the headers -as documented in the HTTP RFC. - -``` erlang -StreamRef = gun:get(Pid, "/"), -receive - {'DOWN', Tag, _, _, Reason} -> - error_logger:error_msg("Oops!"), - exit(Reason); - {gun_response, Pid, StreamRef, fin, Status, Headers} -> - no_data; - {gun_response, Pid, StreamRef, nofin, Status, Headers} -> - receive_data(Pid, StreamRef) -after 1000 -> - exit(timeout) -end. -``` - -The `receive_data/2` function could look like this: - -``` erlang -receive_data(Pid, Tag, StreamRef) -> - receive - {'DOWN', Tag, _, _, Reason} -> - {error, incomplete}; - {gun_data, Pid, StreamRef, nofin, Data} -> - io:format("~s~n", [Data]), - receive_data(Pid, Tag, StreamRef); - {gun_data, Pid, StreamRef, fin, Data} -> - io:format("~s~n", [Data]) - after 1000 -> - {error, 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 may also use Gun in a synchronous manner by writing your -own functions that perform a receive like demonstrated above. - -Dealing with server-pushed streams ----------------------------------- - -When using SPDY the server may decide to push extra resources -after a request is performed. It will send a `gun_push` message -which contains two references, one for the pushed stream, and -one for the request this stream is associated with. - -Pushed streams typically feature a body. Replying to a pushed -stream is forbidden and Gun will send an error message if -attempted. - -Pushed streams can be received like this: - -``` erlang -receive - {gun_push, Pid, PushedStreamRef, StreamRef, - Method, Host, Path, Headers} -> - %% ... -end -``` - -The pushed stream gets a new identifier but you still receive -the `StreamRef` this stream is associated to. diff --git a/guide/protocols.md b/guide/protocols.md deleted file mode 100644 index c3aef6f..0000000 --- a/guide/protocols.md +++ /dev/null @@ -1,79 +0,0 @@ -Supported protocols -=================== - -This chapter describes the supported protocols and lists -the calls that are valid for each of them. - -HTTP ----- - -HTTP is a text request-response protocol. The client -initiates requests and then waits for the server responses. -The server has no means of creating requests or pushing -data to the client. - -SPDY ----- - -SPDY is a binary protocol based on HTTP, compatible with -the HTTP semantics, that reduces the complexity of parsing -requests and responses, compresses the HTTP headers and -allows the server to push data directly to the client. - -Websocket ---------- - -Websocket is a binary protocol established over HTTP that -allows asynchronous concurrent communication between the -client and the server. A Websocket server can push data to -the client at any time. - -Websocket over SPDY is not supported by the Gun client at -this time. - -Operations by protocol ----------------------- - -This table lists all Gun operations and whether they are -compatible with the supported protocols. - -| Operation | SPDY | HTTP | Websocket | -| ---------- | ---- | ---- | --------- | -| delete | yes | yes | no | -| get | yes | yes | no | -| head | yes | yes | no | -| options | yes | yes | no | -| patch | yes | yes | no | -| post | yes | yes | no | -| put | yes | yes | no | -| request | yes | yes | no | -| response | yes | no | no | -| data | yes | yes | no | -| cancel | yes | yes | no | -| ws_upgrade | no | yes | no | -| ws_send | no | no | yes | - -While the `cancel` operation is available to HTTP, its effects -will only be local, as there is no way to tell the server to -stop sending data. Gun instead just doesn't forward the messages -for this stream anymore. - -Messages by protocol --------------------- - -This table lists all messages that can be received depending -on the current protocol. - -| Message | SPDY | HTTP | Websocket | -| ------------------------------- | ---- | ---- | --------- | -| {gun_push, ...} | yes | no | no | -| {gun_response, ...} | yes | yes | no | -| {gun_data, ...} | yes | yes | no | -| {gun_error, _, StreamRef, _} | yes | yes | no | -| {gun_error, _, _} | yes | yes | yes | -| {gun_ws_upgrade, _, ok} | no | yes | no | -| {gun_ws_upgrade, _, error, ...} | no | yes | no | -| {gun_ws, ...} | no | no | yes | - -Do not forget that other messages may still be in the mailbox -after you upgrade to Websocket. diff --git a/guide/toc.md b/guide/toc.md deleted file mode 100644 index 00bd6bd..0000000 --- a/guide/toc.md +++ /dev/null @@ -1,11 +0,0 @@ -Gun User Guide -============== - -The Gun User Guide explains in details how the Gun client -should be used for communicating with Web servers. - - * [Introduction](introduction.md) - * [Connection](connect.md) - * [Supported protocols](protocols.md) - * [Using HTTP](http.md) - * [Using Websocket](websocket.md) diff --git a/guide/websocket.md b/guide/websocket.md deleted file mode 100644 index 26b73c2..0000000 --- a/guide/websocket.md +++ /dev/null @@ -1,85 +0,0 @@ -Using Websocket -=============== - -This chapter describes how to use the Gun client for -communicating with a Websocket server. - -HTTP upgrade ------------- - -Websocket is a protocol built on top of HTTP. To use Websocket, -you must first request for the connection to be upgraded. - -Gun allows you to perform Websocket upgrade requests by using -the `gun:ws_upgrade/{2,3}` function. Gun will fill out all -necessary headers for performing the Websocket upgrade, but -you can optionally specify additional headers, for example if -you would like to setup a custom sub-protocol. - -``` erlang -%% Without headers. -gun:ws_upgrade(Pid, "/websocket"). -%% With headers. -gun:ws_upgrade(Pid, "/websocket", [ - {"sec-websocket-protocol", "mychat"} -]). -``` - -The success or failure of this operation will be sent as a -message. - -``` erlang -receive - {gun_ws_upgrade, Pid, ok} -> - upgrade_success(Pid); - {gun_ws_upgrade, Pid, error, IsFin, Status, Headers} -> - exit({ws_upgrade_failed, Status, Headers}); - %% More clauses here as needed. -after 1000 -> - exit(timeout); -end. -``` - -Sending data ------------- - -You can then use the `gun:ws_send/2` function to send one or -more frames to the server. - -``` erlang -%% Send one text frame. -gun:ws_send(Pid, {text, "Hello!"}). -%% Send one text frame, one binary frame and close the connection. -gun:ws_send(Pid, [ - {text, "Hello!"}, - {binary, SomeBin}, - close -]). -``` - -Note that if you send a close frame, Gun will close the connection -cleanly and will not attempt to reconnect afterwards, similar to -calling `gun:shutdown/1`. - -Receiving data --------------- - -Every time Gun receives a frame from the server a message will be -sent to the controlling process. This message will always contain -a single frame. - -``` erlang -receive - {gun_ws, Pid, Frame} -> - handle_frame(Pid, Frame); - {gun_error, Pid, Reason} -> - error_logger:error_msg("Oops! ~p~n", [Reason]), - upgrade_again(Pid) -end. -``` - -Gun will automatically send ping messages to the server to keep -the connection alive, however if the connection dies and Gun has -to reconnect it will not upgrade to Websocket automatically, you -need to perform the operation when you receive the `gun_error` -message. |