diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy_handler.erl | 299 | ||||
-rw-r--r-- | src/cowboy_http_handler.erl | 27 | ||||
-rw-r--r-- | src/cowboy_long_polling.erl | 191 | ||||
-rw-r--r-- | src/cowboy_loop_handler.erl | 27 | ||||
-rw-r--r-- | src/cowboy_req.erl | 10 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 43 | ||||
-rw-r--r-- | src/cowboy_static.erl | 55 | ||||
-rw-r--r-- | src/cowboy_sub_protocol.erl | 7 | ||||
-rw-r--r-- | src/cowboy_websocket.erl | 80 | ||||
-rw-r--r-- | src/cowboy_websocket_handler.erl | 33 |
10 files changed, 335 insertions, 437 deletions
diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl index 1e8261f..a666714 100644 --- a/src/cowboy_handler.erl +++ b/src/cowboy_handler.erl @@ -17,287 +17,86 @@ %% 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. -%% -%% 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. -module(cowboy_handler). -behaviour(cowboy_middleware). -export([execute/2]). --export([handler_loop/4]). - --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 :: undefined | reference(), - resp_sent = false :: boolean() -}). +-export([terminate/5]). --spec execute(Req, Env) - -> {ok, Req, Env} | {suspend, ?MODULE, handler_loop, [any()]} +-spec execute(Req, Env) -> {ok, Req, Env} 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), - MaxBuffer = case lists:keyfind(loop_max_buffer, 1, Env) of - false -> 5000; - {_, MaxBuffer0} -> MaxBuffer0 - 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()} | {suspend, module(), atom(), [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_after_callback(Req2, State, Handler, HandlerState); - {loop, Req2, HandlerState, hibernate} -> - handler_after_callback(Req2, State#state{hibernate=true}, - Handler, HandlerState); - {loop, Req2, HandlerState, Timeout} -> - State2 = handler_loop_timeout(State#state{loop_timeout=Timeout}), - handler_after_callback(Req2, State2, Handler, HandlerState); - {loop, Req2, HandlerState, Timeout, hibernate} -> - State2 = handler_loop_timeout(State#state{ - hibernate=true, loop_timeout=Timeout}), - handler_after_callback(Req2, State2, Handler, HandlerState); - {shutdown, Req2, HandlerState} -> - terminate_request(Req2, State, Handler, HandlerState, - {normal, shutdown}); - {upgrade, protocol, Module} -> - upgrade_protocol(Req, State, Handler, HandlerOpts, Module); - {upgrade, protocol, Module, Req2, HandlerOpts2} -> - upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module) + try Handler:init(Req, HandlerOpts) of + {http, Req2, State} -> + handle(Req2, Env, Handler, State); + {shutdown, Req2, State} -> + terminate(Req2, Env, Handler, State, {normal, shutdown}); + {Mod, Req2, State} -> + upgrade(Req2, Env, Handler, State, infinity, run, Mod); + {Mod, Req2, State, hibernate} -> + upgrade(Req2, Env, Handler, State, infinity, hibernate, Mod); + {Mod, Req2, State, Timeout} -> + upgrade(Req2, Env, Handler, State, Timeout, run, Mod); + {Mod, Req2, State, Timeout, hibernate} -> + upgrade(Req2, Env, Handler, State, Timeout, hibernate, Mod) catch Class:Reason -> Stacktrace = erlang:get_stacktrace(), cowboy_req:maybe_reply(Stacktrace, Req), erlang:Class([ {reason, Reason}, - {mfa, {Handler, init, 3}}, + {mfa, {Handler, init, 2}}, {stacktrace, Stacktrace}, {req, cowboy_req:to_list(Req)}, {opts, HandlerOpts} ]) end. --spec upgrade_protocol(Req, #state{}, module(), any(), module()) - -> {ok, Req, Env} - | {suspend, module(), atom(), any()} - | {halt, 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()} 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, - {normal, shutdown}) +handle(Req, Env, Handler, State) -> + try Handler:handle(Req, State) of + {ok, Req2, State2} -> + terminate(Req2, Env, Handler, State2, {normal, shutdown}) catch Class:Reason -> Stacktrace = erlang:get_stacktrace(), cowboy_req:maybe_reply(Stacktrace, Req), - handler_terminate(Req, Handler, HandlerState, Reason), + _ = terminate(Req, Env, Handler, State, Reason), erlang:Class([ {reason, Reason}, {mfa, {Handler, handle, 2}}, {stacktrace, Stacktrace}, {req, cowboy_req:to_list(Req)}, - {state, HandlerState} + {state, State} ]) end. -%% Update the state if the response was sent in the callback. --spec handler_after_callback(Req, #state{}, module(), any()) - -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} - 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). - --spec handler_before_loop(Req, #state{}, module(), any()) - -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} - when Req::cowboy_req:req(). -handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) -> - [Socket, Transport] = cowboy_req:get([socket, transport], Req), - Transport:setopts(Socket, [{active, once}]), - {suspend, ?MODULE, handler_loop, - [Req, State#state{hibernate=false}, Handler, HandlerState]}; -handler_before_loop(Req, State, 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{}. -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) +upgrade(Req, Env, Handler, State, Timeout, Hibernate, Mod) -> + Mod2 = case Mod of + long_polling -> cowboy_long_polling; + rest -> cowboy_rest; + ws -> cowboy_websocket; + _ when Mod =/= http -> Mod end, - TRef = erlang:start_timer(Timeout, self(), ?MODULE), - State#state{loop_timeout_ref=TRef}. - --spec handler_loop(Req, #state{}, module(), any()) - -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} - when Req::cowboy_req:req(). -handler_loop(Req, State=#state{loop_buffer_size=NbBytes, - loop_max_buffer=Threshold, loop_timeout_ref=TRef, - resp_sent=RespSent}, 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}), - _ = if RespSent -> ok; true -> - cowboy_req:reply(500, Req) - end, - exit(normal); - true -> - Req2 = cowboy_req:append_buffer(Data, Req), - State2 = handler_loop_timeout(State#state{ - loop_buffer_size=NbBytes2}), - handler_before_loop(Req2, State2, Handler, HandlerState) + Mod2:upgrade(Req, Env, Handler, State, Timeout, Hibernate). + +-spec terminate(Req, Env, module(), any(), {normal, shutdown} | {error, atom()} | any()) + -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). +terminate(Req, Env, Handler, State, Reason) -> + Result = case erlang:function_exported(Handler, terminate, 3) of + true -> + try + Handler:terminate(Reason, cowboy_req:lock(Req), State) + catch Class:Reason2 -> + erlang:Class([ + {reason, Reason2}, + {mfa, {Handler, terminate, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, State}, + {terminate_reason, Reason} + ]) end; - {Closed, Socket} -> - terminate_request(Req, State, Handler, HandlerState, - {error, closed}); - {Error, Socket, Reason} -> - terminate_request(Req, State, Handler, HandlerState, - {error, Reason}); - {timeout, TRef, ?MODULE} -> - handler_after_loop(Req, State, Handler, HandlerState, - {normal, timeout}); - {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> - handler_loop(Req, State, Handler, HandlerState); - Message -> - %% 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) - end. - --spec handler_call(Req, #state{}, module(), any(), any()) - -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} - when Req::cowboy_req:req(). -handler_call(Req, State=#state{resp_sent=RespSent}, - Handler, HandlerState, Message) -> - try Handler:info(Message, Req, HandlerState) of - {ok, Req2, HandlerState2} -> - handler_after_loop(Req2, State, Handler, HandlerState2, - {normal, shutdown}); - {loop, Req2, HandlerState2} -> - handler_after_callback(Req2, State, Handler, HandlerState2); - {loop, Req2, HandlerState2, hibernate} -> - handler_after_callback(Req2, State#state{hibernate=true}, - Handler, HandlerState2) - catch Class:Reason -> - Stacktrace = erlang:get_stacktrace(), - if RespSent -> ok; true -> - cowboy_req:maybe_reply(Stacktrace, Req) - end, - handler_terminate(Req, Handler, HandlerState, Reason), - erlang:Class([ - {reason, Reason}, - {mfa, {Handler, info, 3}}, - {stacktrace, Stacktrace}, - {req, cowboy_req:to_list(Req)}, - {state, HandlerState} - ]) - end. - -%% 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). - --spec terminate_request(Req, #state{}, module(), any(), - {normal, timeout | shutdown} | {error, atom()}) -> - {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). -terminate_request(Req, #state{env=Env, loop_timeout_ref=TRef}, - Handler, HandlerState, Reason) -> - HandlerRes = handler_terminate(Req, Handler, HandlerState, Reason), - _ = case TRef of - undefined -> ignore; - TRef -> erlang:cancel_timer(TRef) + false -> + ok end, - flush_timeouts(), - {ok, Req, [{result, HandlerRes}|Env]}. - --spec handler_terminate(cowboy_req:req(), module(), any(), - {normal, timeout | shutdown} | {error, atom()}) -> ok. -handler_terminate(Req, Handler, HandlerState, Reason) -> - try - Handler:terminate(Reason, cowboy_req:lock(Req), HandlerState) - catch Class:Reason2 -> - erlang:Class([ - {reason, Reason2}, - {mfa, {Handler, terminate, 3}}, - {stacktrace, erlang:get_stacktrace()}, - {req, cowboy_req:to_list(Req)}, - {state, HandlerState}, - {terminate_reason, Reason} - ]) - end. - --spec flush_timeouts() -> ok. -flush_timeouts() -> - receive - {timeout, TRef, ?MODULE} when is_reference(TRef) -> - flush_timeouts() - after 0 -> - ok - end. + {ok, Req, [{result, Result}|Env]}. diff --git a/src/cowboy_http_handler.erl b/src/cowboy_http_handler.erl index 14c7987..75b41d2 100644 --- a/src/cowboy_http_handler.erl +++ b/src/cowboy_http_handler.erl @@ -16,22 +16,21 @@ -type opts() :: any(). -type state() :: any(). --type terminate_reason() :: {normal, shutdown} - | {normal, timeout} %% Only occurs in loop handlers. - | {error, closed} %% Only occurs in loop handlers. - | {error, overflow} %% Only occurs in loop handlers. - | {error, atom()}. +%% @todo see terminate +%-type terminate_reason() :: {normal, shutdown} +% | {normal, timeout} %% Only occurs in loop handlers. +% | {error, closed} %% Only occurs in loop handlers. +% | {error, overflow} %% Only occurs in loop handlers. +% | {error, atom()}. --callback init({atom(), http}, Req, opts()) - -> {ok, Req, state()} - | {loop, Req, state()} - | {loop, Req, state(), hibernate} - | {loop, Req, state(), timeout()} - | {loop, Req, state(), timeout(), hibernate} +-callback init(Req, opts()) + -> {http, Req, state()} + | {long_polling | rest | ws | module(), Req, state()} + | {long_polling | rest | ws | module(), Req, state(), hibernate} + | {long_polling | rest | ws | module(), Req, state(), timeout()} + | {long_polling | rest | ws | module(), Req, state(), timeout(), hibernate} | {shutdown, Req, state()} - | {upgrade, protocol, module()} - | {upgrade, protocol, module(), Req, opts()} when Req::cowboy_req:req(). -callback handle(Req, State) -> {ok, Req, State} when Req::cowboy_req:req(), State::state(). --callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. +%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. diff --git a/src/cowboy_long_polling.erl b/src/cowboy_long_polling.erl new file mode 100644 index 0000000..6616a78 --- /dev/null +++ b/src/cowboy_long_polling.erl @@ -0,0 +1,191 @@ +%% Copyright (c) 2011-2014, 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. + +%% 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/2</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. +-module(cowboy_long_polling). +-behaviour(cowboy_sub_protocol). + +-export([upgrade/6]). +-export([loop/4]). + +-record(state, { + env :: cowboy_middleware:env(), + hibernate = false :: boolean(), + buffer_size = 0 :: non_neg_integer(), + max_buffer = 5000 :: non_neg_integer() | infinity, + timeout = infinity :: timeout(), + timeout_ref = undefined :: undefined | reference(), + resp_sent = false :: boolean() +}). + +-spec upgrade(Req, Env, module(), any(), timeout(), run | hibernate) + -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(), Env::cowboy_middleware:env(). +upgrade(Req, Env, Handler, HandlerState, Timeout, run) -> + State = #state{env=Env, max_buffer=get_max_buffer(Env), timeout=Timeout}, + State2 = timeout(State), + after_call(Req, State2, Handler, HandlerState); +upgrade(Req, Env, Handler, HandlerState, Timeout, hibernate) -> + State = #state{env=Env, max_buffer=get_max_buffer(Env), hibernate=true, timeout=Timeout}, + State2 = timeout(State), + after_call(Req, State2, Handler, HandlerState). + +get_max_buffer(Env) -> + case lists:keyfind(loop_max_buffer, 1, Env) of + false -> 5000; + {_, MaxBuffer} -> MaxBuffer + end. + +%% Update the state if the response was sent in the callback. +after_call(Req, State=#state{resp_sent=false}, Handler, + HandlerState) -> + receive + {cowboy_req, resp_sent} -> + before_loop(Req, State#state{resp_sent=true}, Handler, HandlerState) + after 0 -> + before_loop(Req, State, Handler, HandlerState) + end; +after_call(Req, State, Handler, HandlerState) -> + before_loop(Req, State, Handler, HandlerState). + +before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) -> + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + Transport:setopts(Socket, [{active, once}]), + {suspend, ?MODULE, loop, [Req, State#state{hibernate=false}, Handler, HandlerState]}; +before_loop(Req, State, Handler, HandlerState) -> + [Socket, Transport] = cowboy_req:get([socket, transport], Req), + Transport:setopts(Socket, [{active, once}]), + loop(Req, State, Handler, HandlerState). + +%% Almost the same code can be found in cowboy_websocket. +timeout(State=#state{timeout=infinity}) -> + State#state{timeout_ref=undefined}; +timeout(State=#state{timeout=Timeout, + timeout_ref=PrevRef}) -> + _ = case PrevRef of + undefined -> ignore; + PrevRef -> erlang:cancel_timer(PrevRef) + end, + TRef = erlang:start_timer(Timeout, self(), ?MODULE), + State#state{timeout_ref=TRef}. + +-spec loop(Req, #state{}, module(), any()) + -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} + when Req::cowboy_req:req(). +loop(Req, State=#state{env=Env, buffer_size=NbBytes, + max_buffer=Threshold, timeout_ref=TRef, + resp_sent=RespSent}, 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 -> + _ = if RespSent -> ok; true -> + cowboy_req:reply(500, Req) + end, + _ = cowboy_handler:terminate(Req, Env, Handler, HandlerState, {error, overflow}), + exit(normal); + true -> + Req2 = cowboy_req:append_buffer(Data, Req), + State2 = timeout(State#state{buffer_size=NbBytes2}), + before_loop(Req2, State2, Handler, HandlerState) + end; + {Closed, Socket} -> + terminate(Req, State, Handler, HandlerState, {error, closed}); + {Error, Socket, Reason} -> + terminate(Req, State, Handler, HandlerState, {error, Reason}); + {timeout, TRef, ?MODULE} -> + after_loop(Req, State, Handler, HandlerState, {normal, timeout}); + {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> + loop(Req, State, Handler, HandlerState); + Message -> + %% 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, + call(Req2, State, Handler, HandlerState, Message) + end. + +call(Req, State=#state{env=Env, resp_sent=RespSent}, + Handler, HandlerState, Message) -> + try Handler:info(Message, Req, HandlerState) of + {ok, Req2, HandlerState2} -> + after_loop(Req2, State, Handler, HandlerState2, {normal, shutdown}); + {loop, Req2, HandlerState2} -> + after_call(Req2, State, Handler, HandlerState2); + {loop, Req2, HandlerState2, hibernate} -> + after_call(Req2, State#state{hibernate=true}, Handler, HandlerState2) + catch Class:Reason -> + Stacktrace = erlang:get_stacktrace(), + if RespSent -> ok; true -> + cowboy_req:maybe_reply(Stacktrace, Req) + end, + _ = cowboy_handler:terminate(Req, Env, Handler, HandlerState, {error, overflow}), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, info, 3}}, + {stacktrace, Stacktrace}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) + end. + +%% 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. +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(Req2, State, Handler, HandlerState, Reason). + +terminate(Req, #state{env=Env, timeout_ref=TRef}, + Handler, HandlerState, Reason) -> + _ = case TRef of + undefined -> ignore; + TRef -> erlang:cancel_timer(TRef) + end, + flush_timeouts(), + cowboy_handler:terminate(Req, Env, Handler, HandlerState, Reason). + +flush_timeouts() -> + receive + {timeout, TRef, ?MODULE} when is_reference(TRef) -> + flush_timeouts() + after 0 -> + ok + end. diff --git a/src/cowboy_loop_handler.erl b/src/cowboy_loop_handler.erl index edef77f..5e50c34 100644 --- a/src/cowboy_loop_handler.erl +++ b/src/cowboy_loop_handler.erl @@ -16,25 +16,24 @@ -type opts() :: any(). -type state() :: any(). --type terminate_reason() :: {normal, shutdown} - | {normal, timeout} - | {error, closed} - | {error, overflow} - | {error, atom()}. +%% @todo see terminate +%-type terminate_reason() :: {normal, shutdown} +% | {normal, timeout} +% | {error, closed} +% | {error, overflow} +% | {error, atom()}. --callback init({atom(), http}, Req, opts()) - -> {ok, Req, state()} - | {loop, Req, state()} - | {loop, Req, state(), hibernate} - | {loop, Req, state(), timeout()} - | {loop, Req, state(), timeout(), hibernate} +-callback init(Req, opts()) + -> {http, Req, state()} + | {long_polling | rest | ws | module(), Req, state()} + | {long_polling | rest | ws | module(), Req, state(), hibernate} + | {long_polling | rest | ws | module(), Req, state(), timeout()} + | {long_polling | rest | ws | module(), Req, state(), timeout(), hibernate} | {shutdown, Req, state()} - | {upgrade, protocol, module()} - | {upgrade, protocol, module(), Req, opts()} when Req::cowboy_req:req(). -callback info(any(), Req, State) -> {ok, Req, State} | {loop, Req, State} | {loop, Req, State, hibernate} when Req::cowboy_req:req(), State::state(). --callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. +%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 23a3868..a6e2de5 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -83,9 +83,6 @@ -export([get/2]). -export([set/2]). -export([set_bindings/4]). - -%% Misc API. --export([compact/1]). -export([lock/1]). -export([to_list/1]). @@ -1002,13 +999,6 @@ set_bindings(HostInfo, PathInfo, Bindings, Req) -> Req#http_req{host_info=HostInfo, path_info=PathInfo, bindings=Bindings}. -%% Misc API. - --spec compact(Req) -> Req when Req::req(). -compact(Req) -> - Req#http_req{host_info=undefined, path_info=undefined, - bindings=undefined, headers=[]}. - -spec lock(Req) -> Req when Req::req(). lock(Req) -> Req#http_req{resp_state=locked}. diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 4ea3010..63f33e6 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -17,7 +17,7 @@ -module(cowboy_rest). -behaviour(cowboy_sub_protocol). --export([upgrade/4]). +-export([upgrade/6]). -record(state, { env :: cowboy_middleware:env(), @@ -55,31 +55,12 @@ expires :: undefined | no_call | calendar:datetime() | binary() }). --spec upgrade(Req, Env, module(), any()) +-spec upgrade(Req, Env, module(), any(), infinity, run) -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -upgrade(Req, Env, Handler, HandlerOpts) -> +upgrade(Req, Env, Handler, HandlerState, infinity, run) -> Method = cowboy_req:method(Req), - case erlang:function_exported(Handler, rest_init, 2) of - true -> - try Handler:rest_init(Req, HandlerOpts) of - {ok, Req2, HandlerState} -> - service_available(Req2, #state{env=Env, method=Method, - handler=Handler, handler_state=HandlerState}) - catch Class:Reason -> - Stacktrace = erlang:get_stacktrace(), - cowboy_req:maybe_reply(Stacktrace, Req), - erlang:Class([ - {reason, Reason}, - {mfa, {Handler, rest_init, 2}}, - {stacktrace, Stacktrace}, - {req, cowboy_req:to_list(Req)}, - {opts, HandlerOpts} - ]) - end; - false -> - service_available(Req, #state{env=Env, method=Method, - handler=Handler}) - end. + service_available(Req, #state{env=Env, method=Method, + handler=Handler, handler_state=HandlerState}). service_available(Req, State) -> expect(Req, State, service_available, true, fun known_methods/2, 503). @@ -986,13 +967,9 @@ next(Req, State, StatusCode) when is_integer(StatusCode) -> respond(Req, State, StatusCode) -> terminate(cowboy_req:reply(StatusCode, Req), State). -terminate(Req, State=#state{env=Env}) -> - rest_terminate(Req, State), - {ok, Req, [{result, ok}|Env]}. - error_terminate(Req, State=#state{handler=Handler, handler_state=HandlerState}, Class, Reason, Callback) -> - rest_terminate(Req, State), + _ = terminate(Req, State), Stacktrace = erlang:get_stacktrace(), cowboy_req:maybe_reply(Stacktrace, Req), erlang:Class([ @@ -1003,9 +980,5 @@ error_terminate(Req, State=#state{handler=Handler, handler_state=HandlerState}, {state, HandlerState} ]). -rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> - case erlang:function_exported(Handler, rest_terminate, 2) of - true -> ok = Handler:rest_terminate( - cowboy_req:lock(Req), HandlerState); - false -> ok - end. +terminate(Req, #state{env=Env, handler=Handler, handler_state=HandlerState}) -> + cowboy_handler:terminate(Req, Env, Handler, HandlerState, {normal, shutdown}). diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl index 1d5286e..ecca6fa 100644 --- a/src/cowboy_static.erl +++ b/src/cowboy_static.erl @@ -15,8 +15,7 @@ -module(cowboy_static). --export([init/3]). --export([rest_init/2]). +-export([init/2]). -export([malformed_request/2]). -export([forbidden/2]). -export([content_types_provided/2]). @@ -39,33 +38,27 @@ -type state() :: {binary(), {ok, #file_info{}} | {error, atom()}, extra()}. --spec init(_, _, _) -> {upgrade, protocol, cowboy_rest}. -init(_, _, _) -> - {upgrade, protocol, cowboy_rest}. - %% Resolve the file that will be sent and get its file information. %% If the handler is configured to manage a directory, check that the %% requested file is inside the configured directory. --spec rest_init(Req, opts()) - -> {ok, Req, error | state()} - when Req::cowboy_req:req(). -rest_init(Req, {Name, Path}) -> - rest_init_opts(Req, {Name, Path, []}); -rest_init(Req, {Name, App, Path}) +-spec init(Req, opts()) -> {rest, Req, error | state()} when Req::cowboy_req:req(). +init(Req, {Name, Path}) -> + init_opts(Req, {Name, Path, []}); +init(Req, {Name, App, Path}) when Name =:= priv_file; Name =:= priv_dir -> - rest_init_opts(Req, {Name, App, Path, []}); -rest_init(Req, Opts) -> - rest_init_opts(Req, Opts). - -rest_init_opts(Req, {priv_file, App, Path, Extra}) -> - rest_init_info(Req, absname(priv_path(App, Path)), Extra); -rest_init_opts(Req, {file, Path, Extra}) -> - rest_init_info(Req, absname(Path), Extra); -rest_init_opts(Req, {priv_dir, App, Path, Extra}) -> - rest_init_dir(Req, priv_path(App, Path), Extra); -rest_init_opts(Req, {dir, Path, Extra}) -> - rest_init_dir(Req, Path, Extra). + init_opts(Req, {Name, App, Path, []}); +init(Req, Opts) -> + init_opts(Req, Opts). + +init_opts(Req, {priv_file, App, Path, Extra}) -> + init_info(Req, absname(priv_path(App, Path)), Extra); +init_opts(Req, {file, Path, Extra}) -> + init_info(Req, absname(Path), Extra); +init_opts(Req, {priv_dir, App, Path, Extra}) -> + init_dir(Req, priv_path(App, Path), Extra); +init_opts(Req, {dir, Path, Extra}) -> + init_dir(Req, Path, Extra). priv_path(App, Path) -> case code:priv_dir(App) of @@ -83,18 +76,18 @@ absname(Path) when is_list(Path) -> absname(Path) when is_binary(Path) -> filename:absname(Path). -rest_init_dir(Req, Path, Extra) when is_list(Path) -> - rest_init_dir(Req, list_to_binary(Path), Extra); -rest_init_dir(Req, Path, Extra) -> +init_dir(Req, Path, Extra) when is_list(Path) -> + init_dir(Req, list_to_binary(Path), Extra); +init_dir(Req, Path, Extra) -> Dir = fullpath(filename:absname(Path)), PathInfo = cowboy_req:path_info(Req), Filepath = filename:join([Dir|PathInfo]), Len = byte_size(Dir), case fullpath(Filepath) of << Dir:Len/binary, $/, _/binary >> -> - rest_init_info(Req, Filepath, Extra); + init_info(Req, Filepath, Extra); _ -> - {ok, Req, error} + {rest, Req, error} end. fullpath(Path) -> @@ -110,9 +103,9 @@ fullpath([<<"..">>|Tail], [_|Acc]) -> fullpath([Segment|Tail], Acc) -> fullpath(Tail, [Segment|Acc]). -rest_init_info(Req, Path, Extra) -> +init_info(Req, Path, Extra) -> Info = file:read_file_info(Path, [{time, universal}]), - {ok, Req, {Path, Info, Extra}}. + {rest, Req, {Path, Info, Extra}}. -ifdef(TEST). fullpath_test_() -> diff --git a/src/cowboy_sub_protocol.erl b/src/cowboy_sub_protocol.erl index 713c3cd..f177263 100644 --- a/src/cowboy_sub_protocol.erl +++ b/src/cowboy_sub_protocol.erl @@ -15,9 +15,6 @@ -module(cowboy_sub_protocol). --callback upgrade(Req, Env, module(), any()) - -> {ok, Req, Env} - | {suspend, module(), atom(), [any()]} - | {halt, Req} - | {error, cowboy:http_status(), Req} +-callback upgrade(Req, Env, module(), any(), timeout(), run | hibernate) + -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {halt, Req} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index 1c115b5..e0b20ca 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -17,7 +17,7 @@ -module(cowboy_websocket). -behaviour(cowboy_sub_protocol). --export([upgrade/4]). +-export([upgrade/6]). -export([handler_loop/4]). -type close_code() :: 1000..4999. @@ -53,19 +53,18 @@ deflate_state :: undefined | port() }). --spec upgrade(Req, Env, module(), any()) - -> {ok, Req, Env} - | {suspend, module(), atom(), [any()]} +-spec upgrade(Req, Env, module(), any(), timeout(), run | hibernate) + -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -upgrade(Req, Env, Handler, HandlerOpts) -> +upgrade(Req, Env, Handler, HandlerState, Timeout, Hibernate) -> {_, Ref} = lists:keyfind(listener, 1, Env), ranch:remove_connection(Ref), [Socket, Transport] = cowboy_req:get([socket, transport], Req), - State = #state{env=Env, socket=Socket, transport=Transport, - handler=Handler}, + State = #state{env=Env, socket=Socket, transport=Transport, handler=Handler, + timeout=Timeout, hibernate=Hibernate =:= hibernate}, try websocket_upgrade(State, Req) of {ok, State2, Req2} -> - handler_init(State2, Req2, HandlerOpts) + websocket_handshake(State2, Req2, HandlerState) catch _:_ -> receive {cowboy_req, resp_sent} -> ok @@ -121,38 +120,6 @@ websocket_extensions(State, Req) -> end end. --spec handler_init(#state{}, Req, any()) - -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} - when Req::cowboy_req:req(). -handler_init(State=#state{env=Env, transport=Transport, - handler=Handler}, Req, HandlerOpts) -> - try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of - {ok, Req2, HandlerState} -> - websocket_handshake(State, Req2, HandlerState); - {ok, Req2, HandlerState, hibernate} -> - websocket_handshake(State#state{hibernate=true}, - Req2, HandlerState); - {ok, Req2, HandlerState, Timeout} -> - websocket_handshake(State#state{timeout=Timeout}, - Req2, HandlerState); - {ok, Req2, HandlerState, Timeout, hibernate} -> - websocket_handshake(State#state{timeout=Timeout, - hibernate=true}, Req2, HandlerState); - {shutdown, Req2} -> - cowboy_req:ensure_response(Req2, 400), - {ok, Req2, [{result, closed}|Env]} - catch Class:Reason -> - Stacktrace = erlang:get_stacktrace(), - cowboy_req:maybe_reply(Stacktrace, Req), - erlang:Class([ - {reason, Reason}, - {mfa, {Handler, websocket_init, 3}}, - {stacktrace, Stacktrace}, - {req, cowboy_req:to_list(Req)}, - {opts, HandlerOpts} - ]) - end. - -spec websocket_handshake(#state{}, Req, any()) -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} @@ -703,6 +670,15 @@ websocket_send({Type, Payload0}, State=#state{socket=Socket, transport=Transport {Transport:send(Socket, [<< 1:1, Rsv/bits, Opcode:4, 0:1, BinLen/bits >>, Payload]), State2}. +-spec payload_length_to_binary(0..16#7fffffffffffffff) + -> << _:7 >> | << _:23 >> | << _:71 >>. +payload_length_to_binary(N) -> + case N of + N when N =< 125 -> << N:7 >>; + N when N =< 16#ffff -> << 126:7, N:16 >>; + N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >> + end. + -spec websocket_send_many([frame()], #state{}) -> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}. websocket_send_many([], State) -> @@ -739,26 +715,6 @@ websocket_close(State=#state{socket=Socket, transport=Transport}, -> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). handler_terminate(#state{env=Env, handler=Handler}, - Req, HandlerState, TerminateReason) -> - try - Handler:websocket_terminate(TerminateReason, Req, HandlerState) - catch Class:Reason -> - erlang:Class([ - {reason, Reason}, - {mfa, {Handler, websocket_terminate, 3}}, - {stacktrace, erlang:get_stacktrace()}, - {req, cowboy_req:to_list(Req)}, - {state, HandlerState}, - {terminate_reason, TerminateReason} - ]) - end, + Req, HandlerState, Reason) -> + _ = cowboy_handler:terminate(Req, Env, Handler, HandlerState, Reason), {ok, Req, [{result, closed}|Env]}. - --spec payload_length_to_binary(0..16#7fffffffffffffff) - -> << _:7 >> | << _:23 >> | << _:71 >>. -payload_length_to_binary(N) -> - case N of - N when N =< 125 -> << N:7 >>; - N when N =< 16#ffff -> << 126:7, N:16 >>; - N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >> - end. diff --git a/src/cowboy_websocket_handler.erl b/src/cowboy_websocket_handler.erl index 177e5f6..a6a036b 100644 --- a/src/cowboy_websocket_handler.erl +++ b/src/cowboy_websocket_handler.erl @@ -16,21 +16,23 @@ -type opts() :: any(). -type state() :: any(). --type terminate_reason() :: {normal, shutdown} - | {normal, timeout} - | {error, closed} - | {remote, closed} - | {remote, cowboy_websocket:close_code(), binary()} - | {error, badencoding} - | {error, badframe} - | {error, atom()}. +%% @todo see terminate +%-type terminate_reason() :: {normal, shutdown} +% | {normal, timeout} +% | {error, closed} +% | {remote, closed} +% | {remote, cowboy_websocket:close_code(), binary()} +% | {error, badencoding} +% | {error, badframe} +% | {error, atom()}. --callback websocket_init(atom(), Req, opts()) - -> {ok, Req, state()} - | {ok, Req, state(), hibernate} - | {ok, Req, state(), timeout()} - | {ok, Req, state(), timeout(), hibernate} - | {shutdown, Req} +-callback init(Req, opts()) + -> {http, Req, state()} + | {long_polling | rest | ws | module(), Req, state()} + | {long_polling | rest | ws | module(), Req, state(), hibernate} + | {long_polling | rest | ws | module(), Req, state(), timeout()} + | {long_polling | rest | ws | module(), Req, state(), timeout(), hibernate} + | {shutdown, Req, state()} when Req::cowboy_req:req(). -callback websocket_handle({text | binary | ping | pong, binary()}, Req, State) -> {ok, Req, State} @@ -46,5 +48,4 @@ | {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State, hibernate} | {shutdown, Req, State} when Req::cowboy_req:req(), State::state(). --callback websocket_terminate(terminate_reason(), cowboy_req:req(), state()) - -> ok. +%% @todo optional -callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok. |