diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy_handler.erl | 77 | ||||
-rw-r--r-- | src/cowboy_req.erl | 6 |
2 files changed, 66 insertions, 17 deletions
diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl index 7ed7db3..65e22b7 100644 --- a/src/cowboy_handler.erl +++ b/src/cowboy_handler.erl @@ -18,6 +18,16 @@ %% environment values. The result of this execution is added to the %% environment under the <em>result</em> value. %% +%% 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. +%% %% @see cowboy_http_handler -module(cowboy_handler). -behaviour(cowboy_middleware). @@ -28,8 +38,10 @@ -record(state, { env :: cowboy_middleware:env(), hibernate = false :: boolean(), + loop_buffer_size = 0 :: non_neg_integer(), + loop_max_buffer = 5000 :: non_neg_integer() | infinity, loop_timeout = infinity :: timeout(), - loop_timeout_ref :: undefined | reference(), + loop_timeout_ref = undefined :: undefined | reference(), resp_sent = false :: boolean() }). @@ -41,7 +53,12 @@ execute(Req, Env) -> {_, Handler} = lists:keyfind(handler, 1, Env), {_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env), - handler_init(Req, #state{env=Env}, Handler, HandlerOpts). + case lists:keyfind(loop_max_buffer, 1, Env) of + false -> MaxBuffer = 5000, ok; + {_, MaxBuffer} -> ok + end, + handler_init(Req, #state{env=Env, loop_max_buffer=MaxBuffer}, + Handler, HandlerOpts). -spec handler_init(Req, #state{}, module(), any()) -> {ok, Req, cowboy_middleware:env()} @@ -53,17 +70,17 @@ handler_init(Req, State, Handler, HandlerOpts) -> {ok, Req2, HandlerState} -> handler_handle(Req2, State, Handler, HandlerState); {loop, Req2, HandlerState} -> - handler_before_loop(Req2, State#state{hibernate=false}, - Handler, HandlerState); + handler_before_loop(Req2, State, 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); + State2 = handler_loop_timeout(State#state{loop_timeout=Timeout}), + handler_before_loop(Req2, State2, Handler, HandlerState); {loop, Req2, HandlerState, Timeout, hibernate} -> - handler_before_loop(Req2, State#state{ - hibernate=true, loop_timeout=Timeout}, Handler, HandlerState); + State2 = handler_loop_timeout(State#state{ + hibernate=true, loop_timeout=Timeout}), + handler_before_loop(Req2, State2, Handler, HandlerState); {shutdown, Req2, HandlerState} -> terminate_request(Req2, State, Handler, HandlerState, {normal, shutdown}); @@ -123,12 +140,14 @@ handler_handle(Req, State, Handler, HandlerState) -> | {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), + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + Transport:setopts(Socket, [{active, once}]), {suspend, ?MODULE, handler_loop, - [Req, State2#state{hibernate=false}, Handler, HandlerState]}; + [Req, State#state{hibernate=false}, Handler, HandlerState]}; handler_before_loop(Req, State, Handler, HandlerState) -> - State2 = handler_loop_timeout(State), - handler_loop(Req, State2, Handler, HandlerState). + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + Transport:setopts(Socket, [{active, once}]), + handler_loop(Req, State, Handler, HandlerState). %% Almost the same code can be found in cowboy_websocket. -spec handler_loop_timeout(#state{}) -> #state{}. @@ -136,8 +155,10 @@ 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, + _ = case PrevRef of + undefined -> ignore; + PrevRef -> erlang:cancel_timer(PrevRef) + end, TRef = erlang:start_timer(Timeout, self(), ?MODULE), State#state{loop_timeout_ref=TRef}. @@ -146,16 +167,38 @@ handler_loop_timeout(State=#state{loop_timeout=Timeout, -> {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) -> +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(), receive + {OK, Socket, Data} -> + NbBytes2 = NbBytes + byte_size(Data), + if NbBytes2 > Threshold -> + _ = handler_terminate(Req, Handler, HandlerState, + {error, overflow}), + error_terminate(Req, State); + true -> + Req2 = cowboy_req:append_buffer(Data, Req), + State2 = handler_loop_timeout(State#state{ + loop_buffer_size=NbBytes2}), + handler_loop(Req2, State2, Handler, HandlerState) + end; + {Closed, Socket} -> + terminate_request(Req, State, Handler, HandlerState, + {error, closed}); + {Error, Socket, Reason} -> + terminate_request(Req, State, Handler, HandlerState, + {error, Reason}); {cowboy_req, resp_sent} -> - handler_loop(Req, State#state{resp_sent=true}, + handler_before_loop(Req, State#state{resp_sent=true}, Handler, HandlerState); {timeout, TRef, ?MODULE} -> terminate_request(Req, State, Handler, HandlerState, {normal, timeout}); {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> - handler_loop(Req, State, Handler, HandlerState); + handler_before_loop(Req, State, Handler, HandlerState); Message -> handler_call(Req, State, Handler, HandlerState, Message) end. diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index c807a75..d416916 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -104,6 +104,7 @@ -export([ensure_response/2]). %% Private setter/getter API. +-export([append_buffer/2]). -export([get/2]). -export([set/2]). -export([set_bindings/4]). @@ -1066,6 +1067,11 @@ ensure_response(#http_req{socket=Socket, transport=Transport, %% Private setter/getter API. %% @private +-spec append_buffer(binary(), Req) -> Req when Req::req(). +append_buffer(Suffix, Req=#http_req{buffer=Buffer}) -> + Req#http_req{buffer= << Buffer/binary, Suffix/binary >>}. + +%% @private -spec get(atom(), req()) -> any(); ([atom()], req()) -> any(). get(List, Req) when is_list(List) -> [g(Atom, Req) || Atom <- List]; |