123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- [[ws_handlers]]
- == Websocket handlers
- Websocket handlers provide an interface for upgrading HTTP/1.1
- connections to Websocket and sending or receiving frames on
- the Websocket connection.
- As Websocket connections are established through the HTTP/1.1
- upgrade mechanism, Websocket handlers need to be able to first
- receive the HTTP request for the upgrade, before switching to
- Websocket and taking over the connection. They can then receive
- or send Websocket frames, handle incoming Erlang messages or
- close the connection.
- === Upgrade
- The `init/2` callback is called when the request is received.
- To establish a Websocket connection, you must switch to the
- `cowboy_websocket` module:
- [source,erlang]
- ----
- init(Req, State) ->
- {cowboy_websocket, Req, State}.
- ----
- Cowboy will perform the Websocket handshake immediately. Note
- that the handshake will fail if the client did not request an
- upgrade to Websocket.
- The Req object becomes unavailable after this function returns.
- Any information required for proper execution of the Websocket
- handler must be saved in the state.
- === Subprotocol
- The client may provide a list of Websocket subprotocols it
- supports in the sec-websocket-protocol header. The server *must*
- select one of them and send it back to the client or the
- handshake will fail.
- For example, a client could understand both STOMP and MQTT over
- Websocket, and provide the header:
- ----
- sec-websocket-protocol: v12.stomp, mqtt
- ----
- If the server only understands MQTT it can return:
- ----
- sec-websocket-protocol: mqtt
- ----
- This selection must be done in `init/2`. An example usage could
- be:
- [source,erlang]
- ----
- init(Req0, State) ->
- case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req0) of
- undefined ->
- {cowboy_websocket, Req0, State};
- Subprotocols ->
- case lists:keymember(<<"mqtt">>, 1, Subprotocols) of
- true ->
- Req = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
- <<"mqtt">>, Req0),
- {cowboy_websocket, Req, State};
- false ->
- Req = cowboy_req:reply(400, Req0),
- {ok, Req, State}
- end
- end.
- ----
- === Post-upgrade initialization
- Cowboy has separate processes for handling the connection
- and requests. Because Websocket takes over the connection,
- the Websocket protocol handling occurs in a different
- process than the request handling.
- This is reflected in the different callbacks Websocket
- handlers have. The `init/2` callback is called from the
- temporary request process and the `websocket_` callbacks
- from the connection process.
- This means that some initialization cannot be done from
- `init/2`. Anything that would require the current pid,
- or be tied to the current pid, will not work as intended.
- The optional `websocket_init/1` can be used instead:
- [source,erlang]
- ----
- websocket_init(State) ->
- erlang:start_timer(1000, self(), <<"Hello!">>),
- {ok, State}.
- ----
- All Websocket callbacks share the same return values. This
- means that we can send frames to the client right after
- the upgrade:
- [source,erlang]
- ----
- websocket_init(State) ->
- {reply, {text, <<"Hello!">>}, State}.
- ----
- === Receiving frames
- Cowboy will call `websocket_handle/2` whenever a text, binary,
- ping or pong frame arrives from the client.
- The handler can handle or ignore the frames. It can also
- send frames back to the client or stop the connection.
- The following snippet echoes back any text frame received and
- ignores all others:
- [source,erlang]
- ----
- websocket_handle(Frame = {text, _}, State) ->
- {reply, Frame, State};
- websocket_handle(_Frame, State) ->
- {ok, State}.
- ----
- Note that ping and pong frames require no action from the
- handler as Cowboy will automatically reply to ping frames.
- They are provided for informative purposes only.
- === Receiving Erlang messages
- Cowboy will call `websocket_info/2` whenever an Erlang message
- arrives.
- The handler can handle or ignore the messages. It can also
- send frames to the client or stop the connection.
- The following snippet forwards log messages to the client
- and ignores all others:
- [source,erlang]
- ----
- websocket_info({log, Text}, State) ->
- {reply, {text, Text}, State};
- websocket_info(_Info, State) ->
- {ok, State}.
- ----
- === Sending frames
- // @todo This will be deprecated and eventually replaced with a
- // {Commands, State} interface that allows providing more
- // functionality easily.
- All `websocket_` callbacks share return values. They may
- send zero, one or many frames to the client.
- To send nothing, just return an ok tuple:
- [source,erlang]
- ----
- websocket_info(_Info, State) ->
- {ok, State}.
- ----
- To send one frame, return a reply tuple with the frame to send:
- [source,erlang]
- ----
- websocket_info(_Info, State) ->
- {reply, {text, <<"Hello!">>}, State}.
- ----
- You can send frames of any type: text, binary, ping, pong
- or close frames.
- To send many frames at once, return a reply tuple with the
- list of frames to send:
- [source,erlang]
- ----
- websocket_info(_Info, State) ->
- {reply, [
- {text, "Hello"},
- {text, <<"world!">>},
- {binary, <<0:8000>>}
- ], State}.
- ----
- They are sent in the given order.
- === Keeping the connection alive
- Cowboy will automatically respond to ping frames sent by
- the client. They are still forwarded to the handler for
- informative purposes, but no further action is required.
- Cowboy does not send ping frames itself. The handler can
- do it if required. A better solution in most cases is to
- let the client handle pings. Doing it from the handler
- would imply having an additional timer per connection and
- this can be a considerable cost for servers that need to
- handle large numbers of connections.
- Cowboy can be configured to close idle connections
- automatically. It is highly recommended to configure
- a timeout here, to avoid having processes linger longer
- than needed.
- The `init/2` callback can set the timeout to be used
- for the connection. For example, this would make Cowboy
- close connections idle for more than 30 seconds:
- [source,erlang]
- ----
- init(Req, State) ->
- {cowboy_websocket, Req, State, #{
- idle_timeout => 30000}}.
- ----
- This value cannot be changed once it is set. It defaults to
- `60000`.
- === Saving memory
- The Websocket connection process can be set to hibernate
- after the callback returns.
- Simply add an `hibernate` field to the ok or reply tuples:
- [source,erlang]
- ----
- websocket_init(State) ->
- {ok, State, hibernate}.
- websocket_handle(_Frame, State) ->
- {ok, State, hibernate}.
- websocket_info(_Info, State) ->
- {reply, {text, <<"Hello!">>}, State, hibernate}.
- ----
- It is highly recommended to write your handlers with
- hibernate enabled, as this allows to greatly reduce the
- memory usage. Do note however that an increase in the
- CPU usage or latency can be observed instead, in particular
- for the more busy connections.
- === Closing the connection
- The connection can be closed at any time, either by telling
- Cowboy to stop it or by sending a close frame.
- To tell Cowboy to close the connection, use a stop tuple:
- [source,erlang]
- ----
- websocket_info(_Info, State) ->
- {stop, State}.
- ----
- Sending a `close` frame will immediately initiate the closing
- of the Websocket connection. Note that when sending a list of
- frames that include a close frame, any frame found after the
- close frame will not be sent.
- The following example sends a close frame with a reason message:
- [source,erlang]
- ----
- websocket_info(_Info, State) ->
- {reply, {close, 1000, <<"some-reason">>}, State}.
- ----
|