aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cowboy_clock.erl4
-rw-r--r--src/cowboy_dispatcher.erl258
-rw-r--r--src/cowboy_http.erl68
-rw-r--r--src/cowboy_multipart.erl14
-rw-r--r--src/cowboy_protocol.erl696
-rw-r--r--src/cowboy_req.erl527
-rw-r--r--src/cowboy_rest.erl57
-rw-r--r--src/cowboy_static.erl4
-rw-r--r--src/cowboy_websocket.erl15
9 files changed, 894 insertions, 749 deletions
diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl
index 747bffe..5e2bf44 100644
--- a/src/cowboy_clock.erl
+++ b/src/cowboy_clock.erl
@@ -62,14 +62,14 @@ stop() ->
%% @doc Return the current date and time formatted according to RFC-1123.
%%
-%% This format is used in the <em>'Date'</em> header sent with HTTP responses.
+%% This format is used in the <em>date</em> header sent with HTTP responses.
-spec rfc1123() -> binary().
rfc1123() ->
ets:lookup_element(?TABLE, rfc1123, 2).
%% @doc Return the current date and time formatted according to RFC-2109.
%%
-%% This format is used in the <em>'Set-Cookie'</em> header sent with
+%% This format is used in the <em>set-cookie</em> header sent with
%% HTTP responses.
-spec rfc2109(calendar:datetime()) -> binary().
rfc2109(LocalTime) ->
diff --git a/src/cowboy_dispatcher.erl b/src/cowboy_dispatcher.erl
index 445e8fa..cfb8fb6 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]).
--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,42 +36,6 @@
%% API.
-%% @doc Split a hostname into a list of tokens.
--spec split_host(binary())
- -> {tokens(), binary(), undefined | inet:port_number()}.
-split_host(<<>>) ->
- {[], <<>>, undefined};
-split_host(Host) ->
- case binary:split(Host, <<":">>) of
- [Host] ->
- {binary:split(Host, <<".">>, [global, trim]), Host, undefined};
- [Host2, Port] ->
- {binary:split(Host2, <<".">>, [global, trim]), Host2,
- list_to_integer(binary_to_list(Port))}
- 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:split(Path, <<"?">>) of
- [Path] -> {do_split_path(Path, <<"/">>, URLDec), Path, <<>>};
- [<<>>, Qs] -> {[], <<>>, Qs};
- [Path2, Qs] -> {do_split_path(Path2, <<"/">>, URLDec), Path2, Qs}
- end.
-
--spec do_split_path(binary(), <<_:8>>, fun((binary()) -> binary())) -> tokens().
-do_split_path(RawPath, Separator, URLDec) ->
- EncodedPath = case binary:split(RawPath, Separator, [global, trim]) of
- [<<>>|Path] -> Path;
- Path -> Path
- end,
- [URLDec(Token) || Token <- EncodedPath].
-
%% @doc Match hostname tokens and path tokens against dispatch rules.
%%
%% It is typically used for matching tokens for the hostname and path of
@@ -101,53 +63,90 @@ do_split_path(RawPath, Separator, URLDec) ->
%% 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(), 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 try_match(host, Host, HostMatch) of
+match([{'_', PathMatchs}|_Tail], _, Path) ->
+ match_path(PathMatchs, undefined, Path, []);
+match([{HostMatch, PathMatchs}|Tail], 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, Tokens, Path);
+ {true, Bindings, undefined} ->
+ match_path(PathMatchs, undefined, Path, Bindings);
+ {true, Bindings, HostInfo} ->
+ match_path(PathMatchs, lists:reverse(HostInfo),
+ Path, Bindings)
+ end;
+match(Dispatch, Host, Path) ->
+ match(Dispatch, split_host(Host), Path).
--spec match_path(tokens(), dispatch_path(), bindings(),
- HostInfo::undefined | tokens())
+-spec match_path(dispatch_path(),
+ 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 try_match(path, 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], HostInfo, Tokens,
+ Bindings) when is_list(Tokens) ->
+ case list_match(Tokens, PathMatch, []) of
false ->
- match_path(Path, Tail, HostBinds, HostInfo);
+ match_path(Tail, 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, HostInfo, Path, Bindings) ->
+ match_path(Dispatch, HostInfo, split_path(Path), Bindings).
%% Internal.
--spec try_match(host | path, tokens(), match_rule())
- -> {true, bindings(), undefined | tokens()} | false.
-try_match(host, List, Match) ->
- list_match(lists:reverse(List), lists:reverse(Match), []);
-try_match(path, List, Match) ->
- list_match(List, Match, []).
+%% @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()) -> tokens().
+split_path(<< $/, Path/bits >>) ->
+ split_path(Path, []).
+
+split_path(Path, Acc) ->
+ case binary:match(Path, <<"/">>) of
+ nomatch when Path =:= <<>> ->
+ lists:reverse([cowboy_http:urldecode(S) || S <- Acc]);
+ nomatch ->
+ lists:reverse([cowboy_http:urldecode(S) || S <- [Path|Acc]]);
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Path,
+ split_path(Rest, [Segment|Acc])
+ end.
-spec list_match(tokens(), match_rule(), bindings())
-> {true, bindings(), undefined | tokens()} | false.
@@ -177,66 +176,30 @@ list_match(_List, _Match, _Binds) ->
split_host_test_() ->
%% {Host, Result}
Tests = [
- {<<"">>, {[], <<"">>, undefined}},
- {<<".........">>, {[], <<".........">>, undefined}},
- {<<"*">>, {[<<"*">>], <<"*">>, undefined}},
+ {<<"">>, []},
+ {<<"*">>, [<<"*">>]},
{<<"cowboy.ninenines.eu">>,
- {[<<"cowboy">>, <<"ninenines">>, <<"eu">>],
- <<"cowboy.ninenines.eu">>, undefined}},
- {<<"ninenines..eu">>,
- {[<<"ninenines">>, <<>>, <<"eu">>],
- <<"ninenines..eu">>, undefined}},
+ [<<"eu">>, <<"ninenines">>, <<"cowboy">>]},
{<<"ninenines.eu">>,
- {[<<"ninenines">>, <<"eu">>], <<"ninenines.eu">>, undefined}},
- {<<"ninenines.eu:8080">>,
- {[<<"ninenines">>, <<"eu">>], <<"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">>,
- {[<<"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">>],
- <<"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: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) end} || {P, R} <- Tests].
match_test_() ->
Dispatch = [
@@ -261,28 +224,30 @@ match_test_() ->
],
%% {Host, Path, Result}
Tests = [
- {[<<"any">>], [], {ok, match_any, [], []}},
- {[<<"www">>, <<"any">>, <<"ninenines">>, <<"eu">>],
- [<<"users">>, <<"42">>, <<"mails">>],
+ {<<"any">>, <<"/">>, {ok, match_any, [], []}},
+ {<<"www.any.ninenines.eu">>, <<"/users/42/mails">>,
{ok, match_any_subdomain_users, [], []}},
- {[<<"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">>],
+ {<<"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">>}]}},
- {[<<"erlang">>, <<"fr">>], '_',
+ {<<"erlang.fr">>, '_',
{ok, match_erlang_ext, [], [{ext, <<"fr">>}]}},
- {[<<"any">>], [<<"users">>, <<"444">>, <<"friends">>],
+ {<<"any">>, <<"/users/444/friends">>,
{ok, match_users_friends, [], [{id, <<"444">>}]}},
- {[<<"ninenines">>, <<"fr">>], [<<"threads">>, <<"987">>],
+ {<<"ninenines.fr">>, <<"/threads/987">>,
{ok, match_duplicate_vars, [we, {expect, two}, var, here],
- [{var, <<"fr">>}, {var, <<"987">>}]}}
+ [{var, <<"fr">>}, {var, <<"987">>}]}}
],
[{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, H, P)
end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].
match_info_test_() ->
@@ -295,24 +260,21 @@ match_info_test_() ->
]}
],
Tests = [
- {[<<"ninenines">>, <<"eu">>], [],
+ {<<"ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [], undefined}},
- {[<<"bugs">>, <<"ninenines">>, <<"eu">>], [],
+ {<<"bugs.ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [<<"bugs">>], undefined}},
- {[<<"cowboy">>, <<"bugs">>, <<"ninenines">>, <<"eu">>], [],
+ {<<"cowboy.bugs.ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [<<"cowboy">>, <<"bugs">>], undefined}},
- {[<<"www">>, <<"ninenines">>, <<"eu">>],
- [<<"pathinfo">>, <<"is">>, <<"next">>],
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next">>,
{ok, match_path, [], [], undefined, []}},
- {[<<"www">>, <<"ninenines">>, <<"eu">>],
- [<<"pathinfo">>, <<"is">>, <<"next">>, <<"path_info">>],
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/path_info">>,
{ok, match_path, [], [], undefined, [<<"path_info">>]}},
- {[<<"www">>, <<"ninenines">>, <<"eu">>],
- [<<"pathinfo">>, <<"is">>, <<"next">>, <<"foo">>, <<"bar">>],
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/foo/bar">>,
{ok, match_path, [], [], undefined, [<<"foo">>, <<"bar">>]}}
],
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
- R = match(H, P, Dispatch)
+ R = match(Dispatch, H, P)
end} || {H, P, R} <- Tests].
-endif.
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index f3457dc..e0b1632 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -42,41 +42,18 @@
-export([ce_identity/1]).
%% Interpretation.
--export([connection_to_atom/1]).
-export([version_to_binary/1]).
-export([urldecode/1]).
-export([urldecode/2]).
-export([urlencode/1]).
-export([urlencode/2]).
--export([x_www_form_urlencoded/2]).
-
--type method() :: 'OPTIONS' | 'GET' | 'HEAD'
- | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | binary().
--type uri() :: '*' | {absoluteURI, http | https, Host::binary(),
- Port::integer() | undefined, Path::binary()}
- | {scheme, Scheme::binary(), binary()}
- | {abs_path, binary()} | binary().
+-export([x_www_form_urlencoded/1]).
+
-type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}.
--type header() :: 'Cache-Control' | 'Connection' | 'Date' | 'Pragma'
- | 'Transfer-Encoding' | 'Upgrade' | 'Via' | 'Accept' | 'Accept-Charset'
- | 'Accept-Encoding' | 'Accept-Language' | 'Authorization' | 'From' | 'Host'
- | 'If-Modified-Since' | 'If-Match' | 'If-None-Match' | 'If-Range'
- | 'If-Unmodified-Since' | 'Max-Forwards' | 'Proxy-Authorization' | 'Range'
- | 'Referer' | 'User-Agent' | 'Age' | 'Location' | 'Proxy-Authenticate'
- | 'Public' | 'Retry-After' | 'Server' | 'Vary' | 'Warning'
- | 'Www-Authenticate' | 'Allow' | 'Content-Base' | 'Content-Encoding'
- | 'Content-Language' | 'Content-Length' | 'Content-Location'
- | 'Content-Md5' | 'Content-Range' | 'Content-Type' | 'Etag'
- | 'Expires' | 'Last-Modified' | 'Accept-Ranges' | 'Set-Cookie'
- | 'Set-Cookie2' | 'X-Forwarded-For' | 'Cookie' | 'Keep-Alive'
- | 'Proxy-Connection' | binary().
--type headers() :: [{header(), iodata()}].
+-type headers() :: [{binary(), iodata()}].
-type status() :: non_neg_integer() | binary().
--export_type([method/0]).
--export_type([uri/0]).
-export_type([version/0]).
--export_type([header/0]).
-export_type([headers/0]).
-export_type([status/0]).
@@ -795,20 +772,6 @@ ce_identity(Data) ->
%% Interpretation.
-%% @doc Walk through a tokens list and return whether
-%% the connection is keepalive or closed.
-%%
-%% The connection token is expected to be lower-case.
--spec connection_to_atom([binary()]) -> keepalive | close.
-connection_to_atom([]) ->
- keepalive;
-connection_to_atom([<<"keep-alive">>|_Tail]) ->
- keepalive;
-connection_to_atom([<<"close">>|_Tail]) ->
- close;
-connection_to_atom([_Any|Tail]) ->
- connection_to_atom(Tail).
-
%% @doc Convert an HTTP version tuple to its binary form.
-spec version_to_binary(version()) -> binary().
version_to_binary({1, 1}) -> <<"HTTP/1.1">>;
@@ -898,15 +861,14 @@ tohexu(C) when C < 17 -> $A + C - 10.
tohexl(C) when C < 10 -> $0 + C;
tohexl(C) when C < 17 -> $a + C - 10.
--spec x_www_form_urlencoded(binary(), fun((binary()) -> binary())) ->
- list({binary(), binary() | true}).
-x_www_form_urlencoded(<<>>, _URLDecode) ->
+-spec x_www_form_urlencoded(binary()) -> list({binary(), binary() | true}).
+x_www_form_urlencoded(<<>>) ->
[];
-x_www_form_urlencoded(Qs, URLDecode) ->
+x_www_form_urlencoded(Qs) ->
Tokens = binary:split(Qs, <<"&">>, [global, trim]),
[case binary:split(Token, <<"=">>) of
- [Token] -> {URLDecode(Token), true};
- [Name, Value] -> {URLDecode(Name), URLDecode(Value)}
+ [Token] -> {urldecode(Token), true};
+ [Name, Value] -> {urldecode(Name), urldecode(Value)}
end || Token <- Tokens].
%% Tests.
@@ -1052,16 +1014,6 @@ asctime_date_test_() ->
],
[{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests].
-connection_to_atom_test_() ->
- %% {Tokens, Result}
- Tests = [
- {[<<"close">>], close},
- {[<<"keep-alive">>], keepalive},
- {[<<"keep-alive">>, <<"upgrade">>], keepalive}
- ],
- [{lists:flatten(io_lib:format("~p", [T])),
- fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
-
content_type_test_() ->
%% {ContentType, Result}
Tests = [
@@ -1100,9 +1052,7 @@ x_www_form_urlencoded_test_() ->
{<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]},
{<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]}
],
- URLDecode = fun urldecode/1,
- [{Qs, fun() -> R = x_www_form_urlencoded(
- Qs, URLDecode) end} || {Qs, R} <- Tests].
+ [{Qs, fun() -> R = x_www_form_urlencoded(Qs) end} || {Qs, R} <- Tests].
urldecode_test_() ->
U = fun urldecode/2,
diff --git a/src/cowboy_multipart.erl b/src/cowboy_multipart.erl
index 4040073..fc889ef 100644
--- a/src/cowboy_multipart.erl
+++ b/src/cowboy_multipart.erl
@@ -23,7 +23,7 @@
-type more(T) :: T | {more, parser(T)}.
-type part_result() :: headers() | eof.
-type headers() :: {headers, http_headers(), body_cont()}.
--type http_headers() :: [{atom() | binary(), binary()}].
+-type http_headers() :: [{binary(), binary()}].
-type body_cont() :: cont(more(body_result())).
-type cont(T) :: fun(() -> T).
-type body_result() :: {body, binary(), body_cont()} | end_of_part().
@@ -135,7 +135,11 @@ parse_headers(Bin, Pattern) ->
parse_headers(Bin, Pattern, Acc) ->
case erlang:decode_packet(httph_bin, Bin, []) of
{ok, {http_header, _, Name, _, Value}, Rest} ->
- parse_headers(Rest, Pattern, [{Name, Value} | Acc]);
+ Name2 = case is_atom(Name) of
+ true -> cowboy_bstr:to_lower(atom_to_binary(Name, latin1));
+ false -> cowboy_bstr:to_lower(Name)
+ end,
+ parse_headers(Rest, Pattern, [{Name2, Value} | Acc]);
{ok, http_eoh, Rest} ->
Headers = lists:reverse(Acc),
{headers, Headers, fun () -> parse_body(Rest, Pattern) end};
@@ -205,7 +209,7 @@ multipart_test_() ->
{<<"preamble\r\n--boundary--">>, []},
{<<"--boundary--\r\nepilogue">>, []},
{<<"\r\n--boundary\r\nA:b\r\nC:d\r\n\r\n\r\n--boundary--">>,
- [{[{<<"A">>, <<"b">>}, {<<"C">>, <<"d">>}], <<>>}]},
+ [{[{<<"a">>, <<"b">>}, {<<"c">>, <<"d">>}], <<>>}]},
{
<<
"--boundary\r\nX-Name:answer\r\n\r\n42"
@@ -213,8 +217,8 @@ multipart_test_() ->
"\r\n--boundary--"
>>,
[
- {[{<<"X-Name">>, <<"answer">>}], <<"42">>},
- {[{'Server', <<"Cowboy">>}], <<"It rocks!\r\n">>}
+ {[{<<"x-name">>, <<"answer">>}], <<"42">>},
+ {[{<<"server">>, <<"Cowboy">>}], <<"It rocks!\r\n">>}
]
}
],
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl
index 2e734c5..bf81e52 100644
--- a/src/cowboy_protocol.erl
+++ b/src/cowboy_protocol.erl
@@ -20,11 +20,23 @@
%% <dt>dispatch</dt><dd>The dispatch list for this protocol.</dd>
%% <dt>max_empty_lines</dt><dd>Max number of empty lines before a request.
%% Defaults to 5.</dd>
+%% <dt>max_header_name_length</dt><dd>Max length allowed for header names.
+%% Defaults to 64.</dd>
+%% <dt>max_header_value_length</dt><dd>Max length allowed for header values.
+%% Defaults to 4096.</dd>
+%% <dt>max_headers</dt><dd>Max number of headers allowed.
+%% Defaults to 100.</dd>
+%% <dt>max_keepalive</dt><dd>Max number of requests allowed in a single
+%% keep-alive session. Defaults to infinity.</dd>
+%% <dt>max_request_line_length</dt><dd>Max length allowed for the request
+%% line. Defaults to 4096.</dd>
+%% <dt>onrequest</dt><dd>Optional fun that allows Req interaction before
+%% any dispatching is done. Host info, path info and bindings are thus
+%% not available at this point.</dd>
+%% <dt>onresponse</dt><dd>Optional fun that allows replacing a response
+%% sent by the application based on its status code or headers.</dd>
%% <dt>timeout</dt><dd>Time in milliseconds before an idle
%% connection is closed. Defaults to 5000 milliseconds.</dd>
-%% <dt>urldecode</dt><dd>Function and options argument to use when decoding
-%% URL encoded strings. Defaults to `{fun cowboy_http:urldecode/2, crash}'.
-%% </dd>
%% </dl>
%%
%% Note that there is no need to monitor these processes when using Cowboy as
@@ -39,12 +51,8 @@
%% Internal.
-export([init/4]).
--export([parse_request/1]).
--export([handler_loop/3]).
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
+-export([parse_request/3]).
+-export([handler_loop/4]).
-type onrequest_fun() :: fun((Req) -> Req).
-type onresponse_fun() ::
@@ -58,19 +66,16 @@
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(),
- max_line_length :: integer(),
+ max_empty_lines :: non_neg_integer(),
+ req_keepalive = 1 :: non_neg_integer(),
+ max_keepalive :: non_neg_integer(),
+ max_request_line_length :: non_neg_integer(),
+ max_header_name_length :: non_neg_integer(),
+ max_header_value_length :: non_neg_integer(),
+ max_headers :: non_neg_integer(),
timeout :: timeout(),
- buffer = <<>> :: binary(),
- 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()
@@ -99,244 +104,442 @@ get_value(Key, Opts, Default) ->
init(ListenerPid, Socket, Transport, Opts) ->
Dispatch = get_value(dispatch, Opts, []),
MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
+ MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
+ MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096),
+ MaxHeaders = get_value(max_headers, Opts, 100),
MaxKeepalive = get_value(max_keepalive, Opts, infinity),
- MaxLineLength = get_value(max_line_length, Opts, 4096),
+ MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096),
OnRequest = get_value(onrequest, Opts, undefined),
OnResponse = get_value(onresponse, Opts, undefined),
Timeout = get_value(timeout, Opts, 5000),
- URLDecDefault = {fun cowboy_http:urldecode/2, crash},
- URLDec = get_value(urldecode, Opts, URLDecDefault),
ok = ranch:accept_ack(ListenerPid),
- wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport,
- dispatch=Dispatch, max_empty_lines=MaxEmptyLines,
- max_keepalive=MaxKeepalive, max_line_length=MaxLineLength,
- timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse,
- urldecode=URLDec}).
+ wait_request(<<>>, #state{listener=ListenerPid, socket=Socket,
+ transport=Transport, dispatch=Dispatch,
+ max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
+ max_request_line_length=MaxRequestLineLength,
+ max_header_name_length=MaxHeaderNameLength,
+ max_header_value_length=MaxHeaderValueLength, max_headers=MaxHeaders,
+ timeout=Timeout, onrequest=OnRequest, onresponse=OnResponse}, 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(#state{}) -> ok.
+-spec parse_request(binary(), #state{}, non_neg_integer()) -> ok.
+%% Empty lines must be using \r\n.
+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(State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
- case erlang:decode_packet(http_bin, Buffer, []) of
- {ok, Request, Rest} -> request(Request, State#state{buffer=Rest});
- {more, _Length} when byte_size(Buffer) > MaxLength ->
- error_terminate(413, State);
- {more, _Length} -> wait_request(State);
- {error, _Reason} -> error_terminate(400, State)
+parse_request(Buffer, State=#state{max_request_line_length=MaxLength,
+ max_empty_lines=MaxEmpty}, ReqEmpty) ->
+ case binary:match(Buffer, <<"\n">>) of
+ nomatch when byte_size(Buffer) > MaxLength ->
+ error_terminate(414, State);
+ nomatch ->
+ wait_request(Buffer, State, ReqEmpty);
+ {1, _} when ReqEmpty =:= MaxEmpty ->
+ error_terminate(400, State);
+ {1, _} ->
+ << _:16, Rest/binary >> = Buffer,
+ parse_request(Rest, State, ReqEmpty + 1);
+ {_, _} ->
+ parse_method(Buffer, State, <<>>)
end.
--spec wait_request(#state{}) -> ok.
-wait_request(State=#state{socket=Socket, transport=Transport,
- timeout=T, buffer=Buffer}) ->
- case Transport:recv(Socket, 0, T) of
- {ok, Data} -> parse_request(State#state{
- buffer= << Buffer/binary, Data/binary >>});
- {error, _Reason} -> terminate(State)
+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.
--spec request({http_request, cowboy_http:method(), cowboy_http:uri(),
- cowboy_http:version()}, #state{}) -> ok.
-request({http_request, _Method, _URI, Version}, State)
- when Version =/= {1, 0}, Version =/= {1, 1} ->
- error_terminate(505, State);
-%% We still receive the original Host header.
-request({http_request, Method, {absoluteURI, _Scheme, _Host, _Port, Path},
- Version}, State) ->
- request({http_request, Method, {abs_path, Path}, Version}, State);
-request({http_request, Method, {abs_path, AbsPath}, Version},
- State=#state{socket=Socket, transport=Transport,
- req_keepalive=Keepalive, max_keepalive=MaxKeepalive,
- onresponse=OnResponse, urldecode={URLDecFun, URLDecArg}=URLDec}) ->
- URLDecode = fun(Bin) -> URLDecFun(Bin, URLDecArg) end,
- {PathTokens, RawPath, Qs}
- = cowboy_dispatcher:split_path(AbsPath, URLDecode),
- ConnAtom = if Keepalive < MaxKeepalive -> version_to_connection(Version);
- true -> close
- end,
- parse_header(cowboy_req:new(Socket, Transport, ConnAtom, Method, Version,
- RawPath, Qs, OnResponse, URLDec), State#state{path_tokens=PathTokens});
-request({http_request, Method, '*', Version},
- State=#state{socket=Socket, transport=Transport,
- req_keepalive=Keepalive, max_keepalive=MaxKeepalive,
- onresponse=OnResponse, urldecode=URLDec}) ->
- ConnAtom = if Keepalive < MaxKeepalive -> version_to_connection(Version);
- true -> close
- end,
- parse_header(cowboy_req:new(Socket, Transport, ConnAtom, Method, Version,
- <<"*">>, <<>>, OnResponse, URLDec), State#state{path_tokens='*'});
-request({http_request, _Method, _URI, _Version}, State) ->
- error_terminate(501, State);
-request({http_error, <<"\r\n">>},
- State=#state{req_empty_lines=N, max_empty_lines=N}) ->
+parse_uri(<< $\r, _/bits >>, State, _) ->
error_terminate(400, State);
-request({http_error, <<"\r\n">>}, State=#state{req_empty_lines=N}) ->
- parse_request(State#state{req_empty_lines=N + 1});
-request(_Any, State) ->
- error_terminate(400, State).
-
--spec parse_header(cowboy_req:req(), #state{}) -> ok.
-parse_header(Req, State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
- case erlang:decode_packet(httph_bin, Buffer, []) of
- {ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest});
- {more, _Length} when byte_size(Buffer) > MaxLength ->
- error_terminate(413, State);
- {more, _Length} -> wait_header(Req, State);
- {error, _Reason} -> 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.
--spec wait_header(cowboy_req:req(), #state{}) -> ok.
-wait_header(Req, State=#state{socket=Socket,
- transport=Transport, timeout=T, buffer=Buffer}) ->
- case Transport:recv(Socket, 0, T) of
- {ok, Data} -> parse_header(Req, State#state{
- buffer= << Buffer/binary, Data/binary >>});
- {error, timeout} -> error_terminate(408, State);
- {error, closed} -> terminate(State)
+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.
--spec header({http_header, integer(), cowboy_http:header(), any(), binary()}
- | http_eoh, cowboy_req:req(), #state{}) -> ok.
-header({http_header, _I, 'Host', _R, RawHost}, Req,
- State=#state{host_tokens=undefined, transport=Transport}) ->
- RawHost2 = cowboy_bstr:to_lower(RawHost),
- case catch cowboy_dispatcher:split_host(RawHost2) of
- {HostTokens, Host, undefined} ->
- Port = default_port(Transport:name()),
- parse_header(cowboy_req:set_host(Host, Port, RawHost, Req),
- State#state{host_tokens=HostTokens});
- {HostTokens, Host, Port} ->
- parse_header(cowboy_req:set_host(Host, Port, RawHost, Req),
- State#state{host_tokens=HostTokens});
- {'EXIT', _Reason} ->
- error_terminate(400, State)
- end;
-%% Ignore Host headers if we already have it.
-header({http_header, _I, 'Host', _R, _V}, Req, State) ->
- parse_header(Req, State);
-header({http_header, _I, 'Connection', _R, Connection}, Req,
- State=#state{req_keepalive=Keepalive, max_keepalive=MaxKeepalive})
- when Keepalive < MaxKeepalive ->
- parse_header(cowboy_req:set_connection(Connection, Req), State);
-header({http_header, _I, Field, _R, Value}, Req, State) ->
- Field2 = format_header(Field),
- parse_header(cowboy_req:add_header(Field2, Value, Req), State);
-%% The Host header is required in HTTP/1.1 and optional in HTTP/1.0.
-header(http_eoh, Req, State=#state{host_tokens=undefined,
- buffer=Buffer, transport=Transport}) ->
- case cowboy_req:version(Req) of
- {{1, 1}, _} ->
+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).
+
+%% Stop receiving data if we have more than allowed number of headers.
+wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, _, Headers)
+ when length(Headers) >= MaxHeaders ->
+ error_terminate(400, 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(400, State);
+ nomatch ->
+ 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_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(400, State);
- {{1, 0}, Req2} ->
- Port = default_port(Transport:name()),
- onrequest(
- cowboy_req:set_buffer(Buffer,
- cowboy_req:set_host(<<>>, Port, <<>>, Req2)),
- State#state{buffer= <<>>, host_tokens=[]})
+ nomatch ->
+ 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.
+
+%% 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.
+
+%% 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;
-header(http_eoh, Req, State=#state{buffer=Buffer}) ->
- onrequest(cowboy_req:set_buffer(Buffer, Req), State#state{buffer= <<>>});
-header(_Any, _Req, State) ->
- error_terminate(400, State).
+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(400, 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);
+ 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},
+ 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),
+ 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);
+ case cowboy_req:get(resp_state, Req2) of
+ 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}, Host, Path) ->
+ case cowboy_dispatcher:match(Dispatch, 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{}.
@@ -349,76 +552,74 @@ 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}) ->
+%% @private
+-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.
-next_request(Req, State=#state{
- req_keepalive=Keepalive}, HandlerRes) ->
+next_request(Req, State=#state{req_keepalive=Keepalive}, HandlerRes) ->
cowboy_req:ensure_response(Req, 204),
- {BodyRes, Buffer} = case cowboy_req:skip_body(Req) of
- {ok, Req2} -> {ok, cowboy_req:get_buffer(Req2)};
- {error, _} -> {close, <<>>}
+ {BodyRes, [Buffer, Connection]} = case cowboy_req:skip_body(Req) of
+ {ok, Req2} -> {ok, cowboy_req:get([buffer, connection], Req2)};
+ {error, _} -> {close, [<<>>, close]}
end,
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
- case {HandlerRes, BodyRes, cowboy_req:get_connection(Req)} of
+ case {HandlerRes, BodyRes, Connection} of
{ok, ok, keepalive} ->
- ?MODULE:parse_request(State#state{
- buffer=Buffer, host_tokens=undefined, path_tokens=undefined,
- req_empty_lines=0, req_keepalive=Keepalive + 1});
+ ?MODULE:parse_request(Buffer, State#state{
+ req_keepalive=Keepalive + 1}, 0);
_Closed ->
terminate(State)
end.
@@ -431,7 +632,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)),
ok
end,
terminate(State).
@@ -440,55 +642,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(cowboy_http:version()) -> keepalive | close.
-version_to_connection({1, 1}) -> keepalive;
-version_to_connection(_Any) -> close.
-
--spec default_port(atom()) -> 80 | 443.
-default_port(ssl) -> 443;
-default_port(_) -> 80.
-
-%% @todo While 32 should be enough for everybody, we should probably make
-%% this configurable or something.
--spec format_header(atom()) -> atom(); (binary()) -> binary().
-format_header(Field) when is_atom(Field) ->
- Field;
-format_header(Field) when byte_size(Field) =< 20; byte_size(Field) > 32 ->
- Field;
-format_header(Field) ->
- format_header(Field, true, <<>>).
-
--spec format_header(binary(), boolean(), binary()) -> binary().
-format_header(<<>>, _Any, Acc) ->
- Acc;
-%% Replicate a bug in OTP for compatibility reasons when there's a - right
-%% after another. Proper use should always be 'true' instead of 'not Bool'.
-format_header(<< $-, Rest/bits >>, Bool, Acc) ->
- format_header(Rest, not Bool, << Acc/binary, $- >>);
-format_header(<< C, Rest/bits >>, true, Acc) ->
- format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_upper(C)) >>);
-format_header(<< C, Rest/bits >>, false, Acc) ->
- format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_lower(C)) >>).
-
-%% Tests.
-
--ifdef(TEST).
-
-format_header_test_() ->
- %% {Header, Result}
- Tests = [
- {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
- {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
- {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
- {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
- %% These last tests ensures we're formatting headers exactly like OTP.
- %% Even though it's dumb, it's better for compatibility reasons.
- {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--version">>},
- {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
- ],
- [{H, fun() -> R = format_header(H) end} || {H, R} <- Tests].
-
--endif.
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 151b4dc..cdd1e06 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/13]).
-export([method/1]).
-export([version/1]).
-export([peer/1]).
@@ -56,6 +56,7 @@
-export([qs_val/2]).
-export([qs_val/3]).
-export([qs_vals/1]).
+-export([fragment/1]).
-export([host_url/1]).
-export([url/1]).
-export([binding/2]).
@@ -103,14 +104,9 @@
-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([get/2]).
+-export([set/2]).
-export([set_bindings/4]).
--export([get_resp_state/1]).
--export([get_buffer/1]).
--export([get_connection/1]).
%% Misc API.
-export([compact/1]).
@@ -122,6 +118,8 @@
-include_lib("eunit/include/eunit.hrl").
-endif.
+-type resp_body_fun() :: fun(() -> {sent, non_neg_integer()}).
+
-record(http_req, {
%% Transport.
socket = undefined :: undefined | inet:socket(),
@@ -130,7 +128,7 @@
%% Request.
pid = undefined :: pid(),
- method = 'GET' :: cowboy_http:method(),
+ method = <<"GET">> :: binary(),
version = {1, 1} :: cowboy_http:version(),
peer = undefined :: undefined | {inet:ip_address(), inet:port_number()},
host = undefined :: undefined | binary(),
@@ -140,6 +138,7 @@
path_info = undefined :: undefined | cowboy_dispatcher:tokens(),
qs = undefined :: binary(),
qs_vals = undefined :: undefined | list({binary(), binary() | true}),
+ fragment = undefined :: binary(),
bindings = undefined :: undefined | cowboy_dispatcher:bindings(),
headers = [] :: cowboy_http:headers(),
p_headers = [] :: [any()], %% @todo Improve those specs.
@@ -154,12 +153,10 @@
%% Response.
resp_state = waiting :: locked | waiting | chunks | done,
resp_headers = [] :: cowboy_http:headers(),
- resp_body = <<>> :: iodata()
- | {non_neg_integer(), fun(() -> {sent, non_neg_integer()})},
+ resp_body = <<>> :: iodata() | {non_neg_integer(), resp_body_fun()},
%% Functions.
- onresponse = undefined :: undefined | cowboy_protocol:onresponse_fun(),
- urldecode :: {fun((binary(), T) -> binary()), T}
+ onresponse = undefined :: undefined | cowboy_protocol:onresponse_fun()
}).
-opaque req() :: #http_req{}.
@@ -171,18 +168,40 @@
%%
%% This function takes care of setting the owner's pid to self().
%% @private
--spec new(inet:socket(), module(), keepalive | close,
- cowboy_http:method(), cowboy_http:version(), binary(), binary(),
- undefined | fun(), undefined | {fun(), atom()})
+%%
+%% Since we always need to parse the Connection header, we do it
+%% in an optimized way and add the parsed value to p_headers' cache.
+-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())
-> req().
-new(Socket, Transport, Connection, Method, Version, Path, Qs,
- OnResponse, URLDecode) ->
- #http_req{socket=Socket, transport=Transport, connection=Connection,
- pid=self(), method=Method, version=Version, path=Path, qs=Qs,
- onresponse=OnResponse, urldecode=URLDecode}.
+new(Socket, Transport, Method, Path, Query, Fragment,
+ Version, Headers, Host, Port, Buffer, CanKeepalive,
+ OnResponse) ->
+ Req = #http_req{socket=Socket, transport=Transport, pid=self(),
+ method=Method, path=Path, qs=Query, fragment=Fragment, version=Version,
+ headers=Headers, host=Host, port=Port, buffer=Buffer,
+ onresponse=OnResponse},
+ case CanKeepalive of
+ false ->
+ Req#http_req{connection=close};
+ true ->
+ case lists:keyfind(<<"connection">>, 1, Headers) of
+ false when Version =:= {1, 1} ->
+ Req; %% keepalive
+ false ->
+ Req#http_req{connection=close};
+ {_, ConnectionHeader} ->
+ Tokens = parse_connection_before(ConnectionHeader, []),
+ Connection = connection_to_atom(Tokens),
+ Req#http_req{connection=Connection,
+ p_headers=[{<<"connection">>, Tokens}]}
+ end
+ end.
%% @doc Return the HTTP method of the request.
--spec method(Req) -> {cowboy_http:method(), Req} when Req::req().
+-spec method(Req) -> {binary(), Req} when Req::req().
method(Req) ->
{Req#http_req.method, Req}.
@@ -203,8 +222,8 @@ peer(Req) ->
%% @doc Returns the peer address calculated from headers.
-spec peer_addr(Req) -> {inet:ip_address(), Req} when Req::req().
peer_addr(Req = #http_req{}) ->
- {RealIp, Req1} = header(<<"X-Real-Ip">>, Req),
- {ForwardedForRaw, Req2} = header(<<"X-Forwarded-For">>, Req1),
+ {RealIp, Req1} = header(<<"x-real-ip">>, Req),
+ {ForwardedForRaw, Req2} = header(<<"x-forwarded-for">>, Req1),
{{PeerIp, _PeerPort}, Req3} = peer(Req2),
ForwardedFor = case ForwardedForRaw of
undefined ->
@@ -267,10 +286,9 @@ qs_val(Name, Req) when is_binary(Name) ->
%% missing.
-spec qs_val(binary(), Req, Default)
-> {binary() | true | Default, Req} when Req::req(), Default::any().
-qs_val(Name, Req=#http_req{qs=RawQs, qs_vals=undefined,
- urldecode={URLDecFun, URLDecArg}}, Default) when is_binary(Name) ->
- QsVals = cowboy_http:x_www_form_urlencoded(
- RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end),
+qs_val(Name, Req=#http_req{qs=RawQs, qs_vals=undefined}, Default)
+ when is_binary(Name) ->
+ QsVals = cowboy_http:x_www_form_urlencoded(RawQs),
qs_val(Name, Req#http_req{qs_vals=QsVals}, Default);
qs_val(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.qs_vals) of
@@ -280,14 +298,17 @@ qs_val(Name, Req, Default) ->
%% @doc Return the full list of query string values.
-spec qs_vals(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
-qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined,
- urldecode={URLDecFun, URLDecArg}}) ->
- QsVals = cowboy_http:x_www_form_urlencoded(
- RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end),
+qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) ->
+ QsVals = cowboy_http:x_www_form_urlencoded(RawQs),
qs_vals(Req#http_req{qs_vals=QsVals});
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
{QsVals, Req}.
+%% @doc Return the raw fragment directly taken from the request.
+-spec fragment(Req) -> {binary(), Req} when Req::req().
+fragment(Req) ->
+ {Req#http_req.fragment, Req}.
+
%% @doc Return the request URL as a binary without the path and query string.
%%
%% The URL includes the scheme, host and port only.
@@ -308,15 +329,19 @@ host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) ->
%% @doc Return the full request URL as a binary.
%%
-%% The URL includes the scheme, host, port, path and query string.
+%% The URL includes the scheme, host, port, path, query string and fragment.
-spec url(Req) -> {binary(), Req} when Req::req().
-url(Req=#http_req{path=Path, qs=QS}) ->
+url(Req=#http_req{path=Path, qs=QS, fragment=Fragment}) ->
{HostURL, Req2} = host_url(Req),
QS2 = case QS of
<<>> -> <<>>;
_ -> << "?", QS/binary >>
end,
- {<< HostURL/binary, Path/binary, QS2/binary >>, Req2}.
+ Fragment2 = case Fragment of
+ <<>> -> <<>>;
+ _ -> << "#", Fragment/binary >>
+ end,
+ {<< HostURL/binary, Path/binary, QS2/binary, Fragment2/binary >>, Req2}.
%% @equiv binding(Name, Req, undefined)
-spec binding(atom(), Req) -> {binary() | undefined, Req} when Req::req().
@@ -339,15 +364,15 @@ bindings(Req) ->
{Req#http_req.bindings, Req}.
%% @equiv header(Name, Req, undefined)
--spec header(atom() | binary(), Req)
+-spec header(binary(), Req)
-> {binary() | undefined, Req} when Req::req().
-header(Name, Req) when is_atom(Name) orelse is_binary(Name) ->
+header(Name, Req) ->
header(Name, Req, undefined).
%% @doc Return the header value for the given key, or a default if missing.
--spec header(atom() | binary(), Req, Default)
+-spec header(binary(), Req, Default)
-> {binary() | Default, Req} when Req::req(), Default::any().
-header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) ->
+header(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.headers) of
{Name, Value} -> {Value, Req};
false -> {Default, Req}
@@ -363,7 +388,7 @@ headers(Req) ->
%% When the value isn't found, a proper default value for the type
%% returned is used as a return value.
%% @see parse_header/3
--spec parse_header(cowboy_http:header(), Req)
+-spec parse_header(binary(), Req)
-> {ok, any(), Req} | {undefined, binary(), Req}
| {error, badarg} when Req::req().
parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
@@ -373,76 +398,72 @@ parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
end.
%% @doc Default values for semantic header parsing.
--spec parse_header_default(cowboy_http:header()) -> any().
-parse_header_default('Connection') -> [];
-parse_header_default('Transfer-Encoding') -> [<<"identity">>];
+-spec parse_header_default(binary()) -> any().
+parse_header_default(<<"connection">>) -> [];
+parse_header_default(<<"transfer-encoding">>) -> [<<"identity">>];
parse_header_default(_Name) -> undefined.
%% @doc Semantically parse headers.
%%
%% When the header is unknown, the value is returned directly without parsing.
--spec parse_header(cowboy_http:header(), Req, any())
+-spec parse_header(binary(), Req, any())
-> {ok, any(), Req} | {undefined, binary(), Req}
| {error, badarg} when Req::req().
-parse_header(Name, Req, Default) when Name =:= 'Accept' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:media_range/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Accept-Charset' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept-charset">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Accept-Encoding' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept-encoding">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:conneg/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Accept-Language' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept-language">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Connection' ->
- parse_header(Name, Req, Default,
- fun (Value) ->
- cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
- end);
-parse_header(Name, Req, Default) when Name =:= 'Content-Length' ->
+parse_header(Name, Req, Default) when Name =:= <<"content-length">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:digits(Value)
end);
-parse_header(Name, Req, Default) when Name =:= 'Content-Type' ->
+parse_header(Name, Req, Default) when Name =:= <<"content-type">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:content_type(Value)
end);
-parse_header(Name, Req, Default) when Name =:= <<"Expect">> ->
+parse_header(Name, Req, Default) when Name =:= <<"expect">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2)
end);
parse_header(Name, Req, Default)
- when Name =:= 'If-Match'; Name =:= 'If-None-Match' ->
+ when Name =:= <<"if-match">>; Name =:= <<"if-none-match">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:entity_tag_match(Value)
end);
parse_header(Name, Req, Default)
- when Name =:= 'If-Modified-Since'; Name =:= 'If-Unmodified-Since' ->
+ when Name =:= <<"if-modified-since">>;
+ Name =:= <<"if-unmodified-since">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:http_date(Value)
end);
%% @todo Extension parameters.
-parse_header(Name, Req, Default) when Name =:= 'Transfer-Encoding' ->
+parse_header(Name, Req, Default) when Name =:= <<"transfer-encoding">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Upgrade' ->
+parse_header(Name, Req, Default) when Name =:= <<"upgrade">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
@@ -475,7 +496,7 @@ cookie(Name, Req) when is_binary(Name) ->
-spec cookie(binary(), Req, Default)
-> {binary() | true | Default, Req} when Req::req(), Default::any().
cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) ->
- case header('Cookie', Req) of
+ case header(<<"cookie">>, Req) of
{undefined, Req2} ->
{Default, Req2#http_req{cookies=[]}};
{RawCookie, Req2} ->
@@ -491,7 +512,7 @@ cookie(Name, Req, Default) ->
%% @doc Return the full list of cookie values.
-spec cookies(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
cookies(Req=#http_req{cookies=undefined}) ->
- case header('Cookie', Req) of
+ case header(<<"cookie">>, Req) of
{undefined, Req2} ->
{[], Req2#http_req{cookies=[]}};
{RawCookie, Req2} ->
@@ -532,8 +553,8 @@ set_meta(Name, Value, Req=#http_req{meta=Meta}) ->
%% @doc Return whether the request message has a body.
-spec has_body(Req) -> {boolean(), Req} when Req::req().
has_body(Req) ->
- Has = lists:keymember('Content-Length', 1, Req#http_req.headers) orelse
- lists:keymember('Transfer-Encoding', 1, Req#http_req.headers),
+ Has = lists:keymember(<<"content-length">>, 1, Req#http_req.headers) orelse
+ lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers),
{Has, Req}.
%% @doc Return the request message body length, if known.
@@ -542,11 +563,11 @@ has_body(Req) ->
%% and the body hasn't been read at the time of the call.
-spec body_length(Req) -> {undefined | non_neg_integer(), Req} when Req::req().
body_length(Req) ->
- case lists:keymember('Transfer-Encoding', 1, Req#http_req.headers) of
+ case lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers) of
true ->
{undefined, Req};
false ->
- {ok, Length, Req2} = parse_header('Content-Length', Req, 0),
+ {ok, Length, Req2} = parse_header(<<"content-length">>, Req, 0),
{Length, Req2}
end.
@@ -586,7 +607,7 @@ init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
| {done, Req} | {error, atom()} when Req::req().
stream_body(Req=#http_req{body_state=waiting,
version=Version, transport=Transport, socket=Socket}) ->
- case parse_header(<<"Expect">>, Req) of
+ case parse_header(<<"expect">>, Req) of
{ok, [<<"100-continue">>], Req1} ->
HTTPVer = cowboy_http:version_to_binary(Version),
Transport:send(Socket,
@@ -594,7 +615,7 @@ stream_body(Req=#http_req{body_state=waiting,
{ok, undefined, Req1} ->
ok
end,
- case parse_header('Transfer-Encoding', Req1) of
+ case parse_header(<<"transfer-encoding">>, Req1) of
{ok, [<<"chunked">>], Req2} ->
stream_body(Req2#http_req{body_state=
{stream, fun cowboy_http:te_chunked/2, {0, 0},
@@ -657,13 +678,13 @@ transfer_decode(Data, Req=#http_req{
-> Req when Req::req().
transfer_decode_done(Length, Rest, Req=#http_req{
headers=Headers, p_headers=PHeaders}) ->
- Headers2 = lists:keystore('Content-Length', 1, Headers,
- {'Content-Length', list_to_binary(integer_to_list(Length))}),
+ Headers2 = lists:keystore(<<"content-length">>, 1, Headers,
+ {<<"content-length">>, list_to_binary(integer_to_list(Length))}),
%% At this point we just assume TEs were all decoded.
- Headers3 = lists:keydelete('Transfer-Encoding', 1, Headers2),
- PHeaders2 = lists:keystore('Content-Length', 1, PHeaders,
- {'Content-Length', Length}),
- PHeaders3 = lists:keydelete('Transfer-Encoding', 1, PHeaders2),
+ Headers3 = lists:keydelete(<<"transfer-encoding">>, 1, Headers2),
+ PHeaders2 = lists:keystore(<<"content-length">>, 1, PHeaders,
+ {<<"content-length">>, Length}),
+ PHeaders3 = lists:keydelete(<<"transfer-encoding">>, 1, PHeaders2),
Req#http_req{buffer=Rest, body_state=done,
headers=Headers3, p_headers=PHeaders3}.
@@ -718,11 +739,10 @@ skip_body(Req) ->
-spec body_qs(Req)
-> {ok, [{binary(), binary() | true}], Req} | {error, atom()}
when Req::req().
-body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) ->
+body_qs(Req) ->
case body(Req) of
{ok, Body, Req2} ->
- {ok, cowboy_http:x_www_form_urlencoded(
- Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2};
+ {ok, cowboy_http:x_www_form_urlencoded(Body), Req2};
{error, Reason} ->
{error, Reason}
end.
@@ -743,9 +763,9 @@ body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) ->
| {end_of_part | eof, Req} when Req::req().
multipart_data(Req=#http_req{body_state=waiting}) ->
{ok, {<<"multipart">>, _SubType, Params}, Req2} =
- parse_header('Content-Type', Req),
+ parse_header(<<"content-type">>, Req),
{_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params),
- {ok, Length, Req3} = parse_header('Content-Length', Req2),
+ {ok, Length, Req3} = parse_header(<<"content-length">>, Req2),
multipart_data(Req3, Length, {more, cowboy_multipart:parser(Boundary)});
multipart_data(Req=#http_req{multipart={Length, Cont}}) ->
multipart_data(Req, Length, Cont());
@@ -796,11 +816,10 @@ set_resp_cookie(Name, Value, Options, Req) ->
set_resp_header(HeaderName, HeaderValue, Req).
%% @doc Add a header to the response.
--spec set_resp_header(cowboy_http:header(), iodata(), Req)
+-spec set_resp_header(binary(), iodata(), Req)
-> Req when Req::req().
set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) ->
- NameBin = header_to_binary(Name),
- Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}.
+ Req#http_req{resp_headers=[{Name, Value}|RespHeaders]}.
%% @doc Add a body to the response.
%%
@@ -825,16 +844,15 @@ set_resp_body(Body, Req) ->
%% `reply/3'.
%%
%% @see cowboy_req:transport/1.
--spec set_resp_body_fun(non_neg_integer(),
- fun(() -> {sent, non_neg_integer()}), Req) -> Req when Req::req().
+-spec set_resp_body_fun(non_neg_integer(), resp_body_fun(), Req)
+ -> Req when Req::req().
set_resp_body_fun(StreamLen, StreamFun, Req) ->
Req#http_req{resp_body={StreamLen, StreamFun}}.
%% @doc Return whether the given header has been set for the response.
--spec has_resp_header(cowboy_http:header(), req()) -> boolean().
+-spec has_resp_header(binary(), req()) -> boolean().
has_resp_header(Name, #http_req{resp_headers=RespHeaders}) ->
- NameBin = header_to_binary(Name),
- lists:keymember(NameBin, 1, RespHeaders).
+ lists:keymember(Name, 1, RespHeaders).
%% @doc Return whether a body has been set for the response.
-spec has_resp_body(req()) -> boolean().
@@ -844,7 +862,7 @@ has_resp_body(#http_req{resp_body=RespBody}) ->
iolist_size(RespBody) > 0.
%% Remove a header previously set for the response.
--spec delete_resp_header(cowboy_http:header(), Req)
+-spec delete_resp_header(binary(), Req)
-> Req when Req::req().
delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
RespHeaders2 = lists:keydelete(Name, 1, RespHeaders),
@@ -862,29 +880,35 @@ reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
reply(Status, Headers, Body, Req).
%% @doc Send a reply to the client.
--spec reply(cowboy_http:status(), cowboy_http:headers(), iodata(), Req)
+-spec reply(cowboy_http:status(), cowboy_http:headers(),
+ iodata() | {non_neg_integer() | resp_body_fun()}, Req)
-> {ok, Req} when Req::req().
-reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport,
+reply(Status, Headers, Body, Req=#http_req{
version=Version, connection=Connection,
method=Method, resp_state=waiting, resp_headers=RespHeaders}) ->
RespConn = response_connection(Headers, Connection),
- ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end,
HTTP11Headers = case Version of
- {1, 1} -> [{<<"Connection">>, atom_to_connection(Connection)}];
+ {1, 1} -> [{<<"connection">>, atom_to_connection(Connection)}];
_ -> []
end,
- {ReplyType, Req2} = response(Status, Headers, RespHeaders, [
- {<<"Content-Length">>, integer_to_list(ContentLen)},
- {<<"Date">>, cowboy_clock:rfc1123()},
- {<<"Server">>, <<"Cowboy">>}
- |HTTP11Headers], Req),
- if Method =:= 'HEAD' -> ok;
- ReplyType =:= hook -> ok; %% Hook replied for us, stop there.
- true ->
- case Body of
- {_, StreamFun} -> StreamFun();
- _ -> Transport:send(Socket, Body)
- end
+ case Body of
+ {ContentLength, BodyFun} ->
+ {RespType, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"content-length">>, integer_to_list(ContentLength)},
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ |HTTP11Headers], <<>>, Req),
+ if RespType =/= hook, Method =/= <<"HEAD">> -> BodyFun();
+ true -> ok
+ end;
+ _ ->
+ {_, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"content-length">>, integer_to_list(iolist_size(Body))},
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ |HTTP11Headers],
+ case Method of <<"HEAD">> -> <<>>; _ -> Body end,
+ Req)
end,
{ok, Req2#http_req{connection=RespConn, resp_state=done,
resp_headers=[], resp_body= <<>>}}.
@@ -904,14 +928,14 @@ chunked_reply(Status, Headers, Req=#http_req{
RespConn = response_connection(Headers, Connection),
HTTP11Headers = case Version of
{1, 1} -> [
- {<<"Connection">>, atom_to_connection(Connection)},
- {<<"Transfer-Encoding">>, <<"chunked">>}];
+ {<<"connection">>, atom_to_connection(Connection)},
+ {<<"transfer-encoding">>, <<"chunked">>}];
_ -> []
end,
{_, Req2} = response(Status, Headers, RespHeaders, [
- {<<"Date">>, cowboy_clock:rfc1123()},
- {<<"Server">>, <<"Cowboy">>}
- |HTTP11Headers], Req),
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ |HTTP11Headers], <<>>, Req),
{ok, Req2#http_req{connection=RespConn, resp_state=chunks,
resp_headers=[], resp_body= <<>>}}.
@@ -919,7 +943,7 @@ chunked_reply(Status, Headers, Req=#http_req{
%%
%% A chunked reply must have been initiated before calling this function.
-spec chunk(iodata(), req()) -> ok | {error, atom()}.
-chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) ->
+chunk(_Data, #http_req{method= <<"HEAD">>}) ->
ok;
chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) ->
Transport:send(Socket, Data);
@@ -934,8 +958,8 @@ chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
upgrade_reply(Status, Headers, Req=#http_req{
resp_state=waiting, resp_headers=RespHeaders}) ->
{_, Req2} = response(Status, Headers, RespHeaders, [
- {<<"Connection">>, <<"Upgrade">>}
- ], Req),
+ {<<"connection">>, <<"Upgrade">>}
+ ], <<>>, Req),
{ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
%% @doc Ensure the response has been sent fully.
@@ -950,7 +974,7 @@ ensure_response(Req=#http_req{resp_state=waiting}, Status) ->
_ = reply(Status, [], [], Req),
ok;
%% Terminate the chunked body for HTTP/1.1 only.
-ensure_response(#http_req{method='HEAD', resp_state=chunks}, _) ->
+ensure_response(#http_req{method= <<"HEAD">>, resp_state=chunks}, _) ->
ok;
ensure_response(#http_req{version={1, 0}, resp_state=chunks}, _) ->
ok;
@@ -962,29 +986,70 @@ 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]}.
+-spec get(atom(), req()) -> any(); ([atom()], req()) -> any().
+get(List, Req) when is_list(List) ->
+ [g(Atom, Req) || Atom <- List];
+get(Atom, Req) when is_atom(Atom) ->
+ g(Atom, Req).
+
+g(bindings, #http_req{bindings=Ret}) -> Ret;
+g(body_state, #http_req{body_state=Ret}) -> Ret;
+g(buffer, #http_req{buffer=Ret}) -> Ret;
+g(connection, #http_req{connection=Ret}) -> Ret;
+g(cookies, #http_req{cookies=Ret}) -> Ret;
+g(fragment, #http_req{fragment=Ret}) -> Ret;
+g(headers, #http_req{headers=Ret}) -> Ret;
+g(host, #http_req{host=Ret}) -> Ret;
+g(host_info, #http_req{host_info=Ret}) -> Ret;
+g(meta, #http_req{meta=Ret}) -> Ret;
+g(method, #http_req{method=Ret}) -> Ret;
+g(multipart, #http_req{multipart=Ret}) -> Ret;
+g(onresponse, #http_req{onresponse=Ret}) -> Ret;
+g(p_headers, #http_req{p_headers=Ret}) -> Ret;
+g(path, #http_req{path=Ret}) -> Ret;
+g(path_info, #http_req{path_info=Ret}) -> Ret;
+g(peer, #http_req{peer=Ret}) -> Ret;
+g(pid, #http_req{pid=Ret}) -> Ret;
+g(port, #http_req{port=Ret}) -> Ret;
+g(qs, #http_req{qs=Ret}) -> Ret;
+g(qs_vals, #http_req{qs_vals=Ret}) -> Ret;
+g(resp_body, #http_req{resp_body=Ret}) -> Ret;
+g(resp_headers, #http_req{resp_headers=Ret}) -> Ret;
+g(resp_state, #http_req{resp_state=Ret}) -> Ret;
+g(socket, #http_req{socket=Ret}) -> Ret;
+g(transport, #http_req{transport=Ret}) -> Ret;
+g(version, #http_req{version=Ret}) -> Ret.
%% @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(cowboy_http:header(), 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}.
+-spec set([{atom(), any()}], Req) -> Req when Req::req().
+set([], Req) -> Req;
+set([{bindings, Val}|Tail], Req) -> set(Tail, Req#http_req{bindings=Val});
+set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val});
+set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val});
+set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val});
+set([{cookies, Val}|Tail], Req) -> set(Tail, Req#http_req{cookies=Val});
+set([{fragment, Val}|Tail], Req) -> set(Tail, Req#http_req{fragment=Val});
+set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val});
+set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val});
+set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val});
+set([{meta, Val}|Tail], Req) -> set(Tail, Req#http_req{meta=Val});
+set([{method, Val}|Tail], Req) -> set(Tail, Req#http_req{method=Val});
+set([{multipart, Val}|Tail], Req) -> set(Tail, Req#http_req{multipart=Val});
+set([{onresponse, Val}|Tail], Req) -> set(Tail, Req#http_req{onresponse=Val});
+set([{p_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{p_headers=Val});
+set([{path, Val}|Tail], Req) -> set(Tail, Req#http_req{path=Val});
+set([{path_info, Val}|Tail], Req) -> set(Tail, Req#http_req{path_info=Val});
+set([{peer, Val}|Tail], Req) -> set(Tail, Req#http_req{peer=Val});
+set([{pid, Val}|Tail], Req) -> set(Tail, Req#http_req{pid=Val});
+set([{port, Val}|Tail], Req) -> set(Tail, Req#http_req{port=Val});
+set([{qs, Val}|Tail], Req) -> set(Tail, Req#http_req{qs=Val});
+set([{qs_vals, Val}|Tail], Req) -> set(Tail, Req#http_req{qs_vals=Val});
+set([{resp_body, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_body=Val});
+set([{resp_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_headers=Val});
+set([{resp_state, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_state=Val});
+set([{socket, Val}|Tail], Req) -> set(Tail, Req#http_req{socket=Val});
+set([{transport, Val}|Tail], Req) -> set(Tail, Req#http_req{transport=Val});
+set([{version, Val}|Tail], Req) -> set(Tail, Req#http_req{version=Val}).
%% @private
-spec set_bindings(cowboy_dispatcher:tokens(), cowboy_dispatcher:tokens(),
@@ -993,21 +1058,6 @@ set_bindings(HostInfo, PathInfo, Bindings, Req) ->
Req#http_req{host_info=HostInfo, path_info=PathInfo,
bindings=Bindings}.
-%% @private
--spec get_resp_state(req()) -> locked | waiting | chunks | done.
-get_resp_state(#http_req{resp_state=RespState}) ->
- RespState.
-
-%% @private
--spec get_buffer(req()) -> binary().
-get_buffer(#http_req{buffer=Buffer}) ->
- Buffer.
-
-%% @private
--spec get_connection(req()) -> keepalive | close.
-get_connection(#http_req{connection=Connection}) ->
- Connection.
-
%% Misc API.
%% @doc Compact the request data by removing all non-system information.
@@ -1050,9 +1100,9 @@ transport(#http_req{transport=Transport, socket=Socket}) ->
%% Internal.
-spec response(cowboy_http:status(), cowboy_http:headers(),
- cowboy_http:headers(), cowboy_http:headers(), Req)
+ cowboy_http:headers(), cowboy_http:headers(), iodata(), Req)
-> {normal | hook, Req} when Req::req().
-response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{
+response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
socket=Socket, transport=Transport, version=Version,
pid=ReqPid, onresponse=OnResponse}) ->
FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders),
@@ -1070,7 +1120,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{
(status(Status))/binary, "\r\n" >>,
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
|| {Key, Value} <- FullHeaders],
- Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]),
+ Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body]),
ReqPid ! {?MODULE, resp_sent},
normal;
_ ->
@@ -1084,25 +1134,19 @@ response_connection([], Connection) ->
Connection;
response_connection([{Name, Value}|Tail], Connection) ->
case Name of
- 'Connection' -> response_connection_parse(Value);
- Name when is_atom(Name) -> response_connection(Tail, Connection);
- Name ->
- Name2 = cowboy_bstr:to_lower(Name),
- case Name2 of
- <<"connection">> -> response_connection_parse(Value);
- _Any -> response_connection(Tail, Connection)
- end
+ <<"connection">> -> response_connection_parse(Value);
+ _ -> response_connection(Tail, Connection)
end.
-spec response_connection_parse(binary()) -> keepalive | close.
response_connection_parse(ReplyConn) ->
Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2),
- cowboy_http:connection_to_atom(Tokens).
+ connection_to_atom(Tokens).
-spec response_merge_headers(cowboy_http:headers(), cowboy_http:headers(),
cowboy_http:headers()) -> cowboy_http:headers().
response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
- Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers],
+ Headers2 = [{Key, Value} || {Key, Value} <- Headers],
merge_headers(
merge_headers(Headers2, RespHeaders),
DefaultHeaders).
@@ -1114,7 +1158,7 @@ merge_headers(Headers, []) ->
merge_headers(Headers, [{Name, Value}|Tail]) ->
Headers2 = case lists:keymember(Name, 1, Headers) of
true -> Headers;
- false -> Headers ++ [{Name, Value}]
+ false -> [{Name, Value}|Headers]
end,
merge_headers(Headers2, Tail).
@@ -1125,6 +1169,74 @@ atom_to_connection(keepalive) ->
atom_to_connection(close) ->
<<"close">>.
+%% Optimized parsing functions for the Connection header.
+parse_connection_before(<<>>, Acc) ->
+ lists:reverse(Acc);
+parse_connection_before(<< C, Rest/bits >>, Acc)
+ when C =:= $,; C =:= $\s; C =:= $\t ->
+ parse_connection_before(Rest, Acc);
+parse_connection_before(Buffer, Acc) ->
+ parse_connection(Buffer, Acc, <<>>).
+
+%% An evil block of code appeared!
+parse_connection(<<>>, Acc, <<>>) ->
+ lists:reverse(Acc);
+parse_connection(<<>>, Acc, Token) ->
+ lists:reverse([Token|Acc]);
+parse_connection(<< C, Rest/bits >>, Acc, Token)
+ when C =:= $,; C =:= $\s; C =:= $\t ->
+ parse_connection_after(Rest, [Token|Acc]);
+parse_connection(<< C, Rest/bits >>, Acc, Token) ->
+ case C of
+ $A -> parse_connection(Rest, Acc, << Token/binary, $a >>);
+ $B -> parse_connection(Rest, Acc, << Token/binary, $b >>);
+ $C -> parse_connection(Rest, Acc, << Token/binary, $c >>);
+ $D -> parse_connection(Rest, Acc, << Token/binary, $d >>);
+ $E -> parse_connection(Rest, Acc, << Token/binary, $e >>);
+ $F -> parse_connection(Rest, Acc, << Token/binary, $f >>);
+ $G -> parse_connection(Rest, Acc, << Token/binary, $g >>);
+ $H -> parse_connection(Rest, Acc, << Token/binary, $h >>);
+ $I -> parse_connection(Rest, Acc, << Token/binary, $i >>);
+ $J -> parse_connection(Rest, Acc, << Token/binary, $j >>);
+ $K -> parse_connection(Rest, Acc, << Token/binary, $k >>);
+ $L -> parse_connection(Rest, Acc, << Token/binary, $l >>);
+ $M -> parse_connection(Rest, Acc, << Token/binary, $m >>);
+ $N -> parse_connection(Rest, Acc, << Token/binary, $n >>);
+ $O -> parse_connection(Rest, Acc, << Token/binary, $o >>);
+ $P -> parse_connection(Rest, Acc, << Token/binary, $p >>);
+ $Q -> parse_connection(Rest, Acc, << Token/binary, $q >>);
+ $R -> parse_connection(Rest, Acc, << Token/binary, $r >>);
+ $S -> parse_connection(Rest, Acc, << Token/binary, $s >>);
+ $T -> parse_connection(Rest, Acc, << Token/binary, $t >>);
+ $U -> parse_connection(Rest, Acc, << Token/binary, $u >>);
+ $V -> parse_connection(Rest, Acc, << Token/binary, $v >>);
+ $W -> parse_connection(Rest, Acc, << Token/binary, $w >>);
+ $X -> parse_connection(Rest, Acc, << Token/binary, $x >>);
+ $Y -> parse_connection(Rest, Acc, << Token/binary, $y >>);
+ $Z -> parse_connection(Rest, Acc, << Token/binary, $z >>);
+ C -> parse_connection(Rest, Acc, << Token/binary, C >>)
+ end.
+
+parse_connection_after(<<>>, Acc) ->
+ lists:reverse(Acc);
+parse_connection_after(<< $,, Rest/bits >>, Acc) ->
+ parse_connection_before(Rest, Acc);
+parse_connection_after(<< C, Rest/bits >>, Acc)
+ when C =:= $\s; C =:= $\t ->
+ parse_connection_after(Rest, Acc).
+
+%% @doc Walk through a tokens list and return whether
+%% the connection is keepalive or closed.
+%%
+%% We don't match on "keep-alive" since it is the default value.
+-spec connection_to_atom([binary()]) -> keepalive | close.
+connection_to_atom([]) ->
+ keepalive;
+connection_to_atom([<<"close">>|_]) ->
+ close;
+connection_to_atom([_|Tail]) ->
+ connection_to_atom(Tail).
+
-spec status(cowboy_http:status()) -> binary().
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
@@ -1185,61 +1297,6 @@ status(510) -> <<"510 Not Extended">>;
status(511) -> <<"511 Network Authentication Required">>;
status(B) when is_binary(B) -> B.
--spec header_to_binary(cowboy_http:header()) -> binary().
-header_to_binary('Cache-Control') -> <<"Cache-Control">>;
-header_to_binary('Connection') -> <<"Connection">>;
-header_to_binary('Date') -> <<"Date">>;
-header_to_binary('Pragma') -> <<"Pragma">>;
-header_to_binary('Transfer-Encoding') -> <<"Transfer-Encoding">>;
-header_to_binary('Upgrade') -> <<"Upgrade">>;
-header_to_binary('Via') -> <<"Via">>;
-header_to_binary('Accept') -> <<"Accept">>;
-header_to_binary('Accept-Charset') -> <<"Accept-Charset">>;
-header_to_binary('Accept-Encoding') -> <<"Accept-Encoding">>;
-header_to_binary('Accept-Language') -> <<"Accept-Language">>;
-header_to_binary('Authorization') -> <<"Authorization">>;
-header_to_binary('From') -> <<"From">>;
-header_to_binary('Host') -> <<"Host">>;
-header_to_binary('If-Modified-Since') -> <<"If-Modified-Since">>;
-header_to_binary('If-Match') -> <<"If-Match">>;
-header_to_binary('If-None-Match') -> <<"If-None-Match">>;
-header_to_binary('If-Range') -> <<"If-Range">>;
-header_to_binary('If-Unmodified-Since') -> <<"If-Unmodified-Since">>;
-header_to_binary('Max-Forwards') -> <<"Max-Forwards">>;
-header_to_binary('Proxy-Authorization') -> <<"Proxy-Authorization">>;
-header_to_binary('Range') -> <<"Range">>;
-header_to_binary('Referer') -> <<"Referer">>;
-header_to_binary('User-Agent') -> <<"User-Agent">>;
-header_to_binary('Age') -> <<"Age">>;
-header_to_binary('Location') -> <<"Location">>;
-header_to_binary('Proxy-Authenticate') -> <<"Proxy-Authenticate">>;
-header_to_binary('Public') -> <<"Public">>;
-header_to_binary('Retry-After') -> <<"Retry-After">>;
-header_to_binary('Server') -> <<"Server">>;
-header_to_binary('Vary') -> <<"Vary">>;
-header_to_binary('Warning') -> <<"Warning">>;
-header_to_binary('Www-Authenticate') -> <<"Www-Authenticate">>;
-header_to_binary('Allow') -> <<"Allow">>;
-header_to_binary('Content-Base') -> <<"Content-Base">>;
-header_to_binary('Content-Encoding') -> <<"Content-Encoding">>;
-header_to_binary('Content-Language') -> <<"Content-Language">>;
-header_to_binary('Content-Length') -> <<"Content-Length">>;
-header_to_binary('Content-Location') -> <<"Content-Location">>;
-header_to_binary('Content-Md5') -> <<"Content-Md5">>;
-header_to_binary('Content-Range') -> <<"Content-Range">>;
-header_to_binary('Content-Type') -> <<"Content-Type">>;
-header_to_binary('Etag') -> <<"Etag">>;
-header_to_binary('Expires') -> <<"Expires">>;
-header_to_binary('Last-Modified') -> <<"Last-Modified">>;
-header_to_binary('Accept-Ranges') -> <<"Accept-Ranges">>;
-header_to_binary('Set-Cookie') -> <<"Set-Cookie">>;
-header_to_binary('Set-Cookie2') -> <<"Set-Cookie2">>;
-header_to_binary('X-Forwarded-For') -> <<"X-Forwarded-For">>;
-header_to_binary('Cookie') -> <<"Cookie">>;
-header_to_binary('Keep-Alive') -> <<"Keep-Alive">>;
-header_to_binary('Proxy-Connection') -> <<"Proxy-Connection">>;
-header_to_binary(B) when is_binary(B) -> B.
-
%% Tests.
-ifdef(TEST).
@@ -1247,25 +1304,45 @@ header_to_binary(B) when is_binary(B) -> B.
url_test() ->
{<<"http://localhost/path">>, _ } =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"http://localhost:443/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"http://localhost:8080/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"http://localhost:8080/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
+ pid=self()}),
+ {<<"http://localhost:8080/path?dummy=2785#fragment">>, _} =
+ url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
+ pid=self()}),
{<<"https://localhost/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"https://localhost:8443/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"https://localhost:8443/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
+ pid=self()}),
+ {<<"https://localhost:8443/path?dummy=2785#fragment">>, _} =
+ url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
+ pid=self()}),
ok.
+connection_to_atom_test_() ->
+ %% {Tokens, Result}
+ Tests = [
+ {[<<"close">>], close},
+ {[<<"keep-alive">>], keepalive},
+ {[<<"keep-alive">>, <<"upgrade">>], keepalive}
+ ],
+ [{lists:flatten(io_lib:format("~p", [T])),
+ fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
+
-endif.
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index da52ffe..2f9faa8 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -23,7 +23,7 @@
-export([upgrade/4]).
-record(state, {
- method = undefined :: cowboy_http:method(),
+ method = undefined :: binary(),
%% Handler.
handler :: atom(),
@@ -58,16 +58,16 @@
-> {ok, Req} | close when Req::cowboy_req:req().
upgrade(_ListenerPid, Handler, Opts, Req) ->
try
- {Method, Req1} = cowboy_req:method(Req),
+ Method = cowboy_req:get(method, Req),
case erlang:function_exported(Handler, rest_init, 2) of
true ->
- case Handler:rest_init(Req1, Opts) of
+ case Handler:rest_init(Req, Opts) of
{ok, Req2, HandlerState} ->
service_available(Req2, #state{method=Method,
handler=Handler, handler_state=HandlerState})
end;
false ->
- service_available(Req1, #state{method=Method,
+ service_available(Req, #state{method=Method,
handler=Handler})
end
catch Class:Reason ->
@@ -87,9 +87,10 @@ service_available(Req, State) ->
%% known_methods/2 should return a list of atoms or binary methods.
known_methods(Req, State=#state{method=Method}) ->
case call(Req, State, known_methods) of
- no_call when Method =:= 'HEAD'; Method =:= 'GET'; Method =:= 'POST';
- Method =:= 'PUT'; Method =:= 'DELETE'; Method =:= 'TRACE';
- Method =:= 'CONNECT'; Method =:= 'OPTIONS' ->
+ no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
+ Method =:= <<"POST">>; Method =:= <<"PUT">>;
+ Method =:= <<"DELETE">>; Method =:= <<"TRACE">>;
+ Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">> ->
next(Req, State, fun uri_too_long/2);
no_call ->
next(Req, State, 501);
@@ -109,10 +110,10 @@ uri_too_long(Req, State) ->
%% allowed_methods/2 should return a list of atoms or binary methods.
allowed_methods(Req, State=#state{method=Method}) ->
case call(Req, State, allowed_methods) of
- no_call when Method =:= 'HEAD'; Method =:= 'GET' ->
+ no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
next(Req, State, fun malformed_request/2);
no_call ->
- method_not_allowed(Req, State, ['GET', 'HEAD']);
+ method_not_allowed(Req, State, [<<"GET">>, <<"HEAD">>]);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{List, Req2, HandlerState} ->
@@ -172,7 +173,7 @@ valid_entity_length(Req, State) ->
%% If you need to add additional headers to the response at this point,
%% you should do it directly in the options/2 call using set_resp_headers.
-options(Req, State=#state{method='OPTIONS'}) ->
+options(Req, State=#state{method= <<"OPTIONS">>}) ->
case call(Req, State, options) of
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
@@ -209,7 +210,7 @@ content_types_provided(Req, State) ->
CTP2 = [normalize_content_types(P) || P <- CTP],
State2 = State#state{
handler_state=HandlerState, content_types_p=CTP2},
- {ok, Accept, Req3} = cowboy_req:parse_header('Accept', Req2),
+ {ok, Accept, Req3} = cowboy_req:parse_header(<<"accept">>, Req2),
case Accept of
undefined ->
{PMT, _Fun} = HeadCTP = hd(CTP2),
@@ -304,7 +305,7 @@ languages_provided(Req, State) ->
{LP, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState, languages_p=LP},
{ok, AcceptLanguage, Req3} =
- cowboy_req:parse_header('Accept-Language', Req2),
+ cowboy_req:parse_header(<<"accept-language">>, Req2),
case AcceptLanguage of
undefined ->
set_language(Req3, State2#state{language_a=hd(LP)});
@@ -366,7 +367,7 @@ charsets_provided(Req, State) ->
{CP, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState, charsets_p=CP},
{ok, AcceptCharset, Req3} =
- cowboy_req:parse_header('Accept-Charset', Req2),
+ cowboy_req:parse_header(<<"accept-charset">>, Req2),
case AcceptCharset of
undefined ->
set_content_type(Req3, State2#state{
@@ -478,7 +479,7 @@ resource_exists(Req, State) ->
fun if_match_exists/2, fun if_match_musnt_exist/2).
if_match_exists(Req, State) ->
- case cowboy_req:parse_header('If-Match', Req) of
+ case cowboy_req:parse_header(<<"if-match">>, Req) of
{ok, undefined, Req2} ->
if_unmodified_since_exists(Req2, State);
{ok, '*', Req2} ->
@@ -496,13 +497,13 @@ if_match(Req, State, EtagsList) ->
end.
if_match_musnt_exist(Req, State) ->
- case cowboy_req:header('If-Match', Req) of
+ case cowboy_req:header(<<"if-match">>, Req) of
{undefined, Req2} -> is_put_to_missing_resource(Req2, State);
{_Any, Req2} -> precondition_failed(Req2, State)
end.
if_unmodified_since_exists(Req, State) ->
- case cowboy_req:parse_header('If-Unmodified-Since', Req) of
+ case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
{ok, undefined, Req2} ->
if_none_match_exists(Req2, State);
{ok, IfUnmodifiedSince, Req2} ->
@@ -520,7 +521,7 @@ if_unmodified_since(Req, State, IfUnmodifiedSince) ->
end.
if_none_match_exists(Req, State) ->
- case cowboy_req:parse_header('If-None-Match', Req) of
+ case cowboy_req:parse_header(<<"if-none-match">>, Req) of
{ok, undefined, Req2} ->
if_modified_since_exists(Req2, State);
{ok, '*', Req2} ->
@@ -542,13 +543,13 @@ if_none_match(Req, State, EtagsList) ->
end.
precondition_is_head_get(Req, State=#state{method=Method})
- when Method =:= 'HEAD'; Method =:= 'GET' ->
+ when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
not_modified(Req, State);
precondition_is_head_get(Req, State) ->
precondition_failed(Req, State).
if_modified_since_exists(Req, State) ->
- case cowboy_req:parse_header('If-Modified-Since', Req) of
+ case cowboy_req:parse_header(<<"if-modified-since">>, Req) of
{ok, undefined, Req2} ->
method(Req2, State);
{ok, IfModifiedSince, Req2} ->
@@ -584,7 +585,7 @@ not_modified(Req, State) ->
precondition_failed(Req, State) ->
respond(Req, State, 412).
-is_put_to_missing_resource(Req, State=#state{method='PUT'}) ->
+is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
moved_permanently(Req, State, fun is_conflict/2);
is_put_to_missing_resource(Req, State) ->
previously_existed(Req, State).
@@ -626,7 +627,7 @@ moved_temporarily(Req, State) ->
is_post_to_missing_resource(Req, State, 410)
end.
-is_post_to_missing_resource(Req, State=#state{method='POST'}, OnFalse) ->
+is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
allow_missing_post(Req, State, OnFalse);
is_post_to_missing_resource(Req, State, OnFalse) ->
respond(Req, State, OnFalse).
@@ -634,14 +635,14 @@ is_post_to_missing_resource(Req, State, OnFalse) ->
allow_missing_post(Req, State, OnFalse) ->
expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse).
-method(Req, State=#state{method='DELETE'}) ->
+method(Req, State=#state{method= <<"DELETE">>}) ->
delete_resource(Req, State);
-method(Req, State=#state{method='POST'}) ->
+method(Req, State=#state{method= <<"POST">>}) ->
post_is_create(Req, State);
-method(Req, State=#state{method='PUT'}) ->
+method(Req, State=#state{method= <<"PUT">>}) ->
is_conflict(Req, State);
method(Req, State=#state{method=Method})
- when Method =:= 'GET'; Method =:= 'HEAD' ->
+ when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
set_resp_body(Req, State);
method(Req, State) ->
multiple_choices(Req, State).
@@ -692,8 +693,8 @@ is_conflict(Req, State) ->
expect(Req, State, is_conflict, false, fun put_resource/2, 409).
put_resource(Req, State) ->
- {Path, Req2} = cowboy_req:path(Req),
- put_resource(cowboy_req:set_meta(put_path, Path, Req2),
+ Path = cowboy_req:get(path, Req),
+ put_resource(cowboy_req:set_meta(put_path, Path, Req),
State, fun is_new_resource/2).
%% content_types_accepted should return a list of media types and their
@@ -714,7 +715,7 @@ put_resource(Req, State, OnTrue) ->
CTA2 = [normalize_content_types(P) || P <- CTA],
State2 = State#state{handler_state=HandlerState},
{ok, ContentType, Req3}
- = cowboy_req:parse_header('Content-Type', Req2),
+ = cowboy_req:parse_header(<<"content-type">>, Req2),
choose_content_type(Req3, State2, OnTrue, ContentType, CTA2)
end.
diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl
index aaf798c..5450115 100644
--- a/src/cowboy_static.erl
+++ b/src/cowboy_static.erl
@@ -247,9 +247,9 @@ rest_init(Req, Opts) ->
%% @private Only allow GET and HEAD requests on files.
-spec allowed_methods(Req, #state{})
- -> {[atom()], Req, #state{}} when Req::cowboy_req:req().
+ -> {[binary()], Req, #state{}} when Req::cowboy_req:req().
allowed_methods(Req, State) ->
- {['GET', 'HEAD'], Req, State}.
+ {[<<"GET">>, <<"HEAD">>], Req, State}.
%% @private
-spec malformed_request(Req, #state{})
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index a6f2be3..dcd3008 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -72,11 +72,12 @@ upgrade(ListenerPid, Handler, Opts, Req) ->
-> {ok, #state{}, Req} when Req::cowboy_req:req().
websocket_upgrade(State, Req) ->
{ok, ConnTokens, Req2}
- = cowboy_req:parse_header('Connection', Req),
+ = cowboy_req:parse_header(<<"connection">>, Req),
true = lists:member(<<"upgrade">>, ConnTokens),
%% @todo Should probably send a 426 if the Upgrade header is missing.
- {ok, [<<"websocket">>], Req3} = cowboy_req:parse_header('Upgrade', Req2),
- {Version, Req4} = cowboy_req:header(<<"Sec-Websocket-Version">>, Req3),
+ {ok, [<<"websocket">>], Req3}
+ = cowboy_req:parse_header(<<"upgrade">>, Req2),
+ {Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3),
websocket_upgrade(Version, State, Req4).
%% @todo Handle the Sec-Websocket-Protocol header.
@@ -90,9 +91,9 @@ websocket_upgrade(State, Req) ->
%% a reply before sending it. Therefore we calculate the challenge
%% key only in websocket_handshake/3.
websocket_upgrade(undefined, State, Req) ->
- {Origin, Req2} = cowboy_req:header(<<"Origin">>, Req),
- {Key1, Req3} = cowboy_req:header(<<"Sec-Websocket-Key1">>, Req2),
- {Key2, Req4} = cowboy_req:header(<<"Sec-Websocket-Key2">>, Req3),
+ {Origin, Req2} = cowboy_req:header(<<"origin">>, Req),
+ {Key1, Req3} = cowboy_req:header(<<"sec-websocket-key1">>, Req2),
+ {Key2, Req4} = cowboy_req:header(<<"sec-websocket-key2">>, Req3),
false = lists:member(undefined, [Origin, Key1, Key2]),
EOP = binary:compile_pattern(<< 255 >>),
{ok, State#state{version=0, origin=Origin, challenge={Key1, Key2},
@@ -101,7 +102,7 @@ websocket_upgrade(undefined, State, Req) ->
websocket_upgrade(Version, State, Req)
when Version =:= <<"7">>; Version =:= <<"8">>;
Version =:= <<"13">> ->
- {Key, Req2} = cowboy_req:header(<<"Sec-Websocket-Key">>, Req),
+ {Key, Req2} = cowboy_req:header(<<"sec-websocket-key">>, Req),
false = Key =:= undefined,
Challenge = hybi_challenge(Key),
IntVersion = list_to_integer(binary_to_list(Version)),