aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2012-09-26 14:11:53 +0200
committerLoïc Hoguin <[email protected]>2012-09-26 14:20:29 +0200
commitb2243aa54438dbea4137960c9dc6f54ac746f429 (patch)
treeb7f3768cc6fea9bcc9d4363012a6cdf149178977 /src
parentbfab8d4b22d858e7cffa97d04210a62fae56681c (diff)
downloadcowboy-b2243aa54438dbea4137960c9dc6f54ac746f429.tar.gz
cowboy-b2243aa54438dbea4137960c9dc6f54ac746f429.tar.bz2
cowboy-b2243aa54438dbea4137960c9dc6f54ac746f429.zip
Optimize cowboy_protocol
* #state{} changes are avoided where possible * #state{} is now smaller and use less memory * the Req object is created only after the whole request is parsed * parsing makes use of a single binary match context * external calls are avoided in the critical path * URL fragment is now extracted properly (retrieval API next commit) * argument orders to local functions modified to avoid extra operations * dispatching waits as long as possible before tokenizing host/path * handler opts are no longer shown in the error messages except in init The code may not look as beautiful as it was before. But it really is, for parsing code. The parsing section of the file may be skipped if your eyes start to burn.
Diffstat (limited to 'src')
-rw-r--r--src/cowboy_dispatcher.erl275
-rw-r--r--src/cowboy_http.erl51
-rw-r--r--src/cowboy_protocol.erl585
-rw-r--r--src/cowboy_req.erl66
4 files changed, 514 insertions, 463 deletions
diff --git a/src/cowboy_dispatcher.erl b/src/cowboy_dispatcher.erl
index c52aba9..f5dfcd9 100644
--- a/src/cowboy_dispatcher.erl
+++ b/src/cowboy_dispatcher.erl
@@ -17,16 +17,14 @@
-module(cowboy_dispatcher).
%% API.
--export([split_host/1]).
--export([split_path/2]).
--export([match/3]).
+-export([match/4]).
--type bindings() :: list({atom(), binary()}).
--type tokens() :: list(binary()).
--type match_rule() :: '_' | '*' | list(binary() | '_' | '...' | atom()).
--type dispatch_path() :: list({match_rule(), module(), any()}).
+-type bindings() :: [{atom(), binary()}].
+-type tokens() :: [binary()].
+-type match_rule() :: '_' | '*' | [binary() | '_' | '...' | atom()].
+-type dispatch_path() :: [{match_rule(), module(), any()}].
-type dispatch_rule() :: {Host::match_rule(), Path::dispatch_path()}.
--type dispatch_rules() :: list(dispatch_rule()).
+-type dispatch_rules() :: [dispatch_rule()].
-export_type([bindings/0]).
-export_type([tokens/0]).
@@ -38,60 +36,6 @@
%% API.
-%% @doc Split a hostname into a list of tokens.
--spec split_host(binary())
- -> {tokens(), binary(), undefined | inet:port_number()}.
-split_host(Host) ->
- case binary:match(Host, <<":">>) of
- nomatch ->
- {do_split_host(Host, []), Host, undefined};
- {Pos, _} ->
- << Host2:Pos/binary, _:8, Port/bits >> = Host,
- {do_split_host(Host2, []), Host2,
- list_to_integer(binary_to_list(Port))}
- end.
-
-do_split_host(Host, Acc) ->
- case binary:match(Host, <<".">>) of
- nomatch when Host =:= <<>> ->
- Acc;
- nomatch ->
- [Host|Acc];
- {Pos, _} ->
- << Segment:Pos/binary, _:8, Rest/bits >> = Host,
- false = byte_size(Segment) == 0,
- do_split_host(Rest, [Segment|Acc])
- end.
-
-%% @doc Split a path into a list of path segments.
-%%
-%% Following RFC2396, this function may return path segments containing any
-%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped
-%% and part of a path segment.
--spec split_path(binary(), fun((binary()) -> binary())) ->
- {tokens(), binary(), binary()}.
-split_path(Path, URLDec) ->
- case binary:match(Path, <<"?">>) of
- nomatch ->
- {do_split_path(Path, URLDec), Path, <<>>};
- {Pos, _} ->
- << Path2:Pos/binary, _:8, Qs/bits >> = Path,
- {do_split_path(Path2, URLDec), Path2, Qs}
- end.
-
-do_split_path(<< "/", Path/bits >>, URLDec) ->
- do_split_path(Path, URLDec, []).
-do_split_path(Path, URLDec, Acc) ->
- case binary:match(Path, <<"/">>) of
- nomatch when Path =:= <<>> ->
- lists:reverse([URLDec(S) || S <- Acc]);
- nomatch ->
- lists:reverse([URLDec(S) || S <- [Path|Acc]]);
- {Pos, _} ->
- << Segment:Pos/binary, _:8, Rest/bits >> = Path,
- do_split_path(Rest, URLDec, [Segment|Acc])
- end.
-
%% @doc Match hostname tokens and path tokens against dispatch rules.
%%
%% It is typically used for matching tokens for the hostname and path of
@@ -119,47 +63,93 @@ do_split_path(Path, URLDec, Acc) ->
%% options found in the dispatch list, a key-value list of bindings and
%% the tokens that were matched by the <em>'...'</em> atom for both the
%% hostname and path.
--spec match(Host::tokens(), Path::tokens(), dispatch_rules())
+-spec match(dispatch_rules(), fun((binary()) -> binary()),
+ Host::binary() | tokens(), Path::binary())
-> {ok, module(), any(), bindings(),
HostInfo::undefined | tokens(),
PathInfo::undefined | tokens()}
| {error, notfound, host} | {error, notfound, path}.
-match(_Host, _Path, []) ->
+match([], _, _, _) ->
{error, notfound, host};
-match(_Host, Path, [{'_', PathMatchs}|_Tail]) ->
- match_path(Path, PathMatchs, [], undefined);
-match(Host, Path, [{HostMatch, PathMatchs}|Tail]) ->
- case list_match(Host, lists:reverse(HostMatch), []) of
+match([{'_', PathMatchs}|_Tail], URLDecode, _, Path) ->
+ match_path(PathMatchs, URLDecode, undefined, Path, []);
+match([{HostMatch, PathMatchs}|Tail], URLDecode, Tokens, Path)
+ when is_list(Tokens) ->
+ case list_match(Tokens, lists:reverse(HostMatch), []) of
false ->
- match(Host, Path, Tail);
- {true, HostBinds, undefined} ->
- match_path(Path, PathMatchs, HostBinds, undefined);
- {true, HostBinds, HostInfo} ->
- match_path(Path, PathMatchs, HostBinds, lists:reverse(HostInfo))
- end.
+ match(Tail, URLDecode, Tokens, Path);
+ {true, Bindings, undefined} ->
+ match_path(PathMatchs, URLDecode, undefined, Path, Bindings);
+ {true, Bindings, HostInfo} ->
+ match_path(PathMatchs, URLDecode, lists:reverse(HostInfo),
+ Path, Bindings)
+ end;
+match(Dispatch, URLDecode, Host, Path) ->
+ match(Dispatch, URLDecode, split_host(Host), Path).
--spec match_path(tokens(), dispatch_path(), bindings(),
- HostInfo::undefined | tokens())
+-spec match_path(dispatch_path(), fun((binary()) -> binary()),
+ HostInfo::undefined | tokens(), binary() | tokens(), bindings())
-> {ok, module(), any(), bindings(),
HostInfo::undefined | tokens(),
PathInfo::undefined | tokens()}
| {error, notfound, path}.
-match_path(_Path, [], _HostBinds, _HostInfo) ->
+match_path([], _, _, _, _) ->
{error, notfound, path};
-match_path(_Path, [{'_', Handler, Opts}|_Tail], HostBinds, HostInfo) ->
- {ok, Handler, Opts, HostBinds, HostInfo, undefined};
-match_path('*', [{'*', Handler, Opts}|_Tail], HostBinds, HostInfo) ->
- {ok, Handler, Opts, HostBinds, HostInfo, undefined};
-match_path(Path, [{PathMatch, Handler, Opts}|Tail], HostBinds, HostInfo) ->
- case list_match(Path, PathMatch, []) of
+match_path([{'_', Handler, Opts}|_Tail], _, HostInfo, _, Bindings) ->
+ {ok, Handler, Opts, Bindings, HostInfo, undefined};
+match_path([{'*', Handler, Opts}|_Tail], _, HostInfo, '*', Bindings) ->
+ {ok, Handler, Opts, Bindings, HostInfo, undefined};
+match_path([{PathMatch, Handler, Opts}|Tail], URLDecode, HostInfo, Tokens,
+ Bindings) when is_list(Tokens) ->
+ case list_match(Tokens, PathMatch, []) of
false ->
- match_path(Path, Tail, HostBinds, HostInfo);
+ match_path(Tail, URLDecode, HostInfo, Tokens, Bindings);
{true, PathBinds, PathInfo} ->
- {ok, Handler, Opts, HostBinds ++ PathBinds, HostInfo, PathInfo}
- end.
+ {ok, Handler, Opts, Bindings ++ PathBinds, HostInfo, PathInfo}
+ end;
+match_path(Dispatch, URLDecode, HostInfo, Path, Bindings) ->
+ match_path(Dispatch, URLDecode, HostInfo,
+ split_path(Path, URLDecode), Bindings).
%% Internal.
+%% @doc Split a hostname into a list of tokens.
+-spec split_host(binary()) -> tokens().
+split_host(Host) ->
+ split_host(Host, []).
+
+split_host(Host, Acc) ->
+ case binary:match(Host, <<".">>) of
+ nomatch when Host =:= <<>> ->
+ Acc;
+ nomatch ->
+ [Host|Acc];
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Host,
+ false = byte_size(Segment) == 0,
+ split_host(Rest, [Segment|Acc])
+ end.
+
+%% @doc Split a path into a list of path segments.
+%%
+%% Following RFC2396, this function may return path segments containing any
+%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped
+%% and part of a path segment.
+-spec split_path(binary(), fun((binary()) -> binary())) -> tokens().
+split_path(<< $/, Path/bits >>, URLDec) ->
+ split_path(Path, URLDec, []).
+
+split_path(Path, URLDec, Acc) ->
+ case binary:match(Path, <<"/">>) of
+ nomatch when Path =:= <<>> ->
+ lists:reverse([URLDec(S) || S <- Acc]);
+ nomatch ->
+ lists:reverse([URLDec(S) || S <- [Path|Acc]]);
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Path,
+ split_path(Rest, URLDec, [Segment|Acc])
+ end.
+
-spec list_match(tokens(), match_rule(), bindings())
-> {true, bindings(), undefined | tokens()} | false.
%% Atom '...' matches any trailing path, stop right now.
@@ -188,64 +178,32 @@ list_match(_List, _Match, _Binds) ->
split_host_test_() ->
%% {Host, Result}
Tests = [
- {<<"">>, {[], <<"">>, undefined}},
- {<<"*">>, {[<<"*">>], <<"*">>, undefined}},
+ {<<"">>, []},
+ {<<"*">>, [<<"*">>]},
{<<"cowboy.ninenines.eu">>,
- {[<<"eu">>, <<"ninenines">>, <<"cowboy">>],
- <<"cowboy.ninenines.eu">>, undefined}},
+ [<<"eu">>, <<"ninenines">>, <<"cowboy">>]},
{<<"ninenines.eu">>,
- {[<<"eu">>, <<"ninenines">>], <<"ninenines.eu">>, undefined}},
- {<<"ninenines.eu:8080">>,
- {[<<"eu">>, <<"ninenines">>], <<"ninenines.eu">>, 8080}},
+ [<<"eu">>, <<"ninenines">>]},
{<<"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z">>,
- {[<<"z">>, <<"y">>, <<"x">>, <<"w">>, <<"v">>, <<"u">>, <<"t">>,
- <<"s">>, <<"r">>, <<"q">>, <<"p">>, <<"o">>, <<"n">>, <<"m">>,
- <<"l">>, <<"k">>, <<"j">>, <<"i">>, <<"h">>, <<"g">>, <<"f">>,
- <<"e">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>],
- <<"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z">>,
- undefined}}
+ [<<"z">>, <<"y">>, <<"x">>, <<"w">>, <<"v">>, <<"u">>, <<"t">>,
+ <<"s">>, <<"r">>, <<"q">>, <<"p">>, <<"o">>, <<"n">>, <<"m">>,
+ <<"l">>, <<"k">>, <<"j">>, <<"i">>, <<"h">>, <<"g">>, <<"f">>,
+ <<"e">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>]}
],
[{H, fun() -> R = split_host(H) end} || {H, R} <- Tests].
-split_host_fail_test_() ->
- Tests = [
- <<".........">>,
- <<"ninenines..eu">>,
- <<"ninenines.eu:owns">>,
- <<"ninenines.eu: owns">>,
- <<"ninenines.eu:42fun">>,
- <<"ninenines.eu: 42fun">>,
- <<"ninenines.eu:42 fun">>,
- <<"ninenines.eu:fun 42">>,
- <<"ninenines.eu: 42">>,
- <<":owns">>,
- <<":42 fun">>
- ],
- [{H, fun() -> case catch split_host(H) of
- {'EXIT', _Reason} -> ok
- end end} || H <- Tests].
-
split_path_test_() ->
%% {Path, Result, QueryString}
Tests = [
- {<<"/?">>, [], <<"/">>, <<"">>},
- {<<"/???">>, [], <<"/">>, <<"??">>},
- {<<"/">>, [], <<"/">>, <<"">>},
- {<<"/extend//cowboy">>, [<<"extend">>, <<>>, <<"cowboy">>],
- <<"/extend//cowboy">>, <<>>},
- {<<"/users">>, [<<"users">>], <<"/users">>, <<"">>},
- {<<"/users?">>, [<<"users">>], <<"/users">>, <<"">>},
- {<<"/users?a">>, [<<"users">>], <<"/users">>, <<"a">>},
- {<<"/users/42/friends?a=b&c=d&e=notsure?whatever">>,
- [<<"users">>, <<"42">>, <<"friends">>],
- <<"/users/42/friends">>, <<"a=b&c=d&e=notsure?whatever">>},
- {<<"/users/a+b/c%21d?e+f=g+h">>,
- [<<"users">>, <<"a b">>, <<"c!d">>],
- <<"/users/a+b/c%21d">>, <<"e+f=g+h">>}
+ {<<"/">>, []},
+ {<<"/extend//cowboy">>, [<<"extend">>, <<>>, <<"cowboy">>]},
+ {<<"/users">>, [<<"users">>]},
+ {<<"/users/42/friends">>, [<<"users">>, <<"42">>, <<"friends">>]},
+ {<<"/users/a+b/c%21d">>, [<<"users">>, <<"a b">>, <<"c!d">>]}
],
URLDecode = fun(Bin) -> cowboy_http:urldecode(Bin, crash) end,
- [{P, fun() -> {R, RawP, Qs} = split_path(P, URLDecode) end}
- || {P, R, RawP, Qs} <- Tests].
+ [{P, fun() -> R = split_path(P, URLDecode) end}
+ || {P, R} <- Tests].
match_test_() ->
Dispatch = [
@@ -270,28 +228,31 @@ match_test_() ->
],
%% {Host, Path, Result}
Tests = [
- {[<<"any">>], [], {ok, match_any, [], []}},
- {[<<"eu">>, <<"ninenines">>, <<"any">>, <<"www">>],
- [<<"users">>, <<"42">>, <<"mails">>],
+ {<<"any">>, <<"/">>, {ok, match_any, [], []}},
+ {<<"www.any.ninenines.eu">>, <<"/users/42/mails">>,
{ok, match_any_subdomain_users, [], []}},
- {[<<"eu">>, <<"ninenines">>, <<"www">>],
- [<<"users">>, <<"42">>, <<"mails">>], {ok, match_any, [], []}},
- {[<<"eu">>, <<"ninenines">>, <<"www">>], [], {ok, match_any, [], []}},
- {[<<"eu">>, <<"ninenines">>, <<"any">>, <<"www">>],
- [<<"not_users">>, <<"42">>, <<"mails">>], {error, notfound, path}},
- {[<<"eu">>, <<"ninenines">>], [], {ok, match_extend, [], []}},
- {[<<"eu">>, <<"ninenines">>], [<<"users">>, <<"42">>, <<"friends">>],
+ {<<"www.ninenines.eu">>, <<"/users/42/mails">>,
+ {ok, match_any, [], []}},
+ {<<"www.ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], []}},
+ {<<"www.any.ninenines.eu">>, <<"/not_users/42/mails">>,
+ {error, notfound, path}},
+ {<<"ninenines.eu">>, <<"/">>,
+ {ok, match_extend, [], []}},
+ {<<"ninenines.eu">>, <<"/users/42/friends">>,
{ok, match_extend_users_friends, [], [{id, <<"42">>}]}},
- {[<<"fr">>, <<"erlang">>], '_',
+ {<<"erlang.fr">>, '_',
{ok, match_erlang_ext, [], [{ext, <<"fr">>}]}},
- {[<<"any">>], [<<"users">>, <<"444">>, <<"friends">>],
+ {<<"any">>, <<"/users/444/friends">>,
{ok, match_users_friends, [], [{id, <<"444">>}]}},
- {[<<"fr">>, <<"ninenines">>], [<<"threads">>, <<"987">>],
+ {<<"ninenines.fr">>, <<"/threads/987">>,
{ok, match_duplicate_vars, [we, {expect, two}, var, here],
- [{var, <<"fr">>}, {var, <<"987">>}]}}
+ [{var, <<"fr">>}, {var, <<"987">>}]}}
],
+ URLDecode = fun(Bin) -> cowboy_http:urldecode(Bin, crash) end,
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
- {ok, Handler, Opts, Binds, undefined, undefined} = match(H, P, Dispatch)
+ {ok, Handler, Opts, Binds, undefined, undefined}
+ = match(Dispatch, URLDecode, H, P)
end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].
match_info_test_() ->
@@ -304,24 +265,22 @@ match_info_test_() ->
]}
],
Tests = [
- {[<<"eu">>, <<"ninenines">>], [],
+ {<<"ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [], undefined}},
- {[<<"eu">>, <<"ninenines">>, <<"bugs">>], [],
+ {<<"bugs.ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [<<"bugs">>], undefined}},
- {[<<"eu">>, <<"ninenines">>, <<"bugs">>, <<"cowboy">>], [],
+ {<<"cowboy.bugs.ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [<<"cowboy">>, <<"bugs">>], undefined}},
- {[<<"eu">>, <<"ninenines">>, <<"www">>],
- [<<"pathinfo">>, <<"is">>, <<"next">>],
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next">>,
{ok, match_path, [], [], undefined, []}},
- {[<<"eu">>, <<"ninenines">>, <<"www">>],
- [<<"pathinfo">>, <<"is">>, <<"next">>, <<"path_info">>],
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/path_info">>,
{ok, match_path, [], [], undefined, [<<"path_info">>]}},
- {[<<"eu">>, <<"ninenines">>, <<"www">>],
- [<<"pathinfo">>, <<"is">>, <<"next">>, <<"foo">>, <<"bar">>],
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/foo/bar">>,
{ok, match_path, [], [], undefined, [<<"foo">>, <<"bar">>]}}
],
+ URLDecode = fun(Bin) -> cowboy_http:urldecode(Bin, crash) end,
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
- R = match(H, P, Dispatch)
+ R = match(Dispatch, URLDecode, H, P)
end} || {H, P, R} <- Tests].
-endif.
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index a936e2c..c3bef2f 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -17,7 +17,6 @@
-module(cowboy_http).
%% Parsing.
--export([request_line/1]).
-export([list/2]).
-export([nonempty_list/2]).
-export([content_type/1]).
@@ -51,15 +50,10 @@
-export([urlencode/2]).
-export([x_www_form_urlencoded/2]).
--type uri() :: '*' | {absoluteURI, http | https, Host::binary(),
- Port::integer() | undefined, Path::binary()}
- | {scheme, Scheme::binary(), binary()}
- | {abs_path, binary()} | binary().
-type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}.
-type headers() :: [{binary(), iodata()}].
-type status() :: non_neg_integer() | binary().
--export_type([uri/0]).
-export_type([version/0]).
-export_type([headers/0]).
-export_type([status/0]).
@@ -70,51 +64,6 @@
%% Parsing.
-%% @doc Parse a request-line.
--spec request_line(binary())
- -> {binary(), binary(), version()} | {error, badarg}.
-request_line(Data) ->
- token(Data,
- fun (Rest, Method) ->
- whitespace(Rest,
- fun (Rest2) ->
- uri_to_abspath(Rest2,
- fun (Rest3, AbsPath) ->
- whitespace(Rest3,
- fun (<< "HTTP/", Maj, ".", Min, _/binary >>)
- when Maj >= $0, Maj =< $9,
- Min >= $0, Min =< $9 ->
- {Method, AbsPath, {Maj - $0, Min - $0}};
- (_) ->
- {error, badarg}
- end)
- end)
- end)
- end).
-
-%% We just want to extract the path/qs and skip everything else.
-%% We do not really parse the URI, nor do we need to.
-uri_to_abspath(Data, Fun) ->
- case binary:match(Data, <<" ">>) of
- nomatch -> %% We require the HTTP version.
- {error, badarg};
- {Pos1, _} ->
- << URI:Pos1/binary, _:8, Rest/bits >> = Data,
- case binary:match(URI, <<"://">>) of
- nomatch -> %% Already is a path or "*".
- Fun(Rest, URI);
- {Pos2, _} ->
- << _:Pos2/binary, _:24, NoScheme/bits >> = Rest,
- case binary:match(NoScheme, <<"/">>) of
- nomatch ->
- Fun(Rest, <<"/">>);
- {Pos3, _} ->
- << _:Pos3/binary, _:8, NoHost/bits >> = NoScheme,
- Fun(Rest, << "/", NoHost/binary >>)
- end
- end
- end.
-
%% @doc Parse a non-empty list of the given type.
-spec nonempty_list(binary(), fun()) -> [any(), ...] | {error, badarg}.
nonempty_list(Data, Fun) ->
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.
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 9cc7760..10c5e96 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -42,7 +42,7 @@
-module(cowboy_req).
%% Request API.
--export([new/9]).
+-export([new/14]).
-export([method/1]).
-export([version/1]).
-export([peer/1]).
@@ -103,10 +103,6 @@
-export([ensure_response/2]).
%% Private setter/getter API.
--export([set_host/4]).
--export([set_connection/2]).
--export([add_header/3]).
--export([set_buffer/2]).
-export([set_bindings/4]).
-export([get_resp_state/1]).
-export([get_buffer/1]).
@@ -172,15 +168,36 @@
%%
%% This function takes care of setting the owner's pid to self().
%% @private
--spec new(inet:socket(), module(), keepalive | close,
- binary(), cowboy_http:version(), binary(), binary(),
- undefined | fun(), undefined | {fun(), atom()})
+%% @todo Fragment.
+-spec new(inet:socket(), module(), binary(), binary(), binary(), binary(),
+ cowboy_http:version(), cowboy_http:headers(), binary(),
+ inet:port_number() | undefined, binary(), boolean(),
+ undefined | cowboy_protocol:onresponse_fun(),
+ undefined | {fun(), atom()})
-> req().
-new(Socket, Transport, Connection, Method, Version, Path, Qs,
+new(Socket, Transport, Method, Path, Query, _Fragment,
+ Version, Headers, Host, Port, Buffer, CanKeepalive,
OnResponse, URLDecode) ->
- #http_req{socket=Socket, transport=Transport, connection=Connection,
- pid=self(), method=Method, version=Version, path=Path, qs=Qs,
- onresponse=OnResponse, urldecode=URLDecode}.
+ Req = #http_req{socket=Socket, transport=Transport, pid=self(),
+ method=Method, path=Path, qs=Query, version=Version,
+ headers=Headers, host=Host, port=Port, buffer=Buffer,
+ onresponse=OnResponse, urldecode=URLDecode},
+ case CanKeepalive of
+ false ->
+ Req#http_req{connection=close};
+ true ->
+ case lists:keymember(<<"connection">>, 1, Headers) of
+ false when Version =:= {1, 1} ->
+ Req; %% keepalive
+ false ->
+ Req#http_req{connection=close};
+ true ->
+ {ok, Tokens, Req2} = parse_header(<<"connection">>, Req),
+ %% @todo Might want to bring this function into cowboy_req.
+ Connection = cowboy_http:connection_to_atom(Tokens),
+ Req2#http_req{connection=Connection}
+ end
+ end.
%% @doc Return the HTTP method of the request.
-spec method(Req) -> {binary(), Req} when Req::req().
@@ -968,31 +985,6 @@ ensure_response(#http_req{socket=Socket, transport=Transport,
%% Private setter/getter API.
%% @private
--spec set_host(binary(), inet:port_number(), binary(), Req)
- -> Req when Req::req().
-set_host(Host, Port, RawHost, Req=#http_req{headers=Headers}) ->
- Req#http_req{host=Host, port=Port, headers=[{<<"host">>, RawHost}|Headers]}.
-
-%% @private
--spec set_connection(binary(), Req) -> Req when Req::req().
-set_connection(RawConnection, Req=#http_req{headers=Headers}) ->
- Req2 = Req#http_req{headers=[{<<"connection">>, RawConnection}|Headers]},
- {ok, ConnTokens, Req3} = parse_header(<<"connection">>, Req2),
- ConnAtom = cowboy_http:connection_to_atom(ConnTokens),
- Req3#http_req{connection=ConnAtom}.
-
-%% @private
--spec add_header(binary(), binary(), Req)
- -> Req when Req::req().
-add_header(Name, Value, Req=#http_req{headers=Headers}) ->
- Req#http_req{headers=[{Name, Value}|Headers]}.
-
-%% @private
--spec set_buffer(binary(), Req) -> Req when Req::req().
-set_buffer(Buffer, Req) ->
- Req#http_req{buffer=Buffer}.
-
-%% @private
-spec set_bindings(cowboy_dispatcher:tokens(), cowboy_dispatcher:tokens(),
cowboy_dispatcher:bindings(), Req) -> Req when Req::req().
set_bindings(HostInfo, PathInfo, Bindings, Req) ->