diff options
Diffstat (limited to 'doc')
-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 | 28 | ||||
-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 |
7 files changed, 812 insertions, 0 deletions
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/doc/src/guide/introduction.asciidoc b/doc/src/guide/introduction.asciidoc new file mode 100644 index 0000000..ade3d80 --- /dev/null +++ b/doc/src/guide/introduction.asciidoc @@ -0,0 +1,28 @@ +== Introduction + +Gun is an Erlang HTTP client with support for HTTP/1.1, SPDY and Websocket. + +=== Prerequisites + +Knowledge of Erlang, but also of the HTTP/1.1, SPDY and Websocket +protocols is required in order to read this guide. + +=== Supported platforms + +Gun is tested and supported on Linux. + +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 intended. + +=== 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. + +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. |