0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

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.
This commit is contained in:
Loïc Hoguin 2013-01-03 22:47:51 +01:00
parent 73d86057f2
commit 1b3f510b7e
12 changed files with 529 additions and 251 deletions

74
guide/middlewares.md Normal file
View file

@ -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`.

View file

@ -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}]}]
).
```

View file

@ -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
src/cowboy_handler.erl Normal file
View file

@ -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
src/cowboy_middleware.erl Normal file
View file

@ -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().

View file

@ -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 execute(cowboy_req:req(), #state{}) -> ok.
execute(Req, State=#state{middlewares=Middlewares, env=Env}) ->
execute(Req, State, Env, Middlewares).
-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 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),

View file

@ -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
src/cowboy_router.erl Normal file
View file

@ -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.

View file

@ -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.

View file

@ -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].

View file

@ -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}]
),

View file

@ -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].