2014-02-06 19:57:23 +01:00
|
|
|
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
|
2013-01-03 22:47:51 +01:00
|
|
|
%%
|
|
|
|
%% 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.
|
|
|
|
|
2014-03-26 19:05:59 +01:00
|
|
|
%% Handler middleware.
|
2013-01-03 22:47:51 +01:00
|
|
|
%%
|
|
|
|
%% 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.
|
|
|
|
%%
|
2013-02-11 09:03:13 +01:00
|
|
|
%% When using loop handlers, we are receiving data from the socket because we
|
|
|
|
%% want to know when the socket gets closed. This is generally not an issue
|
|
|
|
%% because these kinds of requests are generally not pipelined, and don't have
|
|
|
|
%% a body. If they do have a body, this body is often read in the
|
|
|
|
%% <em>init/3</em> callback and this is no problem. Otherwise, this data
|
|
|
|
%% accumulates in a buffer until we reach a certain threshold of 5000 bytes
|
|
|
|
%% by default. This can be configured through the <em>loop_max_buffer</em>
|
|
|
|
%% environment value. The request will be terminated with an
|
|
|
|
%% <em>{error, overflow}</em> reason if this threshold is reached.
|
2013-01-03 22:47:51 +01:00
|
|
|
-module(cowboy_handler).
|
|
|
|
-behaviour(cowboy_middleware).
|
|
|
|
|
|
|
|
-export([execute/2]).
|
|
|
|
-export([handler_loop/4]).
|
|
|
|
|
|
|
|
-record(state, {
|
|
|
|
env :: cowboy_middleware:env(),
|
|
|
|
hibernate = false :: boolean(),
|
2013-02-11 09:03:13 +01:00
|
|
|
loop_buffer_size = 0 :: non_neg_integer(),
|
|
|
|
loop_max_buffer = 5000 :: non_neg_integer() | infinity,
|
2013-01-03 22:47:51 +01:00
|
|
|
loop_timeout = infinity :: timeout(),
|
2013-02-11 09:03:13 +01:00
|
|
|
loop_timeout_ref = undefined :: undefined | reference(),
|
2013-01-29 14:35:26 +01:00
|
|
|
resp_sent = false :: boolean()
|
2013-01-03 22:47:51 +01:00
|
|
|
}).
|
|
|
|
|
|
|
|
-spec execute(Req, Env)
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, Env} | {suspend, ?MODULE, handler_loop, [any()]}
|
2013-01-03 22:47:51 +01:00
|
|
|
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),
|
2013-02-21 10:27:26 +04:00
|
|
|
MaxBuffer = case lists:keyfind(loop_max_buffer, 1, Env) of
|
|
|
|
false -> 5000;
|
|
|
|
{_, MaxBuffer0} -> MaxBuffer0
|
2013-02-11 09:03:13 +01:00
|
|
|
end,
|
|
|
|
handler_init(Req, #state{env=Env, loop_max_buffer=MaxBuffer},
|
|
|
|
Handler, HandlerOpts).
|
2013-01-03 22:47:51 +01:00
|
|
|
|
|
|
|
-spec handler_init(Req, #state{}, module(), any())
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
|
2013-01-03 22:47:51 +01:00
|
|
|
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} ->
|
2013-02-17 00:17:43 +00:00
|
|
|
handler_after_callback(Req2, State, Handler, HandlerState);
|
2013-01-03 22:47:51 +01:00
|
|
|
{loop, Req2, HandlerState, hibernate} ->
|
2013-02-17 00:17:43 +00:00
|
|
|
handler_after_callback(Req2, State#state{hibernate=true},
|
2013-01-03 22:47:51 +01:00
|
|
|
Handler, HandlerState);
|
|
|
|
{loop, Req2, HandlerState, Timeout} ->
|
2013-02-11 09:03:13 +01:00
|
|
|
State2 = handler_loop_timeout(State#state{loop_timeout=Timeout}),
|
2013-02-17 00:17:43 +00:00
|
|
|
handler_after_callback(Req2, State2, Handler, HandlerState);
|
2013-01-03 22:47:51 +01:00
|
|
|
{loop, Req2, HandlerState, Timeout, hibernate} ->
|
2013-02-11 09:03:13 +01:00
|
|
|
State2 = handler_loop_timeout(State#state{
|
|
|
|
hibernate=true, loop_timeout=Timeout}),
|
2013-02-17 00:17:43 +00:00
|
|
|
handler_after_callback(Req2, State2, Handler, HandlerState);
|
2013-01-03 22:47:51 +01:00
|
|
|
{shutdown, Req2, HandlerState} ->
|
2013-01-22 02:34:18 +01:00
|
|
|
terminate_request(Req2, State, Handler, HandlerState,
|
|
|
|
{normal, shutdown});
|
2013-01-03 22:47:51 +01:00
|
|
|
%% @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 ->
|
2013-08-24 20:36:23 +02:00
|
|
|
cowboy_req:maybe_reply(500, Req),
|
|
|
|
erlang:Class([
|
|
|
|
{reason, Reason},
|
|
|
|
{mfa, {Handler, init, 3}},
|
|
|
|
{stacktrace, erlang:get_stacktrace()},
|
|
|
|
{req, cowboy_req:to_list(Req)},
|
|
|
|
{opts, HandlerOpts}
|
|
|
|
])
|
2013-01-03 22:47:51 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
-spec upgrade_protocol(Req, #state{}, module(), any(), module())
|
|
|
|
-> {ok, Req, Env}
|
|
|
|
| {suspend, module(), atom(), any()}
|
|
|
|
| {halt, Req}
|
2013-05-16 16:29:24 +02:00
|
|
|
| {error, cowboy:http_status(), Req}
|
2013-01-03 22:47:51 +01:00
|
|
|
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())
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
|
2013-01-03 22:47:51 +01:00
|
|
|
handler_handle(Req, State, Handler, HandlerState) ->
|
|
|
|
try Handler:handle(Req, HandlerState) of
|
|
|
|
{ok, Req2, HandlerState2} ->
|
2013-01-22 02:34:18 +01:00
|
|
|
terminate_request(Req2, State, Handler, HandlerState2,
|
|
|
|
{normal, shutdown})
|
2013-01-03 22:47:51 +01:00
|
|
|
catch Class:Reason ->
|
2013-08-24 20:36:23 +02:00
|
|
|
cowboy_req:maybe_reply(500, Req),
|
2013-01-22 02:34:18 +01:00
|
|
|
handler_terminate(Req, Handler, HandlerState, Reason),
|
2013-08-24 20:36:23 +02:00
|
|
|
erlang:Class([
|
|
|
|
{reason, Reason},
|
|
|
|
{mfa, {Handler, handle, 2}},
|
|
|
|
{stacktrace, erlang:get_stacktrace()},
|
|
|
|
{req, cowboy_req:to_list(Req)},
|
|
|
|
{state, HandlerState}
|
|
|
|
])
|
2013-01-03 22:47:51 +01:00
|
|
|
end.
|
|
|
|
|
2013-02-17 00:17:43 +00:00
|
|
|
%% Update the state if the response was sent in the callback.
|
|
|
|
-spec handler_after_callback(Req, #state{}, module(), any())
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
|
2013-02-17 00:17:43 +00:00
|
|
|
when Req::cowboy_req:req().
|
|
|
|
handler_after_callback(Req, State=#state{resp_sent=false}, Handler,
|
|
|
|
HandlerState) ->
|
|
|
|
receive
|
|
|
|
{cowboy_req, resp_sent} ->
|
|
|
|
handler_before_loop(Req, State#state{resp_sent=true}, Handler,
|
|
|
|
HandlerState)
|
|
|
|
after 0 ->
|
|
|
|
handler_before_loop(Req, State, Handler, HandlerState)
|
|
|
|
end;
|
|
|
|
handler_after_callback(Req, State, Handler, HandlerState) ->
|
|
|
|
handler_before_loop(Req, State, Handler, HandlerState).
|
|
|
|
|
2013-01-03 22:47:51 +01:00
|
|
|
-spec handler_before_loop(Req, #state{}, module(), any())
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
|
2013-01-03 22:47:51 +01:00
|
|
|
when Req::cowboy_req:req().
|
|
|
|
handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
|
2013-02-11 09:03:13 +01:00
|
|
|
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
|
|
|
|
Transport:setopts(Socket, [{active, once}]),
|
2013-01-03 22:47:51 +01:00
|
|
|
{suspend, ?MODULE, handler_loop,
|
2013-02-11 09:03:13 +01:00
|
|
|
[Req, State#state{hibernate=false}, Handler, HandlerState]};
|
2013-01-03 22:47:51 +01:00
|
|
|
handler_before_loop(Req, State, Handler, HandlerState) ->
|
2013-02-11 09:03:13 +01:00
|
|
|
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
|
|
|
|
Transport:setopts(Socket, [{active, once}]),
|
|
|
|
handler_loop(Req, State, Handler, HandlerState).
|
2013-01-03 22:47:51 +01:00
|
|
|
|
|
|
|
%% 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}) ->
|
2013-02-11 09:03:13 +01:00
|
|
|
_ = case PrevRef of
|
|
|
|
undefined -> ignore;
|
|
|
|
PrevRef -> erlang:cancel_timer(PrevRef)
|
|
|
|
end,
|
2013-01-03 22:47:51 +01:00
|
|
|
TRef = erlang:start_timer(Timeout, self(), ?MODULE),
|
|
|
|
State#state{loop_timeout_ref=TRef}.
|
|
|
|
|
|
|
|
-spec handler_loop(Req, #state{}, module(), any())
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
|
2013-01-03 22:47:51 +01:00
|
|
|
when Req::cowboy_req:req().
|
2013-02-11 09:03:13 +01:00
|
|
|
handler_loop(Req, State=#state{loop_buffer_size=NbBytes,
|
|
|
|
loop_max_buffer=Threshold, loop_timeout_ref=TRef},
|
|
|
|
Handler, HandlerState) ->
|
|
|
|
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
|
|
|
|
{OK, Closed, Error} = Transport:messages(),
|
2013-01-03 22:47:51 +01:00
|
|
|
receive
|
2013-02-11 09:03:13 +01:00
|
|
|
{OK, Socket, Data} ->
|
|
|
|
NbBytes2 = NbBytes + byte_size(Data),
|
|
|
|
if NbBytes2 > Threshold ->
|
|
|
|
_ = handler_terminate(Req, Handler, HandlerState,
|
|
|
|
{error, overflow}),
|
2013-08-24 20:36:23 +02:00
|
|
|
cowboy_req:maybe_reply(500, Req),
|
|
|
|
exit(normal);
|
2013-02-11 09:03:13 +01:00
|
|
|
true ->
|
|
|
|
Req2 = cowboy_req:append_buffer(Data, Req),
|
|
|
|
State2 = handler_loop_timeout(State#state{
|
|
|
|
loop_buffer_size=NbBytes2}),
|
2013-04-12 14:34:36 +02:00
|
|
|
handler_before_loop(Req2, State2, Handler, HandlerState)
|
2013-02-11 09:03:13 +01:00
|
|
|
end;
|
|
|
|
{Closed, Socket} ->
|
|
|
|
terminate_request(Req, State, Handler, HandlerState,
|
|
|
|
{error, closed});
|
|
|
|
{Error, Socket, Reason} ->
|
|
|
|
terminate_request(Req, State, Handler, HandlerState,
|
|
|
|
{error, Reason});
|
2013-01-03 22:47:51 +01:00
|
|
|
{timeout, TRef, ?MODULE} ->
|
2013-02-15 01:10:59 +04:00
|
|
|
handler_after_loop(Req, State, Handler, HandlerState,
|
2013-01-22 02:34:18 +01:00
|
|
|
{normal, timeout});
|
2013-01-03 22:47:51 +01:00
|
|
|
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
|
2013-11-18 20:32:47 +00:00
|
|
|
handler_loop(Req, State, Handler, HandlerState);
|
2013-01-03 22:47:51 +01:00
|
|
|
Message ->
|
2013-04-12 14:16:59 +02:00
|
|
|
%% We set the socket back to {active, false} mode in case
|
|
|
|
%% the handler is going to call recv. We also flush any
|
|
|
|
%% data received after that and put it into the buffer.
|
|
|
|
%% We do not check the size here, if data keeps coming
|
|
|
|
%% we'll error out on the next packet received.
|
|
|
|
Transport:setopts(Socket, [{active, false}]),
|
|
|
|
Req2 = receive {OK, Socket, Data} ->
|
|
|
|
cowboy_req:append_buffer(Data, Req)
|
|
|
|
after 0 ->
|
|
|
|
Req
|
|
|
|
end,
|
|
|
|
handler_call(Req2, State, Handler, HandlerState, Message)
|
2013-01-03 22:47:51 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
-spec handler_call(Req, #state{}, module(), any(), any())
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
|
2013-01-03 22:47:51 +01:00
|
|
|
when Req::cowboy_req:req().
|
2013-08-24 20:36:23 +02:00
|
|
|
handler_call(Req, State=#state{resp_sent=RespSent},
|
|
|
|
Handler, HandlerState, Message) ->
|
2013-01-03 22:47:51 +01:00
|
|
|
try Handler:info(Message, Req, HandlerState) of
|
|
|
|
{ok, Req2, HandlerState2} ->
|
2013-02-15 01:10:59 +04:00
|
|
|
handler_after_loop(Req2, State, Handler, HandlerState2,
|
2013-01-22 02:34:18 +01:00
|
|
|
{normal, shutdown});
|
2013-01-03 22:47:51 +01:00
|
|
|
{loop, Req2, HandlerState2} ->
|
2013-02-17 00:17:43 +00:00
|
|
|
handler_after_callback(Req2, State, Handler, HandlerState2);
|
2013-01-03 22:47:51 +01:00
|
|
|
{loop, Req2, HandlerState2, hibernate} ->
|
2013-02-17 00:17:43 +00:00
|
|
|
handler_after_callback(Req2, State#state{hibernate=true},
|
2013-01-03 22:47:51 +01:00
|
|
|
Handler, HandlerState2)
|
|
|
|
catch Class:Reason ->
|
2013-08-24 20:36:23 +02:00
|
|
|
if RespSent ->
|
|
|
|
ok;
|
|
|
|
true ->
|
|
|
|
cowboy_req:maybe_reply(500, Req)
|
|
|
|
end,
|
2013-01-22 02:34:18 +01:00
|
|
|
handler_terminate(Req, Handler, HandlerState, Reason),
|
2013-08-24 20:36:23 +02:00
|
|
|
erlang:Class([
|
|
|
|
{reason, Reason},
|
|
|
|
{mfa, {Handler, info, 3}},
|
|
|
|
{stacktrace, erlang:get_stacktrace()},
|
|
|
|
{req, cowboy_req:to_list(Req)},
|
|
|
|
{state, HandlerState}
|
|
|
|
])
|
2013-01-03 22:47:51 +01:00
|
|
|
end.
|
|
|
|
|
2013-02-15 01:10:59 +04:00
|
|
|
%% It is sometimes important to make a socket passive as it was initially
|
|
|
|
%% and as it is expected to be by cowboy_protocol, right after we're done
|
|
|
|
%% with loop handling. The browser may freely pipeline a bunch of requests
|
|
|
|
%% if previous one was, say, a JSONP long-polling request.
|
|
|
|
-spec handler_after_loop(Req, #state{}, module(), any(),
|
|
|
|
{normal, timeout | shutdown} | {error, atom()}) ->
|
|
|
|
{ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
|
|
|
|
handler_after_loop(Req, State, Handler, HandlerState, Reason) ->
|
|
|
|
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
|
|
|
|
Transport:setopts(Socket, [{active, false}]),
|
|
|
|
{OK, _Closed, _Error} = Transport:messages(),
|
|
|
|
Req2 = receive
|
|
|
|
{OK, Socket, Data} ->
|
|
|
|
cowboy_req:append_buffer(Data, Req)
|
|
|
|
after 0 ->
|
|
|
|
Req
|
|
|
|
end,
|
|
|
|
terminate_request(Req2, State, Handler, HandlerState, Reason).
|
|
|
|
|
2013-01-22 02:34:18 +01:00
|
|
|
-spec terminate_request(Req, #state{}, module(), any(),
|
|
|
|
{normal, timeout | shutdown} | {error, atom()}) ->
|
2013-01-03 22:47:51 +01:00
|
|
|
{ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
|
2013-11-18 20:32:47 +00:00
|
|
|
terminate_request(Req, #state{env=Env, loop_timeout_ref=TRef},
|
|
|
|
Handler, HandlerState, Reason) ->
|
2013-01-22 02:34:18 +01:00
|
|
|
HandlerRes = handler_terminate(Req, Handler, HandlerState, Reason),
|
2013-11-18 20:32:47 +00:00
|
|
|
_ = case TRef of
|
|
|
|
undefined -> ignore;
|
|
|
|
TRef -> erlang:cancel_timer(TRef)
|
|
|
|
end,
|
|
|
|
flush_timeouts(),
|
2013-01-03 22:47:51 +01:00
|
|
|
{ok, Req, [{result, HandlerRes}|Env]}.
|
|
|
|
|
2013-01-22 02:34:18 +01:00
|
|
|
-spec handler_terminate(cowboy_req:req(), module(), any(),
|
|
|
|
{normal, timeout | shutdown} | {error, atom()}) -> ok.
|
|
|
|
handler_terminate(Req, Handler, HandlerState, Reason) ->
|
2013-01-03 22:47:51 +01:00
|
|
|
try
|
2013-01-22 02:34:18 +01:00
|
|
|
Handler:terminate(Reason, cowboy_req:lock(Req), HandlerState)
|
|
|
|
catch Class:Reason2 ->
|
2013-08-24 20:36:23 +02:00
|
|
|
erlang:Class([
|
|
|
|
{reason, Reason2},
|
|
|
|
{mfa, {Handler, terminate, 3}},
|
|
|
|
{stacktrace, erlang:get_stacktrace()},
|
|
|
|
{req, cowboy_req:to_list(Req)},
|
|
|
|
{state, HandlerState},
|
|
|
|
{terminate_reason, Reason}
|
|
|
|
])
|
2013-01-03 22:47:51 +01:00
|
|
|
end.
|
2013-11-18 20:32:47 +00:00
|
|
|
|
|
|
|
-spec flush_timeouts() -> ok.
|
|
|
|
flush_timeouts() ->
|
|
|
|
receive
|
|
|
|
{timeout, TRef, ?MODULE} when is_reference(TRef) ->
|
|
|
|
flush_timeouts()
|
|
|
|
after 0 ->
|
|
|
|
ok
|
|
|
|
end.
|