0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 04:10:24 +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.

View file

@ -59,8 +59,6 @@ Middlewares:
* ssl - Secure communication over sockets
* crypto - Crypto functions
// @todo Explicitly depend on ssl.
All these applications must be started before the `cowboy`
application. To start Cowboy and all dependencies at once:

View file

@ -29,8 +29,6 @@ Loop handlers implement the following interface:
init(Req, State)
-> {cowboy_loop, Req, State}
| {cowboy_loop, Req, State, hibernate}
| {cowboy_loop, Req, State, timeout()}
| {cowboy_loop, Req, State, timeout(), hibernate}
info(Info, Req, State)
-> {ok, Req, State}
@ -42,7 +40,7 @@ terminate(Reason, Req, State) -> ok %% optional
Req :: cowboy_req:req()
State :: any()
Info :: any()
Reason :: stop | timeout
Reason :: stop
| {crash, error | exit | throw, any()}
----
@ -65,37 +63,15 @@ stop::
The handler requested to close the connection by returning
a `stop` tuple.
timeout::
The connection has been closed due to inactivity. The timeout
value can be configured from `init/2`. The response sent when
this happens is a `204 No Content`.
{crash, Class, Reason}::
A crash occurred in the handler. `Class` and `Reason` can be
used to obtain more information about the crash. The function
`erlang:get_stacktrace/0` can also be called to obtain the
stacktrace of the process when the crash occurred.
//{error, overflow}::
// The connection is being closed and the process terminated
// because the buffer Cowboy uses to keep data sent by the
// client has reached its maximum. The buffer size can be
// configured through the environment value `loop_max_buffer`
// and defaults to 5000 bytes.
// +
// If the long running request comes with a body it is recommended
// to process this body before switching to the loop sub protocol.
//
//{error, closed}::
// The socket has been closed brutally without a close frame being
// received first.
//
//{error, Reason}::
// A socket error ocurred.
== Changelog
* *2.0*: Cowboy temporarily no longer checks the socket for data with HTTP/1.1.
* *2.0*: Loop handlers no longer need to handle overflow/timeouts.
* *1.0*: Behavior introduced.
== See also

View file

@ -21,9 +21,6 @@ REST handlers implement the following interface:
----
init(Req, State)
-> {cowboy_rest, Req, State}
| {cowboy_rest, Req, State, hibernate}
| {cowboy_rest, Req, State, timeout()}
| {cowboy_rest, Req, State, timeout(), hibernate}
Callback(Req, State)
-> {Result, Req, State}

View file

@ -10,31 +10,6 @@ The module `cowboy_websocket` implements Websocket
as a Ranch protocol. It also defines a callback interface
for handling Websocket connections.
== Options
[source,erlang]
----
opts() :: #{
websocket_compress := boolean()
}
----
Configuration for the Websocket protocol.
This configuration is passed to Cowboy when starting listeners
using `cowboy:start_clear/4` or `cowboy:start_tls/4` functions.
It can be updated without restarting listeners using the
Ranch functions `ranch:get_protocol_options/1` and
`ranch:set_protocol_options/2`.
The default value is given next to the option name:
websocket_compress (false)::
Whether to enable the Websocket frame compression
extension. Frames will only be compressed for the
clients that support this extension.
== Callbacks
Websocket handlers must implement the following callback
@ -44,9 +19,7 @@ interface:
----
init(Req, State)
-> {cowboy_websocket, Req, State}
| {cowboy_websocket, Req, State, hibernate}
| {cowboy_websocket, Req, State, timeout()}
| {cowboy_websocket, Req, State, timeout(), hibernate}
| {cowboy_websocket, Req, State, Opts}
websocket_init(State) -> CallResult %% optional
websocket_handle(InFrame, State) -> CallResult
@ -56,6 +29,7 @@ terminate(Reason, undefined, State) -> ok %% optional
Req :: cowboy_req:req()
State :: any()
Opts :: cowboy_websocket:opts()
InFrame :: {text | binary | ping | pong, binary()}
OutFrame :: cow_ws:frame()
Info :: any()
@ -152,6 +126,42 @@ timeout::
{error, Reason}::
A socket error ocurred.
== Types
=== opts()
[source,erlang]
----
opts() :: #{
compress => boolean(),
idle_timeout => timeout()
}
----
Websocket handler options.
This configuration is passed to Cowboy from the `init/2`
function:
[source,erlang]
----
init(Req, State) ->
Opts = #{compress => true},
{cowboy_websocket, Req, State, Opts}.
----
The default value is given next to the option name:
compress (false)::
Whether to enable the Websocket frame compression
extension. Frames will only be compressed for the
clients that support this extension.
idle_timeout (60000)::
Time in milliseconds that Cowboy will keep the
connection open without receiving anything from
the client.
== Changelog
* *2.0*: The Req object is no longer passed to Websocket callbacks.

View file

@ -11,7 +11,7 @@ init(Req0, Opts) ->
<<"content-type">> => <<"text/event-stream">>
}, Req0),
erlang:send_after(1000, self(), {message, "Tick"}),
{cowboy_loop, Req, Opts, 5000}.
{cowboy_loop, Req, Opts}.
info({message, Msg}, Req, State) ->
cowboy_req:stream_body(["id: ", id(), "\ndata: ", Msg, "\n\n"], nofin, Req),

View file

@ -20,7 +20,7 @@ define tpl_cowboy.loop
-export([info/3]).
init(Req, State) ->
{cowboy_loop, Req, State, 5000, hibernate}.
{cowboy_loop, Req, State, hibernate}.
info(_Info, Req, State) ->
{ok, Req, State, hibernate}.

View file

@ -25,9 +25,7 @@
-callback init(Req, any())
-> {ok | module(), Req, any()}
| {module(), Req, any(), hibernate}
| {module(), Req, any(), timeout()}
| {module(), Req, any(), timeout(), hibernate}
| {module(), Req, any(), any()}
when Req::cowboy_req:req().
-callback terminate(any(), cowboy_req:req(), any()) -> ok.
@ -41,13 +39,9 @@ execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
Result = terminate(normal, Req2, State, Handler),
{ok, Req2, Env#{result => Result}};
{Mod, Req2, State} ->
Mod:upgrade(Req2, Env, Handler, State, infinity, run);
{Mod, Req2, State, hibernate} ->
Mod:upgrade(Req2, Env, Handler, State, infinity, hibernate);
{Mod, Req2, State, Timeout} ->
Mod:upgrade(Req2, Env, Handler, State, Timeout, run);
{Mod, Req2, State, Timeout, hibernate} ->
Mod:upgrade(Req2, Env, Handler, State, Timeout, hibernate)
Mod:upgrade(Req2, Env, Handler, State);
{Mod, Req2, State, Opts} ->
Mod:upgrade(Req2, Env, Handler, State, Opts)
catch Class:Reason ->
terminate({crash, Class, Reason}, Req, HandlerOpts, Handler),
erlang:raise(Class, Reason, erlang:get_stacktrace())

View file

@ -136,11 +136,10 @@ init(Parent, Ref, Socket, Transport, Opts) ->
%% Timeouts:
%% - waiting for new request (if no stream is currently running)
%% -> request_timeout: for whole request/headers, set at init/when we set ps_request_line{} state
%% - waiting for body (if a stream requested the body to be read)
%% -> read_body_timeout: amount of time we wait without receiving any data when reading the body
%% - waiting for new request, or body (when a stream is currently running)
%% -> idle_timeout: amount of time we wait without receiving any data
%% - if we skip the body, skip only for a specific duration
%% -> skip_body_timeout: also have a skip_body_length
%% - none if we have a stream running and it didn't request the body to be read
%% - global
%% -> inactivity_timeout: max time to wait without anything happening before giving up

View file

@ -12,27 +12,18 @@
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% When using loop handlers, we are receiving data from the socket because we
%% want to know when the socket gets closed. This is generally not an issue
%% because these kinds of requests are generally not pipelined, and don't have
%% a body. If they do have a body, this body is often read in the
%% <em>init/2</em> callback and this is no problem. Otherwise, this data
%% accumulates in a buffer until we reach a certain threshold of 5000 bytes
%% by default. This can be configured through the <em>loop_max_buffer</em>
%% environment value. The request will be terminated with an
%% <em>{error, overflow}</em> reason if this threshold is reached.
-module(cowboy_loop).
-behaviour(cowboy_sub_protocol).
-export([upgrade/6]).
-export([upgrade/4]).
-export([upgrade/5]).
-export([loop/4]).
-callback init(Req, any())
-> {ok | module(), Req, any()}
| {module(), Req, any(), hibernate}
| {module(), Req, any(), timeout()}
| {module(), Req, any(), timeout(), hibernate}
| {module(), Req, any(), any()}
when Req::cowboy_req:req().
-callback info(any(), Req, State)
-> {ok, Req, State}
| {ok, Req, State, hibernate}
@ -42,97 +33,44 @@
-callback terminate(any(), cowboy_req:req(), any()) -> ok.
-optional_callbacks([terminate/3]).
-record(state, {
env :: cowboy_middleware:env(),
hibernate = false :: boolean(),
buffer_size = 0 :: non_neg_integer(),
max_buffer = 5000 :: non_neg_integer() | infinity,
timeout = infinity :: timeout(),
timeout_ref = undefined :: undefined | reference()
}).
-spec upgrade(Req, Env, module(), any(), timeout(), run | hibernate)
-> {ok, Req, Env} | {suspend, module(), atom(), [any()]}
-spec upgrade(Req, Env, module(), any())
-> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req, Env, Handler, HandlerState, Timeout, Hibernate) ->
State = #state{env=Env, max_buffer=get_max_buffer(Env), timeout=Timeout,
hibernate=Hibernate =:= hibernate},
State2 = timeout(State),
before_loop(Req, State2, Handler, HandlerState).
upgrade(Req, Env, Handler, HandlerState) ->
loop(Req, Env, Handler, HandlerState).
get_max_buffer(#{loop_max_buffer := MaxBuffer}) -> MaxBuffer;
get_max_buffer(_) -> 5000.
-spec upgrade(Req, Env, module(), any(), hibernate)
-> {suspend, ?MODULE, loop, [any()]}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req, Env, Handler, HandlerState, hibernate) ->
suspend(Req, Env, Handler, HandlerState).
before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
%% @todo Yeah we can't get the socket anymore.
%% Everything changes since we are a separate process now.
%% Proper flow control at the connection level should be implemented
%% instead of what we have here.
% [Socket, Transport] = cowboy_req:get([socket, transport], Req),
% Transport:setopts(Socket, [{active, once}]),
{suspend, ?MODULE, loop, [Req, State#state{hibernate=false}, Handler, HandlerState]};
before_loop(Req, State, Handler, HandlerState) ->
%% Same here.
% [Socket, Transport] = cowboy_req:get([socket, transport], Req),
% Transport:setopts(Socket, [{active, once}]),
loop(Req, State, Handler, HandlerState).
%% Almost the same code can be found in cowboy_websocket.
timeout(State=#state{timeout=infinity}) ->
State#state{timeout_ref=undefined};
timeout(State=#state{timeout=Timeout,
timeout_ref=PrevRef}) ->
_ = case PrevRef of
undefined -> ignore%;
% @todo PrevRef -> erlang:cancel_timer(PrevRef)
end,
TRef = erlang:start_timer(Timeout, self(), ?MODULE),
State#state{timeout_ref=TRef}.
-spec loop(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
loop(Req, State=#state{timeout_ref=TRef}, Handler, HandlerState) ->
-spec loop(Req, Env, module(), any())
-> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
%% @todo Handle system messages.
loop(Req, Env, Handler, HandlerState) ->
receive
{timeout, TRef, ?MODULE} ->
terminate(Req, State, Handler, HandlerState, timeout);
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
loop(Req, State, Handler, HandlerState);
Message ->
call(Req, State, Handler, HandlerState, Message)
call(Req, Env, Handler, HandlerState, Message)
end.
call(Req, State, Handler, HandlerState, Message) ->
try Handler:info(Message, Req, HandlerState) of
{ok, Req2, HandlerState2} ->
before_loop(Req2, State, Handler, HandlerState2);
{ok, Req2, HandlerState2, hibernate} ->
before_loop(Req2, State#state{hibernate=true}, Handler, HandlerState2);
{stop, Req2, HandlerState2} ->
terminate(Req2, State, Handler, HandlerState2, stop)
call(Req0, Env, Handler, HandlerState0, Message) ->
try Handler:info(Message, Req0, HandlerState0) of
{ok, Req, HandlerState} ->
loop(Req, Env, Handler, HandlerState);
{ok, Req, HandlerState, hibernate} ->
suspend(Req, Env, Handler, HandlerState);
{stop, Req, HandlerState} ->
terminate(Req, Env, Handler, HandlerState, stop)
catch Class:Reason ->
cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler),
cowboy_handler:terminate({crash, Class, Reason}, Req0, HandlerState0, Handler),
erlang:raise(Class, Reason, erlang:get_stacktrace())
end.
terminate(Req, #state{env=Env, timeout_ref=TRef},
Handler, HandlerState, Reason) ->
_ = case TRef of
undefined -> ignore;
TRef -> erlang:cancel_timer(TRef)
end,
flush_timeouts(),
suspend(Req, Env, Handler, HandlerState) ->
{suspend, ?MODULE, loop, [Req, Env, Handler, HandlerState]}.
terminate(Req, Env, Handler, HandlerState, Reason) ->
Result = cowboy_handler:terminate(Reason, Req, HandlerState, Handler),
{ok, Req, Env#{result => Result}}.
flush_timeouts() ->
receive
{timeout, TRef, ?MODULE} when is_reference(TRef) ->
flush_timeouts()
after 0 ->
ok
end.

View file

@ -17,15 +17,14 @@
-module(cowboy_rest).
-behaviour(cowboy_sub_protocol).
-export([upgrade/6]).
-export([upgrade/4]).
-export([upgrade/5]).
%% Common handler callbacks.
-callback init(Req, any())
-> {ok | module(), Req, any()}
| {module(), Req, any(), hibernate}
| {module(), Req, any(), timeout()}
| {module(), Req, any(), timeout(), hibernate}
| {module(), Req, any(), any()}
when Req::cowboy_req:req().
-callback terminate(any(), cowboy_req:req(), any()) -> ok.
@ -232,14 +231,20 @@
expires :: undefined | no_call | calendar:datetime() | binary()
}).
-spec upgrade(Req, Env, module(), any(), infinity, run)
-spec upgrade(Req, Env, module(), any())
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req0, Env, Handler, HandlerState, infinity, run) ->
upgrade(Req0, Env, Handler, HandlerState) ->
Method = cowboy_req:method(Req0),
{ok, Req, Result} = service_available(Req0, #state{method=Method,
handler=Handler, handler_state=HandlerState}),
{ok, Req, Env#{result => Result}}.
-spec upgrade(Req, Env, module(), any(), any())
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
%% cowboy_rest takes no options.
upgrade(Req, Env, Handler, HandlerState, _Opts) ->
upgrade(Req, Env, Handler, HandlerState).
service_available(Req, State) ->
expect(Req, State, service_available, true, fun known_methods/2, 503).

View file

@ -15,6 +15,10 @@
-module(cowboy_sub_protocol).
-callback upgrade(Req, Env, module(), any(), timeout(), run | hibernate)
-callback upgrade(Req, Env, module(), any())
-> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
-callback upgrade(Req, Env, module(), any(), any())
-> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().

View file

@ -17,7 +17,8 @@
-module(cowboy_websocket).
-behaviour(cowboy_sub_protocol).
-export([upgrade/6]).
-export([upgrade/4]).
-export([upgrade/5]).
-export([takeover/7]).
-export([handler_loop/3]).
@ -34,9 +35,7 @@
-callback init(Req, any())
-> {ok | module(), Req, any()}
| {module(), Req, any(), hibernate}
| {module(), Req, any(), timeout()}
| {module(), Req, any(), timeout(), hibernate}
| {module(), Req, any(), any()}
when Req::cowboy_req:req().
-callback websocket_init(State)
@ -53,6 +52,12 @@
-callback terminate(any(), cowboy_req:req(), any()) -> ok.
-optional_callbacks([terminate/3]).
-type opts() :: #{
idle_timeout => timeout(),
compress => boolean()
}.
-export_type([opts/0]).
-record(state, {
socket = undefined :: inet:socket() | undefined,
transport = undefined :: module(),
@ -60,6 +65,7 @@
key = undefined :: undefined | binary(),
timeout = infinity :: timeout(),
timeout_ref = undefined :: undefined | reference(),
compress = false :: boolean(),
messages = undefined :: undefined | {atom(), atom(), atom()},
hibernate = false :: boolean(),
frag_state = undefined :: cow_ws:frag_state(),
@ -70,14 +76,22 @@
%% Stream process.
-spec upgrade(Req, Env, module(), any(), timeout(), run | hibernate)
-spec upgrade(Req, Env, module(), any())
-> {ok, Req, Env}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req, Env, Handler, HandlerState) ->
upgrade(Req, Env, Handler, HandlerState, #{}).
-spec upgrade(Req, Env, module(), any(), opts())
-> {ok, Req, Env}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
%% @todo Immediately crash if a response has already been sent.
%% @todo Error out if HTTP/2.
upgrade(Req0, Env, Handler, HandlerState, Timeout, Hibernate) ->
try websocket_upgrade(#state{handler=Handler, timeout=Timeout,
hibernate=Hibernate =:= hibernate}, Req0) of
upgrade(Req0, Env, Handler, HandlerState, Opts) ->
Timeout = maps:get(idle_timeout, Opts, 60000),
Compress = maps:get(compress, Opts, false),
State0 = #state{handler=Handler, timeout=Timeout, compress=Compress},
try websocket_upgrade(State0, Req0) of
{ok, State, Req} ->
websocket_handshake(State, Req, HandlerState, Env)
catch _:_ ->
@ -104,14 +118,13 @@ websocket_upgrade(State, Req) ->
-spec websocket_extensions(#state{}, Req)
-> {ok, #state{}, Req} when Req::cowboy_req:req().
websocket_extensions(State, Req=#{ref := Ref}) ->
websocket_extensions(State=#state{compress=Compress}, Req) ->
%% @todo We want different options for this. For example
%% * compress everything auto
%% * compress only text auto
%% * compress only binary auto
%% * compress nothing auto (but still enabled it)
%% * disable compression
Compress = maps:get(websocket_compress, ranch:get_protocol_options(Ref), false),
case {Compress, cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req)} of
{true, Extensions} when Extensions =/= undefined ->
websocket_extensions(State, Req, Extensions, []);
@ -170,6 +183,7 @@ websocket_handshake(State=#state{key=Key},
{#state{}, any()}) -> ok.
takeover(_Parent, Ref, Socket, Transport, _Opts, Buffer,
{State0=#state{handler=Handler}, HandlerState}) ->
%% @todo We should have an option to disable this behavior.
ranch:remove_connection(Ref),
State1 = handler_loop_timeout(State0#state{socket=Socket, transport=Transport}),
State = State1#state{key=undefined, messages=Transport:messages()},

View file

@ -11,7 +11,7 @@
init(Req, _) ->
erlang:send_after(200, self(), timeout),
{cowboy_loop, Req, 2, 5000, hibernate}.
{cowboy_loop, Req, 2, hibernate}.
info(timeout, Req, 0) ->
%% @todo Why 102?

View file

@ -11,7 +11,7 @@
init(Req, _) ->
self() ! timeout,
{cowboy_loop, Req, undefined, 5000, hibernate}.
{cowboy_loop, Req, undefined, hibernate}.
info(timeout, Req0, State) ->
{ok, Body, Req} = cowboy_req:read_body(Req0),

View file

@ -12,7 +12,7 @@
init(Req, _) ->
erlang:send_after(1000, self(), timeout),
{cowboy_loop, Req, undefined, 200, hibernate}.
{cowboy_loop, Req, undefined, hibernate}.
info(timeout, Req, State) ->
{stop, cowboy_req:reply(500, Req), State}.

View file

@ -9,7 +9,7 @@
init(Req, _) ->
receive after 100 -> ok end,
self() ! stream,
{cowboy_loop, Req, undefined, 100}.
{cowboy_loop, Req, undefined}.
info(stream, Req, undefined) ->
stream(Req, 1, <<>>).

View file

@ -39,15 +39,13 @@ init_per_group(Name = autobahn, Config) ->
{skip, "Autobahn Test Suite not installed."};
_ ->
{ok, _} = cowboy:start_clear(Name, 100, [{port, 33080}], #{
env => #{dispatch => init_dispatch()},
websocket_compress => true
env => #{dispatch => init_dispatch()}
}),
Config
end;
init_per_group(Name = ws, Config) ->
cowboy_test:init_http(Name, #{
env => #{dispatch => init_dispatch()},
websocket_compress => true
env => #{dispatch => init_dispatch()}
}, Config).
end_per_group(Listener, _Config) ->

View file

@ -7,7 +7,9 @@
-export([websocket_info/2]).
init(Req, _) ->
{cowboy_websocket, Req, undefined}.
{cowboy_websocket, Req, undefined, #{
compress => true
}}.
websocket_handle({text, Data}, State) ->
{reply, {text, Data}, State};

View file

@ -9,7 +9,9 @@
init(Req, Opts) ->
[Protocol | _] = cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req),
Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>, Protocol, Req),
{cowboy_websocket, Req2, Opts, 1000}.
{cowboy_websocket, Req2, Opts, #{
idle_timeout => 1000
}}.
websocket_handle(_Frame, State) ->
{ok, State}.

View file

@ -8,7 +8,9 @@
init(Req, _) ->
erlang:start_timer(500, self(), should_not_cancel_timer),
{cowboy_websocket, Req, undefined, 1000}.
{cowboy_websocket, Req, undefined, #{
idle_timeout => 1000
}}.
websocket_handle({text, Data}, State) ->
{reply, {text, Data}, State};

View file

@ -3,11 +3,17 @@
-module(ws_timeout_hibernate).
-export([init/2]).
-export([websocket_init/1]).
-export([websocket_handle/2]).
-export([websocket_info/2]).
init(Req, _) ->
{cowboy_websocket, Req, undefined, 1000, hibernate}.
{cowboy_websocket, Req, undefined, #{
idle_timeout => 1000
}}.
websocket_init(State) ->
{ok, State, hibernate}.
websocket_handle(_Frame, State) ->
{ok, State, hibernate}.