mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-16 05:00:24 +00:00
Update and improve Websocket chapters in the guide
This commit is contained in:
parent
64f07fe9a3
commit
239e5e0ba7
3 changed files with 259 additions and 59 deletions
|
@ -61,7 +61,7 @@ Server push technologies
|
||||||
Using Websocket
|
Using Websocket
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
* The Websocket protocol
|
* [The Websocket protocol](ws_protocol.md)
|
||||||
* [Handling Websocket connections](ws_handlers.md)
|
* [Handling Websocket connections](ws_handlers.md)
|
||||||
|
|
||||||
Advanced HTTP
|
Advanced HTTP
|
||||||
|
|
|
@ -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
|
Websocket handlers essentially act as a bridge between the client
|
||||||
between the user's browser and the server. Requests that are upgraded
|
and the Erlang system. They will typically do little more than
|
||||||
are then handled by websocket handlers.
|
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
|
First, the `init/3` callback is called. This callback is common
|
||||||
the drafts that were previously implemented by browsers. Websocket
|
to all handlers. To establish a Websocket connection, this function
|
||||||
is implemented by most browsers today, although for backward
|
must return an `upgrade` tuple.
|
||||||
compatibility reasons a solution like [Bullet](https://github.com/extend/bullet)
|
|
||||||
might be preferred.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
init({tcp, http}, Req, Opts) ->
|
init(_, Req, Opts) ->
|
||||||
{upgrade, protocol, cowboy_websocket}.
|
{upgrade, protocol, cowboy_websocket}.
|
||||||
```
|
```
|
||||||
|
|
||||||
Cowboy will then switch the protocol and call `websocket_init`,
|
It is also possible to return an update Req object and options
|
||||||
followed by zero or more calls to `websocket_handle` and
|
using the longer form of this tuple.
|
||||||
`websocket_info`. Then, when the connection is shutting down,
|
|
||||||
`websocket_terminate` will be called.
|
|
||||||
|
|
||||||
The following handler sends a message every second. It also echoes
|
|
||||||
back what it receives.
|
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
-module(my_ws_handler).
|
init(_Type, Req, Opts) ->
|
||||||
-behaviour(cowboy_websocket_handler).
|
{upgrade, protocol, cowboy_websocket, Req, Opts}.
|
||||||
|
```
|
||||||
|
|
||||||
-export([init/3]).
|
Upon receiving this tuple, Cowboy will switch to the code
|
||||||
-export([websocket_init/3]).
|
that handles Websocket connections. It does not immediately
|
||||||
-export([websocket_handle/3]).
|
perform the handshake however. First, it calls the `websocket_init/3`
|
||||||
-export([websocket_info/3]).
|
callback.
|
||||||
-export([websocket_terminate/3]).
|
|
||||||
|
|
||||||
init({tcp, http}, Req, Opts) ->
|
This function must be used to initialize the state, and can
|
||||||
{upgrade, protocol, cowboy_websocket}.
|
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.
|
||||||
|
|
||||||
websocket_init(TransportName, Req, _Opts) ->
|
``` erlang
|
||||||
erlang:start_timer(1000, self(), <<"Hello!">>),
|
websocket_init(_Type, Req, _Opts) ->
|
||||||
{ok, Req, undefined_state}.
|
{ok, Req, #state{}}.
|
||||||
|
```
|
||||||
|
|
||||||
websocket_handle({text, Msg}, Req, State) ->
|
A `shutdown` tuple can be returned to refuse to perform the
|
||||||
{reply, {text, << "That's what she said! ", Msg/binary >>}, Req, State};
|
handshake. When doing so, Cowboy will send a `400 Bad Request`
|
||||||
websocket_handle(_Data, Req, State) ->
|
response to the client and close the connection.
|
||||||
{ok, Req, State}.
|
|
||||||
|
|
||||||
websocket_info({timeout, _Ref, Msg}, Req, State) ->
|
``` erlang
|
||||||
erlang:start_timer(1000, self(), <<"How' you doin'?">>),
|
websocket_init(_Type, Req, _Opts) ->
|
||||||
{reply, {text, Msg}, Req, State};
|
{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 snippet echoes back any text frame received and
|
||||||
|
ignores all others.
|
||||||
|
|
||||||
|
``` 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, 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) ->
|
websocket_info(_Info, Req, State) ->
|
||||||
{ok, Req, State}.
|
{ok, Req, State}.
|
||||||
|
|
||||||
websocket_terminate(_Reason, _Req, _State) ->
|
|
||||||
ok.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
``` 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.
|
||||||
|
|
45
guide/ws_protocol.md
Normal file
45
guide/ws_protocol.md
Normal file
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue