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:
parent
73d86057f2
commit
1b3f510b7e
12 changed files with 529 additions and 251 deletions
74
guide/middlewares.md
Normal file
74
guide/middlewares.md
Normal 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`.
|
|
@ -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`.
|
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
|
``` erlang
|
||||||
{ok, Routes} = cowboy_routing:compile([
|
{ok, Routes} = cowboy_routing:compile([
|
||||||
%% {URIHost, list({URIPath, Handler, Opts})}
|
%% {HostMatch, list({PathMatch, Handler, Opts})}
|
||||||
{'_', [{'_', my_handler, []}]}
|
{'_', [{'_', my_handler, []}]}
|
||||||
]),
|
]),
|
||||||
%% Name, NbAcceptors, TransOpts, ProtoOpts
|
%% Name, NbAcceptors, TransOpts, ProtoOpts
|
||||||
cowboy:start_http(my_http_listener, 100,
|
cowboy:start_http(my_http_listener, 100,
|
||||||
[{port, 8080}],
|
[{port, 8080}],
|
||||||
[{routes, Routes}]
|
[{env, [{routes, Routes}]}]
|
||||||
).
|
).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,12 @@ Cowboy User Guide
|
||||||
* [Hooks](hooks.md)
|
* [Hooks](hooks.md)
|
||||||
* On request
|
* On request
|
||||||
* On response
|
* On response
|
||||||
|
* [Middlewares](middlewares.md)
|
||||||
|
* Purpose
|
||||||
|
* Usage
|
||||||
|
* Configuration
|
||||||
|
* Routing middleware
|
||||||
|
* Handler middleware
|
||||||
* [Internals](internals.md)
|
* [Internals](internals.md)
|
||||||
* Architecture
|
* Architecture
|
||||||
* Efficiency considerations
|
* Efficiency considerations
|
||||||
|
|
201
src/cowboy_handler.erl
Normal file
201
src/cowboy_handler.erl
Normal 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
36
src/cowboy_middleware.erl
Normal 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().
|
|
@ -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>
|
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
|
||||||
%%
|
%%
|
||||||
%% Permission to use, copy, modify, and/or distribute this software for any
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
@ -17,7 +17,8 @@
|
||||||
%%
|
%%
|
||||||
%% The available options are:
|
%% The available options are:
|
||||||
%% <dl>
|
%% <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.
|
%% <dt>max_empty_lines</dt><dd>Max number of empty lines before a request.
|
||||||
%% Defaults to 5.</dd>
|
%% Defaults to 5.</dd>
|
||||||
%% <dt>max_header_name_length</dt><dd>Max length allowed for header names.
|
%% <dt>max_header_name_length</dt><dd>Max length allowed for header names.
|
||||||
|
@ -30,6 +31,8 @@
|
||||||
%% keep-alive session. Defaults to infinity.</dd>
|
%% keep-alive session. Defaults to infinity.</dd>
|
||||||
%% <dt>max_request_line_length</dt><dd>Max length allowed for the request
|
%% <dt>max_request_line_length</dt><dd>Max length allowed for the request
|
||||||
%% line. Defaults to 4096.</dd>
|
%% 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
|
%% <dt>onrequest</dt><dd>Optional fun that allows Req interaction before
|
||||||
%% any dispatching is done. Host info, path info and bindings are thus
|
%% any dispatching is done. Host info, path info and bindings are thus
|
||||||
%% not available at this point.</dd>
|
%% not available at this point.</dd>
|
||||||
|
@ -41,9 +44,6 @@
|
||||||
%%
|
%%
|
||||||
%% Note that there is no need to monitor these processes when using Cowboy as
|
%% 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.
|
%% an application as it already supervises them under the listener supervisor.
|
||||||
%%
|
|
||||||
%% @see cowboy_dispatcher
|
|
||||||
%% @see cowboy_http_handler
|
|
||||||
-module(cowboy_protocol).
|
-module(cowboy_protocol).
|
||||||
|
|
||||||
%% API.
|
%% API.
|
||||||
|
@ -52,20 +52,19 @@
|
||||||
%% Internal.
|
%% Internal.
|
||||||
-export([init/4]).
|
-export([init/4]).
|
||||||
-export([parse_request/3]).
|
-export([parse_request/3]).
|
||||||
-export([handler_loop/4]).
|
-export([resume/6]).
|
||||||
|
|
||||||
-type onrequest_fun() :: fun((Req) -> Req).
|
-type onrequest_fun() :: fun((Req) -> Req).
|
||||||
-type onresponse_fun() ::
|
-type onresponse_fun() ::
|
||||||
fun((cowboy_http:status(), cowboy_http:headers(), iodata(), Req) -> Req).
|
fun((cowboy_http:status(), cowboy_http:headers(), iodata(), Req) -> Req).
|
||||||
|
|
||||||
-export_type([onrequest_fun/0]).
|
-export_type([onrequest_fun/0]).
|
||||||
-export_type([onresponse_fun/0]).
|
-export_type([onresponse_fun/0]).
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
listener :: pid(),
|
|
||||||
socket :: inet:socket(),
|
socket :: inet:socket(),
|
||||||
transport :: module(),
|
transport :: module(),
|
||||||
dispatch :: cowboy_dispatcher:dispatch_rules(),
|
middlewares :: [module()],
|
||||||
|
env :: cowboy_middleware:env(),
|
||||||
onrequest :: undefined | onrequest_fun(),
|
onrequest :: undefined | onrequest_fun(),
|
||||||
onresponse = undefined :: undefined | onresponse_fun(),
|
onresponse = undefined :: undefined | onresponse_fun(),
|
||||||
max_empty_lines :: non_neg_integer(),
|
max_empty_lines :: non_neg_integer(),
|
||||||
|
@ -75,10 +74,7 @@
|
||||||
max_header_name_length :: non_neg_integer(),
|
max_header_name_length :: non_neg_integer(),
|
||||||
max_header_value_length :: non_neg_integer(),
|
max_header_value_length :: non_neg_integer(),
|
||||||
max_headers :: non_neg_integer(),
|
max_headers :: non_neg_integer(),
|
||||||
timeout :: timeout(),
|
timeout :: timeout()
|
||||||
hibernate = false :: boolean(),
|
|
||||||
loop_timeout = infinity :: timeout(),
|
|
||||||
loop_timeout_ref :: undefined | reference()
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%% API.
|
%% API.
|
||||||
|
@ -102,19 +98,20 @@ get_value(Key, Opts, Default) ->
|
||||||
%% @private
|
%% @private
|
||||||
-spec init(pid(), inet:socket(), module(), any()) -> ok.
|
-spec init(pid(), inet:socket(), module(), any()) -> ok.
|
||||||
init(ListenerPid, Socket, Transport, Opts) ->
|
init(ListenerPid, Socket, Transport, Opts) ->
|
||||||
Dispatch = get_value(dispatch, Opts, []),
|
|
||||||
MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
|
MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
|
||||||
MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
|
MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
|
||||||
MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096),
|
MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096),
|
||||||
MaxHeaders = get_value(max_headers, Opts, 100),
|
MaxHeaders = get_value(max_headers, Opts, 100),
|
||||||
MaxKeepalive = get_value(max_keepalive, Opts, infinity),
|
MaxKeepalive = get_value(max_keepalive, Opts, infinity),
|
||||||
MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096),
|
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),
|
OnRequest = get_value(onrequest, Opts, undefined),
|
||||||
OnResponse = get_value(onresponse, Opts, undefined),
|
OnResponse = get_value(onresponse, Opts, undefined),
|
||||||
Timeout = get_value(timeout, Opts, 5000),
|
Timeout = get_value(timeout, Opts, 5000),
|
||||||
ok = ranch:accept_ack(ListenerPid),
|
ok = ranch:accept_ack(ListenerPid),
|
||||||
wait_request(<<>>, #state{listener=ListenerPid, socket=Socket,
|
wait_request(<<>>, #state{socket=Socket, transport=Transport,
|
||||||
transport=Transport, dispatch=Dispatch,
|
middlewares=Middlewares, env=Env,
|
||||||
max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
|
max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
|
||||||
max_request_line_length=MaxRequestLineLength,
|
max_request_line_length=MaxRequestLineLength,
|
||||||
max_header_name_length=MaxHeaderNameLength,
|
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,
|
Req = cowboy_req:new(Socket, Transport, Method, Path, Query, Fragment,
|
||||||
Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive,
|
Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive,
|
||||||
OnResponse),
|
OnResponse),
|
||||||
onrequest(Req, State, Host).
|
onrequest(Req, State).
|
||||||
|
|
||||||
%% Call the global onrequest callback. The callback can send a reply,
|
%% 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
|
%% 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
|
%% one. Note that since we haven't dispatched yet, we don't know the
|
||||||
%% handler, host_info, path_info or bindings yet.
|
%% handler, host_info, path_info or bindings yet.
|
||||||
-spec onrequest(cowboy_req:req(), #state{}, binary()) -> ok.
|
-spec onrequest(cowboy_req:req(), #state{}) -> ok.
|
||||||
onrequest(Req, State=#state{onrequest=undefined}, Host) ->
|
onrequest(Req, State=#state{onrequest=undefined}) ->
|
||||||
dispatch(Req, State, Host, cowboy_req:get(path, Req));
|
execute(Req, State);
|
||||||
onrequest(Req, State=#state{onrequest=OnRequest}, Host) ->
|
onrequest(Req, State=#state{onrequest=OnRequest}) ->
|
||||||
Req2 = OnRequest(Req),
|
Req2 = OnRequest(Req),
|
||||||
case cowboy_req:get(resp_state, Req2) of
|
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)
|
_ -> next_request(Req2, State, ok)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec dispatch(cowboy_req:req(), #state{}, binary(), binary()) -> ok.
|
-spec execute(cowboy_req:req(), #state{}) -> ok.
|
||||||
dispatch(Req, State=#state{dispatch=Dispatch}, Host, Path) ->
|
execute(Req, State=#state{middlewares=Middlewares, env=Env}) ->
|
||||||
case cowboy_dispatcher:match(Dispatch, Host, Path) of
|
execute(Req, State, Env, Middlewares).
|
||||||
{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.
|
-spec execute(cowboy_req:req(), #state{}, cowboy_middleware:env(), [module()])
|
||||||
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())
|
|
||||||
-> ok.
|
-> ok.
|
||||||
upgrade_protocol(Req, State=#state{listener=ListenerPid},
|
execute(Req, State, Env, []) ->
|
||||||
Handler, Opts, Module) ->
|
next_request(Req, State, get_value(result, Env, ok));
|
||||||
case Module:upgrade(ListenerPid, Handler, Opts, Req) of
|
execute(Req, State, Env, [Middleware|Tail]) ->
|
||||||
{UpgradeRes, Req2} -> next_request(Req2, State, UpgradeRes);
|
case Middleware:execute(Req, Env) of
|
||||||
_Any -> terminate(State)
|
{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.
|
end.
|
||||||
|
|
||||||
-spec handler_handle(cowboy_req:req(), #state{}, module(), any()) -> ok.
|
-spec resume(#state{}, cowboy_middleware:env(), [module()],
|
||||||
handler_handle(Req, State, Handler, HandlerState) ->
|
module(), module(), [any()]) -> ok.
|
||||||
try Handler:handle(Req, HandlerState) of
|
resume(State, Env, Tail, Module, Function, Args) ->
|
||||||
{ok, Req2, HandlerState2} ->
|
case apply(Module, Function, Args) of
|
||||||
terminate_request(Req2, State, Handler, HandlerState2)
|
{ok, Req2, Env2} ->
|
||||||
catch Class:Reason ->
|
execute(Req2, State, Env2, Tail);
|
||||||
error_logger:error_msg(
|
{suspend, Module2, Function2, Args2} ->
|
||||||
"** Cowboy handler ~p terminating in ~p/~p~n"
|
erlang:hibernate(?MODULE, resume,
|
||||||
" for the reason ~p:~p~n"
|
[State, Env, Tail, Module2, Function2, Args2]);
|
||||||
"** Handler state was ~p~n"
|
{halt, Req2} ->
|
||||||
"** Request was ~p~n"
|
next_request(Req2, State, ok);
|
||||||
"** Stacktrace: ~p~n~n",
|
{error, Code, Req2} ->
|
||||||
[Handler, handle, 2, Class, Reason, HandlerState,
|
error_terminate(Code, Req2, State)
|
||||||
cowboy_req:to_list(Req), erlang:get_stacktrace()]),
|
|
||||||
handler_terminate(Req, Handler, HandlerState),
|
|
||||||
error_terminate(500, Req, State)
|
|
||||||
end.
|
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.
|
-spec next_request(cowboy_req:req(), #state{}, any()) -> ok.
|
||||||
next_request(Req, State=#state{req_keepalive=Keepalive}, HandlerRes) ->
|
next_request(Req, State=#state{req_keepalive=Keepalive}, HandlerRes) ->
|
||||||
cowboy_req:ensure_response(Req, 204),
|
cowboy_req:ensure_response(Req, 204),
|
||||||
|
|
|
@ -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
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||||
%% purpose with or without fee is hereby granted, provided that the above
|
%% purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -23,6 +23,7 @@
|
||||||
-export([upgrade/4]).
|
-export([upgrade/4]).
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
|
env :: cowboy_middleware:env(),
|
||||||
method = undefined :: binary(),
|
method = undefined :: binary(),
|
||||||
|
|
||||||
%% Handler.
|
%% Handler.
|
||||||
|
@ -54,31 +55,31 @@
|
||||||
%% You do not need to call this function manually. To upgrade to the REST
|
%% 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>
|
%% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
|
||||||
%% in your <em>cowboy_http_handler:init/3</em> handler function.
|
%% in your <em>cowboy_http_handler:init/3</em> handler function.
|
||||||
-spec upgrade(pid(), module(), any(), Req)
|
-spec upgrade(Req, Env, module(), any())
|
||||||
-> {ok, Req} | close when Req::cowboy_req:req().
|
-> {ok, Req, Env} | {error, 500, Req}
|
||||||
upgrade(_ListenerPid, Handler, Opts, Req) ->
|
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
||||||
|
upgrade(Req, Env, Handler, HandlerOpts) ->
|
||||||
try
|
try
|
||||||
Method = cowboy_req:get(method, Req),
|
Method = cowboy_req:get(method, Req),
|
||||||
case erlang:function_exported(Handler, rest_init, 2) of
|
case erlang:function_exported(Handler, rest_init, 2) of
|
||||||
true ->
|
true ->
|
||||||
case Handler:rest_init(Req, Opts) of
|
case Handler:rest_init(Req, HandlerOpts) of
|
||||||
{ok, Req2, HandlerState} ->
|
{ok, Req2, HandlerState} ->
|
||||||
service_available(Req2, #state{method=Method,
|
service_available(Req2, #state{env=Env, method=Method,
|
||||||
handler=Handler, handler_state=HandlerState})
|
handler=Handler, handler_state=HandlerState})
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
service_available(Req, #state{method=Method,
|
service_available(Req, #state{env=Env, method=Method,
|
||||||
handler=Handler})
|
handler=Handler})
|
||||||
end
|
end
|
||||||
catch Class:Reason ->
|
catch Class:Reason ->
|
||||||
PLReq = cowboy_req:to_list(Req),
|
|
||||||
error_logger:error_msg(
|
error_logger:error_msg(
|
||||||
"** Cowboy handler ~p terminating in ~p/~p~n"
|
"** Cowboy handler ~p terminating in ~p/~p~n"
|
||||||
" for the reason ~p:~p~n** Options were ~p~n"
|
" for the reason ~p:~p~n** Options were ~p~n"
|
||||||
"** Request was ~p~n** Stacktrace: ~p~n~n",
|
"** Request was ~p~n** Stacktrace: ~p~n~n",
|
||||||
[Handler, rest_init, 2, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]),
|
[Handler, rest_init, 2, Class, Reason, HandlerOpts,
|
||||||
{ok, _Req2} = cowboy_req:reply(500, Req),
|
cowboy_req:to_list(Req), erlang:get_stacktrace()]),
|
||||||
close
|
{error, 500, Req}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
service_available(Req, State) ->
|
service_available(Req, State) ->
|
||||||
|
@ -738,8 +739,7 @@ choose_content_type(Req,
|
||||||
"function ~p/~p was not exported~n"
|
"function ~p/~p was not exported~n"
|
||||||
"** Request was ~p~n** State was ~p~n~n",
|
"** Request was ~p~n** State was ~p~n~n",
|
||||||
[Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]),
|
[Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]),
|
||||||
{ok, _} = cowboy_req:reply(500, Req),
|
{error, 500, Req};
|
||||||
close;
|
|
||||||
{halt, Req2, HandlerState} ->
|
{halt, Req2, HandlerState} ->
|
||||||
terminate(Req2, State#state{handler_state=HandlerState});
|
terminate(Req2, State#state{handler_state=HandlerState});
|
||||||
{true, Req2, 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"
|
"function ~p/~p was not exported~n"
|
||||||
"** Request was ~p~n** State was ~p~n~n",
|
"** Request was ~p~n** State was ~p~n~n",
|
||||||
[Handler, Fun, 2, cowboy_req:to_list(Req5), HandlerState]),
|
[Handler, Fun, 2, cowboy_req:to_list(Req5), HandlerState]),
|
||||||
{ok, _} = cowboy_req:reply(500, Req5),
|
{error, 500, Req5};
|
||||||
close;
|
|
||||||
{halt, Req6, HandlerState} ->
|
{halt, Req6, HandlerState} ->
|
||||||
terminate(Req6, State4#state{handler_state=HandlerState});
|
terminate(Req6, State4#state{handler_state=HandlerState});
|
||||||
{Body, Req6, HandlerState} ->
|
{Body, Req6, HandlerState} ->
|
||||||
|
@ -915,10 +914,11 @@ respond(Req, State, StatusCode) ->
|
||||||
{ok, Req2} = cowboy_req:reply(StatusCode, Req),
|
{ok, Req2} = cowboy_req:reply(StatusCode, Req),
|
||||||
terminate(Req2, State).
|
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
|
case erlang:function_exported(Handler, rest_terminate, 2) of
|
||||||
true -> ok = Handler:rest_terminate(
|
true -> ok = Handler:rest_terminate(
|
||||||
cowboy_req:lock(Req), HandlerState);
|
cowboy_req:lock(Req), HandlerState);
|
||||||
false -> ok
|
false -> ok
|
||||||
end,
|
end,
|
||||||
{ok, Req}.
|
{ok, Req, [{result, ok}|Env]}.
|
||||||
|
|
49
src/cowboy_router.erl
Normal file
49
src/cowboy_router.erl
Normal 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.
|
|
@ -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
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||||
%% purpose with or without fee is hereby granted, provided that the above
|
%% purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -38,11 +38,12 @@
|
||||||
{fin, opcode(), binary()}. %% last fragment has been seen.
|
{fin, opcode(), binary()}. %% last fragment has been seen.
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
|
env :: cowboy_middleware:env(),
|
||||||
socket = undefined :: inet:socket(),
|
socket = undefined :: inet:socket(),
|
||||||
transport = undefined :: module(),
|
transport = undefined :: module(),
|
||||||
version :: 0 | 7 | 8 | 13,
|
version :: 0 | 7 | 8 | 13,
|
||||||
handler :: module(),
|
handler :: module(),
|
||||||
opts :: any(),
|
handler_opts :: any(),
|
||||||
challenge = undefined :: undefined | binary() | {binary(), binary()},
|
challenge = undefined :: undefined | binary() | {binary(), binary()},
|
||||||
timeout = infinity :: timeout(),
|
timeout = infinity :: timeout(),
|
||||||
timeout_ref = undefined :: undefined | reference(),
|
timeout_ref = undefined :: undefined | reference(),
|
||||||
|
@ -58,15 +59,19 @@
|
||||||
%% You do not need to call this function manually. To upgrade to the WebSocket
|
%% 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>
|
%% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
|
||||||
%% in your <em>cowboy_http_handler:init/3</em> handler function.
|
%% in your <em>cowboy_http_handler:init/3</em> handler function.
|
||||||
-spec upgrade(pid(), module(), any(), cowboy_req:req()) -> closed.
|
-spec upgrade(Req, Env, module(), any())
|
||||||
upgrade(ListenerPid, Handler, Opts, Req) ->
|
-> {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),
|
ranch_listener:remove_connection(ListenerPid),
|
||||||
{ok, Transport, Socket} = cowboy_req:transport(Req),
|
{ok, Transport, Socket} = cowboy_req:transport(Req),
|
||||||
State = #state{socket=Socket, transport=Transport,
|
State = #state{env=Env, socket=Socket, transport=Transport,
|
||||||
handler=Handler, opts=Opts},
|
handler=Handler, handler_opts=HandlerOpts},
|
||||||
case catch websocket_upgrade(State, Req) of
|
case catch websocket_upgrade(State, Req) of
|
||||||
{ok, State2, Req2} -> handler_init(State2, Req2);
|
{ok, State2, Req2} -> handler_init(State2, Req2);
|
||||||
{'EXIT', _Reason} -> upgrade_error(Req)
|
{'EXIT', _Reason} -> upgrade_error(Req, Env)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec websocket_upgrade(#state{}, Req)
|
-spec websocket_upgrade(#state{}, Req)
|
||||||
|
@ -110,10 +115,13 @@ websocket_upgrade(Version, State, Req)
|
||||||
{ok, State#state{version=IntVersion, challenge=Challenge},
|
{ok, State#state{version=IntVersion, challenge=Challenge},
|
||||||
cowboy_req:set_meta(websocket_version, IntVersion, Req2)}.
|
cowboy_req:set_meta(websocket_version, IntVersion, Req2)}.
|
||||||
|
|
||||||
-spec handler_init(#state{}, cowboy_req:req()) -> closed.
|
-spec handler_init(#state{}, Req)
|
||||||
handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
|
-> {ok, Req, cowboy_middleware:env()} | {error, 400, Req}
|
||||||
Req) ->
|
| {suspend, module(), atom(), [any()]}
|
||||||
try Handler:websocket_init(Transport:name(), Req, Opts) of
|
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} ->
|
{ok, Req2, HandlerState} ->
|
||||||
websocket_handshake(State, Req2, HandlerState);
|
websocket_handshake(State, Req2, HandlerState);
|
||||||
{ok, Req2, HandlerState, hibernate} ->
|
{ok, Req2, HandlerState, hibernate} ->
|
||||||
|
@ -127,27 +135,31 @@ handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
|
||||||
hibernate=true}, Req2, HandlerState);
|
hibernate=true}, Req2, HandlerState);
|
||||||
{shutdown, Req2} ->
|
{shutdown, Req2} ->
|
||||||
cowboy_req:ensure_response(Req2, 400),
|
cowboy_req:ensure_response(Req2, 400),
|
||||||
closed
|
{ok, Req2, [{result, closed}|Env]}
|
||||||
catch Class:Reason ->
|
catch Class:Reason ->
|
||||||
upgrade_error(Req),
|
|
||||||
error_logger:error_msg(
|
error_logger:error_msg(
|
||||||
"** Cowboy handler ~p terminating in ~p/~p~n"
|
"** Cowboy handler ~p terminating in ~p/~p~n"
|
||||||
" for the reason ~p:~p~n** Options were ~p~n"
|
" for the reason ~p:~p~n** Options were ~p~n"
|
||||||
"** Request was ~p~n** Stacktrace: ~p~n~n",
|
"** Request was ~p~n** Stacktrace: ~p~n~n",
|
||||||
[Handler, websocket_init, 3, Class, Reason, Opts,
|
[Handler, websocket_init, 3, Class, Reason, HandlerOpts,
|
||||||
cowboy_req:to_list(Req),erlang:get_stacktrace()])
|
cowboy_req:to_list(Req),erlang:get_stacktrace()]),
|
||||||
|
upgrade_error(Req, Env)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec upgrade_error(cowboy_req:req()) -> closed.
|
-spec upgrade_error(Req, Env) -> {ok, Req, Env} | {error, 400, Req}
|
||||||
upgrade_error(Req) ->
|
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
||||||
|
upgrade_error(Req, Env) ->
|
||||||
receive
|
receive
|
||||||
{cowboy_req, resp_sent} -> closed
|
{cowboy_req, resp_sent} ->
|
||||||
|
{ok, Req, [{result, closed}|Env]}
|
||||||
after 0 ->
|
after 0 ->
|
||||||
_ = cowboy_req:reply(400, [], [], Req),
|
{error, 400, Req}
|
||||||
closed
|
|
||||||
end.
|
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,
|
websocket_handshake(State=#state{socket=Socket, transport=Transport,
|
||||||
version=0, origin=Origin, challenge={Key1, Key2}},
|
version=0, origin=Origin, challenge={Key1, Key2}},
|
||||||
Req, HandlerState) ->
|
Req, HandlerState) ->
|
||||||
|
@ -192,14 +204,16 @@ websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
|
||||||
handler_before_loop(State2#state{messages=Transport:messages()},
|
handler_before_loop(State2#state{messages=Transport:messages()},
|
||||||
Req2, HandlerState, <<>>).
|
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{
|
handler_before_loop(State=#state{
|
||||||
socket=Socket, transport=Transport, hibernate=true},
|
socket=Socket, transport=Transport, hibernate=true},
|
||||||
Req, HandlerState, SoFar) ->
|
Req, HandlerState, SoFar) ->
|
||||||
Transport:setopts(Socket, [{active, once}]),
|
Transport:setopts(Socket, [{active, once}]),
|
||||||
catch erlang:hibernate(?MODULE, handler_loop,
|
{suspend, ?MODULE, handler_loop,
|
||||||
[State#state{hibernate=false}, Req, HandlerState, SoFar]),
|
[State#state{hibernate=false}, Req, HandlerState, SoFar]};
|
||||||
closed;
|
|
||||||
handler_before_loop(State=#state{socket=Socket, transport=Transport},
|
handler_before_loop(State=#state{socket=Socket, transport=Transport},
|
||||||
Req, HandlerState, SoFar) ->
|
Req, HandlerState, SoFar) ->
|
||||||
Transport:setopts(Socket, [{active, once}]),
|
Transport:setopts(Socket, [{active, once}]),
|
||||||
|
@ -215,7 +229,10 @@ handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) ->
|
||||||
State#state{timeout_ref=TRef}.
|
State#state{timeout_ref=TRef}.
|
||||||
|
|
||||||
%% @private
|
%% @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{
|
handler_loop(State=#state{
|
||||||
socket=Socket, messages={OK, Closed, Error}, timeout_ref=TRef},
|
socket=Socket, messages={OK, Closed, Error}, timeout_ref=TRef},
|
||||||
Req, HandlerState, SoFar) ->
|
Req, HandlerState, SoFar) ->
|
||||||
|
@ -237,7 +254,10 @@ handler_loop(State=#state{
|
||||||
SoFar, websocket_info, Message, fun handler_before_loop/4)
|
SoFar, websocket_info, Message, fun handler_before_loop/4)
|
||||||
end.
|
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.
|
%% No more data.
|
||||||
websocket_data(State, Req, HandlerState, <<>>) ->
|
websocket_data(State, Req, HandlerState, <<>>) ->
|
||||||
handler_before_loop(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_data(State, Req, HandlerState, _Data) ->
|
||||||
websocket_close(State, Req, HandlerState, {error, badframe}).
|
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(), 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.
|
%% A fragmented message MUST start a non-zero opcode.
|
||||||
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
|
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
|
||||||
_Fin=0, _Rsv=0, _Opcode=0, _Mask, _PayloadLen, _Rest, _Buffer) ->
|
_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}).
|
websocket_close(State, Req, HandlerState, {error, badframe}).
|
||||||
|
|
||||||
%% hybi routing depending on whether unmasking is needed.
|
%% hybi routing depending on whether unmasking is needed.
|
||||||
-spec websocket_before_unmask(#state{}, cowboy_req:req(), any(), binary(),
|
-spec websocket_before_unmask(#state{}, Req, any(), binary(),
|
||||||
binary(), opcode(), 0 | 1, non_neg_integer() | undefined) -> closed.
|
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,
|
websocket_before_unmask(State, Req, HandlerState, Data,
|
||||||
Rest, Opcode, Mask, PayloadLen) ->
|
Rest, Opcode, Mask, PayloadLen) ->
|
||||||
case {Mask, PayloadLen} of
|
case {Mask, PayloadLen} of
|
||||||
|
@ -366,15 +391,21 @@ websocket_before_unmask(State, Req, HandlerState, Data,
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% hybi unmasking.
|
%% hybi unmasking.
|
||||||
-spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
|
-spec websocket_unmask(#state{}, Req, any(), binary(),
|
||||||
opcode(), binary(), mask_key()) -> closed.
|
opcode(), binary(), mask_key())
|
||||||
|
-> {ok, Req, cowboy_middleware:env()}
|
||||||
|
| {suspend, module(), atom(), [any()]}
|
||||||
|
when Req::cowboy_req:req().
|
||||||
websocket_unmask(State, Req, HandlerState, RemainingData,
|
websocket_unmask(State, Req, HandlerState, RemainingData,
|
||||||
Opcode, Payload, MaskKey) ->
|
Opcode, Payload, MaskKey) ->
|
||||||
websocket_unmask(State, Req, HandlerState, RemainingData,
|
websocket_unmask(State, Req, HandlerState, RemainingData,
|
||||||
Opcode, Payload, MaskKey, <<>>).
|
Opcode, Payload, MaskKey, <<>>).
|
||||||
|
|
||||||
-spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
|
-spec websocket_unmask(#state{}, Req, any(), binary(),
|
||||||
opcode(), binary(), mask_key(), binary()) -> closed.
|
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,
|
websocket_unmask(State, Req, HandlerState, RemainingData,
|
||||||
Opcode, << O:32, Rest/bits >>, MaskKey, Acc) ->
|
Opcode, << O:32, Rest/bits >>, MaskKey, Acc) ->
|
||||||
T = O bxor MaskKey,
|
T = O bxor MaskKey,
|
||||||
|
@ -404,8 +435,10 @@ websocket_unmask(State, Req, HandlerState, RemainingData,
|
||||||
Opcode, Acc).
|
Opcode, Acc).
|
||||||
|
|
||||||
%% hybi dispatching.
|
%% hybi dispatching.
|
||||||
-spec websocket_dispatch(#state{}, cowboy_req:req(), any(), binary(),
|
-spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary())
|
||||||
opcode(), binary()) -> closed.
|
-> {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.
|
%% First frame of a fragmented message unmasked. Expect intermediate or last.
|
||||||
websocket_dispatch(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
|
websocket_dispatch(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
|
||||||
RemainingData, 0, Payload) ->
|
RemainingData, 0, Payload) ->
|
||||||
|
@ -446,10 +479,12 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) ->
|
||||||
handler_call(State, Req, HandlerState, RemainingData,
|
handler_call(State, Req, HandlerState, RemainingData,
|
||||||
websocket_handle, {pong, Payload}, fun websocket_data/4).
|
websocket_handle, {pong, Payload}, fun websocket_data/4).
|
||||||
|
|
||||||
-spec handler_call(#state{}, cowboy_req:req(), any(), binary(),
|
-spec handler_call(#state{}, Req, any(), binary(), atom(), any(), fun())
|
||||||
atom(), any(), fun()) -> closed.
|
-> {ok, Req, cowboy_middleware:env()}
|
||||||
handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
|
| {suspend, module(), atom(), [any()]}
|
||||||
RemainingData, Callback, Message, NextState) ->
|
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
|
try Handler:Callback(Message, Req, HandlerState) of
|
||||||
{ok, Req2, HandlerState2} ->
|
{ok, Req2, HandlerState2} ->
|
||||||
NextState(State, Req2, HandlerState2, RemainingData);
|
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"
|
" for the reason ~p:~p~n** Message was ~p~n"
|
||||||
"** Options were ~p~n** Handler state was ~p~n"
|
"** Options were ~p~n** Handler state was ~p~n"
|
||||||
"** Request was ~p~n** Stacktrace: ~p~n~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()]),
|
HandlerState, PLReq, erlang:get_stacktrace()]),
|
||||||
websocket_close(State, Req, HandlerState, {error, handler})
|
websocket_close(State, Req, HandlerState, {error, handler})
|
||||||
end.
|
end.
|
||||||
|
@ -582,8 +617,9 @@ websocket_send_many([Frame|Tail], State) ->
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()})
|
-spec websocket_close(#state{}, Req, any(), {atom(), atom()})
|
||||||
-> closed.
|
-> {ok, Req, cowboy_middleware:env()}
|
||||||
|
when Req::cowboy_req:req().
|
||||||
websocket_close(State=#state{socket=Socket, transport=Transport, version=0},
|
websocket_close(State=#state{socket=Socket, transport=Transport, version=0},
|
||||||
Req, HandlerState, Reason) ->
|
Req, HandlerState, Reason) ->
|
||||||
Transport:send(Socket, << 255, 0 >>),
|
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 >>),
|
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>),
|
||||||
handler_terminate(State, Req, HandlerState, Reason).
|
handler_terminate(State, Req, HandlerState, Reason).
|
||||||
|
|
||||||
-spec handler_terminate(#state{}, cowboy_req:req(),
|
-spec handler_terminate(#state{}, Req, any(), atom() | {atom(), atom()})
|
||||||
any(), atom() | {atom(), atom()}) -> closed.
|
-> {ok, Req, cowboy_middleware:env()}
|
||||||
handler_terminate(#state{handler=Handler, opts=Opts},
|
when Req::cowboy_req:req().
|
||||||
|
handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts},
|
||||||
Req, HandlerState, TerminateReason) ->
|
Req, HandlerState, TerminateReason) ->
|
||||||
try
|
try
|
||||||
Handler:websocket_terminate(TerminateReason, Req, HandlerState)
|
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"
|
" for the reason ~p:~p~n** Initial reason was ~p~n"
|
||||||
"** Options were ~p~n** Handler state was ~p~n"
|
"** Options were ~p~n** Handler state was ~p~n"
|
||||||
"** Request was ~p~n** Stacktrace: ~p~n~n",
|
"** Request was ~p~n** Stacktrace: ~p~n~n",
|
||||||
[Handler, websocket_terminate, 3, Class, Reason, TerminateReason, Opts,
|
[Handler, websocket_terminate, 3, Class, Reason, TerminateReason,
|
||||||
HandlerState, PLReq, erlang:get_stacktrace()])
|
HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()])
|
||||||
end,
|
end,
|
||||||
closed.
|
{ok, Req, [{result, closed}|Env]}.
|
||||||
|
|
||||||
%% hixie-76 specific.
|
%% hixie-76 specific.
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ end_per_suite(_Config) ->
|
||||||
init_per_group(autobahn, Config) ->
|
init_per_group(autobahn, Config) ->
|
||||||
Port = 33080,
|
Port = 33080,
|
||||||
cowboy:start_http(autobahn, 100, [{port, Port}], [
|
cowboy:start_http(autobahn, 100, [{port, Port}], [
|
||||||
{dispatch, init_dispatch()}
|
{env, [{dispatch, init_dispatch()}]}
|
||||||
]),
|
]),
|
||||||
[{port, Port}|Config].
|
[{port, Port}|Config].
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ init_per_group(http, Config) ->
|
||||||
Transport = ranch_tcp,
|
Transport = ranch_tcp,
|
||||||
Config1 = init_static_dir(Config),
|
Config1 = init_static_dir(Config),
|
||||||
{ok, _} = cowboy:start_http(http, 100, [{port, Port}], [
|
{ok, _} = cowboy:start_http(http, 100, [{port, Port}], [
|
||||||
{dispatch, init_dispatch(Config1)},
|
{env, [{dispatch, init_dispatch(Config1)}]},
|
||||||
{max_keepalive, 50},
|
{max_keepalive, 50},
|
||||||
{timeout, 500}
|
{timeout, 500}
|
||||||
]),
|
]),
|
||||||
|
@ -172,7 +172,7 @@ init_per_group(https, Config) ->
|
||||||
application:start(public_key),
|
application:start(public_key),
|
||||||
application:start(ssl),
|
application:start(ssl),
|
||||||
{ok, _} = cowboy:start_https(https, 100, Opts ++ [{port, Port}], [
|
{ok, _} = cowboy:start_https(https, 100, Opts ++ [{port, Port}], [
|
||||||
{dispatch, init_dispatch(Config1)},
|
{env, [{dispatch, init_dispatch(Config1)}]},
|
||||||
{max_keepalive, 50},
|
{max_keepalive, 50},
|
||||||
{timeout, 500}
|
{timeout, 500}
|
||||||
]),
|
]),
|
||||||
|
@ -183,7 +183,7 @@ init_per_group(onrequest, Config) ->
|
||||||
Port = 33082,
|
Port = 33082,
|
||||||
Transport = ranch_tcp,
|
Transport = ranch_tcp,
|
||||||
{ok, _} = cowboy:start_http(onrequest, 100, [{port, Port}], [
|
{ok, _} = cowboy:start_http(onrequest, 100, [{port, Port}], [
|
||||||
{dispatch, init_dispatch(Config)},
|
{env, [{dispatch, init_dispatch(Config)}]},
|
||||||
{max_keepalive, 50},
|
{max_keepalive, 50},
|
||||||
{onrequest, fun onrequest_hook/1},
|
{onrequest, fun onrequest_hook/1},
|
||||||
{timeout, 500}
|
{timeout, 500}
|
||||||
|
@ -195,7 +195,7 @@ init_per_group(onresponse, Config) ->
|
||||||
Port = 33083,
|
Port = 33083,
|
||||||
Transport = ranch_tcp,
|
Transport = ranch_tcp,
|
||||||
{ok, _} = cowboy:start_http(onresponse, 100, [{port, Port}], [
|
{ok, _} = cowboy:start_http(onresponse, 100, [{port, Port}], [
|
||||||
{dispatch, init_dispatch(Config)},
|
{env, [{dispatch, init_dispatch(Config)}]},
|
||||||
{max_keepalive, 50},
|
{max_keepalive, 50},
|
||||||
{onresponse, fun onresponse_hook/4},
|
{onresponse, fun onresponse_hook/4},
|
||||||
{timeout, 500}
|
{timeout, 500}
|
||||||
|
@ -503,8 +503,8 @@ http10_hostless(Config) ->
|
||||||
ranch:start_listener(Name, 5,
|
ranch:start_listener(Name, 5,
|
||||||
?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}],
|
?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}],
|
||||||
cowboy_protocol, [
|
cowboy_protocol, [
|
||||||
{dispatch, [{'_', [
|
{env, [{dispatch, [{'_', [
|
||||||
{[<<"http1.0">>, <<"hostless">>], http_handler, []}]}]},
|
{[<<"http1.0">>, <<"hostless">>], http_handler, []}]}]}]},
|
||||||
{max_keepalive, 50},
|
{max_keepalive, 50},
|
||||||
{timeout, 500}]
|
{timeout, 500}]
|
||||||
),
|
),
|
||||||
|
|
|
@ -79,7 +79,7 @@ end_per_suite(_Config) ->
|
||||||
init_per_group(ws, Config) ->
|
init_per_group(ws, Config) ->
|
||||||
Port = 33080,
|
Port = 33080,
|
||||||
cowboy:start_http(ws, 100, [{port, Port}], [
|
cowboy:start_http(ws, 100, [{port, Port}], [
|
||||||
{dispatch, init_dispatch()}
|
{env, [{dispatch, init_dispatch()}]}
|
||||||
]),
|
]),
|
||||||
[{port, Port}|Config].
|
[{port, Port}|Config].
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue