From 239e5e0ba7f413642aca3cf2735f0709a643fb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 3 Mar 2014 16:59:02 +0100 Subject: Update and improve Websocket chapters in the guide --- guide/toc.md | 2 +- guide/ws_handlers.md | 271 ++++++++++++++++++++++++++++++++++++++++----------- guide/ws_protocol.md | 45 +++++++++ 3 files changed, 259 insertions(+), 59 deletions(-) create mode 100644 guide/ws_protocol.md (limited to 'guide') diff --git a/guide/toc.md b/guide/toc.md index 5229167..26925c5 100644 --- a/guide/toc.md +++ b/guide/toc.md @@ -61,7 +61,7 @@ Server push technologies Using Websocket --------------- - * The Websocket protocol + * [The Websocket protocol](ws_protocol.md) * [Handling Websocket connections](ws_handlers.md) Advanced HTTP diff --git a/guide/ws_handlers.md b/guide/ws_handlers.md index 94bc95e..99f69dc 100644 --- a/guide/ws_handlers.md +++ b/guide/ws_handlers.md @@ -1,75 +1,230 @@ -Websocket handlers -================== +Handling Websocket connections +============================== -Purpose -------- +A special handler is required for handling Websocket connections. +Websocket handlers allow you to initialize the connection, +handle incoming frames from the socket, handle incoming Erlang +messages and then clean up on termination. -Websocket is an extension to HTTP to emulate plain TCP connections -between the user's browser and the server. Requests that are upgraded -are then handled by websocket handlers. +Websocket handlers essentially act as a bridge between the client +and the Erlang system. They will typically do little more than +socket communication and decoding/encoding of frames. -Both sides of the socket can send data at any time asynchronously. +Initialization +-------------- -Websocket is an IETF standard. Cowboy supports the standard and all -the drafts that were previously implemented by browsers. Websocket -is implemented by most browsers today, although for backward -compatibility reasons a solution like [Bullet](https://github.com/extend/bullet) -might be preferred. +First, the `init/3` callback is called. This callback is common +to all handlers. To establish a Websocket connection, this function +must return an `upgrade` tuple. -Usage ------ +``` erlang +init(_, Req, Opts) -> + {upgrade, protocol, cowboy_websocket}. +``` + +It is also possible to return an update Req object and options +using the longer form of this tuple. + +``` erlang +init(_Type, Req, Opts) -> + {upgrade, protocol, cowboy_websocket, Req, Opts}. +``` -Websocket handlers are a bridge between the client and your system. -They can receive data from the client, through `websocket_handle/3`, -or from the system, through `websocket_info/3`. It is up to the -handler to decide to process this data, and optionally send a reply -to the client. +Upon receiving this tuple, Cowboy will switch to the code +that handles Websocket connections. It does not immediately +perform the handshake however. First, it calls the `websocket_init/3` +callback. -The first thing to do to be able to handle websockets is to tell -Cowboy that it should upgrade the connection to use the Websocket -protocol, as follow. +This function must be used to initialize the state, and can +also be used to register the process, start a timer, etc. +As long as the function returns an `ok` tuple, then Cowboy +performs the Websocket handshake. ``` erlang -init({tcp, http}, Req, Opts) -> - {upgrade, protocol, cowboy_websocket}. +websocket_init(_Type, Req, _Opts) -> + {ok, Req, #state{}}. ``` -Cowboy will then switch the protocol and call `websocket_init`, -followed by zero or more calls to `websocket_handle` and -`websocket_info`. Then, when the connection is shutting down, -`websocket_terminate` will be called. +A `shutdown` tuple can be returned to refuse to perform the +handshake. When doing so, Cowboy will send a `400 Bad Request` +response to the client and close the connection. + +``` erlang +websocket_init(_Type, Req, _Opts) -> + {shutdown, Req}. +``` + +It is also possible to perform a `cowboy_req:reply/{2,3,4}` +before returning a `shutdown` tuple, allowing you to override +the response sent back to the client. + +Note that browser support for handling Websocket connection +failures may vary. + +If the sec-websocket-protocol header was sent with the request +for establishing a Websocket connection, then the Websocket +handler *must* select one of these subprotocol and send it +back to the client, otherwise the client might decide to close +the connection, assuming no correct subprotocol was found. + +``` erlang +websocket_init(_Type, Req, _Opts) -> + case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of + {ok, undefined, Req2} -> + {ok, Req, #state{}}; + {ok, Subprotocols, Req2} -> + case lists:keymember(<<"mychat2">>, 1, Subprotocols) of + true -> + Req3 = cowboy:set_resp_header(<<"sec-websocket-protocol">>, + <<"mychat2">>, Req2), + {ok, Req3, #state{}}; + false -> + {shutdown, Req2} + end + end. +``` + +It is not recommended to wait too long inside the `websocket_init/3` +function. Any extra initialization may be done after returning by +sending yourself a message before doing anything. Any message sent +to `self()` from `websocket_init/3` is guaranteed to arrive before +any frames from the client. + +It is also very easy to ensure that this message arrives before +any message from other processes by sending it before registering +or enabling timers. + +``` erlang +websocket_init(_Type, Req, _Opts) -> + self() ! post_init, + %% Register process here... + {ok, Req, #state{}}. + +websocket_info(post_init, Req, State) -> + %% Perform post_init initialization here... + {ok, Req, State}. +``` + +Handling frames from the client +------------------------------- + +Cowboy will call `websocket_handle/3` whenever a text, binary, +ping or pong frame arrives from the client. Note that in the +case of ping and pong frames, no action is expected as Cowboy +automatically replies to ping frames. + +The handler can decide to send frames to the socket, shutdown +or just continue without sending anything. -The following handler sends a message every second. It also echoes -back what it receives. +The following snippet echoes back any text frame received and +ignores all others. ``` erlang --module(my_ws_handler). --behaviour(cowboy_websocket_handler). - --export([init/3]). --export([websocket_init/3]). --export([websocket_handle/3]). --export([websocket_info/3]). --export([websocket_terminate/3]). - -init({tcp, http}, Req, Opts) -> - {upgrade, protocol, cowboy_websocket}. - -websocket_init(TransportName, Req, _Opts) -> - erlang:start_timer(1000, self(), <<"Hello!">>), - {ok, Req, undefined_state}. - -websocket_handle({text, Msg}, Req, State) -> - {reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State}; -websocket_handle(_Data, Req, State) -> - {ok, Req, State}. - -websocket_info({timeout, _Ref, Msg}, Req, State) -> - erlang:start_timer(1000, self(), <<"How' you doin'?">>), - {reply, {text, Msg}, Req, State}; +websocket_handle(Frame = {text, _}, Req, State) -> + {reply, Frame, Req, State}; +websocket_handle(_Frame, Req, State) -> + {ok, Req, State}. +``` + +Handling Erlang messages +------------------------ + +Cowboy will call `websocket_info/3` whenever an Erlang message +arrives. + +The handler can decide to send frames to the socket, shutdown +or just continue without sending anything. + +The following snippet forwards any `log` message to the socket +and ignores all others. + +``` erlang +websocket_info({log, Text}, Req, State) -> + {reply, {text, Text}, Req, State}; websocket_info(_Info, Req, State) -> - {ok, Req, State}. + {ok, Req, State}. +``` + +Sending frames to the socket +---------------------------- + +Cowboy allows sending either a single frame or a list of +frames to the socket. Any frame can be sent: text, binary, ping, +pong or close frames. + +The following example sends three frames using a single `reply` +tuple. -websocket_terminate(_Reason, _Req, _State) -> - ok. +``` erlang +websocket_info(hello_world, Req, State) -> + {reply, [ + {text, "Hello"}, + {text, <<"world!">>}, + {binary, <<0:8000>>} + ], Req, State}; +%% More websocket_info/3 clauses here... +``` + +Note that the payload for text and binary frames is of type +`iodata()`, meaning it can be either a `binary()` or an +`iolist()`. + +Sending a `close` frame will immediately initiate the closing +of the Websocket connection. Be aware that any additional +frames sent by the client or any Erlang messages waiting to +be received will not be processed. Also note that when replying +a list of frames that includes close, any frame found after the +close frame will not be sent. + +Ping and timeout +---------------- + +The biggest performance improvement you can do when dealing +with a huge number of Websocket connections is to reduce the +number of timers that are started on the server. A common use +of timers when dealing with connections is for sending a ping +every once in a while. This should be done exclusively on the +client side. Indeed, a server handling one million Websocket +connections will perform a lot better when it doesn't have to +handle one million extra timers too! + +Cowboy will automatically respond to ping frames sent by the +client. It will still forward the frame to the handler for +informative purpose, but no further action is required. + +Cowboy can be configured to automatically close the Websocket +connection when no data arrives on the socket. It is highly +recommended to configure a timeout for it, as otherwise you +may end up with zombie "half-connected" sockets that may +leave the process alive forever. + +A good timeout value is 60 seconds. + +``` erlang +websocket_init(_Type, Req, _Opts) -> + {ok, Req, #state{}, 60000}. ``` + +This value cannot be changed once it is set. It defaults to +`infinity`. + +Hibernate +--------- + +Most tuples returned from handler callbacks can include an +extra value `hibernate`. After doing any necessary operations +following the return of the callback, Cowboy will hibernate +the process. + +It is highly recommended to hibernate processes that do not +handle much traffic. It is a good idea to hibernate all +connections by default and investigate only when you start +noticing increased CPU usage. + +Supporting older browsers +------------------------- + +Unfortunately Websocket is a relatively recent technology, +which means that not all browsers support it. A library like +[Bullet](https://github.com/extend/bullet) can be used to +emulate Websocket connections on older browsers. diff --git a/guide/ws_protocol.md b/guide/ws_protocol.md new file mode 100644 index 0000000..390751e --- /dev/null +++ b/guide/ws_protocol.md @@ -0,0 +1,45 @@ +The Websocket protocol +====================== + +This chapter explains what Websocket is and why it is +a vital component of soft realtime Web applications. + +Description +----------- + +Websocket is an extension to HTTP that emulates plain TCP +connections between the client, typically a Web browser, +and the server. It uses the HTTP Upgrade mechanism to +establish the connection. + +Websocket connections are asynchronous, unlike HTTP. This +means that not only can the client send frames to the server +at any time, but the server can also send frames to the client +without the client initiating anything other than the +Websocket connection itself. This allows the server to push +data to the client directly. + +Websocket is an IETF standard. Cowboy supports the standard +and all drafts that were previously implemented by browsers, +excluding the initial flawed draft sometimes known as +"version 0". + +Implementation +-------------- + +Cowboy implements Websocket as a protocol upgrade. Once the +upgrade is performed from the `init/3` callback, Cowboy +switches to Websocket. Please consult the next chapter for +more information on initiating and handling Websocket +connections. + +The implementation of Websocket in Cowboy is validated using +the Autobahn test suite, which is an extensive suite of tests +covering all aspects of the protocol. Cowboy passes the +suite with 100% success, including all optional tests. + +Cowboy's Websocket implementation also includes the +x-webkit-deflate-frame compression draft which is being used +by some browsers to reduce the size of data being transmitted. +Cowboy will automatically use compression as long as the +`compress` protocol option is set when starting the listener. -- cgit v1.2.3