0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-15 04:30:25 +00:00

Allow passing options to sub protocols

Before this commit we had an issue where configuring a
Websocket connection was simply not possible without
doing magic, adding callbacks or extra return values.
The init/2 function only allowed setting hibernate
and timeout options.

After this commit, when switching to a different
type of handler you can either return

  {module, Req, State}

or

  {module, Req, State, Opts}

where Opts is any value (as far as the sub protocol
interface is concerned) and is ultimately checked
by the custom handlers.

A large protocol like Websocket would accept only
a map there, with many different options, while a
small interface like loop handlers would allow
passing hibernate and nothing else.

For Websocket, hibernate must be set from the
websocket_init/1 callback, because init/2 executes
in a separate process.

Sub protocols now have two callbacks: one with the
Opts value, one without.

The loop handler code was largely reworked and
simplified. It does not need to manage a timeout
or read from the socket anymore, it's the job of
the protocol code. A lot of unnecessary stuff was
therefore removed.

Websocket compression must now be enabled from
the handler options instead of per listener. This
means that a project can have two separate Websocket
handlers with different options. Compression is
still disabled by default, and the idle_timeout
value was changed from inifnity to 60000 (60 seconds),
as that's safer and is also a good value for mobile
devices.
This commit is contained in:
Loïc Hoguin 2017-02-18 18:26:20 +01:00
parent 80f8cda7ff
commit a45813c60f
No known key found for this signature in database
GPG key ID: 71366FF21851DF03
25 changed files with 171 additions and 244 deletions

View file

@ -34,7 +34,7 @@ loop handler behavior. This tuple may optionally contain
a timeout value and/or the atom `hibernate` to make the
process enter hibernation until a message is received.
This snippet enables the loop handler.
This snippet enables the loop handler:
[source,erlang]
----
@ -42,14 +42,12 @@ init(Req, State) ->
{cowboy_loop, Req, State}.
----
However it is largely recommended that you set a timeout
value. The next example sets a timeout value of 30s and
also makes the process hibernate.
This also makes the process hibernate:
[source,erlang]
----
init(Req, State) ->
{cowboy_loop, Req, State, 30000, hibernate}.
{cowboy_loop, Req, State, hibernate}.
----
=== Receive loop
@ -123,25 +121,6 @@ a subsequent request.
Please refer to the xref:handlers[Handlers chapter]
for general instructions about cleaning up.
=== Timeout
Note that this feature currently does not work. It will be
brought back in a future 2.0 pre-release.
By default Cowboy will not attempt to close the connection
if there is no activity from the client. This is not always
desirable, which is why you can set a timeout. Cowboy will
close the connection if no data was received from the client
after the configured time. The timeout only needs to be set
once and can't be modified afterwards.
Because the request may have had a body, or may be followed
by another request, Cowboy is forced to buffer all data it
receives. This data may grow to become too large though,
so there is a configurable limit for it. The default buffer
size is of 5000 bytes, but it may be changed by setting the
`loop_max_buffer` middleware environment value.
=== Hibernate
To save memory, you may hibernate the process in between

View file

@ -20,31 +20,31 @@ init(Req, State) ->
{cowboy_websocket, Req, State}.
----
The return value may also have a `Timeout` value and/or the
atom `hibernate`. These options are useful for long living
connections. When they are not provided, the timeout value
defaults to `infinity` and the hibernate value to `run`.
The returned tuple may also have a fourth element containing
options for the sub protocol. No option is universal. While
it will usually be a map of options, it doesn't have to be.
For example loop handlers accept the atom `hibernate`.
The following snippet switches to the `my_protocol` sub
protocol, sets the timeout value to 5 seconds and enables
hibernation:
// @todo Yeah maybe what we really need is an Opts map.
[source,erlang]
----
init(Req, State) ->
{my_protocol, Req, State, 5000, hibernate}.
{my_protocol, Req, State, #{
timeout => 5000,
compress => true}}.
----
If a sub protocol does not make use of these options, it should
crash if it receives anything other than the default values.
Sub protocols should ignore unknown options so as to not waste
resources doing unnecessary validation.
=== Upgrade
After the `init/2` function returns, Cowboy will then call the
`upgrade/6` function. This is the only callback defined by the
`cowboy_sub_protocol` behavior.
After the `init/2` function returns, Cowboy will call either
the `upgrade/4` or the `upgrade/5` function. The former is called
when no options were given; the latter when they were given.
The function is named `upgrade` because it mimics the mechanism
of HTTP protocol upgrades. For some sub protocols, like Websocket,
@ -53,16 +53,19 @@ only an upgrade at Cowboy's level and the client has nothing to
do about it.
The upgrade callback receives the Req object, the middleware
environment, the handler and its options, and the aforementioned
timeout and hibernate values.
environment, the handler and its state, and for `upgrade/5`
also the aformentioned options.
[source,erlang]
----
upgrade(Req, Env, Handler, HandlerOpts, Timeout, Hibernate) ->
upgrade(Req, Env, Handler, State) ->
%% Sub protocol code here.
upgrade(Req, Env, Handler, State, Opts) ->
%% Sub protocol code here.
----
This callback is expected to behave like a middleware and to
These callbacks are expected to behave like middlewares and to
return an updated environment and Req object.
Sub protocols are expected to call the `cowboy_handler:terminate/4`

View file

@ -60,13 +60,13 @@ be:
init(Req, State) ->
case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
undefined ->
{ok, Req, State};
{cowboy_websocket, Req, State};
Subprotocols ->
case lists:keymember(<<"mqtt">>, 1, Subprotocols) of
true ->
Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
<<"mqtt">>, Req),
{ok, Req2, State};
{cowboy_websocket, Req2, State};
false ->
{stop, Req, State}
end
@ -210,12 +210,13 @@ 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 60 seconds:
close connections idle for more than 30 seconds:
[source,erlang]
----
init(Req, State) ->
{cowboy_websocket, Req, State, 60000}.
{cowboy_websocket, Req, State, #{
idle_timeout => 30000}}.
----
This value cannot be changed once it is set. It defaults to

View file

@ -65,6 +65,5 @@ Cowboy's Websocket implementation also includes the
permessage-deflate and x-webkit-deflate-frame compression
extensions.
Cowboy will automatically use compression as long as the
`websocket_compress` protocol option is set when starting
the listener.
Cowboy will automatically use compression when the
`compress` option is returned from the `init/2` function.