aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2013-01-03 22:47:51 +0100
committerLoïc Hoguin <[email protected]>2013-01-03 22:47:51 +0100
commit1b3f510b7e8d5413901ba72adfe361773f3e9097 (patch)
tree314d24a8bbbdfc1e326cac28193ab6d9f7dc3b61
parent73d86057f2f9d6b3de5fb12e23b2cd65be50e226 (diff)
downloadcowboy-1b3f510b7e8d5413901ba72adfe361773f3e9097.tar.gz
cowboy-1b3f510b7e8d5413901ba72adfe361773f3e9097.tar.bz2
cowboy-1b3f510b7e8d5413901ba72adfe361773f3e9097.zip
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.
-rw-r--r--guide/middlewares.md74
-rw-r--r--guide/routing.md7
-rw-r--r--guide/toc.md6
-rw-r--r--src/cowboy_handler.erl201
-rw-r--r--src/cowboy_middleware.erl36
-rw-r--r--src/cowboy_protocol.erl220
-rw-r--r--src/cowboy_rest.erl34
-rw-r--r--src/cowboy_router.erl49
-rw-r--r--src/cowboy_websocket.erl137
-rw-r--r--test/autobahn_SUITE.erl2
-rw-r--r--test/http_SUITE.erl12
-rw-r--r--test/ws_SUITE.erl2
12 files changed, 529 insertions, 251 deletions
diff --git a/guide/middlewares.md b/guide/middlewares.md
new file mode 100644
index 0000000..2f583cf
--- /dev/null
+++ b/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`.
diff --git a/guide/routing.md b/guide/routing.md
index 936b37c..7d6fa41 100644
--- a/guide/routing.md
+++ b/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}]}]
).
```
diff --git a/guide/toc.md b/guide/toc.md
index 32234cd..2498b4d 100644
--- a/guide/toc.md
+++ b/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
diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl
new file mode 100644
index 0000000..cc871d9
--- /dev/null
+++ b/src/cowboy_handler.erl
@@ -0,0 +1,201 @@
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
+%%
+%% 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.
diff --git a/src/cowboy_middleware.erl b/src/cowboy_middleware.erl
new file mode 100644
index 0000000..0c1ca77
--- /dev/null
+++ b/src/cowboy_middleware.erl
@@ -0,0 +1,36 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% 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().
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl
index 7344d1f..b82fa2b 100644
--- a/src/cowboy_protocol.erl
+++ b/src/cowboy_protocol.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
%% Copyright (c) 2011, Anthony Ramine <[email protected]>
%%
%% 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),
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index c6b53bd..511cd20 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
%%
%% 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]}.
diff --git a/src/cowboy_router.erl b/src/cowboy_router.erl
new file mode 100644
index 0000000..35c9396
--- /dev/null
+++ b/src/cowboy_router.erl
@@ -0,0 +1,49 @@
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
+%%
+%% 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.
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 8c02ac7..abcc015 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
%%
%% 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.
diff --git a/test/autobahn_SUITE.erl b/test/autobahn_SUITE.erl
index 9ae9d7a..c3fad58 100644
--- a/test/autobahn_SUITE.erl
+++ b/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].
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl
index c90e585..9678c64 100644
--- a/test/http_SUITE.erl
+++ b/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}]
),
diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl
index 34befda..c4aa327 100644
--- a/test/ws_SUITE.erl
+++ b/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].