2014-07-06 13:10:35 +02:00
|
|
|
::: Handling Websocket connections
|
2013-01-01 18:27:41 +01:00
|
|
|
|
2014-03-03 16:59:02 +01:00
|
|
|
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.
|
2013-01-01 18:27:41 +01:00
|
|
|
|
2014-03-03 16:59:02 +01:00
|
|
|
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.
|
2013-01-01 18:27:41 +01:00
|
|
|
|
2014-07-06 13:10:35 +02:00
|
|
|
:: Initialization
|
2013-01-01 18:27:41 +01:00
|
|
|
|
2014-09-26 15:58:44 +03:00
|
|
|
First, the `init/2` callback is called. This callback is common
|
2014-03-03 16:59:02 +01:00
|
|
|
to all handlers. To establish a Websocket connection, this function
|
2014-09-26 15:58:44 +03:00
|
|
|
must return a `ws` tuple.
|
2013-01-03 16:01:49 +01:00
|
|
|
|
2014-03-03 16:59:02 +01:00
|
|
|
``` erlang
|
2014-09-26 15:58:44 +03:00
|
|
|
init(Req, Opts) ->
|
2014-09-30 20:12:13 +03:00
|
|
|
{cowboy_websocket, Req, Opts}.
|
2014-03-03 16:59:02 +01:00
|
|
|
```
|
2013-01-01 18:27:41 +01:00
|
|
|
|
2014-03-03 16:59:02 +01:00
|
|
|
Upon receiving this tuple, Cowboy will switch to the code
|
2014-09-26 15:58:44 +03:00
|
|
|
that handles Websocket connections and perform the handshake
|
|
|
|
immediately.
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
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
|
2014-09-26 15:58:44 +03:00
|
|
|
init(Req, _Opts) ->
|
2014-03-03 16:59:02 +01:00
|
|
|
case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
|
2014-09-23 16:43:29 +03:00
|
|
|
undefined ->
|
2014-03-03 16:59:02 +01:00
|
|
|
{ok, Req, #state{}};
|
2014-09-23 16:43:29 +03:00
|
|
|
Subprotocols ->
|
2014-03-03 16:59:02 +01:00
|
|
|
case lists:keymember(<<"mychat2">>, 1, Subprotocols) of
|
|
|
|
true ->
|
2014-09-23 16:43:29 +03:00
|
|
|
Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
|
|
|
|
<<"mychat2">>, Req),
|
|
|
|
{ok, Req2, #state{}};
|
2014-03-03 16:59:02 +01:00
|
|
|
false ->
|
2014-09-26 15:58:44 +03:00
|
|
|
{shutdown, Req, undefined}
|
2014-03-03 16:59:02 +01:00
|
|
|
end
|
|
|
|
end.
|
|
|
|
```
|
|
|
|
|
2014-09-26 15:58:44 +03:00
|
|
|
It is not recommended to wait too long inside the `init/2`
|
2014-03-03 16:59:02 +01:00
|
|
|
function. Any extra initialization may be done after returning by
|
|
|
|
sending yourself a message before doing anything. Any message sent
|
2014-09-26 15:58:44 +03:00
|
|
|
to `self()` from `init/2` is guaranteed to arrive before
|
2014-03-03 16:59:02 +01:00
|
|
|
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
|
2014-09-26 15:58:44 +03:00
|
|
|
init(Req, _Opts) ->
|
2014-03-03 16:59:02 +01:00
|
|
|
self() ! post_init,
|
|
|
|
%% Register process here...
|
2014-09-30 20:12:13 +03:00
|
|
|
{cowboy_websocket, Req, #state{}}.
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
websocket_info(post_init, Req, State) ->
|
|
|
|
%% Perform post_init initialization here...
|
|
|
|
{ok, Req, State}.
|
|
|
|
```
|
|
|
|
|
2014-07-06 13:10:35 +02:00
|
|
|
:: Handling frames from the client
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
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.
|
2013-01-03 16:01:49 +01:00
|
|
|
|
2014-03-03 16:59:02 +01:00
|
|
|
The following snippet echoes back any text frame received and
|
|
|
|
ignores all others.
|
2013-01-03 16:01:49 +01:00
|
|
|
|
|
|
|
``` erlang
|
2014-03-03 16:59:02 +01:00
|
|
|
websocket_handle(Frame = {text, _}, Req, State) ->
|
|
|
|
{reply, Frame, Req, State};
|
|
|
|
websocket_handle(_Frame, Req, State) ->
|
|
|
|
{ok, Req, State}.
|
|
|
|
```
|
|
|
|
|
2014-07-06 13:10:35 +02:00
|
|
|
:: Handling Erlang messages
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
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};
|
2013-01-03 16:01:49 +01:00
|
|
|
websocket_info(_Info, Req, State) ->
|
2014-03-03 16:59:02 +01:00
|
|
|
{ok, Req, State}.
|
|
|
|
```
|
|
|
|
|
2014-07-06 13:10:35 +02:00
|
|
|
:: Sending frames to the socket
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
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.
|
2013-01-03 16:01:49 +01:00
|
|
|
|
2014-03-03 16:59:02 +01:00
|
|
|
``` 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.
|
|
|
|
|
2014-07-06 13:10:35 +02:00
|
|
|
:: Ping and timeout
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
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
|
2014-09-26 15:58:44 +03:00
|
|
|
init(Req, _Opts) ->
|
2014-09-30 20:12:13 +03:00
|
|
|
{cowboy_websocket, Req, #state{}, 60000}.
|
2013-01-03 16:01:49 +01:00
|
|
|
```
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
This value cannot be changed once it is set. It defaults to
|
|
|
|
`infinity`.
|
|
|
|
|
2014-07-06 13:10:35 +02:00
|
|
|
:: Hibernate
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2014-07-06 13:10:35 +02:00
|
|
|
:: Supporting older browsers
|
2014-03-03 16:59:02 +01:00
|
|
|
|
|
|
|
Unfortunately Websocket is a relatively recent technology,
|
|
|
|
which means that not all browsers support it. A library like
|
2014-07-06 13:10:35 +02:00
|
|
|
^"Bullet^https://github.com/extend/bullet^ can be used to
|
2014-03-03 16:59:02 +01:00
|
|
|
emulate Websocket connections on older browsers.
|