diff options
Diffstat (limited to 'doc/src/guide/ws_handlers.asciidoc')
-rw-r--r-- | doc/src/guide/ws_handlers.asciidoc | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/doc/src/guide/ws_handlers.asciidoc b/doc/src/guide/ws_handlers.asciidoc new file mode 100644 index 0000000..9ddddf4 --- /dev/null +++ b/doc/src/guide/ws_handlers.asciidoc @@ -0,0 +1,196 @@ +[[ws_handlers]] +== Handling Websocket connections + +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 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. + +=== Initialization + +First, the `init/2` callback is called. This callback is common +to all handlers. To establish a Websocket connection, this function +must return a `ws` tuple. + +[source,erlang] +---- +init(Req, _Opts) -> + {cowboy_websocket, Req, #state{}}. +---- + +Upon receiving this tuple, Cowboy will switch to the code +that handles Websocket connections and perform the handshake +immediately. + +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. + +[source,erlang] +---- +init(Req, _Opts) -> + case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of + undefined -> + {ok, Req, #state{}}; + Subprotocols -> + case lists:keymember(<<"mychat2">>, 1, Subprotocols) of + true -> + Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, + <<"mychat2">>, Req), + {ok, Req2, #state{}}; + false -> + {stop, Req, undefined} + end + end. +---- + +It is not recommended to wait too long inside the `init/2` +function. Any extra initialization may be done after returning by +sending yourself a message before doing anything. Any message sent +to `self()` from `init/2` 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. + +[source,erlang] +---- +init(Req, _Opts) -> + self() ! post_init, + %% Register process here... + {cowboy_websocket, 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, stop +or just continue without sending anything. + +The following snippet echoes back any text frame received and +ignores all others. + +[source,erlang] +---- +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, stop +or just continue without sending anything. + +The following snippet forwards any `log` message to the socket +and ignores all others. + +[source,erlang] +---- +websocket_info({log, Text}, Req, State) -> + {reply, {text, Text}, Req, State}; +websocket_info(_Info, 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, in which case the frames are sent +sequentially. Any frame can be sent: text, binary, ping, +pong or close frames. + +The following example sends three frames using a single `reply` +tuple. + +[source,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. + +[source,erlang] +---- +init(Req, _Opts) -> + {cowboy_websocket, 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 +https://github.com/ninenines/bullet[Bullet] can be used to +emulate Websocket connections on older browsers. |