aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_handler.erl
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2013-01-03 22:47:51 +0100
committerLoïc Hoguin <[email protected]>2013-01-03 22:47:51 +0100
commit1b3f510b7e8d5413901ba72adfe361773f3e9097 (patch)
tree314d24a8bbbdfc1e326cac28193ab6d9f7dc3b61 /src/cowboy_handler.erl
parent73d86057f2f9d6b3de5fb12e23b2cd65be50e226 (diff)
downloadcowboy-1b3f510b7e8d5413901ba72adfe361773f3e9097.tar.gz
cowboy-1b3f510b7e8d5413901ba72adfe361773f3e9097.tar.bz2
cowboy-1b3f510b7e8d5413901ba72adfe361773f3e9097.zip
Add middleware support
Middlewares allow customizing the request processing. All existing Cowboy project are incompatible with this commit. You need to change `{dispatch, Dispatch}` in the protocol options to `{env, [{dispatch, Dispatch}]}` to fix your code.
Diffstat (limited to 'src/cowboy_handler.erl')
-rw-r--r--src/cowboy_handler.erl201
1 files changed, 201 insertions, 0 deletions
diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl
new file mode 100644
index 0000000..cc871d9
--- /dev/null
+++ b/src/cowboy_handler.erl
@@ -0,0 +1,201 @@
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc Handler middleware.
+%%
+%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>
+%% environment values. The result of this execution is added to the
+%% environment under the <em>result</em> value.
+%%
+%% @see cowboy_http_handler
+-module(cowboy_handler).
+-behaviour(cowboy_middleware).
+
+-export([execute/2]).
+-export([handler_loop/4]).
+
+-record(state, {
+ env :: cowboy_middleware:env(),
+ hibernate = false :: boolean(),
+ loop_timeout = infinity :: timeout(),
+ loop_timeout_ref :: undefined | reference()
+}).
+
+%% @private
+-spec execute(Req, Env)
+ -> {ok, Req, Env} | {error, 500, Req}
+ | {suspend, ?MODULE, handler_loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req, Env) ->
+ {_, Handler} = lists:keyfind(handler, 1, Env),
+ {_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env),
+ handler_init(Req, #state{env=Env}, Handler, HandlerOpts).
+
+-spec handler_init(Req, #state{}, module(), any())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ when Req::cowboy_req:req().
+handler_init(Req, State, Handler, HandlerOpts) ->
+ Transport = cowboy_req:get(transport, Req),
+ try Handler:init({Transport:name(), http}, Req, HandlerOpts) of
+ {ok, Req2, HandlerState} ->
+ handler_handle(Req2, State, Handler, HandlerState);
+ {loop, Req2, HandlerState} ->
+ handler_before_loop(Req2, State#state{hibernate=false},
+ Handler, HandlerState);
+ {loop, Req2, HandlerState, hibernate} ->
+ handler_before_loop(Req2, State#state{hibernate=true},
+ Handler, HandlerState);
+ {loop, Req2, HandlerState, Timeout} ->
+ handler_before_loop(Req2, State#state{loop_timeout=Timeout},
+ Handler, HandlerState);
+ {loop, Req2, HandlerState, Timeout, hibernate} ->
+ handler_before_loop(Req2, State#state{
+ hibernate=true, loop_timeout=Timeout}, Handler, HandlerState);
+ {shutdown, Req2, HandlerState} ->
+ terminate_request(Req2, State, Handler, HandlerState);
+ %% @todo {upgrade, transport, Module}
+ {upgrade, protocol, Module} ->
+ upgrade_protocol(Req, State, Handler, HandlerOpts, Module);
+ {upgrade, protocol, Module, Req2, HandlerOpts2} ->
+ upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module)
+ catch Class:Reason ->
+ error_logger:error_msg(
+ "** Cowboy handler ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Options were ~p~n"
+ "** Request was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, init, 3, Class, Reason, HandlerOpts,
+ cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+ {error, 500, Req}
+ end.
+
+-spec upgrade_protocol(Req, #state{}, module(), any(), module())
+ -> {ok, Req, Env}
+ | {suspend, module(), atom(), any()}
+ | {halt, Req}
+ | {error, cowboy_http:status(), Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade_protocol(Req, #state{env=Env},
+ Handler, HandlerOpts, Module) ->
+ Module:upgrade(Req, Env, Handler, HandlerOpts).
+
+-spec handler_handle(Req, #state{}, module(), any())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {error, 500, Req}
+ when Req::cowboy_req:req().
+handler_handle(Req, State, Handler, HandlerState) ->
+ try Handler:handle(Req, HandlerState) of
+ {ok, Req2, HandlerState2} ->
+ terminate_request(Req2, State, Handler, HandlerState2)
+ catch Class:Reason ->
+ error_logger:error_msg(
+ "** Cowboy handler ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Handler state was ~p~n"
+ "** Request was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, handle, 2, Class, Reason, HandlerState,
+ cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+ handler_terminate(Req, Handler, HandlerState),
+ {error, 500, Req}
+ end.
+
+%% We don't listen for Transport closes because that would force us
+%% to receive data and buffer it indefinitely.
+-spec handler_before_loop(Req, #state{}, module(), any())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ when Req::cowboy_req:req().
+handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
+ State2 = handler_loop_timeout(State),
+ {suspend, ?MODULE, handler_loop,
+ [Req, State2#state{hibernate=false}, Handler, HandlerState]};
+handler_before_loop(Req, State, Handler, HandlerState) ->
+ State2 = handler_loop_timeout(State),
+ handler_loop(Req, State2, Handler, HandlerState).
+
+%% Almost the same code can be found in cowboy_websocket.
+-spec handler_loop_timeout(#state{}) -> #state{}.
+handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
+ State#state{loop_timeout_ref=undefined};
+handler_loop_timeout(State=#state{loop_timeout=Timeout,
+ loop_timeout_ref=PrevRef}) ->
+ _ = case PrevRef of undefined -> ignore; PrevRef ->
+ erlang:cancel_timer(PrevRef) end,
+ TRef = erlang:start_timer(Timeout, self(), ?MODULE),
+ State#state{loop_timeout_ref=TRef}.
+
+%% @private
+-spec handler_loop(Req, #state{}, module(), any())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ when Req::cowboy_req:req().
+handler_loop(Req, State=#state{loop_timeout_ref=TRef}, Handler, HandlerState) ->
+ receive
+ {timeout, TRef, ?MODULE} ->
+ terminate_request(Req, State, Handler, HandlerState);
+ {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
+ handler_loop(Req, State, Handler, HandlerState);
+ Message ->
+ handler_call(Req, State, Handler, HandlerState, Message)
+ end.
+
+-spec handler_call(Req, #state{}, module(), any(), any())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {error, 500, Req} | {suspend, module(), function(), [any()]}
+ when Req::cowboy_req:req().
+handler_call(Req, State, Handler, HandlerState, Message) ->
+ try Handler:info(Message, Req, HandlerState) of
+ {ok, Req2, HandlerState2} ->
+ terminate_request(Req2, State, Handler, HandlerState2);
+ {loop, Req2, HandlerState2} ->
+ handler_before_loop(Req2, State, Handler, HandlerState2);
+ {loop, Req2, HandlerState2, hibernate} ->
+ handler_before_loop(Req2, State#state{hibernate=true},
+ Handler, HandlerState2)
+ catch Class:Reason ->
+ error_logger:error_msg(
+ "** Cowboy handler ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Handler state was ~p~n"
+ "** Request was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, info, 3, Class, Reason, HandlerState,
+ cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+ handler_terminate(Req, Handler, HandlerState),
+ {error, 500, Req}
+ end.
+
+-spec terminate_request(Req, #state{}, module(), any()) ->
+ {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
+terminate_request(Req, #state{env=Env}, Handler, HandlerState) ->
+ HandlerRes = handler_terminate(Req, Handler, HandlerState),
+ {ok, Req, [{result, HandlerRes}|Env]}.
+
+-spec handler_terminate(cowboy_req:req(), module(), any()) -> ok.
+handler_terminate(Req, Handler, HandlerState) ->
+ try
+ Handler:terminate(cowboy_req:lock(Req), HandlerState)
+ catch Class:Reason ->
+ error_logger:error_msg(
+ "** Cowboy handler ~p terminating in ~p/~p~n"
+ " for the reason ~p:~p~n"
+ "** Handler state was ~p~n"
+ "** Request was ~p~n"
+ "** Stacktrace: ~p~n~n",
+ [Handler, terminate, 2, Class, Reason, HandlerState,
+ cowboy_req:to_list(Req), erlang:get_stacktrace()])
+ end.