Browse Source

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.
Loïc Hoguin 8 years ago
parent
commit
a45813c60f

+ 3 - 24
doc/src/guide/loop_handlers.asciidoc

@@ -34,7 +34,7 @@ loop handler behavior. This tuple may optionally contain
 a timeout value and/or the atom `hibernate` to make the
 a timeout value and/or the atom `hibernate` to make the
 process enter hibernation until a message is received.
 process enter hibernation until a message is received.
 
 
-This snippet enables the loop handler.
+This snippet enables the loop handler:
 
 
 [source,erlang]
 [source,erlang]
 ----
 ----
@@ -42,14 +42,12 @@ init(Req, State) ->
     {cowboy_loop, 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]
 [source,erlang]
 ----
 ----
 init(Req, State) ->
 init(Req, State) ->
-    {cowboy_loop, Req, State, 30000, hibernate}.
+    {cowboy_loop, Req, State, hibernate}.
 ----
 ----
 
 
 === Receive loop
 === Receive loop
@@ -123,25 +121,6 @@ a subsequent request.
 Please refer to the xref:handlers[Handlers chapter]
 Please refer to the xref:handlers[Handlers chapter]
 for general instructions about cleaning up.
 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
 === Hibernate
 
 
 To save memory, you may hibernate the process in between
 To save memory, you may hibernate the process in between

+ 19 - 16
doc/src/guide/sub_protocols.asciidoc

@@ -20,31 +20,31 @@ init(Req, State) ->
     {cowboy_websocket, 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
 The following snippet switches to the `my_protocol` sub
 protocol, sets the timeout value to 5 seconds and enables
 protocol, sets the timeout value to 5 seconds and enables
 hibernation:
 hibernation:
 
 
-// @todo Yeah maybe what we really need is an Opts map.
-
 [source,erlang]
 [source,erlang]
 ----
 ----
 init(Req, State) ->
 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
 === 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
 The function is named `upgrade` because it mimics the mechanism
 of HTTP protocol upgrades. For some sub protocols, like Websocket,
 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.
 do about it.
 
 
 The upgrade callback receives the Req object, the middleware
 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]
 [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.
     %% 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.
 return an updated environment and Req object.
 
 
 Sub protocols are expected to call the `cowboy_handler:terminate/4`
 Sub protocols are expected to call the `cowboy_handler:terminate/4`

+ 5 - 4
doc/src/guide/ws_handlers.asciidoc

@@ -60,13 +60,13 @@ be:
 init(Req, State) ->
 init(Req, State) ->
     case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
     case cowboy_req:parse_header(<<"sec-websocket-protocol">>, Req) of
         undefined ->
         undefined ->
-            {ok, Req, State};
+            {cowboy_websocket, Req, State};
         Subprotocols ->
         Subprotocols ->
             case lists:keymember(<<"mqtt">>, 1, Subprotocols) of
             case lists:keymember(<<"mqtt">>, 1, Subprotocols) of
                 true ->
                 true ->
                     Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
                     Req2 = cowboy_req:set_resp_header(<<"sec-websocket-protocol">>,
                         <<"mqtt">>, Req),
                         <<"mqtt">>, Req),
-                    {ok, Req2, State};
+                    {cowboy_websocket, Req2, State};
                 false ->
                 false ->
                     {stop, Req, State}
                     {stop, Req, State}
             end
             end
@@ -210,12 +210,13 @@ than needed.
 
 
 The `init/2` callback can set the timeout to be used
 The `init/2` callback can set the timeout to be used
 for the connection. For example, this would make Cowboy
 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]
 [source,erlang]
 ----
 ----
 init(Req, State) ->
 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
 This value cannot be changed once it is set. It defaults to

+ 2 - 3
doc/src/guide/ws_protocol.asciidoc

@@ -65,6 +65,5 @@ Cowboy's Websocket implementation also includes the
 permessage-deflate and x-webkit-deflate-frame compression
 permessage-deflate and x-webkit-deflate-frame compression
 extensions.
 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.

+ 0 - 2
doc/src/manual/cowboy_app.asciidoc

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

+ 2 - 26
doc/src/manual/cowboy_loop.asciidoc

@@ -29,8 +29,6 @@ Loop handlers implement the following interface:
 init(Req, State)
 init(Req, State)
     -> {cowboy_loop, Req, State}
     -> {cowboy_loop, Req, State}
      | {cowboy_loop, Req, State, hibernate}
      | {cowboy_loop, Req, State, hibernate}
-     | {cowboy_loop, Req, State, timeout()}
-     | {cowboy_loop, Req, State, timeout(), hibernate}
 
 
 info(Info, Req, State)
 info(Info, Req, State)
     -> {ok, Req, State}
     -> {ok, Req, State}
@@ -42,7 +40,7 @@ terminate(Reason, Req, State) -> ok  %% optional
 Req    :: cowboy_req:req()
 Req    :: cowboy_req:req()
 State  :: any()
 State  :: any()
 Info   :: any()
 Info   :: any()
-Reason :: stop | timeout
+Reason :: stop
         | {crash, error | exit | throw, any()}
         | {crash, error | exit | throw, any()}
 ----
 ----
 
 
@@ -65,37 +63,15 @@ stop::
     The handler requested to close the connection by returning
     The handler requested to close the connection by returning
     a `stop` tuple.
     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}::
 {crash, Class, Reason}::
     A crash occurred in the handler. `Class` and `Reason` can be
     A crash occurred in the handler. `Class` and `Reason` can be
     used to obtain more information about the crash. The function
     used to obtain more information about the crash. The function
     `erlang:get_stacktrace/0` can also be called to obtain the
     `erlang:get_stacktrace/0` can also be called to obtain the
     stacktrace of the process when the crash occurred.
     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
 == 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.
 * *1.0*: Behavior introduced.
 
 
 == See also
 == See also

+ 0 - 3
doc/src/manual/cowboy_rest.asciidoc

@@ -21,9 +21,6 @@ REST handlers implement the following interface:
 ----
 ----
 init(Req, State)
 init(Req, State)
     -> {cowboy_rest, 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)
 Callback(Req, State)
     -> {Result, Req, State}
     -> {Result, Req, State}

+ 38 - 28
doc/src/manual/cowboy_websocket.asciidoc

@@ -10,31 +10,6 @@ The module `cowboy_websocket` implements Websocket
 as a Ranch protocol. It also defines a callback interface
 as a Ranch protocol. It also defines a callback interface
 for handling Websocket connections.
 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
 == Callbacks
 
 
 Websocket handlers must implement the following callback
 Websocket handlers must implement the following callback
@@ -44,9 +19,7 @@ interface:
 ----
 ----
 init(Req, State)
 init(Req, State)
     -> {cowboy_websocket, 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_init(State)            -> CallResult  %% optional
 websocket_handle(InFrame, State) -> CallResult
 websocket_handle(InFrame, State) -> CallResult
@@ -56,6 +29,7 @@ terminate(Reason, undefined, State) -> ok       %% optional
 
 
 Req        :: cowboy_req:req()
 Req        :: cowboy_req:req()
 State      :: any()
 State      :: any()
+Opts       :: cowboy_websocket:opts()
 InFrame    :: {text | binary | ping | pong, binary()}
 InFrame    :: {text | binary | ping | pong, binary()}
 OutFrame   :: cow_ws:frame()
 OutFrame   :: cow_ws:frame()
 Info       :: any()
 Info       :: any()
@@ -152,6 +126,42 @@ timeout::
 {error, Reason}::
 {error, Reason}::
     A socket error ocurred.
     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
 == Changelog
 
 
 * *2.0*: The Req object is no longer passed to Websocket callbacks.
 * *2.0*: The Req object is no longer passed to Websocket callbacks.

+ 1 - 1
examples/eventsource/src/eventsource_handler.erl

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

+ 1 - 1
plugins.mk

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

+ 4 - 10
src/cowboy_handler.erl

@@ -25,9 +25,7 @@
 
 
 -callback init(Req, any())
 -callback init(Req, any())
 	-> {ok | module(), 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().
 	when Req::cowboy_req:req().
 
 
 -callback terminate(any(), cowboy_req:req(), any()) -> ok.
 -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),
 			Result = terminate(normal, Req2, State, Handler),
 			{ok, Req2, Env#{result => Result}};
 			{ok, Req2, Env#{result => Result}};
 		{Mod, Req2, State} ->
 		{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 ->
 	catch Class:Reason ->
 		terminate({crash, Class, Reason}, Req, HandlerOpts, Handler),
 		terminate({crash, Class, Reason}, Req, HandlerOpts, Handler),
 		erlang:raise(Class, Reason, erlang:get_stacktrace())
 		erlang:raise(Class, Reason, erlang:get_stacktrace())

+ 2 - 3
src/cowboy_http.erl

@@ -136,11 +136,10 @@ init(Parent, Ref, Socket, Transport, Opts) ->
 %% Timeouts:
 %% Timeouts:
 %% - waiting for new request (if no stream is currently running)
 %% - 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
 %%   -> 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
 %% - if we skip the body, skip only for a specific duration
 %%   -> skip_body_timeout: also have a skip_body_length
 %%   -> 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
 %% - global
 %%   -> inactivity_timeout: max time to wait without anything happening before giving up
 %%   -> inactivity_timeout: max time to wait without anything happening before giving up
 
 

+ 32 - 94
src/cowboy_loop.erl

@@ -12,27 +12,18 @@
 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 %% 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).
 -module(cowboy_loop).
 -behaviour(cowboy_sub_protocol).
 -behaviour(cowboy_sub_protocol).
 
 
--export([upgrade/6]).
+-export([upgrade/4]).
+-export([upgrade/5]).
 -export([loop/4]).
 -export([loop/4]).
 
 
 -callback init(Req, any())
 -callback init(Req, any())
 	-> {ok | module(), 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().
 	when Req::cowboy_req:req().
+
 -callback info(any(), Req, State)
 -callback info(any(), Req, State)
 	-> {ok, Req, State}
 	-> {ok, Req, State}
 	| {ok, Req, State, hibernate}
 	| {ok, Req, State, hibernate}
@@ -42,97 +33,44 @@
 -callback terminate(any(), cowboy_req:req(), any()) -> ok.
 -callback terminate(any(), cowboy_req:req(), any()) -> ok.
 -optional_callbacks([terminate/3]).
 -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().
 	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).
-
-get_max_buffer(#{loop_max_buffer := MaxBuffer}) -> MaxBuffer;
-get_max_buffer(_) -> 5000.
-
-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).
+upgrade(Req, Env, Handler, HandlerState) ->
+	loop(Req, Env, 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 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).
 
 
--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
 	receive
-		{timeout, TRef, ?MODULE} ->
-			terminate(Req, State, Handler, HandlerState, timeout);
-		{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
-			loop(Req, State, Handler, HandlerState);
 		Message ->
 		Message ->
-			call(Req, State, Handler, HandlerState, Message)
+			call(Req, Env, Handler, HandlerState, Message)
 	end.
 	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 ->
 	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())
 		erlang:raise(Class, Reason, erlang:get_stacktrace())
 	end.
 	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),
 	Result = cowboy_handler:terminate(Reason, Req, HandlerState, Handler),
 	{ok, Req, Env#{result => Result}}.
 	{ok, Req, Env#{result => Result}}.
-
-flush_timeouts() ->
-	receive
-		{timeout, TRef, ?MODULE} when is_reference(TRef) ->
-			flush_timeouts()
-	after 0 ->
-		ok
-	end.

+ 11 - 6
src/cowboy_rest.erl

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

+ 5 - 1
src/cowboy_sub_protocol.erl

@@ -15,6 +15,10 @@
 
 
 -module(cowboy_sub_protocol).
 -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}
 	-> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}
 	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
 	when Req::cowboy_req:req(), Env::cowboy_middleware:env().

+ 24 - 10
src/cowboy_websocket.erl

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

+ 1 - 1
test/handlers/long_polling_h.erl

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

+ 1 - 1
test/handlers/loop_handler_body_h.erl

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

+ 1 - 1
test/handlers/loop_handler_timeout_h.erl

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

+ 1 - 1
test/http_SUITE_data/http_loop_stream_recv.erl

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

+ 2 - 4
test/ws_SUITE.erl

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

+ 3 - 1
test/ws_SUITE_data/ws_echo.erl

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

+ 3 - 1
test/ws_SUITE_data/ws_subprotocol.erl

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

+ 3 - 1
test/ws_SUITE_data/ws_timeout_cancel.erl

@@ -8,7 +8,9 @@
 
 
 init(Req, _) ->
 init(Req, _) ->
 	erlang:start_timer(500, self(), should_not_cancel_timer),
 	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) ->
 websocket_handle({text, Data}, State) ->
 	{reply, {text, Data}, State};
 	{reply, {text, Data}, State};

+ 7 - 1
test/ws_SUITE_data/ws_timeout_hibernate.erl

@@ -3,11 +3,17 @@
 -module(ws_timeout_hibernate).
 -module(ws_timeout_hibernate).
 
 
 -export([init/2]).
 -export([init/2]).
+-export([websocket_init/1]).
 -export([websocket_handle/2]).
 -export([websocket_handle/2]).
 -export([websocket_info/2]).
 -export([websocket_info/2]).
 
 
 init(Req, _) ->
 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) ->
 websocket_handle(_Frame, State) ->
 	{ok, State, hibernate}.
 	{ok, State, hibernate}.