Browse Source

Add middleware support

Middlewares allow customizing the request processing.

All existing Cowboy project are incompatible with this commit.
You need to change `{dispatch, Dispatch}` in the protocol options
to `{env, [{dispatch, Dispatch}]}` to fix your code.
Loïc Hoguin 12 years ago
parent
commit
1b3f510b7e

+ 74 - 0
guide/middlewares.md

@@ -0,0 +1,74 @@
+Middlewares
+===========
+
+Purpose
+-------
+
+Cowboy delegates the request processing to middleware components.
+By default, two middlewares are defined, for the routing and handling
+of the request, as is detailed in most of this guide.
+
+Middlewares give you complete control over how requests are to be
+processed. You can add your own middlewares to the mix or completely
+change the chain of middlewares as needed.
+
+Cowboy will execute all middlewares in the given order, unless one
+of them decides to stop processing.
+
+Usage
+-----
+
+Middlewares only need to implement a single callback: `execute/2`.
+It is defined in the `cowboy_middleware` behavior.
+
+This callback has two arguments. The first is the `Req` object.
+The second is the environment.
+
+Middlewares can return one of four different values:
+ *  `{ok, Req, Env}` to continue the request processing
+ *  `{suspend, Module, Function, Args}` to hibernate
+ *  `{halt, Req}` to stop processing and move on to the next request
+ *  `{error, StatusCode, Req}` to reply an error and close the socket
+
+Of note is that when hibernating, processing will resume on the given
+MFA, discarding all previous stacktrace. Make sure you keep the `Req`
+and `Env` in the arguments of this MFA for later use.
+
+If an error happens during middleware processing, Cowboy will not try
+to send an error back to the socket, the process will just crash. It
+is up to the middleware to make sure that a reply is sent if something
+goes wrong.
+
+Configuration
+-------------
+
+The middleware environment is defined as the `env` protocol option.
+In the previous chapters we saw it briefly when we needed to pass
+the routing information. It is a list of tuples with the first
+element being an atom and the second any Erlang term.
+
+Two values in the environment are reserved:
+ *  `listener` contains the pid of the listener process
+ *  `result` contains the result of the processing
+
+The `listener` value is always defined. The `result` value can be
+set by any middleware. If set to anything other than `ok`, Cowboy
+will not process any subsequent requests on this connection.
+
+The middlewares that come with Cowboy may define or require other
+environment values to perform.
+
+Routing middleware
+------------------
+
+@todo Routing middleware value is renamed very soon.
+
+The routing middleware requires the `dispatch` value. If routing
+succeeds, it will put the handler name and options in the `handler`
+and `handler_opts` values of the environment, respectively.
+
+Handler middleware
+------------------
+
+The handler middleware requires the `handler` and `handler_opts`
+values. It puts the result of the request handling into `result`.

+ 2 - 5
guide/routing.md

@@ -135,18 +135,15 @@ handler to run instead of having to parse the routes repeatedly.
 
 This can be done with a simple call to `cowboy_routing:compile/1`.
 
-@todo Note that the `routes` option will be specified slightly differently
-when middleware support gets in.
-
 ``` erlang
 {ok, Routes} = cowboy_routing:compile([
-    %% {URIHost, list({URIPath, Handler, Opts})}
+    %% {HostMatch, list({PathMatch, Handler, Opts})}
     {'_', [{'_', my_handler, []}]}
 ]),
 %% Name, NbAcceptors, TransOpts, ProtoOpts
 cowboy:start_http(my_http_listener, 100,
     [{port, 8080}],
-    [{routes, Routes}]
+    [{env, [{routes, Routes}]}]
 ).
 ```
 

+ 6 - 0
guide/toc.md

@@ -43,6 +43,12 @@ Cowboy User Guide
  *  [Hooks](hooks.md)
    *  On request
    *  On response
+ *  [Middlewares](middlewares.md)
+   *  Purpose
+   *  Usage
+   *  Configuration
+   *  Routing middleware
+   *  Handler middleware
  *  [Internals](internals.md)
    *  Architecture
    *  Efficiency considerations

+ 201 - 0
src/cowboy_handler.erl

@@ -0,0 +1,201 @@
+%% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Handler middleware.
+%%
+%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>
+%% environment values. The result of this execution is added to the
+%% environment under the <em>result</em> value.
+%%
+%% @see cowboy_http_handler
+-module(cowboy_handler).
+-behaviour(cowboy_middleware).
+
+-export([execute/2]).
+-export([handler_loop/4]).
+
+-record(state, {
+	env :: cowboy_middleware:env(),
+	hibernate = false :: boolean(),
+	loop_timeout = infinity :: timeout(),
+	loop_timeout_ref :: undefined | reference()
+}).
+
+%% @private
+-spec execute(Req, Env)
+	-> {ok, Req, Env} | {error, 500, Req}
+	| {suspend, ?MODULE, handler_loop, [any()]}
+	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req, Env) ->
+	{_, Handler} = lists:keyfind(handler, 1, Env),
+	{_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env),
+	handler_init(Req, #state{env=Env}, Handler, HandlerOpts).
+
+-spec handler_init(Req, #state{}, module(), any())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {error, 500, Req} | {suspend, module(), function(), [any()]}
+	when Req::cowboy_req:req().
+handler_init(Req, State, Handler, HandlerOpts) ->
+	Transport = cowboy_req:get(transport, Req),
+	try Handler:init({Transport:name(), http}, Req, HandlerOpts) of
+		{ok, Req2, HandlerState} ->
+			handler_handle(Req2, State, Handler, HandlerState);
+		{loop, Req2, HandlerState} ->
+			handler_before_loop(Req2, State#state{hibernate=false},
+				Handler, HandlerState);
+		{loop, Req2, HandlerState, hibernate} ->
+			handler_before_loop(Req2, State#state{hibernate=true},
+				Handler, HandlerState);
+		{loop, Req2, HandlerState, Timeout} ->
+			handler_before_loop(Req2, State#state{loop_timeout=Timeout},
+				Handler, HandlerState);
+		{loop, Req2, HandlerState, Timeout, hibernate} ->
+			handler_before_loop(Req2, State#state{
+				hibernate=true, loop_timeout=Timeout}, Handler, HandlerState);
+		{shutdown, Req2, HandlerState} ->
+			terminate_request(Req2, State, Handler, HandlerState);
+		%% @todo {upgrade, transport, Module}
+		{upgrade, protocol, Module} ->
+			upgrade_protocol(Req, State, Handler, HandlerOpts, Module);
+		{upgrade, protocol, Module, Req2, HandlerOpts2} ->
+			upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module)
+	catch Class:Reason ->
+		error_logger:error_msg(
+			"** Cowboy handler ~p terminating in ~p/~p~n"
+			"   for the reason ~p:~p~n"
+			"** Options were ~p~n"
+			"** Request was ~p~n"
+			"** Stacktrace: ~p~n~n",
+			[Handler, init, 3, Class, Reason, HandlerOpts,
+				cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+		{error, 500, Req}
+	end.
+
+-spec upgrade_protocol(Req, #state{}, module(), any(), module())
+	-> {ok, Req, Env}
+	| {suspend, module(), atom(), any()}
+	| {halt, Req}
+	| {error, cowboy_http:status(), Req}
+	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade_protocol(Req, #state{env=Env},
+		Handler, HandlerOpts, Module) ->
+	Module:upgrade(Req, Env, Handler, HandlerOpts).
+
+-spec handler_handle(Req, #state{}, module(), any())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {error, 500, Req}
+	when Req::cowboy_req:req().
+handler_handle(Req, State, Handler, HandlerState) ->
+	try Handler:handle(Req, HandlerState) of
+		{ok, Req2, HandlerState2} ->
+			terminate_request(Req2, State, Handler, HandlerState2)
+	catch Class:Reason ->
+		error_logger:error_msg(
+			"** Cowboy handler ~p terminating in ~p/~p~n"
+			"   for the reason ~p:~p~n"
+			"** Handler state was ~p~n"
+			"** Request was ~p~n"
+			"** Stacktrace: ~p~n~n",
+			[Handler, handle, 2, Class, Reason, HandlerState,
+				cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+		handler_terminate(Req, Handler, HandlerState),
+		{error, 500, Req}
+	end.
+
+%% We don't listen for Transport closes because that would force us
+%% to receive data and buffer it indefinitely.
+-spec handler_before_loop(Req, #state{}, module(), any())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {error, 500, Req} | {suspend, module(), function(), [any()]}
+	when Req::cowboy_req:req().
+handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
+	State2 = handler_loop_timeout(State),
+	{suspend, ?MODULE, handler_loop,
+		[Req, State2#state{hibernate=false}, Handler, HandlerState]};
+handler_before_loop(Req, State, Handler, HandlerState) ->
+	State2 = handler_loop_timeout(State),
+	handler_loop(Req, State2, Handler, HandlerState).
+
+%% Almost the same code can be found in cowboy_websocket.
+-spec handler_loop_timeout(#state{}) -> #state{}.
+handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
+	State#state{loop_timeout_ref=undefined};
+handler_loop_timeout(State=#state{loop_timeout=Timeout,
+		loop_timeout_ref=PrevRef}) ->
+	_ = case PrevRef of undefined -> ignore; PrevRef ->
+		erlang:cancel_timer(PrevRef) end,
+	TRef = erlang:start_timer(Timeout, self(), ?MODULE),
+	State#state{loop_timeout_ref=TRef}.
+
+%% @private
+-spec handler_loop(Req, #state{}, module(), any())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {error, 500, Req} | {suspend, module(), function(), [any()]}
+	when Req::cowboy_req:req().
+handler_loop(Req, State=#state{loop_timeout_ref=TRef}, Handler, HandlerState) ->
+	receive
+		{timeout, TRef, ?MODULE} ->
+			terminate_request(Req, State, Handler, HandlerState);
+		{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
+			handler_loop(Req, State, Handler, HandlerState);
+		Message ->
+			handler_call(Req, State, Handler, HandlerState, Message)
+	end.
+
+-spec handler_call(Req, #state{}, module(), any(), any())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {error, 500, Req} | {suspend, module(), function(), [any()]}
+	when Req::cowboy_req:req().
+handler_call(Req, State, Handler, HandlerState, Message) ->
+	try Handler:info(Message, Req, HandlerState) of
+		{ok, Req2, HandlerState2} ->
+			terminate_request(Req2, State, Handler, HandlerState2);
+		{loop, Req2, HandlerState2} ->
+			handler_before_loop(Req2, State, Handler, HandlerState2);
+		{loop, Req2, HandlerState2, hibernate} ->
+			handler_before_loop(Req2, State#state{hibernate=true},
+				Handler, HandlerState2)
+	catch Class:Reason ->
+		error_logger:error_msg(
+			"** Cowboy handler ~p terminating in ~p/~p~n"
+			"   for the reason ~p:~p~n"
+			"** Handler state was ~p~n"
+			"** Request was ~p~n"
+			"** Stacktrace: ~p~n~n",
+			[Handler, info, 3, Class, Reason, HandlerState,
+				cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+		handler_terminate(Req, Handler, HandlerState),
+		{error, 500, Req}
+	end.
+
+-spec terminate_request(Req, #state{}, module(), any()) ->
+	{ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
+terminate_request(Req, #state{env=Env}, Handler, HandlerState) ->
+	HandlerRes = handler_terminate(Req, Handler, HandlerState),
+	{ok, Req, [{result, HandlerRes}|Env]}.
+
+-spec handler_terminate(cowboy_req:req(), module(), any()) -> ok.
+handler_terminate(Req, Handler, HandlerState) ->
+	try
+		Handler:terminate(cowboy_req:lock(Req), HandlerState)
+	catch Class:Reason ->
+		error_logger:error_msg(
+			"** Cowboy handler ~p terminating in ~p/~p~n"
+			"   for the reason ~p:~p~n"
+			"** Handler state was ~p~n"
+			"** Request was ~p~n"
+			"** Stacktrace: ~p~n~n",
+			[Handler, terminate, 2, Class, Reason, HandlerState,
+				cowboy_req:to_list(Req), erlang:get_stacktrace()])
+	end.

+ 36 - 0
src/cowboy_middleware.erl

@@ -0,0 +1,36 @@
+%% Copyright (c) 2013, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Behaviour for middlewares.
+%%
+%% Only one function needs to be implemented, <em>execute/2</em>.
+%% It receives the Req and the environment and returns them
+%% optionally modified. It can decide to stop the processing with
+%% or without an error. It is also possible to hibernate the process
+%% if needed.
+%%
+%% A middleware can perform any operation. Make sure you always return
+%% the last modified Req so that Cowboy has the up to date information
+%% about the request.
+-module(cowboy_middleware).
+
+-type env() :: [{atom(), any()}].
+-export_type([env/0]).
+
+-callback execute(Req, Env)
+	-> {ok, Req, Env}
+	| {suspend, module(), atom(), any()}
+	| {halt, Req}
+	| {error, cowboy_http:status(), Req}
+	when Req::cowboy_req:req(), Env::env().

+ 49 - 171
src/cowboy_protocol.erl

@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
 %% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
 %%
 %% Permission to use, copy, modify, and/or distribute this software for any
@@ -17,7 +17,8 @@
 %%
 %% The available options are:
 %% <dl>
-%%  <dt>dispatch</dt><dd>The dispatch list for this protocol.</dd>
+%%  <dt>env</dt><dd>The environment passed and optionally modified
+%%   by middlewares.</dd>
 %%  <dt>max_empty_lines</dt><dd>Max number of empty lines before a request.
 %%   Defaults to 5.</dd>
 %%  <dt>max_header_name_length</dt><dd>Max length allowed for header names.
@@ -30,6 +31,8 @@
 %%   keep-alive session. Defaults to infinity.</dd>
 %%  <dt>max_request_line_length</dt><dd>Max length allowed for the request
 %%   line. Defaults to 4096.</dd>
+%%  <dt>middlewares</dt><dd>The list of middlewares to execute when a
+%%   request is received.</dd>
 %%  <dt>onrequest</dt><dd>Optional fun that allows Req interaction before
 %%   any dispatching is done. Host info, path info and bindings are thus
 %%   not available at this point.</dd>
@@ -41,9 +44,6 @@
 %%
 %% Note that there is no need to monitor these processes when using Cowboy as
 %% an application as it already supervises them under the listener supervisor.
-%%
-%% @see cowboy_dispatcher
-%% @see cowboy_http_handler
 -module(cowboy_protocol).
 
 %% API.
@@ -52,20 +52,19 @@
 %% Internal.
 -export([init/4]).
 -export([parse_request/3]).
--export([handler_loop/4]).
+-export([resume/6]).
 
 -type onrequest_fun() :: fun((Req) -> Req).
 -type onresponse_fun() ::
 	fun((cowboy_http:status(), cowboy_http:headers(), iodata(), Req) -> Req).
-
 -export_type([onrequest_fun/0]).
 -export_type([onresponse_fun/0]).
 
 -record(state, {
-	listener :: pid(),
 	socket :: inet:socket(),
 	transport :: module(),
-	dispatch :: cowboy_dispatcher:dispatch_rules(),
+	middlewares :: [module()],
+	env :: cowboy_middleware:env(),
 	onrequest :: undefined | onrequest_fun(),
 	onresponse = undefined :: undefined | onresponse_fun(),
 	max_empty_lines :: non_neg_integer(),
@@ -75,10 +74,7 @@
 	max_header_name_length :: non_neg_integer(),
 	max_header_value_length :: non_neg_integer(),
 	max_headers :: non_neg_integer(),
-	timeout :: timeout(),
-	hibernate = false :: boolean(),
-	loop_timeout = infinity :: timeout(),
-	loop_timeout_ref :: undefined | reference()
+	timeout :: timeout()
 }).
 
 %% API.
@@ -102,19 +98,20 @@ get_value(Key, Opts, Default) ->
 %% @private
 -spec init(pid(), inet:socket(), module(), any()) -> ok.
 init(ListenerPid, Socket, Transport, Opts) ->
-	Dispatch = get_value(dispatch, Opts, []),
 	MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
 	MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
 	MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096),
 	MaxHeaders = get_value(max_headers, Opts, 100),
 	MaxKeepalive = get_value(max_keepalive, Opts, infinity),
 	MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096),
+	Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
+	Env = [{listener, ListenerPid}|get_value(env, Opts, [])],
 	OnRequest = get_value(onrequest, Opts, undefined),
 	OnResponse = get_value(onresponse, Opts, undefined),
 	Timeout = get_value(timeout, Opts, 5000),
 	ok = ranch:accept_ack(ListenerPid),
-	wait_request(<<>>, #state{listener=ListenerPid, socket=Socket,
-		transport=Transport, dispatch=Dispatch,
+	wait_request(<<>>, #state{socket=Socket, transport=Transport,
+		middlewares=Middlewares, env=Env,
 		max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
 		max_request_line_length=MaxRequestLineLength,
 		max_header_name_length=MaxHeaderNameLength,
@@ -442,177 +439,58 @@ request(Buffer, State=#state{socket=Socket, transport=Transport,
 	Req = cowboy_req:new(Socket, Transport, Method, Path, Query, Fragment,
 		Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive,
 		OnResponse),
-	onrequest(Req, State, Host).
+	onrequest(Req, State).
 
 %% Call the global onrequest callback. The callback can send a reply,
 %% in which case we consider the request handled and move on to the next
 %% one. Note that since we haven't dispatched yet, we don't know the
 %% handler, host_info, path_info or bindings yet.
--spec onrequest(cowboy_req:req(), #state{}, binary()) -> ok.
-onrequest(Req, State=#state{onrequest=undefined}, Host) ->
-	dispatch(Req, State, Host, cowboy_req:get(path, Req));
-onrequest(Req, State=#state{onrequest=OnRequest}, Host) ->
+-spec onrequest(cowboy_req:req(), #state{}) -> ok.
+onrequest(Req, State=#state{onrequest=undefined}) ->
+	execute(Req, State);
+onrequest(Req, State=#state{onrequest=OnRequest}) ->
 	Req2 = OnRequest(Req),
 	case cowboy_req:get(resp_state, Req2) of
-		waiting -> dispatch(Req2, State, Host, cowboy_req:get(path, Req2));
+		waiting -> execute(Req2, State);
 		_ -> next_request(Req2, State, ok)
 	end.
 
--spec dispatch(cowboy_req:req(), #state{}, binary(), binary()) -> ok.
-dispatch(Req, State=#state{dispatch=Dispatch}, Host, Path) ->
-	case cowboy_dispatcher:match(Dispatch, Host, Path) of
-		{ok, Handler, Opts, Bindings, HostInfo, PathInfo} ->
-			Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req),
-			handler_init(Req2, State, Handler, Opts);
-		{error, notfound, host} ->
-			error_terminate(400, Req, State);
-		{error, badrequest, path} ->
-			error_terminate(400, Req, State);
-		{error, notfound, path} ->
-			error_terminate(404, Req, State)
-	end.
-
--spec handler_init(cowboy_req:req(), #state{}, module(), any()) -> ok.
-handler_init(Req, State=#state{transport=Transport}, Handler, Opts) ->
-	try Handler:init({Transport:name(), http}, Req, Opts) of
-		{ok, Req2, HandlerState} ->
-			handler_handle(Req2, State, Handler, HandlerState);
-		{loop, Req2, HandlerState} ->
-			handler_before_loop(Req2, State#state{hibernate=false},
-				Handler, HandlerState);
-		{loop, Req2, HandlerState, hibernate} ->
-			handler_before_loop(Req2, State#state{hibernate=true},
-				Handler, HandlerState);
-		{loop, Req2, HandlerState, Timeout} ->
-			handler_before_loop(Req2, State#state{loop_timeout=Timeout},
-				Handler, HandlerState);
-		{loop, Req2, HandlerState, Timeout, hibernate} ->
-			handler_before_loop(Req2, State#state{
-				hibernate=true, loop_timeout=Timeout}, Handler, HandlerState);
-		{shutdown, Req2, HandlerState} ->
-			handler_terminate(Req2, Handler, HandlerState);
-		%% @todo {upgrade, transport, Module}
-		{upgrade, protocol, Module} ->
-			upgrade_protocol(Req, State, Handler, Opts, Module);
-		{upgrade, protocol, Module, Req2, Opts2} ->
-			upgrade_protocol(Req2, State, Handler, Opts2, Module)
-	catch Class:Reason ->
-		error_terminate(500, Req, State),
-		error_logger:error_msg(
-			"** Cowboy handler ~p terminating in ~p/~p~n"
-			"   for the reason ~p:~p~n"
-			"** Options were ~p~n"
-			"** Request was ~p~n"
-			"** Stacktrace: ~p~n~n",
-			[Handler, init, 3, Class, Reason, Opts,
-				cowboy_req:to_list(Req), erlang:get_stacktrace()])
-	end.
+-spec execute(cowboy_req:req(), #state{}) -> ok.
+execute(Req, State=#state{middlewares=Middlewares, env=Env}) ->
+	execute(Req, State, Env, Middlewares).
 
--spec upgrade_protocol(cowboy_req:req(), #state{}, module(), any(), module())
+-spec execute(cowboy_req:req(), #state{}, cowboy_middleware:env(), [module()])
 	-> ok.
-upgrade_protocol(Req, State=#state{listener=ListenerPid},
-		Handler, Opts, Module) ->
-	case Module:upgrade(ListenerPid, Handler, Opts, Req) of
-		{UpgradeRes, Req2} -> next_request(Req2, State, UpgradeRes);
-		_Any -> terminate(State)
+execute(Req, State, Env, []) ->
+	next_request(Req, State, get_value(result, Env, ok));
+execute(Req, State, Env, [Middleware|Tail]) ->
+	case Middleware:execute(Req, Env) of
+		{ok, Req2, Env2} ->
+			execute(Req2, State, Env2, Tail);
+		{suspend, Module, Function, Args} ->
+			erlang:hibernate(?MODULE, resume,
+				[State, Env, Tail, Module, Function, Args]);
+		{halt, Req2} ->
+			next_request(Req2, State, ok);
+		{error, Code, Req2} ->
+			error_terminate(Code, Req2, State)
 	end.
 
--spec handler_handle(cowboy_req:req(), #state{}, module(), any()) -> ok.
-handler_handle(Req, State, Handler, HandlerState) ->
-	try Handler:handle(Req, HandlerState) of
-		{ok, Req2, HandlerState2} ->
-			terminate_request(Req2, State, Handler, HandlerState2)
-	catch Class:Reason ->
-		error_logger:error_msg(
-			"** Cowboy handler ~p terminating in ~p/~p~n"
-			"   for the reason ~p:~p~n"
-			"** Handler state was ~p~n"
-			"** Request was ~p~n"
-			"** Stacktrace: ~p~n~n",
-			[Handler, handle, 2, Class, Reason, HandlerState,
-				cowboy_req:to_list(Req), erlang:get_stacktrace()]),
-		handler_terminate(Req, Handler, HandlerState),
-		error_terminate(500, Req, State)
+-spec resume(#state{}, cowboy_middleware:env(), [module()],
+	module(), module(), [any()]) -> ok.
+resume(State, Env, Tail, Module, Function, Args) ->
+	case apply(Module, Function, Args) of
+		{ok, Req2, Env2} ->
+			execute(Req2, State, Env2, Tail);
+		{suspend, Module2, Function2, Args2} ->
+			erlang:hibernate(?MODULE, resume,
+				[State, Env, Tail, Module2, Function2, Args2]);
+		{halt, Req2} ->
+			next_request(Req2, State, ok);
+		{error, Code, Req2} ->
+			error_terminate(Code, Req2, State)
 	end.
 
-%% We don't listen for Transport closes because that would force us
-%% to receive data and buffer it indefinitely.
--spec handler_before_loop(cowboy_req:req(), #state{}, module(), any()) -> ok.
-handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
-	State2 = handler_loop_timeout(State),
-	catch erlang:hibernate(?MODULE, handler_loop,
-		[Req, State2#state{hibernate=false}, Handler, HandlerState]),
-	ok;
-handler_before_loop(Req, State, Handler, HandlerState) ->
-	State2 = handler_loop_timeout(State),
-	handler_loop(Req, State2, Handler, HandlerState).
-
-%% Almost the same code can be found in cowboy_websocket.
--spec handler_loop_timeout(#state{}) -> #state{}.
-handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
-	State#state{loop_timeout_ref=undefined};
-handler_loop_timeout(State=#state{loop_timeout=Timeout,
-		loop_timeout_ref=PrevRef}) ->
-	_ = case PrevRef of undefined -> ignore; PrevRef ->
-		erlang:cancel_timer(PrevRef) end,
-	TRef = erlang:start_timer(Timeout, self(), ?MODULE),
-	State#state{loop_timeout_ref=TRef}.
-
-%% @private
--spec handler_loop(cowboy_req:req(), #state{}, module(), any()) -> ok.
-handler_loop(Req, State=#state{loop_timeout_ref=TRef}, Handler, HandlerState) ->
-	receive
-		{timeout, TRef, ?MODULE} ->
-			terminate_request(Req, State, Handler, HandlerState);
-		{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
-			handler_loop(Req, State, Handler, HandlerState);
-		Message ->
-			handler_call(Req, State, Handler, HandlerState, Message)
-	end.
-
--spec handler_call(cowboy_req:req(), #state{}, module(), any(), any()) -> ok.
-handler_call(Req, State, Handler, HandlerState, Message) ->
-	try Handler:info(Message, Req, HandlerState) of
-		{ok, Req2, HandlerState2} ->
-			terminate_request(Req2, State, Handler, HandlerState2);
-		{loop, Req2, HandlerState2} ->
-			handler_before_loop(Req2, State, Handler, HandlerState2);
-		{loop, Req2, HandlerState2, hibernate} ->
-			handler_before_loop(Req2, State#state{hibernate=true},
-				Handler, HandlerState2)
-	catch Class:Reason ->
-		error_logger:error_msg(
-			"** Cowboy handler ~p terminating in ~p/~p~n"
-			"   for the reason ~p:~p~n"
-			"** Handler state was ~p~n"
-			"** Request was ~p~n"
-			"** Stacktrace: ~p~n~n",
-			[Handler, info, 3, Class, Reason, HandlerState,
-				cowboy_req:to_list(Req), erlang:get_stacktrace()]),
-		handler_terminate(Req, Handler, HandlerState),
-		error_terminate(500, Req, State)
-	end.
-
--spec handler_terminate(cowboy_req:req(), module(), any()) -> ok.
-handler_terminate(Req, Handler, HandlerState) ->
-	try
-		Handler:terminate(cowboy_req:lock(Req), HandlerState)
-	catch Class:Reason ->
-		error_logger:error_msg(
-			"** Cowboy handler ~p terminating in ~p/~p~n"
-			"   for the reason ~p:~p~n"
-			"** Handler state was ~p~n"
-			"** Request was ~p~n"
-			"** Stacktrace: ~p~n~n",
-			[Handler, terminate, 2, Class, Reason, HandlerState,
-				cowboy_req:to_list(Req), erlang:get_stacktrace()])
-	end.
-
--spec terminate_request(cowboy_req:req(), #state{}, module(), any()) -> ok.
-terminate_request(Req, State, Handler, HandlerState) ->
-	HandlerRes = handler_terminate(Req, Handler, HandlerState),
-	next_request(Req, State, HandlerRes).
-
 -spec next_request(cowboy_req:req(), #state{}, any()) -> ok.
 next_request(Req, State=#state{req_keepalive=Keepalive}, HandlerRes) ->
 	cowboy_req:ensure_response(Req, 204),

+ 17 - 17
src/cowboy_rest.erl

@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
 %%
 %% Permission to use, copy, modify, and/or distribute this software for any
 %% purpose with or without fee is hereby granted, provided that the above
@@ -23,6 +23,7 @@
 -export([upgrade/4]).
 
 -record(state, {
+	env :: cowboy_middleware:env(),
 	method = undefined :: binary(),
 
 	%% Handler.
@@ -54,31 +55,31 @@
 %% You do not need to call this function manually. To upgrade to the REST
 %% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
 %% in your <em>cowboy_http_handler:init/3</em> handler function.
--spec upgrade(pid(), module(), any(), Req)
-	-> {ok, Req} | close when Req::cowboy_req:req().
-upgrade(_ListenerPid, Handler, Opts, Req) ->
+-spec upgrade(Req, Env, module(), any())
+	-> {ok, Req, Env} | {error, 500, Req}
+	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerOpts) ->
 	try
 		Method = cowboy_req:get(method, Req),
 		case erlang:function_exported(Handler, rest_init, 2) of
 			true ->
-				case Handler:rest_init(Req, Opts) of
+				case Handler:rest_init(Req, HandlerOpts) of
 					{ok, Req2, HandlerState} ->
-						service_available(Req2, #state{method=Method,
+						service_available(Req2, #state{env=Env, method=Method,
 							handler=Handler, handler_state=HandlerState})
 				end;
 			false ->
-				service_available(Req, #state{method=Method,
+				service_available(Req, #state{env=Env, method=Method,
 					handler=Handler})
 		end
 	catch Class:Reason ->
-		PLReq = cowboy_req:to_list(Req),
 		error_logger:error_msg(
 			"** Cowboy handler ~p terminating in ~p/~p~n"
 			"   for the reason ~p:~p~n** Options were ~p~n"
 			"** Request was ~p~n** Stacktrace: ~p~n~n",
-			[Handler, rest_init, 2, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]),
-		{ok, _Req2} = cowboy_req:reply(500, Req),
-		close
+			[Handler, rest_init, 2, Class, Reason, HandlerOpts,
+				cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+		{error, 500, Req}
 	end.
 
 service_available(Req, State) ->
@@ -738,8 +739,7 @@ choose_content_type(Req,
 				"function ~p/~p was not exported~n"
 				"** Request was ~p~n** State was ~p~n~n",
 				[Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]),
-			{ok, _} = cowboy_req:reply(500, Req),
-			close;
+			{error, 500, Req};
 		{halt, Req2, HandlerState} ->
 			terminate(Req2, State#state{handler_state=HandlerState});
 		{true, Req2, HandlerState} ->
@@ -790,8 +790,7 @@ set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState,
 				"function ~p/~p was not exported~n"
 				"** Request was ~p~n** State was ~p~n~n",
 				[Handler, Fun, 2, cowboy_req:to_list(Req5), HandlerState]),
-			{ok, _} = cowboy_req:reply(500, Req5),
-			close;
+			{error, 500, Req5};
 		{halt, Req6, HandlerState} ->
 			terminate(Req6, State4#state{handler_state=HandlerState});
 		{Body, Req6, HandlerState} ->
@@ -915,10 +914,11 @@ respond(Req, State, StatusCode) ->
 	{ok, Req2} = cowboy_req:reply(StatusCode, Req),
 	terminate(Req2, State).
 
-terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
+terminate(Req, #state{env=Env, handler=Handler,
+		handler_state=HandlerState}) ->
 	case erlang:function_exported(Handler, rest_terminate, 2) of
 		true -> ok = Handler:rest_terminate(
 			cowboy_req:lock(Req), HandlerState);
 		false -> ok
 	end,
-	{ok, Req}.
+	{ok, Req, [{result, ok}|Env]}.

+ 49 - 0
src/cowboy_router.erl

@@ -0,0 +1,49 @@
+%% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Routing middleware.
+%%
+%% Resolve the handler to be used for the request based on the
+%% routing information found in the <em>dispatch</em> environment value.
+%% When found, the handler module and associated data are added to
+%% the environment as the <em>handler</em> and <em>handler_opts</em> values
+%% respectively.
+%%
+%% If the route cannot be found, processing stops with either
+%% a 400 or a 404 reply.
+%%
+%% @see cowboy_dispatcher
+-module(cowboy_router).
+-behaviour(cowboy_middleware).
+
+-export([execute/2]).
+
+%% @private
+-spec execute(Req, Env)
+	-> {ok, Req, Env} | {error, 400 | 404, Req}
+	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req, Env) ->
+	{_, Dispatch} = lists:keyfind(dispatch, 1, Env),
+	[Host, Path] = cowboy_req:get([host, path], Req),
+	case cowboy_dispatcher:match(Dispatch, Host, Path) of
+		{ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
+			Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req),
+			{ok, Req2, [{handler, Handler}, {handler_opts, HandlerOpts}|Env]};
+		{error, notfound, host} ->
+			{error, 400, Req};
+		{error, badrequest, path} ->
+			{error, 400, Req};
+		{error, notfound, path} ->
+			{error, 404, Req}
+	end.

+ 87 - 50
src/cowboy_websocket.erl

@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
 %%
 %% Permission to use, copy, modify, and/or distribute this software for any
 %% purpose with or without fee is hereby granted, provided that the above
@@ -38,11 +38,12 @@
 	{fin, opcode(), binary()}. %% last fragment has been seen.
 
 -record(state, {
+	env :: cowboy_middleware:env(),
 	socket = undefined :: inet:socket(),
 	transport = undefined :: module(),
 	version :: 0 | 7 | 8 | 13,
 	handler :: module(),
-	opts :: any(),
+	handler_opts :: any(),
 	challenge = undefined :: undefined | binary() | {binary(), binary()},
 	timeout = infinity :: timeout(),
 	timeout_ref = undefined :: undefined | reference(),
@@ -58,15 +59,19 @@
 %% You do not need to call this function manually. To upgrade to the WebSocket
 %% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
 %% in your <em>cowboy_http_handler:init/3</em> handler function.
--spec upgrade(pid(), module(), any(), cowboy_req:req()) -> closed.
-upgrade(ListenerPid, Handler, Opts, Req) ->
+-spec upgrade(Req, Env, module(), any())
+	-> {ok, Req, Env} | {error, 400, Req}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerOpts) ->
+	{_, ListenerPid} = lists:keyfind(listener, 1, Env),
 	ranch_listener:remove_connection(ListenerPid),
 	{ok, Transport, Socket} = cowboy_req:transport(Req),
-	State = #state{socket=Socket, transport=Transport,
-		handler=Handler, opts=Opts},
+	State = #state{env=Env, socket=Socket, transport=Transport,
+		handler=Handler, handler_opts=HandlerOpts},
 	case catch websocket_upgrade(State, Req) of
 		{ok, State2, Req2} -> handler_init(State2, Req2);
-		{'EXIT', _Reason} -> upgrade_error(Req)
+		{'EXIT', _Reason} -> upgrade_error(Req, Env)
 	end.
 
 -spec websocket_upgrade(#state{}, Req)
@@ -110,10 +115,13 @@ websocket_upgrade(Version, State, Req)
 	{ok, State#state{version=IntVersion, challenge=Challenge},
 		cowboy_req:set_meta(websocket_version, IntVersion, Req2)}.
 
--spec handler_init(#state{}, cowboy_req:req()) -> closed.
-handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
-		Req) ->
-	try Handler:websocket_init(Transport:name(), Req, Opts) of
+-spec handler_init(#state{}, Req)
+	-> {ok, Req, cowboy_middleware:env()} | {error, 400, Req}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
+handler_init(State=#state{env=Env, transport=Transport,
+		handler=Handler, handler_opts=HandlerOpts}, Req) ->
+	try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of
 		{ok, Req2, HandlerState} ->
 			websocket_handshake(State, Req2, HandlerState);
 		{ok, Req2, HandlerState, hibernate} ->
@@ -127,27 +135,31 @@ handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
 				hibernate=true}, Req2, HandlerState);
 		{shutdown, Req2} ->
 			cowboy_req:ensure_response(Req2, 400),
-			closed
+			{ok, Req2, [{result, closed}|Env]}
 	catch Class:Reason ->
-		upgrade_error(Req),
 		error_logger:error_msg(
 			"** Cowboy handler ~p terminating in ~p/~p~n"
 			"   for the reason ~p:~p~n** Options were ~p~n"
 			"** Request was ~p~n** Stacktrace: ~p~n~n",
-			[Handler, websocket_init, 3, Class, Reason, Opts,
-				cowboy_req:to_list(Req),erlang:get_stacktrace()])
+			[Handler, websocket_init, 3, Class, Reason, HandlerOpts,
+				cowboy_req:to_list(Req),erlang:get_stacktrace()]),
+		upgrade_error(Req, Env)
 	end.
 
--spec upgrade_error(cowboy_req:req()) -> closed.
-upgrade_error(Req) ->
+-spec upgrade_error(Req, Env) -> {ok, Req, Env} | {error, 400, Req}
+	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade_error(Req, Env) ->
 	receive
-		{cowboy_req, resp_sent} -> closed
+		{cowboy_req, resp_sent} ->
+			{ok, Req, [{result, closed}|Env]}
 	after 0 ->
-		_ = cowboy_req:reply(400, [], [], Req),
-		closed
+		{error, 400, Req}
 	end.
 
--spec websocket_handshake(#state{}, cowboy_req:req(), any()) -> closed.
+-spec websocket_handshake(#state{}, Req, any())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 websocket_handshake(State=#state{socket=Socket, transport=Transport,
 		version=0, origin=Origin, challenge={Key1, Key2}},
 		Req, HandlerState) ->
@@ -192,14 +204,16 @@ websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
 	handler_before_loop(State2#state{messages=Transport:messages()},
 		Req2, HandlerState, <<>>).
 
--spec handler_before_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed.
+-spec handler_before_loop(#state{}, Req, any(), binary())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 handler_before_loop(State=#state{
 			socket=Socket, transport=Transport, hibernate=true},
 		Req, HandlerState, SoFar) ->
 	Transport:setopts(Socket, [{active, once}]),
-	catch erlang:hibernate(?MODULE, handler_loop,
-		[State#state{hibernate=false}, Req, HandlerState, SoFar]),
-	closed;
+	{suspend, ?MODULE, handler_loop,
+		[State#state{hibernate=false}, Req, HandlerState, SoFar]};
 handler_before_loop(State=#state{socket=Socket, transport=Transport},
 		Req, HandlerState, SoFar) ->
 	Transport:setopts(Socket, [{active, once}]),
@@ -215,7 +229,10 @@ handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) ->
 	State#state{timeout_ref=TRef}.
 
 %% @private
--spec handler_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed.
+-spec handler_loop(#state{}, Req, any(), binary())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 handler_loop(State=#state{
 			socket=Socket, messages={OK, Closed, Error}, timeout_ref=TRef},
 		Req, HandlerState, SoFar) ->
@@ -237,7 +254,10 @@ handler_loop(State=#state{
 				SoFar, websocket_info, Message, fun handler_before_loop/4)
 	end.
 
--spec websocket_data(#state{}, cowboy_req:req(), any(), binary()) -> closed.
+-spec websocket_data(#state{}, Req, any(), binary())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 %% No more data.
 websocket_data(State, Req, HandlerState, <<>>) ->
 	handler_before_loop(State, Req, HandlerState, <<>>);
@@ -294,9 +314,11 @@ websocket_data(State, Req, HandlerState,
 websocket_data(State, Req, HandlerState, _Data) ->
 	websocket_close(State, Req, HandlerState, {error, badframe}).
 
--spec websocket_data(#state{}, cowboy_req:req(), any(), non_neg_integer(),
+-spec websocket_data(#state{}, Req, any(), non_neg_integer(),
 		non_neg_integer(), non_neg_integer(), non_neg_integer(),
-		non_neg_integer(), binary(), binary()) -> closed.
+		non_neg_integer(), binary(), binary())
+	-> {ok, Req, cowboy_middleware:env()}
+	when Req::cowboy_req:req().
 %% A fragmented message MUST start a non-zero opcode.
 websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
 		_Fin=0, _Rsv=0, _Opcode=0, _Mask, _PayloadLen, _Rest, _Buffer) ->
@@ -349,8 +371,11 @@ websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask,
 		websocket_close(State, Req, HandlerState, {error, badframe}).
 
 %% hybi routing depending on whether unmasking is needed.
--spec websocket_before_unmask(#state{}, cowboy_req:req(), any(), binary(),
-	binary(), opcode(), 0 | 1, non_neg_integer() | undefined) -> closed.
+-spec websocket_before_unmask(#state{}, Req, any(), binary(),
+	binary(), opcode(), 0 | 1, non_neg_integer() | undefined)
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 websocket_before_unmask(State, Req, HandlerState, Data,
 		Rest, Opcode, Mask, PayloadLen) ->
 	case {Mask, PayloadLen} of
@@ -366,15 +391,21 @@ websocket_before_unmask(State, Req, HandlerState, Data,
 	end.
 
 %% hybi unmasking.
--spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
-	opcode(), binary(), mask_key()) -> closed.
+-spec websocket_unmask(#state{}, Req, any(), binary(),
+	opcode(), binary(), mask_key())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 websocket_unmask(State, Req, HandlerState, RemainingData,
 		Opcode, Payload, MaskKey) ->
 	websocket_unmask(State, Req, HandlerState, RemainingData,
 		Opcode, Payload, MaskKey, <<>>).
 
--spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
-	opcode(), binary(), mask_key(), binary()) -> closed.
+-spec websocket_unmask(#state{}, Req, any(), binary(),
+	opcode(), binary(), mask_key(), binary())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 websocket_unmask(State, Req, HandlerState, RemainingData,
 		Opcode, << O:32, Rest/bits >>, MaskKey, Acc) ->
 	T = O bxor MaskKey,
@@ -404,8 +435,10 @@ websocket_unmask(State, Req, HandlerState, RemainingData,
 		Opcode, Acc).
 
 %% hybi dispatching.
--spec websocket_dispatch(#state{}, cowboy_req:req(), any(), binary(),
-	opcode(), binary()) -> closed.
+-spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
 %% First frame of a fragmented message unmasked. Expect intermediate or last.
 websocket_dispatch(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
 		RemainingData, 0, Payload) ->
@@ -446,10 +479,12 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) ->
 	handler_call(State, Req, HandlerState, RemainingData,
 		websocket_handle, {pong, Payload}, fun websocket_data/4).
 
--spec handler_call(#state{}, cowboy_req:req(), any(), binary(),
-	atom(), any(), fun()) -> closed.
-handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
-		RemainingData, Callback, Message, NextState) ->
+-spec handler_call(#state{}, Req, any(), binary(), atom(), any(), fun())
+	-> {ok, Req, cowboy_middleware:env()}
+	| {suspend, module(), atom(), [any()]}
+	when Req::cowboy_req:req().
+handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req,
+		HandlerState, RemainingData, Callback, Message, NextState) ->
 	try Handler:Callback(Message, Req, HandlerState) of
 		{ok, Req2, HandlerState2} ->
 			NextState(State, Req2, HandlerState2, RemainingData);
@@ -515,7 +550,7 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
 			"   for the reason ~p:~p~n** Message was ~p~n"
 			"** Options were ~p~n** Handler state was ~p~n"
 			"** Request was ~p~n** Stacktrace: ~p~n~n",
-			[Handler, Callback, 3, Class, Reason, Message, Opts,
+			[Handler, Callback, 3, Class, Reason, Message, HandlerOpts,
 			 HandlerState, PLReq, erlang:get_stacktrace()]),
 		websocket_close(State, Req, HandlerState, {error, handler})
 	end.
@@ -582,8 +617,9 @@ websocket_send_many([Frame|Tail], State) ->
 		Error -> Error
 	end.
 
--spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()})
-	-> closed.
+-spec websocket_close(#state{}, Req, any(), {atom(), atom()})
+	-> {ok, Req, cowboy_middleware:env()}
+	when Req::cowboy_req:req().
 websocket_close(State=#state{socket=Socket, transport=Transport, version=0},
 		Req, HandlerState, Reason) ->
 	Transport:send(Socket, << 255, 0 >>),
@@ -593,9 +629,10 @@ websocket_close(State=#state{socket=Socket, transport=Transport},
 	Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>),
 	handler_terminate(State, Req, HandlerState, Reason).
 
--spec handler_terminate(#state{}, cowboy_req:req(),
-	any(), atom() | {atom(), atom()}) -> closed.
-handler_terminate(#state{handler=Handler, opts=Opts},
+-spec handler_terminate(#state{}, Req, any(), atom() | {atom(), atom()})
+	-> {ok, Req, cowboy_middleware:env()}
+	when Req::cowboy_req:req().
+handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts},
 		Req, HandlerState, TerminateReason) ->
 	try
 		Handler:websocket_terminate(TerminateReason, Req, HandlerState)
@@ -606,10 +643,10 @@ handler_terminate(#state{handler=Handler, opts=Opts},
 			"   for the reason ~p:~p~n** Initial reason was ~p~n"
 			"** Options were ~p~n** Handler state was ~p~n"
 			"** Request was ~p~n** Stacktrace: ~p~n~n",
-			[Handler, websocket_terminate, 3, Class, Reason, TerminateReason, Opts,
-			 HandlerState, PLReq, erlang:get_stacktrace()])
+			[Handler, websocket_terminate, 3, Class, Reason, TerminateReason,
+				HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()])
 	end,
-	closed.
+	{ok, Req, [{result, closed}|Env]}.
 
 %% hixie-76 specific.
 

+ 1 - 1
test/autobahn_SUITE.erl

@@ -64,7 +64,7 @@ end_per_suite(_Config) ->
 init_per_group(autobahn, Config) ->
 	Port = 33080,
 	cowboy:start_http(autobahn, 100, [{port, Port}], [
-		{dispatch, init_dispatch()}
+		{env, [{dispatch, init_dispatch()}]}
 	]),
 	[{port, Port}|Config].
 

+ 6 - 6
test/http_SUITE.erl

@@ -153,7 +153,7 @@ init_per_group(http, Config) ->
 	Transport = ranch_tcp,
 	Config1 = init_static_dir(Config),
 	{ok, _} = cowboy:start_http(http, 100, [{port, Port}], [
-		{dispatch, init_dispatch(Config1)},
+		{env, [{dispatch, init_dispatch(Config1)}]},
 		{max_keepalive, 50},
 		{timeout, 500}
 	]),
@@ -172,7 +172,7 @@ init_per_group(https, Config) ->
 	application:start(public_key),
 	application:start(ssl),
 	{ok, _} = cowboy:start_https(https, 100, Opts ++ [{port, Port}], [
-		{dispatch, init_dispatch(Config1)},
+		{env, [{dispatch, init_dispatch(Config1)}]},
 		{max_keepalive, 50},
 		{timeout, 500}
 	]),
@@ -183,7 +183,7 @@ init_per_group(onrequest, Config) ->
 	Port = 33082,
 	Transport = ranch_tcp,
 	{ok, _} = cowboy:start_http(onrequest, 100, [{port, Port}], [
-		{dispatch, init_dispatch(Config)},
+		{env, [{dispatch, init_dispatch(Config)}]},
 		{max_keepalive, 50},
 		{onrequest, fun onrequest_hook/1},
 		{timeout, 500}
@@ -195,7 +195,7 @@ init_per_group(onresponse, Config) ->
 	Port = 33083,
 	Transport = ranch_tcp,
 	{ok, _} = cowboy:start_http(onresponse, 100, [{port, Port}], [
-		{dispatch, init_dispatch(Config)},
+		{env, [{dispatch, init_dispatch(Config)}]},
 		{max_keepalive, 50},
 		{onresponse, fun onresponse_hook/4},
 		{timeout, 500}
@@ -503,8 +503,8 @@ http10_hostless(Config) ->
 	ranch:start_listener(Name, 5,
 		?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}],
 		cowboy_protocol, [
-			{dispatch, [{'_', [
-				{[<<"http1.0">>, <<"hostless">>], http_handler, []}]}]},
+			{env, [{dispatch, [{'_', [
+				{[<<"http1.0">>, <<"hostless">>], http_handler, []}]}]}]},
 			{max_keepalive, 50},
 			{timeout, 500}]
 	),

+ 1 - 1
test/ws_SUITE.erl

@@ -79,7 +79,7 @@ end_per_suite(_Config) ->
 init_per_group(ws, Config) ->
 	Port = 33080,
 	cowboy:start_http(ws, 100, [{port, Port}], [
-		{dispatch, init_dispatch()}
+		{env, [{dispatch, init_dispatch()}]}
 	]),
 	[{port, Port}|Config].