diff options
Diffstat (limited to 'src/cowboy_protocol.erl')
-rw-r--r-- | src/cowboy_protocol.erl | 585 |
1 files changed, 368 insertions, 217 deletions
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index 0fe6ed2..b0b5aa6 100644 --- a/src/cowboy_protocol.erl +++ b/src/cowboy_protocol.erl @@ -39,8 +39,8 @@ %% Internal. -export([init/4]). --export([parse_request/2]). --export([handler_loop/3]). +-export([parse_request/3]). +-export([handler_loop/4]). -type onrequest_fun() :: fun((Req) -> Req). -type onresponse_fun() :: @@ -54,11 +54,9 @@ socket :: inet:socket(), transport :: module(), dispatch :: cowboy_dispatcher:dispatch_rules(), - handler :: {module(), any()}, onrequest :: undefined | onrequest_fun(), onresponse = undefined :: undefined | onresponse_fun(), urldecode :: {fun((binary(), T) -> binary()), T}, - req_empty_lines = 0 :: integer(), max_empty_lines :: integer(), req_keepalive = 1 :: integer(), max_keepalive :: integer(), @@ -66,8 +64,6 @@ max_header_name_length :: integer(), max_header_value_length :: integer(), timeout :: timeout(), - host_tokens = undefined :: undefined | cowboy_dispatcher:tokens(), - path_tokens = undefined :: undefined | '*' | cowboy_dispatcher:tokens(), hibernate = false :: boolean(), loop_timeout = infinity :: timeout(), loop_timeout_ref :: undefined | reference() @@ -113,254 +109,425 @@ init(ListenerPid, Socket, Transport, Opts) -> max_header_name_length=MaxHeaderNameLength, max_header_value_length=MaxHeaderValueLength, timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse, - urldecode=URLDec}). - --spec wait_request(binary(), #state{}) -> ok. -wait_request(Buffer, State=#state{ - socket=Socket, transport=Transport, timeout=T}) -> - case Transport:recv(Socket, 0, T) of - {ok, Data} -> parse_request(<< Buffer/binary, Data/binary >>, State); - {error, _Reason} -> terminate(State) + urldecode=URLDec}, 0). + +%% Request parsing. +%% +%% The next set of functions is the request parsing code. All of it +%% runs using a single binary match context. This optimization ends +%% right after the header parsing is finished and the code becomes +%% more interesting past that point. + +-spec wait_request(binary(), #state{}, non_neg_integer()) -> ok. +wait_request(Buffer, State=#state{socket=Socket, transport=Transport, + timeout=Timeout}, ReqEmpty) -> + case Transport:recv(Socket, 0, Timeout) of + {ok, Data} -> + parse_request(<< Buffer/binary, Data/binary >>, State, ReqEmpty); + {error, _} -> + terminate(State) end. %% @private --spec parse_request(binary(), #state{}) -> ok. +-spec parse_request(binary(), #state{}, non_neg_integer()) -> ok. %% Empty lines must be using \r\n. -parse_request(<< "\n", _/binary >>, State) -> +parse_request(<< $\n, _/binary >>, State, _) -> error_terminate(400, State); %% We limit the length of the Request-line to MaxLength to avoid endlessly %% reading from the socket and eventually crashing. parse_request(Buffer, State=#state{max_request_line_length=MaxLength, - req_empty_lines=ReqEmpty, max_empty_lines=MaxEmpty}) -> - case binary:match(Buffer, <<"\r\n">>) of + max_empty_lines=MaxEmpty}, ReqEmpty) -> + case binary:match(Buffer, <<"\n">>) of nomatch when byte_size(Buffer) > MaxLength -> error_terminate(413, State); nomatch -> - wait_request(Buffer, State); - {0, _} when ReqEmpty =:= MaxEmpty -> + wait_request(Buffer, State, ReqEmpty); + {1, _} when ReqEmpty =:= MaxEmpty -> error_terminate(400, State); - {0, _} -> + {1, _} -> << _:16, Rest/binary >> = Buffer, - parse_request(Rest, State#state{req_empty_lines=ReqEmpty + 1}); - {Pos, _} -> - << RequestLine:Pos/binary, _:16, Rest/binary >> = Buffer, - case cowboy_http:request_line(RequestLine) of - {Method, AbsPath, Version} -> - request(Rest, State, Method, AbsPath, Version); - {error, _} -> - error_terminate(400, State) - end + parse_request(Rest, State, ReqEmpty + 1); + {_, _} -> + parse_method(Buffer, State, <<>>) end. --spec request(binary(), #state{}, binary(), binary(), cowboy_http:version()) - -> ok. -request(_, State, _, _, Version) - when Version =/= {1, 0}, Version =/= {1, 1} -> - error_terminate(505, State); -request(Buffer, State=#state{socket=Socket, transport=Transport, - onresponse=OnResponse, urldecode=URLDec}, - Method, <<"*">>, Version) -> - Connection = version_to_connection(State, Version), - parse_header(Buffer, State#state{path_tokens= '*'}, - cowboy_req:new(Socket, Transport, Connection, Method, Version, - <<"*">>, <<>>, OnResponse, URLDec)); -request(Buffer, State=#state{socket=Socket, transport=Transport, - onresponse=OnResponse, urldecode=URLDec={URLDecFun, URLDecArg}}, - Method, AbsPath, Version) -> - Connection = version_to_connection(State, Version), - {PathTokens, Path, Qs} = cowboy_dispatcher:split_path(AbsPath, - fun(Bin) -> URLDecFun(Bin, URLDecArg) end), - parse_header(Buffer, State#state{path_tokens=PathTokens}, - cowboy_req:new(Socket, Transport, Connection, Method, Version, - Path, Qs, OnResponse, URLDec)). - --spec parse_header(binary(), #state{}, cowboy_req:req()) -> ok. -parse_header(<< "\r\n", Rest/binary >>, State, Req) -> - header_end(Rest, State, Req); -parse_header(Buffer, State=#state{max_header_name_length=MaxLength}, Req) -> +parse_method(<< C, Rest/bits >>, State, SoFar) -> + case C of + $\r -> error_terminate(400, State); + $\s -> parse_uri(Rest, State, SoFar); + _ -> parse_method(Rest, State, << SoFar/binary, C >>) + end. + +parse_uri(<< $\r, _/bits >>, State, _) -> + error_terminate(400, State); +parse_uri(<< "* ", Rest/bits >>, State, Method) -> + parse_version(Rest, State, Method, <<"*">>, <<>>, <<>>); +parse_uri(<< "http://", Rest/bits >>, State, Method) -> + parse_uri_skip_host(Rest, State, Method); +parse_uri(<< "https://", Rest/bits >>, State, Method) -> + parse_uri_skip_host(Rest, State, Method); +parse_uri(Buffer, State, Method) -> + parse_uri_path(Buffer, State, Method, <<>>). + +parse_uri_skip_host(<< C, Rest/bits >>, State, Method) -> + case C of + $\r -> error_terminate(400, State); + $/ -> parse_uri_path(Rest, State, Method, <<"/">>); + _ -> parse_uri_skip_host(Rest, State, Method) + end. + +parse_uri_path(<< C, Rest/bits >>, State, Method, SoFar) -> + case C of + $\r -> error_terminate(400, State); + $\s -> parse_version(Rest, State, Method, SoFar, <<>>, <<>>); + $? -> parse_uri_query(Rest, State, Method, SoFar, <<>>); + $# -> parse_uri_fragment(Rest, State, Method, SoFar, <<>>, <<>>); + _ -> parse_uri_path(Rest, State, Method, << SoFar/binary, C >>) + end. + +parse_uri_query(<< C, Rest/bits >>, S, M, P, SoFar) -> + case C of + $\r -> error_terminate(400, S); + $\s -> parse_version(Rest, S, M, P, SoFar, <<>>); + $# -> parse_uri_fragment(Rest, S, M, P, SoFar, <<>>); + _ -> parse_uri_query(Rest, S, M, P, << SoFar/binary, C >>) + end. + +parse_uri_fragment(<< C, Rest/bits >>, S, M, P, Q, SoFar) -> + case C of + $\r -> error_terminate(400, S); + $\s -> parse_version(Rest, S, M, P, Q, SoFar); + _ -> parse_uri_fragment(Rest, S, M, P, Q, << SoFar/binary, C >>) + end. + +parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q, F) -> + parse_header(Rest, S, M, P, Q, F, {1, 1}, []); +parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q, F) -> + parse_header(Rest, S, M, P, Q, F, {1, 0}, []); +parse_version(_, State, _, _, _, _) -> + error_terminate(505, State). + +wait_header(Buffer, State=#state{socket=Socket, transport=Transport, + timeout=Timeout}, M, P, Q, F, V, H) -> + case Transport:recv(Socket, 0, Timeout) of + {ok, Data} -> + parse_header(<< Buffer/binary, Data/binary >>, + State, M, P, Q, F, V, H); + {error, timeout} -> + error_terminate(408, State); + {error, _} -> + terminate(State) + end. + +parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, F, V, Headers) -> + request(Rest, S, M, P, Q, F, V, lists:reverse(Headers)); +parse_header(Buffer, State=#state{max_header_name_length=MaxLength}, + M, P, Q, F, V, H) -> case binary:match(Buffer, <<":">>) of nomatch when byte_size(Buffer) > MaxLength -> error_terminate(413, State); nomatch -> - wait_header(Buffer, State, Req, fun parse_header/3); - {Pos, _} -> - << Name:Pos/binary, _:8, Rest/binary >> = Buffer, - Name2 = cowboy_bstr:to_lower(Name), - Rest2 = cowboy_http:whitespace(Rest, fun(D) -> D end), - parse_header_value(Rest2, State, Req, Name2, <<>>) + wait_header(Buffer, State, M, P, Q, F, V, H); + {_, _} -> + parse_hd_name(Buffer, State, M, P, Q, F, V, H, <<>>) + end. + +%% I know, this isn't exactly pretty. But this is the most critical +%% code path and as such needs to be optimized to death. +%% +%% ... Sorry for your eyes. +%% +%% But let's be honest, that's still pretty readable. +parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, F, V, H, SoFar) -> + case C of + $: -> parse_hd_before_value(Rest, S, M, P, Q, F, V, H, SoFar); + $\s -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, SoFar); + $\t -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, SoFar); + $A -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $a >>); + $B -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $b >>); + $C -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $c >>); + $D -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $d >>); + $E -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $e >>); + $F -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $f >>); + $G -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $g >>); + $H -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $h >>); + $I -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $i >>); + $J -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $j >>); + $K -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $k >>); + $L -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $l >>); + $M -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $m >>); + $N -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $n >>); + $O -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $o >>); + $P -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $p >>); + $Q -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $q >>); + $R -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $r >>); + $S -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $s >>); + $T -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $t >>); + $U -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $u >>); + $V -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $v >>); + $W -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $w >>); + $X -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $x >>); + $Y -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $y >>); + $Z -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $z >>); + C -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, C >>) + end. + +parse_hd_name_ws(<< C, Rest/bits >>, S, M, P, Q, F, V, H, Name) -> + case C of + $\s -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, Name); + $\t -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, Name); + $: -> parse_hd_before_value(Rest, S, M, P, Q, F, V, H, Name) + end. + +wait_hd_before_value(Buffer, State=#state{ + socket=Socket, transport=Transport, timeout=Timeout}, + M, P, Q, F, V, H, N) -> + case Transport:recv(Socket, 0, Timeout) of + {ok, Data} -> + parse_hd_before_value(<< Buffer/binary, Data/binary >>, + State, M, P, Q, F, V, H, N); + {error, timeout} -> + error_terminate(408, State); + {error, _} -> + terminate(State) end. -parse_header_value(Buffer, State=#state{max_header_value_length=MaxLength}, - Req, Name, SoFar) -> - case binary:match(Buffer, <<"\r\n">>) of - nomatch when byte_size(Buffer) + byte_size(SoFar) > MaxLength -> +parse_hd_before_value(<< $\s, Rest/bits >>, S, M, P, Q, F, V, H, N) -> + parse_hd_before_value(Rest, S, M, P, Q, F, V, H, N); +parse_hd_before_value(<< $\t, Rest/bits >>, S, M, P, Q, F, V, H, N) -> + parse_hd_before_value(Rest, S, M, P, Q, F, V, H, N); +parse_hd_before_value(Buffer, State=#state{ + max_header_value_length=MaxLength}, M, P, Q, F, V, H, N) -> + case binary:match(Buffer, <<"\n">>) of + nomatch when byte_size(Buffer) > MaxLength -> error_terminate(413, State); nomatch -> - wait_header(Buffer, State, Req, - fun(B, S, R) -> parse_header_value(B, S, R, Name, SoFar) end); - {Pos, _} when Pos + 2 =:= byte_size(Buffer) -> - wait_header(Buffer, State, Req, - fun(B, S, R) -> parse_header_value(B, S, R, Name, SoFar) end); - {Pos, _} -> - << Value:Pos/binary, _:16, Rest/binary >> = Buffer, - case binary:at(Buffer, Pos + 2) of - C when C =:= $\s; C =:= $\t -> - parse_header_value(Rest, State, Req, Name, - << SoFar/binary, Value/binary >>); - _ -> - header(Rest, State, Req, Name, - << SoFar/binary, Value/binary >>) - end + wait_hd_before_value(Buffer, State, M, P, Q, F, V, H, N); + {_, _} -> + parse_hd_value(Buffer, State, M, P, Q, F, V, H, N, <<>>) end. --spec wait_header(binary(), #state{}, cowboy_req:req(), fun()) -> ok. -wait_header(Buffer, State=#state{socket=Socket, transport=Transport, - timeout=T}, Req, Fun) -> - case Transport:recv(Socket, 0, T) of - {ok, Data} -> Fun(<< Buffer/binary, Data/binary >>, State, Req); - {error, timeout} -> error_terminate(408, State); - {error, closed} -> terminate(State) +%% We completely ignore the first argument which is always +%% the empty binary. We keep it there because we don't want +%% to change the other arguments' position and trigger costy +%% operations for no reasons. +wait_hd_value(_, State=#state{ + socket=Socket, transport=Transport, timeout=Timeout}, + M, P, Q, F, V, H, N, SoFar) -> + case Transport:recv(Socket, 0, Timeout) of + {ok, Data} -> + parse_hd_value(Data, State, M, P, Q, F, V, H, N, SoFar); + {error, timeout} -> + error_terminate(408, State); + {error, _} -> + terminate(State) end. --spec header(binary(), #state{}, cowboy_req:req(), binary(), binary()) -> ok. -header(Buffer, State=#state{host_tokens=undefined, transport=Transport}, - Req, <<"host">>, RawHost) -> - RawHost2 = cowboy_bstr:to_lower(RawHost), - case catch cowboy_dispatcher:split_host(RawHost2) of - {HostTokens, Host, undefined} -> - Port = default_port(Transport:name()), - parse_header(Buffer, State#state{host_tokens=HostTokens}, - cowboy_req:set_host(Host, Port, RawHost, Req)); - {HostTokens, Host, Port} -> - parse_header(Buffer, State#state{host_tokens=HostTokens}, - cowboy_req:set_host(Host, Port, RawHost, Req)); - {'EXIT', _Reason} -> - error_terminate(400, State) +%% Pushing back as much as we could the retrieval of new data +%% to check for multilines allows us to avoid a few tests in +%% the critical path, but forces us to have a special function. +wait_hd_value_nl(_, State=#state{ + socket=Socket, transport=Transport, timeout=Timeout}, + M, P, Q, F, V, Headers, Name, SoFar) -> + case Transport:recv(Socket, 0, Timeout) of + {ok, << C, Data/bits >>} when C =:= $\s; C =:= $\t -> + parse_hd_value(Data, State, M, P, Q, F, V, Headers, Name, SoFar); + {ok, Data} -> + parse_header(Data, State, M, P, Q, F, V, [{Name, SoFar}|Headers]); + {error, timeout} -> + error_terminate(408, State); + {error, _} -> + terminate(State) + end. + +parse_hd_value(<< $\r, Rest/bits >>, S, M, P, Q, F, V, Headers, Name, SoFar) -> + case Rest of + << $\n >> -> + wait_hd_value_nl(<<>>, S, M, P, Q, F, V, Headers, Name, SoFar); + << $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t -> + parse_hd_value(Rest2, S, M, P, Q, F, V, Headers, Name, SoFar); + << $\n, Rest2/bits >> -> + parse_header(Rest2, S, M, P, Q, F, V, [{Name, SoFar}|Headers]) end; -%% Ignore Host headers if we already have it. -header(Buffer, State, Req, <<"host">>, _) -> - parse_header(Buffer, State, Req); -header(Buffer, State=#state{req_keepalive=Keepalive, - max_keepalive=MaxKeepalive}, Req, <<"connection">>, Connection) - when Keepalive < MaxKeepalive -> - parse_header(Buffer, State, cowboy_req:set_connection(Connection, Req)); -header(Buffer, State, Req, Name, Value) -> - parse_header(Buffer, State, cowboy_req:add_header(Name, Value, Req)). - -%% The Host header is required in HTTP/1.1 and optional in HTTP/1.0. -header_end(Buffer, State=#state{host_tokens=undefined, transport=Transport}, - Req) -> - case cowboy_req:version(Req) of - {{1, 1}, _} -> +parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, F, V, H, N, SoFar) -> + parse_hd_value(Rest, S, M, P, Q, F, V, H, N, << SoFar/binary, C >>); +parse_hd_value(<<>>, State=#state{max_header_value_length=MaxLength}, + _, _, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength -> + error_terminate(413, State); +parse_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar) -> + wait_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar). + +request(B, State=#state{transport=Transport}, M, P, Q, F, Version, Headers) -> + case lists:keyfind(<<"host">>, 1, Headers) of + false when Version =:= {1, 1} -> error_terminate(400, State); - {{1, 0}, Req2} -> - Port = default_port(Transport:name()), - onrequest( - cowboy_req:set_buffer(Buffer, - cowboy_req:set_host(<<>>, Port, <<>>, Req2)), - State) - end; -header_end(Buffer, State, Req) -> - onrequest(cowboy_req:set_buffer(Buffer, Req), State). + false -> + request(B, State, M, P, Q, F, Version, Headers, + <<>>, default_port(Transport:name())); + {_, RawHost} -> + case parse_host(RawHost, <<>>) of + {Host, undefined} -> + request(B, State, M, P, Q, F, Version, Headers, + Host, default_port(Transport:name())); + {Host, Port} -> + request(B, State, M, P, Q, F, Version, Headers, + Host, Port) + end + end. + +-spec default_port(atom()) -> 80 | 443. +default_port(ssl) -> 443; +default_port(_) -> 80. + +%% Another hurtful block of code. :) +parse_host(<<>>, Acc) -> + {Acc, undefined}; +parse_host(<< $:, Rest/bits >>, Acc) -> + {Acc, list_to_integer(binary_to_list(Rest))}; +parse_host(<< C, Rest/bits >>, Acc) -> + case C of + $A -> parse_host(Rest, << Acc/binary, $a >>); + $B -> parse_host(Rest, << Acc/binary, $b >>); + $C -> parse_host(Rest, << Acc/binary, $c >>); + $D -> parse_host(Rest, << Acc/binary, $d >>); + $E -> parse_host(Rest, << Acc/binary, $e >>); + $F -> parse_host(Rest, << Acc/binary, $f >>); + $G -> parse_host(Rest, << Acc/binary, $g >>); + $H -> parse_host(Rest, << Acc/binary, $h >>); + $I -> parse_host(Rest, << Acc/binary, $i >>); + $J -> parse_host(Rest, << Acc/binary, $j >>); + $K -> parse_host(Rest, << Acc/binary, $k >>); + $L -> parse_host(Rest, << Acc/binary, $l >>); + $M -> parse_host(Rest, << Acc/binary, $m >>); + $N -> parse_host(Rest, << Acc/binary, $n >>); + $O -> parse_host(Rest, << Acc/binary, $o >>); + $P -> parse_host(Rest, << Acc/binary, $p >>); + $Q -> parse_host(Rest, << Acc/binary, $q >>); + $R -> parse_host(Rest, << Acc/binary, $r >>); + $S -> parse_host(Rest, << Acc/binary, $s >>); + $T -> parse_host(Rest, << Acc/binary, $t >>); + $U -> parse_host(Rest, << Acc/binary, $u >>); + $V -> parse_host(Rest, << Acc/binary, $v >>); + $W -> parse_host(Rest, << Acc/binary, $w >>); + $X -> parse_host(Rest, << Acc/binary, $x >>); + $Y -> parse_host(Rest, << Acc/binary, $y >>); + $Z -> parse_host(Rest, << Acc/binary, $z >>); + _ -> parse_host(Rest, << Acc/binary, C >>) + end. + +%% End of request parsing. +%% +%% We create the Req object and start handling the request. + +request(Buffer, State=#state{socket=Socket, transport=Transport, + req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive, + onresponse=OnResponse, urldecode=URLDecode}, + Method, Path, Query, Fragment, Version, Headers, Host, Port) -> + Req = cowboy_req:new(Socket, Transport, Method, Path, Query, Fragment, + Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive, + OnResponse, URLDecode), + onrequest(Req, State, Host, Path). %% Call the global onrequest callback. The callback can send a reply, %% in which case we consider the request handled and move on to the next %% one. Note that since we haven't dispatched yet, we don't know the %% handler, host_info, path_info or bindings yet. --spec onrequest(cowboy_req:req(), #state{}) -> ok. -onrequest(Req, State=#state{onrequest=undefined}) -> - dispatch(Req, State); -onrequest(Req, State=#state{onrequest=OnRequest}) -> +-spec onrequest(cowboy_req:req(), #state{}, binary(), binary()) -> ok. +onrequest(Req, State=#state{onrequest=undefined}, Host, Path) -> + dispatch(Req, State, Host, Path); +onrequest(Req, State=#state{onrequest=OnRequest}, Host, Path) -> Req2 = OnRequest(Req), case cowboy_req:get_resp_state(Req2) of - waiting -> dispatch(Req2, State); + waiting -> dispatch(Req2, State, Host, Path); _ -> next_request(Req2, State, ok) end. --spec dispatch(cowboy_req:req(), #state{}) -> ok. -dispatch(Req, State=#state{dispatch=Dispatch, - host_tokens=HostTokens, path_tokens=PathTokens}) -> - case cowboy_dispatcher:match(HostTokens, PathTokens, Dispatch) of +-spec dispatch(cowboy_req:req(), #state{}, binary(), binary()) -> ok. +dispatch(Req, State=#state{dispatch=Dispatch, urldecode={URLDecFun, URLDecArg}}, + Host, Path) -> + case cowboy_dispatcher:match(Dispatch, + fun(Bin) -> URLDecFun(Bin, URLDecArg) end, Host, Path) of {ok, Handler, Opts, Bindings, HostInfo, PathInfo} -> Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req), - handler_init(Req2, State#state{handler={Handler, Opts}, - host_tokens=undefined, path_tokens=undefined}); + handler_init(Req2, State, Handler, Opts); {error, notfound, host} -> error_terminate(400, State); {error, notfound, path} -> error_terminate(404, State) end. --spec handler_init(cowboy_req:req(), #state{}) -> ok. -handler_init(Req, State=#state{transport=Transport, - handler={Handler, Opts}}) -> +-spec handler_init(cowboy_req:req(), #state{}, module(), any()) -> ok. +handler_init(Req, State=#state{transport=Transport}, Handler, Opts) -> try Handler:init({Transport:name(), http}, Req, Opts) of {ok, Req2, HandlerState} -> - handler_handle(HandlerState, Req2, State); + handler_handle(Req2, State, Handler, HandlerState); {loop, Req2, HandlerState} -> - handler_before_loop(HandlerState, Req2, State); + handler_before_loop(Req2, State#state{hibernate=false}, + Handler, HandlerState); {loop, Req2, HandlerState, hibernate} -> - handler_before_loop(HandlerState, Req2, - State#state{hibernate=true}); + handler_before_loop(Req2, State#state{hibernate=true}, + Handler, HandlerState); {loop, Req2, HandlerState, Timeout} -> - handler_before_loop(HandlerState, Req2, - State#state{loop_timeout=Timeout}); + handler_before_loop(Req2, State#state{loop_timeout=Timeout}, + Handler, HandlerState); {loop, Req2, HandlerState, Timeout, hibernate} -> - handler_before_loop(HandlerState, Req2, - State#state{hibernate=true, loop_timeout=Timeout}); + handler_before_loop(Req2, State#state{ + hibernate=true, loop_timeout=Timeout}, Handler, HandlerState); {shutdown, Req2, HandlerState} -> - handler_terminate(HandlerState, Req2, State); + handler_terminate(Req2, Handler, HandlerState); %% @todo {upgrade, transport, Module} {upgrade, protocol, Module} -> - upgrade_protocol(Req, State, Module) + upgrade_protocol(Req, State, Handler, Opts, Module) catch Class:Reason -> error_terminate(500, State), - PLReq = cowboy_req:to_list(Req), error_logger:error_msg( "** Handler ~p terminating in init/3~n" " for the reason ~p:~p~n" "** Options were ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]) + "** Request was ~p~n" + "** Stacktrace: ~p~n~n", + [Handler, Class, Reason, Opts, + cowboy_req:to_list(Req), erlang:get_stacktrace()]) end. --spec upgrade_protocol(cowboy_req:req(), #state{}, atom()) -> ok. -upgrade_protocol(Req, State=#state{listener=ListenerPid, - handler={Handler, Opts}}, Module) -> +-spec upgrade_protocol(cowboy_req:req(), #state{}, module(), any(), module()) + -> ok. +upgrade_protocol(Req, State=#state{listener=ListenerPid}, + Handler, Opts, Module) -> case Module:upgrade(ListenerPid, Handler, Opts, Req) of {UpgradeRes, Req2} -> next_request(Req2, State, UpgradeRes); _Any -> terminate(State) end. --spec handler_handle(any(), cowboy_req:req(), #state{}) -> ok. -handler_handle(HandlerState, Req, State=#state{handler={Handler, Opts}}) -> +-spec handler_handle(cowboy_req:req(), #state{}, module(), any()) -> ok. +handler_handle(Req, State, Handler, HandlerState) -> try Handler:handle(Req, HandlerState) of {ok, Req2, HandlerState2} -> - terminate_request(HandlerState2, Req2, State) + terminate_request(Req2, State, Handler, HandlerState2) catch Class:Reason -> - PLReq = cowboy_req:to_list(Req), error_logger:error_msg( "** Handler ~p terminating in handle/2~n" " for the reason ~p:~p~n" - "** Options were ~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Class, Reason, Opts, - HandlerState, PLReq, erlang:get_stacktrace()]), - handler_terminate(HandlerState, Req, State), + "** Handler state was ~p~n" + "** Request was ~p~n" + "** Stacktrace: ~p~n~n", + [Handler, Class, Reason, HandlerState, + cowboy_req:to_list(Req), erlang:get_stacktrace()]), + handler_terminate(Req, Handler, HandlerState), error_terminate(500, State) end. %% We don't listen for Transport closes because that would force us %% to receive data and buffer it indefinitely. --spec handler_before_loop(any(), cowboy_req:req(), #state{}) -> ok. -handler_before_loop(HandlerState, Req, State=#state{hibernate=true}) -> +-spec handler_before_loop(cowboy_req:req(), #state{}, module(), any()) -> ok. +handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) -> State2 = handler_loop_timeout(State), catch erlang:hibernate(?MODULE, handler_loop, - [HandlerState, Req, State2#state{hibernate=false}]), + [Req, State2#state{hibernate=false}, Handler, HandlerState]), ok; -handler_before_loop(HandlerState, Req, State) -> +handler_before_loop(Req, State, Handler, HandlerState) -> State2 = handler_loop_timeout(State), - handler_loop(HandlerState, Req, State2). + handler_loop(Req, State2, Handler, HandlerState). %% Almost the same code can be found in cowboy_websocket. -spec handler_loop_timeout(#state{}) -> #state{}. @@ -373,59 +540,58 @@ handler_loop_timeout(State=#state{loop_timeout=Timeout, TRef = erlang:start_timer(Timeout, self(), ?MODULE), State#state{loop_timeout_ref=TRef}. --spec handler_loop(any(), cowboy_req:req(), #state{}) -> ok. -handler_loop(HandlerState, Req, State=#state{loop_timeout_ref=TRef}) -> +-spec handler_loop(cowboy_req:req(), #state{}, module(), any()) -> ok. +handler_loop(Req, State=#state{loop_timeout_ref=TRef}, Handler, HandlerState) -> receive {timeout, TRef, ?MODULE} -> - terminate_request(HandlerState, Req, State); + terminate_request(Req, State, Handler, HandlerState); {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> - handler_loop(HandlerState, Req, State); + handler_loop(Req, State, Handler, HandlerState); Message -> - handler_call(HandlerState, Req, State, Message) + handler_call(Req, State, Handler, HandlerState, Message) end. --spec handler_call(any(), cowboy_req:req(), #state{}, any()) -> ok. -handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}}, - Message) -> +-spec handler_call(cowboy_req:req(), #state{}, module(), any(), any()) -> ok. +handler_call(Req, State, Handler, HandlerState, Message) -> try Handler:info(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> - terminate_request(HandlerState2, Req2, State); + terminate_request(Req2, State, Handler, HandlerState2); {loop, Req2, HandlerState2} -> - handler_before_loop(HandlerState2, Req2, State); + handler_before_loop(Req2, State, Handler, HandlerState2); {loop, Req2, HandlerState2, hibernate} -> - handler_before_loop(HandlerState2, Req2, - State#state{hibernate=true}) + handler_before_loop(Req2, State#state{hibernate=true}, + Handler, HandlerState2) catch Class:Reason -> - PLReq = cowboy_req:to_list(Req), error_logger:error_msg( "** Handler ~p terminating in info/3~n" " for the reason ~p:~p~n" - "** Options were ~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Class, Reason, Opts, - HandlerState, PLReq, erlang:get_stacktrace()]), - handler_terminate(HandlerState, Req, State), + "** Handler state was ~p~n" + "** Request was ~p~n" + "** Stacktrace: ~p~n~n", + [Handler, Class, Reason, HandlerState, + cowboy_req:to_list(Req), erlang:get_stacktrace()]), + handler_terminate(Req, Handler, HandlerState), error_terminate(500, State) end. --spec handler_terminate(any(), cowboy_req:req(), #state{}) -> ok. -handler_terminate(HandlerState, Req, #state{handler={Handler, Opts}}) -> +-spec handler_terminate(cowboy_req:req(), module(), any()) -> ok. +handler_terminate(Req, Handler, HandlerState) -> try Handler:terminate(cowboy_req:lock(Req), HandlerState) catch Class:Reason -> - PLReq = cowboy_req:to_list(Req), error_logger:error_msg( "** Handler ~p terminating in terminate/2~n" " for the reason ~p:~p~n" - "** Options were ~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Class, Reason, Opts, - HandlerState, PLReq, erlang:get_stacktrace()]) + "** Handler state was ~p~n" + "** Request was ~p~n" + "** Stacktrace: ~p~n~n", + [Handler, Class, Reason, HandlerState, + cowboy_req:to_list(Req), erlang:get_stacktrace()]) end. --spec terminate_request(any(), cowboy_req:req(), #state{}) -> ok. -terminate_request(HandlerState, Req, State) -> - HandlerRes = handler_terminate(HandlerState, Req, State), +-spec terminate_request(cowboy_req:req(), #state{}, module(), any()) -> ok. +terminate_request(Req, State, Handler, HandlerState) -> + HandlerRes = handler_terminate(Req, Handler, HandlerState), next_request(Req, State, HandlerRes). -spec next_request(cowboy_req:req(), #state{}, any()) -> ok. @@ -439,8 +605,8 @@ next_request(Req, State=#state{req_keepalive=Keepalive}, HandlerRes) -> receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, case {HandlerRes, BodyRes, cowboy_req:get_connection(Req)} of {ok, ok, keepalive} -> - ?MODULE:parse_request(Buffer, State#state{handler=undefined, - req_empty_lines=0, req_keepalive=Keepalive + 1}); + ?MODULE:parse_request(Buffer, State#state{ + req_keepalive=Keepalive + 1}, 0); _Closed -> terminate(State) end. @@ -453,7 +619,8 @@ error_terminate(Code, State=#state{socket=Socket, transport=Transport, {cowboy_req, resp_sent} -> ok after 0 -> _ = cowboy_req:reply(Code, cowboy_req:new(Socket, Transport, - close, <<"GET">>, {1, 1}, <<>>, <<>>, OnResponse, undefined)), + <<"GET">>, <<>>, <<>>, <<>>, {1, 1}, [], <<>>, undefined, + <<>>, false, OnResponse, undefined)), ok end, terminate(State). @@ -462,19 +629,3 @@ error_terminate(Code, State=#state{socket=Socket, transport=Transport, terminate(#state{socket=Socket, transport=Transport}) -> Transport:close(Socket), ok. - -%% Internal. - --spec version_to_connection(#state{}, cowboy_http:version()) - -> keepalive | close. -version_to_connection(#state{req_keepalive=Keepalive, - max_keepalive=MaxKeepalive}, _) when Keepalive >= MaxKeepalive -> - close; -version_to_connection(_, {1, 1}) -> - keepalive; -version_to_connection(_, _) -> - close. - --spec default_port(atom()) -> 80 | 443. -default_port(ssl) -> 443; -default_port(_) -> 80. |