diff options
author | Loïc Hoguin <[email protected]> | 2014-09-23 16:43:29 +0300 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2014-09-23 16:43:29 +0300 |
commit | f1c3b6d76f0c97e1ab927c288bb94891ae4c253b (patch) | |
tree | 5a14c007cdcb487032162b3ca96df648f521321a /src | |
parent | b57f94661f5fd186f55eb0fead49849e0b1399d1 (diff) | |
download | cowboy-f1c3b6d76f0c97e1ab927c288bb94891ae4c253b.tar.gz cowboy-f1c3b6d76f0c97e1ab927c288bb94891ae4c253b.tar.bz2 cowboy-f1c3b6d76f0c97e1ab927c288bb94891ae4c253b.zip |
Breaking update of the cowboy_req interface
Simplify the interface for most cowboy_req functions. They all return
a single value except the four body reading functions. The reply functions
now only return a Req value.
Access functions do not return a Req anymore.
Functions that used to cache results do not have a cache anymore.
The interface for accessing query string and cookies has therefore
been changed.
There are now three query string functions: qs/1 provides access
to the raw query string value; parse_qs/1 returns the query string
as a list of key/values; match_qs/2 returns a map containing the
values requested in the second argument, after applying constraints
and default value.
Similarly, there are two cookie functions: parse_cookies/1 and
match_cookies/2. More match functions will be added in future commits.
None of the functions return an error tuple anymore. It either works
or crashes. Cowboy will attempt to provide an appropriate status code
in the response of crashed handlers.
As a result, the content decode function has its return value changed
to a simple binary, and the body reading functions only return on success.
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy.erl | 5 | ||||
-rw-r--r-- | src/cowboy_constraints.erl | 60 | ||||
-rw-r--r-- | src/cowboy_http.erl | 4 | ||||
-rw-r--r-- | src/cowboy_req.erl | 556 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 117 | ||||
-rw-r--r-- | src/cowboy_router.erl | 5 | ||||
-rw-r--r-- | src/cowboy_static.erl | 6 | ||||
-rw-r--r-- | src/cowboy_websocket.erl | 32 |
8 files changed, 401 insertions, 384 deletions
diff --git a/src/cowboy.erl b/src/cowboy.erl index 2b50dfb..8e9232f 100644 --- a/src/cowboy.erl +++ b/src/cowboy.erl @@ -20,6 +20,11 @@ -export([stop_listener/1]). -export([set_env/3]). +-type fields() :: [atom() + | {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()]} + | {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()], any()}]. +-export_type([fields/0]). + -type http_headers() :: [{binary(), iodata()}]. -export_type([http_headers/0]). diff --git a/src/cowboy_constraints.erl b/src/cowboy_constraints.erl new file mode 100644 index 0000000..9a379e1 --- /dev/null +++ b/src/cowboy_constraints.erl @@ -0,0 +1,60 @@ +%% Copyright (c) 2014, Loïc Hoguin <[email protected]> +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(cowboy_constraints). + +-export([validate/2]). + +-type constraint() :: int | nonempty | fun(). +-export_type([constraint/0]). + +-spec validate(binary(), [constraint()]) -> true | {true, any()} | false. +validate(Value, [Constraint]) -> + apply_constraint(Value, Constraint); +validate(Value, Constraints) when is_list(Constraints) -> + validate_list(Value, Constraints, original); +validate(Value, Constraint) -> + apply_constraint(Value, Constraint). + +validate_list(_, [], original) -> + true; +validate_list(Value, [], modified) -> + {true, Value}; +validate_list(Value, [Constraint|Tail], State) -> + case apply_constraint(Value, Constraint) of + true -> + validate_list(Value, Tail, State); + {true, Value2} -> + validate_list(Value2, Tail, modified); + false -> + false + end. + +%% @todo {int, From, To}, etc. +apply_constraint(Value, int) -> + int(Value); +apply_constraint(Value, nonempty) -> + nonempty(Value); +apply_constraint(Value, F) when is_function(F) -> + F(Value). + +%% Constraint functions. + +int(Value) when is_binary(Value) -> + try {true, list_to_integer(binary_to_list(Value))} + catch _:_ -> false + end. + +nonempty(<<>>) -> false; +nonempty(Value) when is_binary(Value) -> true. diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 1cf73bf..177787f 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -852,9 +852,9 @@ parameterized_tokens_param(Data, Fun) -> %% Decoding. %% @todo Move this to cowlib too I suppose. :-) --spec ce_identity(binary()) -> {ok, binary()}. +-spec ce_identity(Data) -> Data when Data::binary(). ce_identity(Data) -> - {ok, Data}. + Data. %% Tests. diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 27c59cf..23a3868 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -26,9 +26,8 @@ -export([path/1]). -export([path_info/1]). -export([qs/1]). --export([qs_val/2]). --export([qs_val/3]). --export([qs_vals/1]). +-export([parse_qs/1]). +-export([match_qs/2]). -export([host_url/1]). -export([url/1]). -export([binding/2]). @@ -39,9 +38,8 @@ -export([headers/1]). -export([parse_header/2]). -export([parse_header/3]). --export([cookie/2]). --export([cookie/3]). --export([cookies/1]). +-export([parse_cookies/1]). +-export([match_cookies/2]). -export([meta/2]). -export([meta/3]). -export([set_meta/3]). @@ -94,9 +92,7 @@ -type cookie_opts() :: cow_cookie:cookie_opts(). -export_type([cookie_opts/0]). --type content_decode_fun() :: fun((binary()) - -> {ok, binary()} - | {error, atom()}). +-type content_decode_fun() :: fun((binary()) -> binary()). -type transfer_decode_fun() :: fun((binary(), any()) -> cow_http_te:decode_ret()). @@ -109,7 +105,7 @@ -export_type([body_opts/0]). -type resp_body_fun() :: fun((any(), module()) -> ok). --type send_chunk_fun() :: fun((iodata()) -> ok | {error, atom()}). +-type send_chunk_fun() :: fun((iodata()) -> ok). -type resp_chunked_fun() :: fun((send_chunk_fun()) -> ok). -record(http_req, { @@ -129,11 +125,8 @@ path = undefined :: binary(), path_info = undefined :: undefined | cowboy_router:tokens(), qs = undefined :: binary(), - qs_vals = undefined :: undefined | list({binary(), binary() | true}), bindings = undefined :: undefined | cowboy_router:bindings(), headers = [] :: cowboy:http_headers(), - p_headers = [] :: [any()], - cookies = undefined :: undefined | [{binary(), binary()}], meta = [] :: [{atom(), any()}], %% Request body. @@ -179,89 +172,67 @@ new(Socket, Transport, Peer, Method, Path, Query, false -> Req#http_req{connection=close}; true -> - case lists:keyfind(<<"connection">>, 1, Headers) of - false -> + case parse_header(<<"connection">>, Req) of + undefined -> case Version of 'HTTP/1.1' -> Req; %% keepalive 'HTTP/1.0' -> Req#http_req{connection=close} end; - {_, ConnectionHeader} -> - Tokens = cow_http_hd:parse_connection(ConnectionHeader), + Tokens -> Connection = connection_to_atom(Tokens), - Req#http_req{connection=Connection, - p_headers=[{<<"connection">>, Tokens}]} + Req#http_req{connection=Connection} end end. --spec method(Req) -> {binary(), Req} when Req::req(). +-spec method(req()) -> binary(). method(Req) -> - {Req#http_req.method, Req}. + Req#http_req.method. --spec version(Req) -> {cowboy:http_version(), Req} when Req::req(). +-spec version(req()) -> cowboy:http_version(). version(Req) -> - {Req#http_req.version, Req}. + Req#http_req.version. --spec peer(Req) - -> {{inet:ip_address(), inet:port_number()}, Req} - when Req::req(). +-spec peer(req()) -> {inet:ip_address(), inet:port_number()}. peer(Req) -> - {Req#http_req.peer, Req}. + Req#http_req.peer. --spec host(Req) -> {binary(), Req} when Req::req(). +-spec host(req()) -> binary(). host(Req) -> - {Req#http_req.host, Req}. + Req#http_req.host. --spec host_info(Req) - -> {cowboy_router:tokens() | undefined, Req} when Req::req(). +-spec host_info(req()) -> cowboy_router:tokens() | undefined. host_info(Req) -> - {Req#http_req.host_info, Req}. + Req#http_req.host_info. --spec port(Req) -> {inet:port_number(), Req} when Req::req(). +-spec port(req()) -> inet:port_number(). port(Req) -> - {Req#http_req.port, Req}. + Req#http_req.port. --spec path(Req) -> {binary(), Req} when Req::req(). +-spec path(req()) -> binary(). path(Req) -> - {Req#http_req.path, Req}. + Req#http_req.path. --spec path_info(Req) - -> {cowboy_router:tokens() | undefined, Req} when Req::req(). +-spec path_info(req()) -> cowboy_router:tokens() | undefined. path_info(Req) -> - {Req#http_req.path_info, Req}. + Req#http_req.path_info. --spec qs(Req) -> {binary(), Req} when Req::req(). +-spec qs(req()) -> binary(). qs(Req) -> - {Req#http_req.qs, Req}. - --spec qs_val(binary(), Req) - -> {binary() | true | undefined, Req} when Req::req(). -qs_val(Name, Req) when is_binary(Name) -> - qs_val(Name, Req, undefined). - --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}, Default) - when is_binary(Name) -> - QsVals = cow_qs:parse_qs(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 - {Name, Value} -> {Value, Req}; - false -> {Default, Req} - end. + Req#http_req.qs. + +-spec parse_qs(req()) -> [{binary(), binary() | true}]. +parse_qs(#http_req{qs=Qs}) -> + cow_qs:parse_qs(Qs). --spec qs_vals(Req) -> {list({binary(), binary() | true}), Req} when Req::req(). -qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) -> - QsVals = cow_qs:parse_qs(RawQs), - qs_vals(Req#http_req{qs_vals=QsVals}); -qs_vals(Req=#http_req{qs_vals=QsVals}) -> - {QsVals, Req}. +-spec match_qs(req(), cowboy:fields()) -> map(). +match_qs(Req, Fields) -> + filter(kvlist_to_map(parse_qs(Req), Fields), Fields). %% The URL includes the scheme, host and port only. --spec host_url(Req) -> {undefined | binary(), Req} when Req::req(). -host_url(Req=#http_req{port=undefined}) -> - {undefined, Req}; -host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) -> +-spec host_url(req()) -> undefined | binary(). +host_url(#http_req{port=undefined}) -> + undefined; +host_url(#http_req{transport=Transport, host=Host, port=Port}) -> TransportName = Transport:name(), Secure = case TransportName of ssl -> <<"s">>; @@ -272,108 +243,101 @@ host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) -> {tcp, 80} -> <<>>; _ -> << ":", (integer_to_binary(Port))/binary >> end, - {<< "http", Secure/binary, "://", Host/binary, PortBin/binary >>, Req}. + << "http", Secure/binary, "://", Host/binary, PortBin/binary >>. %% The URL includes the scheme, host, port, path and query string. --spec url(Req) -> {undefined | binary(), Req} when Req::req(). +-spec url(req()) -> undefined | binary(). url(Req=#http_req{}) -> - {HostURL, Req2} = host_url(Req), - url(HostURL, Req2). + HostURL = host_url(Req), + url(Req, HostURL). -url(undefined, Req=#http_req{}) -> - {undefined, Req}; -url(HostURL, Req=#http_req{path=Path, qs=QS}) -> +url(_, undefined) -> + undefined; +url(#http_req{path=Path, qs=QS}, HostURL) -> QS2 = case QS of <<>> -> <<>>; _ -> << "?", QS/binary >> end, - {<< HostURL/binary, Path/binary, QS2/binary >>, Req}. + << HostURL/binary, Path/binary, QS2/binary >>. --spec binding(atom(), Req) -> {any() | undefined, Req} when Req::req(). -binding(Name, Req) when is_atom(Name) -> +-spec binding(atom(), req()) -> any() | undefined. +binding(Name, Req) -> binding(Name, Req, undefined). --spec binding(atom(), Req, Default) - -> {any() | Default, Req} when Req::req(), Default::any(). +-spec binding(atom(), req(), Default) -> any() | Default when Default::any(). binding(Name, Req, Default) when is_atom(Name) -> case lists:keyfind(Name, 1, Req#http_req.bindings) of - {Name, Value} -> {Value, Req}; - false -> {Default, Req} + {_, Value} -> Value; + false -> Default end. --spec bindings(Req) -> {[{atom(), any()}], Req} when Req::req(). +-spec bindings(req()) -> [{atom(), any()}]. bindings(Req) -> - {Req#http_req.bindings, Req}. + Req#http_req.bindings. --spec header(binary(), Req) - -> {binary() | undefined, Req} when Req::req(). +-spec header(binary(), req()) -> binary() | undefined. header(Name, Req) -> header(Name, Req, undefined). --spec header(binary(), Req, Default) - -> {binary() | Default, Req} when Req::req(), Default::any(). +-spec header(binary(), req(), Default) -> binary() | Default when Default::any(). header(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.headers) of - {Name, Value} -> {Value, Req}; - false -> {Default, Req} + {Name, Value} -> Value; + false -> Default end. --spec headers(Req) -> {cowboy:http_headers(), Req} when Req::req(). +-spec headers(req()) -> cowboy:http_headers(). headers(Req) -> - {Req#http_req.headers, 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}) -> - case lists:keyfind(Name, 1, PHeaders) of - false -> parse_header(Name, Req, parse_header_default(Name)); - {Name, Value} -> {ok, Value, Req} - end. - --spec parse_header_default(binary()) -> any(). -parse_header_default(<<"transfer-encoding">>) -> [<<"identity">>]; -parse_header_default(_Name) -> undefined. - --spec parse_header(binary(), Req, any()) - -> {ok, any(), Req} | {undefined, binary(), Req} - | {error, badarg} when Req::req(). + Req#http_req.headers. + +-spec parse_header(binary(), Req) -> any() when Req::req(). +parse_header(Name = <<"content-length">>, Req) -> + parse_header(Name, Req, 0); +parse_header(Name = <<"cookie">>, Req) -> + parse_header(Name, Req, []); +parse_header(Name = <<"transfer-encoding">>, Req) -> + parse_header(Name, Req, [<<"identity">>]); +parse_header(Name, Req) -> + parse_header(Name, Req, undefined). + +-spec parse_header(binary(), Req, any()) -> any() when Req::req(). parse_header(Name = <<"accept">>, Req, Default) -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:list(Value, fun cowboy_http:media_range/2) - end); + parse_header(Name, Req, Default, fun (Value) -> + cowboy_http:list(Value, fun cowboy_http:media_range/2) end); parse_header(Name = <<"accept-charset">>, Req, Default) -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2) - end); + parse_header(Name, Req, Default, fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2) end); parse_header(Name = <<"accept-encoding">>, Req, Default) -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:list(Value, fun cowboy_http:conneg/2) - end); + parse_header(Name, Req, Default, fun (Value) -> + cowboy_http:list(Value, fun cowboy_http:conneg/2) end); parse_header(Name = <<"accept-language">>, Req, Default) -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2) - end); + parse_header(Name, Req, Default, fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2) end); parse_header(Name = <<"authorization">>, Req, Default) -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:token_ci(Value, fun cowboy_http:authorization/2) - end); + parse_header(Name, Req, Default, fun (Value) -> + cowboy_http:token_ci(Value, fun cowboy_http:authorization/2) end); +parse_header(Name = <<"connection">>, Req, Default) -> + case header(Name, Req) of + undefined -> Default; + Value -> cow_http_hd:parse_connection(Value) + end; parse_header(Name = <<"content-length">>, Req, Default) -> - parse_header(Name, Req, Default, fun cow_http_hd:parse_content_length/1); + case header(Name, Req) of + undefined -> Default; + Value -> cow_http_hd:parse_content_length(Value) + end; parse_header(Name = <<"content-type">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:content_type/1); parse_header(Name = <<"cookie">>, Req, Default) -> - parse_header(Name, Req, Default, fun cow_cookie:parse_cookie/1); + case header(Name, Req) of + undefined -> Default; + %% Flash player incorrectly sends an empty Cookie header. + <<>> -> Default; + Value -> cow_cookie:parse_cookie(Value) + end; parse_header(Name = <<"expect">>, Req, Default) -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2) - end); + 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">> -> @@ -387,80 +351,51 @@ parse_header(Name = <<"range">>, Req, Default) -> parse_header(Name, Req, Default) when Name =:= <<"sec-websocket-protocol">>; Name =:= <<"x-forwarded-for">> -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:nonempty_list(Value, fun cowboy_http:token/2) - end); + parse_header(Name, Req, Default, fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token/2) end); parse_header(Name = <<"transfer-encoding">>, Req, Default) -> - parse_header(Name, Req, Default, fun cow_http_hd:parse_transfer_encoding/1); + case header(Name, Req) of + undefined -> Default; + Value -> cow_http_hd:parse_transfer_encoding(Value) + end; %% @todo Product version. parse_header(Name = <<"upgrade">>, Req, Default) -> - parse_header(Name, Req, Default, - fun (Value) -> - cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) - end); + parse_header(Name, Req, Default, fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) end); parse_header(Name = <<"sec-websocket-extensions">>, Req, Default) -> - parse_header(Name, Req, Default, fun cowboy_http:parameterized_tokens/1); -parse_header(Name, Req, Default) -> - {Value, Req2} = header(Name, Req, Default), - {undefined, Value, Req2}. + parse_header(Name, Req, Default, fun cowboy_http:parameterized_tokens/1). -parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> +%% @todo Remove this function when everything moved to cowlib. +parse_header(Name, Req, Default, ParseFun) -> case header(Name, Req) of - {undefined, Req2} -> - {ok, Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}}; - {Value, Req2} -> - case Fun(Value) of + undefined -> + Default; + Value -> + case ParseFun(Value) of {error, badarg} -> - {error, badarg}; - P -> - {ok, P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}} + error(badarg); + ParsedValue -> + ParsedValue end end. --spec cookie(binary(), Req) - -> {binary() | undefined, Req} when Req::req(). -cookie(Name, Req) when is_binary(Name) -> - cookie(Name, Req, undefined). - --spec cookie(binary(), Req, Default) - -> {binary() | Default, Req} when Req::req(), Default::any(). -cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> - case parse_header(<<"cookie">>, Req) of - {ok, undefined, Req2} -> - {Default, Req2#http_req{cookies=[]}}; - {ok, Cookies, Req2} -> - cookie(Name, Req2#http_req{cookies=Cookies}, Default) - end; -cookie(Name, Req, Default) -> - case lists:keyfind(Name, 1, Req#http_req.cookies) of - {Name, Value} -> {Value, Req}; - false -> {Default, Req} - end. +-spec parse_cookies(req()) -> [{binary(), binary()}]. +parse_cookies(Req) -> + parse_header(<<"cookie">>, Req). --spec cookies(Req) -> {list({binary(), binary()}), Req} when Req::req(). -cookies(Req=#http_req{cookies=undefined}) -> - case parse_header(<<"cookie">>, Req) of - {ok, undefined, Req2} -> - {[], Req2#http_req{cookies=[]}}; - {ok, Cookies, Req2} -> - cookies(Req2#http_req{cookies=Cookies}); - %% Flash player incorrectly sends an empty Cookie header. - {error, badarg} -> - {[], Req#http_req{cookies=[]}} - end; -cookies(Req=#http_req{cookies=Cookies}) -> - {Cookies, Req}. +-spec match_cookies(req(), cowboy:fields()) -> map(). +match_cookies(Req, Fields) -> + filter(kvlist_to_map(parse_cookies(Req), Fields), Fields). --spec meta(atom(), Req) -> {any() | undefined, Req} when Req::req(). +-spec meta(atom(), req()) -> any() | undefined. meta(Name, Req) -> meta(Name, Req, undefined). --spec meta(atom(), Req, any()) -> {any(), Req} when Req::req(). +-spec meta(atom(), req(), any()) -> any(). meta(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.meta) of - {Name, Value} -> {Value, Req}; - false -> {Default, Req} + {Name, Value} -> Value; + false -> Default end. -spec set_meta(atom(), any(), Req) -> Req when Req::req(). @@ -482,37 +417,31 @@ has_body(Req) -> %% The length may not be known if Transfer-Encoding is not identity, %% 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(). +-spec body_length(req()) -> undefined | non_neg_integer(). body_length(Req) -> case parse_header(<<"transfer-encoding">>, Req) of - {ok, [<<"identity">>], Req2} -> - {ok, Length, Req3} = parse_header(<<"content-length">>, Req2, 0), - {Length, Req3}; - {ok, _, Req2} -> - {undefined, Req2} + [<<"identity">>] -> + parse_header(<<"content-length">>, Req); + _ -> + undefined end. --spec body(Req) - -> {ok, binary(), Req} | {more, binary(), Req} - | {error, atom()} when Req::req(). +-spec body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). body(Req) -> body(Req, []). --spec body(Req, body_opts()) - -> {ok, binary(), Req} | {more, binary(), Req} - | {error, atom()} when Req::req(). +-spec body(Req, body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). body(Req=#http_req{body_state=waiting}, Opts) -> %% Send a 100 continue if needed (enabled by default). - Req1 = case lists:keyfind(continue, 1, Opts) of + case lists:keyfind(continue, 1, Opts) of {_, false} -> - Req; + ok; _ -> - {ok, ExpectHeader, Req0} = parse_header(<<"expect">>, Req), + ExpectHeader = parse_header(<<"expect">>, Req), ok = case ExpectHeader of - [<<"100-continue">>] -> continue(Req0); + [<<"100-continue">>] -> continue(Req); _ -> ok - end, - Req0 + end end, %% Initialize body streaming state. CFun = case lists:keyfind(content_decode, 1, Opts) of @@ -523,23 +452,22 @@ body(Req=#http_req{body_state=waiting}, Opts) -> end, case lists:keyfind(transfer_decode, 1, Opts) of false -> - case parse_header(<<"transfer-encoding">>, Req1) of - {ok, [<<"chunked">>], Req2} -> - body(Req2#http_req{body_state={stream, 0, + case parse_header(<<"transfer-encoding">>, Req) of + [<<"chunked">>] -> + body(Req#http_req{body_state={stream, 0, fun cow_http_te:stream_chunked/2, {0, 0}, CFun}}, Opts); - {ok, [<<"identity">>], Req2} -> - {Len, Req3} = body_length(Req2), - case Len of + [<<"identity">>] -> + case body_length(Req) of 0 -> - {ok, <<>>, Req3#http_req{body_state=done}}; - _ -> - body(Req3#http_req{body_state={stream, Len, + {ok, <<>>, Req#http_req{body_state=done}}; + Len -> + body(Req#http_req{body_state={stream, Len, fun cow_http_te:stream_identity/2, {0, Len}, CFun}}, Opts) end end; {_, TFun, TState} -> - body(Req1#http_req{body_state={stream, 0, + body(Req#http_req{body_state={stream, 0, TFun, TState, CFun}}, Opts) end; body(Req=#http_req{body_state=done}, _) -> @@ -568,27 +496,20 @@ body_loop(Req=#http_req{buffer=Buffer, body_state={stream, Length, _, _, _}}, body_decode(Req, ReadTimeout) end, case {Tag, Res} of - {ok, {ok, Data}} -> + {ok, Data} -> {ok, << Acc/binary, Data/binary >>, Req2}; - {more, {ok, Data}} -> + {more, Data} -> Acc2 = << Acc/binary, Data/binary >>, case byte_size(Acc2) >= ChunkLength of true -> {more, Acc2, Req2}; false -> body_loop(Req2, ReadTimeout, ReadLength, ChunkLength, Acc2) - end; - _ -> %% Error. - Res + end end. body_recv(Req=#http_req{transport=Transport, socket=Socket, buffer=Buffer}, ReadTimeout, ReadLength) -> - case Transport:recv(Socket, ReadLength, ReadTimeout) of - {ok, Data} -> - body_decode(Req#http_req{buffer= << Buffer/binary, Data/binary >>}, - ReadTimeout); - Error = {error, _} -> - {error, Error, Req} - end. + {ok, Data} = Transport:recv(Socket, ReadLength, ReadTimeout), + body_decode(Req#http_req{buffer= << Buffer/binary, Data/binary >>}, ReadTimeout). %% Two decodings happen. First a decoding function is applied to the %% transferred data, and then another is applied to the actual content. @@ -617,26 +538,20 @@ body_decode(Req=#http_req{buffer=Data, body_state={stream, _, {more, CDecode(Data2), Req#http_req{body_state={stream, 0, TDecode, TState2, CDecode}, buffer=Rest}}; {done, TotalLength, Rest} -> - {ok, {ok, <<>>}, body_decode_end(Req, TotalLength, Rest)}; + {ok, <<>>, body_decode_end(Req, TotalLength, Rest)}; {done, Data2, TotalLength, Rest} -> {ok, CDecode(Data2), body_decode_end(Req, TotalLength, Rest)} end. -body_decode_end(Req=#http_req{headers=Headers, p_headers=PHeaders}, - TotalLength, Rest) -> +body_decode_end(Req=#http_req{headers=Headers}, TotalLength, Rest) -> Headers2 = lists:keystore(<<"content-length">>, 1, Headers, {<<"content-length">>, integer_to_binary(TotalLength)}), %% 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">>, TotalLength}), - PHeaders3 = lists:keydelete(<<"transfer-encoding">>, 1, PHeaders2), - Req#http_req{buffer=Rest, body_state=done, - headers=Headers3, p_headers=PHeaders3}. - --spec body_qs(Req) - -> {ok, [{binary(), binary() | true}], Req} | {error, atom()} - when Req::req(). + Req#http_req{buffer=Rest, body_state=done, headers=Headers3}. + +-spec body_qs(Req) -> {ok, [{binary(), binary() | true}], Req} + | {badlength, Req} when Req::req(). body_qs(Req) -> body_qs(Req, [ {length, 64000}, @@ -644,15 +559,13 @@ body_qs(Req) -> {read_timeout, 5000}]). -spec body_qs(Req, body_opts()) -> {ok, [{binary(), binary() | true}], Req} - | {badlength, Req} | {error, atom()} when Req::req(). + | {badlength, Req} when Req::req(). body_qs(Req, Opts) -> case body(Req, Opts) of {ok, Body, Req2} -> {ok, cow_qs:parse_qs(Body), Req2}; {more, _, Req2} -> - {badlength, Req2}; - {error, Reason} -> - {error, Reason} + {badlength, Req2} end. %% Multipart API. @@ -730,10 +643,9 @@ part_body(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}, Acc) -> end. init_multipart(Req) -> - {ok, {<<"multipart">>, _, Params}, Req2} - = parse_header(<<"content-type">>, Req), + {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req), {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), - Req2#http_req{multipart={Boundary, <<>>}}. + Req#http_req{multipart={Boundary, <<>>}}. stream_multipart(Req=#http_req{body_state=BodyState, multipart={_, <<>>}}, Opts) -> true = BodyState =/= done, @@ -801,18 +713,18 @@ delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) -> RespHeaders2 = lists:keydelete(Name, 1, RespHeaders), Req#http_req{resp_headers=RespHeaders2}. --spec reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req(). +-spec reply(cowboy:http_status(), Req) -> Req when Req::req(). reply(Status, Req=#http_req{resp_body=Body}) -> reply(Status, [], Body, Req). -spec reply(cowboy:http_status(), cowboy:http_headers(), Req) - -> {ok, Req} when Req::req(). + -> Req when Req::req(). reply(Status, Headers, Req=#http_req{resp_body=Body}) -> reply(Status, Headers, Body, Req). -spec reply(cowboy:http_status(), cowboy:http_headers(), iodata() | {non_neg_integer() | resp_body_fun()}, Req) - -> {ok, Req} when Req::req(). + -> Req when Req::req(). reply(Status, Headers, Body, Req=#http_req{ socket=Socket, transport=Transport, version=Version, connection=Connection, @@ -887,13 +799,13 @@ reply(Status, Headers, Body, Req=#http_req{ RespHeaders, HTTP11Headers, Method, iolist_size(Body)), Req2#http_req{connection=RespConn} end, - {ok, Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. + Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}. reply_may_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method) -> BodySize = iolist_size(Body), - case parse_header(<<"accept-encoding">>, Req) of - {ok, Encodings, Req2} -> + try parse_header(<<"accept-encoding">>, Req) of + Encodings -> CanGzip = (BodySize > 300) andalso (false =:= lists:keyfind(<<"content-encoding">>, 1, Headers)) @@ -908,22 +820,22 @@ reply_may_compress(Status, Headers, Body, Req, case CanGzip of true -> GzBody = zlib:gzip(Body), - {_, Req3} = response(Status, Headers, RespHeaders, [ + {_, Req2} = response(Status, Headers, RespHeaders, [ {<<"content-length">>, integer_to_list(byte_size(GzBody))}, {<<"content-encoding">>, <<"gzip">>}, {<<"date">>, cowboy_clock:rfc1123()}, {<<"server">>, <<"Cowboy">>} |HTTP11Headers], case Method of <<"HEAD">> -> <<>>; _ -> GzBody end, - Req2), - Req3; + Req), + Req2; false -> reply_no_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method, BodySize) - end; - {error, badarg} -> - reply_no_compress(Status, Headers, Body, Req, - RespHeaders, HTTP11Headers, Method, BodySize) + end + catch _:_ -> + reply_no_compress(Status, Headers, Body, Req, + RespHeaders, HTTP11Headers, Method, BodySize) end. reply_no_compress(Status, Headers, Body, Req, @@ -937,17 +849,17 @@ reply_no_compress(Status, Headers, Body, Req, Req), Req2. --spec chunked_reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req(). +-spec chunked_reply(cowboy:http_status(), Req) -> Req when Req::req(). chunked_reply(Status, Req) -> chunked_reply(Status, [], Req). -spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req) - -> {ok, Req} when Req::req(). + -> Req when Req::req(). chunked_reply(Status, Headers, Req) -> {_, Req2} = chunked_response(Status, Headers, Req), - {ok, Req2}. + Req2. --spec chunk(iodata(), req()) -> ok | {error, atom()}. +-spec chunk(iodata(), req()) -> ok. chunk(_Data, #http_req{method= <<"HEAD">>}) -> ok; chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy, @@ -955,10 +867,10 @@ chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy, cowboy_spdy:stream_data(Socket, Data); chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=stream}) -> - Transport:send(Socket, Data); + ok = Transport:send(Socket, Data); chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> - Transport:send(Socket, [integer_to_list(iolist_size(Data), 16), + ok = Transport:send(Socket, [integer_to_list(iolist_size(Data), 16), <<"\r\n">>, Data, <<"\r\n">>]). %% If ever made public, need to send nothing if HEAD. @@ -971,20 +883,20 @@ last_chunk(Req=#http_req{socket=Socket, transport=Transport}) -> Req#http_req{resp_state=done}. -spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req) - -> {ok, Req} when Req::req(). + -> Req when Req::req(). upgrade_reply(Status, Headers, Req=#http_req{transport=Transport, resp_state=waiting, resp_headers=RespHeaders}) when Transport =/= cowboy_spdy -> {_, Req2} = response(Status, Headers, RespHeaders, [ {<<"connection">>, <<"Upgrade">>} ], <<>>, Req), - {ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. + Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}. --spec continue(req()) -> ok | {error, atom()}. +-spec continue(req()) -> ok. continue(#http_req{socket=Socket, transport=Transport, version=Version}) -> HTTPVer = atom_to_binary(Version, latin1), - Transport:send(Socket, + ok = Transport:send(Socket, << HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>). %% Meant to be used internally for sending errors after crashes. @@ -997,9 +909,7 @@ maybe_reply(Stacktrace, Req) -> ok end. -do_maybe_reply([ - {cow_http_hd, _, _, _}, - {cowboy_req, parse_header, _, _}|_], Req) -> +do_maybe_reply([{cow_http_hd, _, _, _}|_], Req) -> cowboy_req:reply(400, Req); do_maybe_reply(_, Req) -> cowboy_req:reply(500, Req). @@ -1039,7 +949,6 @@ 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(headers, #http_req{headers=Ret}) -> Ret; g(host, #http_req{host=Ret}) -> Ret; g(host_info, #http_req{host_info=Ret}) -> Ret; @@ -1047,14 +956,12 @@ 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_compress, #http_req{resp_compress=Ret}) -> Ret; g(resp_headers, #http_req{resp_headers=Ret}) -> Ret; @@ -1069,7 +976,6 @@ 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([{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}); @@ -1077,14 +983,12 @@ 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}); @@ -1102,10 +1006,8 @@ set_bindings(HostInfo, PathInfo, Bindings, Req) -> -spec compact(Req) -> Req when Req::req(). compact(Req) -> - Req#http_req{host_info=undefined, - path_info=undefined, qs_vals=undefined, - bindings=undefined, headers=[], - p_headers=[], cookies=[]}. + Req#http_req{host_info=undefined, path_info=undefined, + bindings=undefined, headers=[]}. -spec lock(Req) -> Req when Req::req(). lock(Req) -> @@ -1193,7 +1095,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{ (status(Status2))/binary, "\r\n" >>, HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>] || {Key, Value} <- FullHeaders2], - Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]), + ok = Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]), ReqPid ! {?MODULE, resp_sent}, normal; _ -> @@ -1319,32 +1221,94 @@ status(510) -> <<"510 Not Extended">>; status(511) -> <<"511 Network Authentication Required">>; status(B) when is_binary(B) -> B. +%% Create map, convert keys to atoms and group duplicate keys into lists. +%% Keys that are not found in the user provided list are entirely skipped. +%% @todo Can probably be done directly while parsing. +kvlist_to_map(KvList, Fields) -> + Keys = [case K of + {Key, _} -> Key; + {Key, _, _} -> Key; + Key -> Key + end || K <- Fields], + kvlist_to_map(KvList, Keys, #{}). + +kvlist_to_map([], _, Map) -> + Map; +kvlist_to_map([{Key, Value}|Tail], Keys, Map) -> + try binary_to_existing_atom(Key, utf8) of + Atom -> + case lists:member(Atom, Keys) of + true -> + case maps:find(Atom, Map) of + {ok, MapValue} when is_list(MapValue) -> + kvlist_to_map(Tail, Keys, + maps:put(Atom, [Value|MapValue], Map)); + {ok, MapValue} -> + kvlist_to_map(Tail, Keys, + maps:put(Atom, [Value, MapValue], Map)); + error -> + kvlist_to_map(Tail, Keys, + maps:put(Atom, Value, Map)) + end; + false -> + kvlist_to_map(Tail, Keys, Map) + end + catch error:badarg -> + kvlist_to_map(Tail, Keys, Map) + end. + +%% Loop through fields, if value is missing and no default, crash; +%% else if value is missing and has a default, set default; +%% otherwise apply constraints. If constraint fails, crash. +filter(Map, []) -> + Map; +filter(Map, [{Key, Constraints}|Tail]) -> + filter_constraints(Map, Tail, Key, maps:get(Key, Map), Constraints); +filter(Map, [{Key, Constraints, Default}|Tail]) -> + case maps:find(Key, Map) of + {ok, Value} -> + filter_constraints(Map, Tail, Key, Value, Constraints); + error -> + filter(maps:put(Key, Default, Map), Tail) + end; +filter(Map, [Key|Tail]) -> + true = maps:is_key(Key, Map), + filter(Map, Tail). + +filter_constraints(Map, Tail, Key, Value, Constraints) -> + case cowboy_constraints:validate(Value, Constraints) of + true -> + filter(Map, Tail); + {true, Value2} -> + filter(maps:put(Key, Value2, Map), Tail) + end. + %% Tests. -ifdef(TEST). url_test() -> - {undefined, _} = + undefined = url(#http_req{transport=ranch_tcp, host= <<>>, port= undefined, path= <<>>, qs= <<>>, pid=self()}), - {<<"http://localhost/path">>, _ } = + <<"http://localhost/path">> = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80, path= <<"/path">>, qs= <<>>, pid=self()}), - {<<"http://localhost:443/path">>, _} = + <<"http://localhost:443/path">> = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443, path= <<"/path">>, qs= <<>>, pid=self()}), - {<<"http://localhost:8080/path">>, _} = + <<"http://localhost:8080/path">> = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080, path= <<"/path">>, qs= <<>>, pid=self()}), - {<<"http://localhost:8080/path?dummy=2785">>, _} = + <<"http://localhost:8080/path?dummy=2785">> = url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080, path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}), - {<<"https://localhost/path">>, _} = + <<"https://localhost/path">> = url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443, path= <<"/path">>, qs= <<>>, pid=self()}), - {<<"https://localhost:8443/path">>, _} = + <<"https://localhost:8443/path">> = url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443, path= <<"/path">>, qs= <<>>, pid=self()}), - {<<"https://localhost:8443/path?dummy=2785">>, _} = + <<"https://localhost:8443/path?dummy=2785">> = url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443, path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}), ok. diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index f779612..4ea3010 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -58,7 +58,7 @@ -spec upgrade(Req, Env, module(), any()) -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). upgrade(Req, Env, Handler, HandlerOpts) -> - Method = cowboy_req:get(method, Req), + Method = cowboy_req:method(Req), case erlang:function_exported(Handler, rest_init, 2) of true -> try Handler:rest_init(Req, HandlerOpts) of @@ -215,16 +215,15 @@ content_types_provided(Req, State) -> no_call -> State2 = State#state{ content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]}, - case cowboy_req:parse_header(<<"accept">>, Req) of - {error, badarg} -> - respond(Req, State2, 400); - {ok, undefined, Req2} -> + try cowboy_req:parse_header(<<"accept">>, Req) of + undefined -> languages_provided( - cowboy_req:set_meta(media_type, {<<"text">>, <<"html">>, []}, Req2), + cowboy_req:set_meta(media_type, {<<"text">>, <<"html">>, []}, Req), State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}}); - {ok, Accept, Req2} -> - Accept2 = prioritize_accept(Accept), - choose_media_type(Req2, State2, Accept2) + Accept -> + choose_media_type(Req, State2, prioritize_accept(Accept)) + catch _:_ -> + respond(Req, State2, 400) end; {halt, Req2, HandlerState} -> terminate(Req2, State#state{handler_state=HandlerState}); @@ -234,17 +233,16 @@ content_types_provided(Req, State) -> CTP2 = [normalize_content_types(P) || P <- CTP], State2 = State#state{ handler_state=HandlerState, content_types_p=CTP2}, - case cowboy_req:parse_header(<<"accept">>, Req2) of - {error, badarg} -> - respond(Req2, State2, 400); - {ok, undefined, Req3} -> + try cowboy_req:parse_header(<<"accept">>, Req2) of + undefined -> {PMT, _Fun} = HeadCTP = hd(CTP2), languages_provided( - cowboy_req:set_meta(media_type, PMT, Req3), + cowboy_req:set_meta(media_type, PMT, Req2), State2#state{content_type_a=HeadCTP}); - {ok, Accept, Req3} -> - Accept2 = prioritize_accept(Accept), - choose_media_type(Req3, State2, Accept2) + Accept -> + choose_media_type(Req2, State2, prioritize_accept(Accept)) + catch _:_ -> + respond(Req2, State2, 400) end end. @@ -335,14 +333,12 @@ languages_provided(Req, State) -> not_acceptable(Req2, State#state{handler_state=HandlerState}); {LP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, languages_p=LP}, - {ok, AcceptLanguage, Req3} = - cowboy_req:parse_header(<<"accept-language">>, Req2), - case AcceptLanguage of + case cowboy_req:parse_header(<<"accept-language">>, Req2) of undefined -> - set_language(Req3, State2#state{language_a=hd(LP)}); + set_language(Req2, State2#state{language_a=hd(LP)}); AcceptLanguage -> AcceptLanguage2 = prioritize_languages(AcceptLanguage), - choose_language(Req3, State2, AcceptLanguage2) + choose_language(Req2, State2, AcceptLanguage2) end end. @@ -397,14 +393,12 @@ charsets_provided(Req, State) -> not_acceptable(Req2, State#state{handler_state=HandlerState}); {CP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, charsets_p=CP}, - {ok, AcceptCharset, Req3} = - cowboy_req:parse_header(<<"accept-charset">>, Req2), - case AcceptCharset of + case cowboy_req:parse_header(<<"accept-charset">>, Req2) of undefined -> - set_content_type(Req3, State2#state{charset_a=hd(CP)}); + set_content_type(Req2, State2#state{charset_a=hd(CP)}); AcceptCharset -> AcceptCharset2 = prioritize_charsets(AcceptCharset), - choose_charset(Req3, State2, AcceptCharset2) + choose_charset(Req2, State2, AcceptCharset2) end end. @@ -524,12 +518,12 @@ resource_exists(Req, State) -> if_match_exists(Req, State) -> State2 = State#state{exists=true}, case cowboy_req:parse_header(<<"if-match">>, Req) of - {ok, undefined, Req2} -> - if_unmodified_since_exists(Req2, State2); - {ok, '*', Req2} -> - if_unmodified_since_exists(Req2, State2); - {ok, ETagsList, Req2} -> - if_match(Req2, State2, ETagsList) + undefined -> + if_unmodified_since_exists(Req, State2); + '*' -> + if_unmodified_since_exists(Req, State2); + ETagsList -> + if_match(Req, State2, ETagsList) end. if_match(Req, State, EtagsList) -> @@ -546,18 +540,18 @@ if_match(Req, State, EtagsList) -> if_match_must_not_exist(Req, State) -> case cowboy_req:header(<<"if-match">>, Req) of - {undefined, Req2} -> is_put_to_missing_resource(Req2, State); - {_Any, Req2} -> precondition_failed(Req2, State) + undefined -> is_put_to_missing_resource(Req, State); + _ -> precondition_failed(Req, State) end. if_unmodified_since_exists(Req, State) -> - case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of - {ok, undefined, Req2} -> - if_none_match_exists(Req2, State); - {ok, IfUnmodifiedSince, Req2} -> - if_unmodified_since(Req2, State, IfUnmodifiedSince); - {error, badarg} -> - if_none_match_exists(Req, State) + try cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of + undefined -> + if_none_match_exists(Req, State); + IfUnmodifiedSince -> + if_unmodified_since(Req, State, IfUnmodifiedSince) + catch _:_ -> + if_none_match_exists(Req, State) end. %% If LastModified is the atom 'no_call', we continue. @@ -574,12 +568,12 @@ if_unmodified_since(Req, State, IfUnmodifiedSince) -> if_none_match_exists(Req, State) -> case cowboy_req:parse_header(<<"if-none-match">>, Req) of - {ok, undefined, Req2} -> - if_modified_since_exists(Req2, State); - {ok, '*', Req2} -> - precondition_is_head_get(Req2, State); - {ok, EtagsList, Req2} -> - if_none_match(Req2, State, EtagsList) + undefined -> + if_modified_since_exists(Req, State); + '*' -> + precondition_is_head_get(Req, State); + EtagsList -> + if_none_match(Req, State, EtagsList) end. if_none_match(Req, State, EtagsList) -> @@ -605,13 +599,13 @@ 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 - {ok, undefined, Req2} -> - method(Req2, State); - {ok, IfModifiedSince, Req2} -> - if_modified_since_now(Req2, State, IfModifiedSince); - {error, badarg} -> - method(Req, State) + try cowboy_req:parse_header(<<"if-modified-since">>, Req) of + undefined -> + method(Req, State); + IfModifiedSince -> + if_modified_since_now(Req, State, IfModifiedSince) + catch _:_ -> + method(Req, State) end. if_modified_since_now(Req, State, IfModifiedSince) -> @@ -741,11 +735,11 @@ accept_resource(Req, State) -> {CTA, Req2, HandlerState} -> CTA2 = [normalize_content_types(P) || P <- CTA], State2 = State#state{handler_state=HandlerState}, - case cowboy_req:parse_header(<<"content-type">>, Req2) of - {ok, ContentType, Req3} -> - choose_content_type(Req3, State2, ContentType, CTA2); - {error, badarg} -> - respond(Req2, State2, 415) + try cowboy_req:parse_header(<<"content-type">>, Req2) of + ContentType -> + choose_content_type(Req2, State2, ContentType, CTA2) + catch _:_ -> + respond(Req2, State2, 415) end end. @@ -990,8 +984,7 @@ next(Req, State, StatusCode) when is_integer(StatusCode) -> respond(Req, State, StatusCode). respond(Req, State, StatusCode) -> - {ok, Req2} = cowboy_req:reply(StatusCode, Req), - terminate(Req2, State). + terminate(cowboy_req:reply(StatusCode, Req), State). terminate(Req, State=#state{env=Env}) -> rest_terminate(Req, State), diff --git a/src/cowboy_router.erl b/src/cowboy_router.erl index ef91c6d..f5b2f26 100644 --- a/src/cowboy_router.erl +++ b/src/cowboy_router.erl @@ -165,7 +165,8 @@ compile_brackets_split(<< C, Rest/binary >>, Acc, N) -> when Req::cowboy_req:req(), Env::cowboy_middleware:env(). execute(Req, Env) -> {_, Dispatch} = lists:keyfind(dispatch, 1, Env), - [Host, Path] = cowboy_req:get([host, path], Req), + Host = cowboy_req:host(Req), + Path = cowboy_req:path(Req), case match(Dispatch, Host, Path) of {ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} -> Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req), @@ -316,7 +317,7 @@ split_host(Host, Acc) -> %% 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(). +-spec split_path(binary()) -> tokens() | badrequest. split_path(<< $/, Path/bits >>) -> split_path(Path, []); split_path(_) -> diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl index fae4568..1d5286e 100644 --- a/src/cowboy_static.erl +++ b/src/cowboy_static.erl @@ -87,14 +87,14 @@ rest_init_dir(Req, Path, Extra) when is_list(Path) -> rest_init_dir(Req, list_to_binary(Path), Extra); rest_init_dir(Req, Path, Extra) -> Dir = fullpath(filename:absname(Path)), - {PathInfo, Req2} = cowboy_req:path_info(Req), + PathInfo = cowboy_req:path_info(Req), Filepath = filename:join([Dir|PathInfo]), Len = byte_size(Dir), case fullpath(Filepath) of << Dir:Len/binary, $/, _/binary >> -> - rest_init_info(Req2, Filepath, Extra); + rest_init_info(Req, Filepath, Extra); _ -> - {ok, Req2, error} + {ok, Req, error} end. fullpath(Path) -> diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index c0f94c4..1c115b5 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -78,26 +78,26 @@ upgrade(Req, Env, Handler, HandlerOpts) -> -spec websocket_upgrade(#state{}, Req) -> {ok, #state{}, Req} when Req::cowboy_req:req(). websocket_upgrade(State, Req) -> - {ok, ConnTokens, Req2} - = cowboy_req:parse_header(<<"connection">>, Req), + ConnTokens = 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), + [<<"websocket">>] = cowboy_req:parse_header(<<"upgrade">>, Req), + Version = cowboy_req:header(<<"sec-websocket-version">>, Req), IntVersion = list_to_integer(binary_to_list(Version)), true = (IntVersion =:= 7) orelse (IntVersion =:= 8) orelse (IntVersion =:= 13), - {Key, Req5} = cowboy_req:header(<<"sec-websocket-key">>, Req4), + Key = cowboy_req:header(<<"sec-websocket-key">>, Req), false = Key =:= undefined, websocket_extensions(State#state{key=Key}, - cowboy_req:set_meta(websocket_version, IntVersion, Req5)). + cowboy_req:set_meta(websocket_version, IntVersion, Req)). -spec websocket_extensions(#state{}, Req) -> {ok, #state{}, Req} when Req::cowboy_req:req(). websocket_extensions(State, Req) -> case cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req) of - {ok, Extensions, Req2} when Extensions =/= undefined -> + undefined -> + {ok, State, cowboy_req:set_meta(websocket_compress, false, Req)}; + Extensions -> [Compress] = cowboy_req:get([resp_compress], Req), case lists:keyfind(<<"x-webkit-deflate-frame">>, 1, Extensions) of {<<"x-webkit-deflate-frame">>, []} when Compress =:= true -> @@ -115,12 +115,10 @@ websocket_extensions(State, Req) -> deflate_frame = true, inflate_state = Inflate, deflate_state = Deflate - }, cowboy_req:set_meta(websocket_compress, true, Req2)}; + }, cowboy_req:set_meta(websocket_compress, true, Req)}; _ -> - {ok, State, cowboy_req:set_meta(websocket_compress, false, Req2)} - end; - _ -> - {ok, State, cowboy_req:set_meta(websocket_compress, false, Req)} + {ok, State, cowboy_req:set_meta(websocket_compress, false, Req)} + end end. -spec handler_init(#state{}, Req, any()) @@ -168,12 +166,8 @@ websocket_handshake(State=#state{ false -> []; true -> [{<<"sec-websocket-extensions">>, <<"x-webkit-deflate-frame">>}] end, - {ok, Req2} = cowboy_req:upgrade_reply( - 101, - [{<<"upgrade">>, <<"websocket">>}, - {<<"sec-websocket-accept">>, Challenge}| - Extensions], - Req), + Req2 = cowboy_req:upgrade_reply(101, [{<<"upgrade">>, <<"websocket">>}, + {<<"sec-websocket-accept">>, Challenge}|Extensions], Req), %% Flush the resp_sent message before moving on. receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, State2 = handler_loop_timeout(State), |