aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cowboy_handler.erl299
-rw-r--r--src/cowboy_http_handler.erl27
-rw-r--r--src/cowboy_long_polling.erl191
-rw-r--r--src/cowboy_loop_handler.erl27
-rw-r--r--src/cowboy_req.erl10
-rw-r--r--src/cowboy_rest.erl43
-rw-r--r--src/cowboy_static.erl55
-rw-r--r--src/cowboy_sub_protocol.erl7
-rw-r--r--src/cowboy_websocket.erl80
-rw-r--r--src/cowboy_websocket_handler.erl33
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.