From 48f39402181d959cad88cb3f460210c007169f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 12 May 2021 11:13:47 +0200 Subject: Cowboy 2.9.0 --- docs/en/cowboy/2.9/guide/ws_handlers.asciidoc | 292 ++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 docs/en/cowboy/2.9/guide/ws_handlers.asciidoc (limited to 'docs/en/cowboy/2.9/guide/ws_handlers.asciidoc') diff --git a/docs/en/cowboy/2.9/guide/ws_handlers.asciidoc b/docs/en/cowboy/2.9/guide/ws_handlers.asciidoc new file mode 100644 index 00000000..5cfdcb16 --- /dev/null +++ b/docs/en/cowboy/2.9/guide/ws_handlers.asciidoc @@ -0,0 +1,292 @@ +[[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) -> + {[{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) -> + {[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) -> + {[{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 the frame to be sent: + +[source,erlang] +---- +websocket_info(_Info, State) -> + {[{text, <<"Hello!">>}], State}. +---- + +You can send frames of any type: text, binary, ping, pong +or close frames. + +You can send many frames at the same time: + +[source,erlang] +---- +websocket_info(_Info, State) -> + {[ + {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`. + +=== Limiting frame sizes + +Cowboy accepts frames of any size by default. You should +limit the size depending on what your handler may handle. +You can do this via the `init/2` callback: + +[source,erlang] +---- +init(Req, State) -> + {cowboy_websocket, Req, State, #{ + max_frame_size => 8000000}}. +---- + +The lack of limit is historical. A future version of +Cowboy will have a more reasonable default. + +=== Saving memory + +The Websocket connection process can be set to hibernate +after the callback returns. + +Simply add an `hibernate` field to the returned tuple: + +[source,erlang] +---- +websocket_init(State) -> + {[], State, hibernate}. + +websocket_handle(_Frame, State) -> + {[], State, hibernate}. + +websocket_info(_Info, State) -> + {[{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) -> + {[{close, 1000, <<"some-reason">>}], State}. +---- -- cgit v1.2.3