diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy_req.erl | 99 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 120 | ||||
-rw-r--r-- | src/cowboy_router.erl | 4 | ||||
-rw-r--r-- | src/cowboy_static.erl | 3 |
4 files changed, 63 insertions, 163 deletions
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index bdebddd..4ec42f9 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -46,7 +46,6 @@ -export([method/1]). -export([version/1]). -export([peer/1]). --export([peer_addr/1]). -export([host/1]). -export([host_info/1]). -export([port/1]). @@ -230,29 +229,6 @@ version(Req) -> peer(Req) -> {Req#http_req.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), - {{PeerIp, _PeerPort}, Req3} = peer(Req2), - ForwardedFor = case ForwardedForRaw of - undefined -> - undefined; - ForwardedForRaw -> - case re:run(ForwardedForRaw, "^(?<first_ip>[^\\,]+)", - [{capture, [first_ip], binary}]) of - {match, [FirstIp]} -> FirstIp; - _Any -> undefined - end - end, - {ok, PeerAddr} = if - is_binary(RealIp) -> inet_parse:address(binary_to_list(RealIp)); - is_binary(ForwardedFor) -> inet_parse:address(binary_to_list(ForwardedFor)); - true -> {ok, PeerIp} - end, - {PeerAddr, Req3}. - %% @doc Return the host binary string. -spec host(Req) -> {binary(), Req} when Req::req(). host(Req) -> @@ -426,61 +402,61 @@ parse_header_default(_Name) -> undefined. -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 = <<"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) when Name =:= <<"accept-charset">> -> +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) when Name =:= <<"accept-encoding">> -> +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) when Name =:= <<"accept-language">> -> +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) when Name =:= <<"authorization">> -> +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) when Name =:= <<"content-length">> -> +parse_header(Name = <<"content-length">>, Req, Default) -> parse_header(Name, Req, Default, fun cowboy_http:digits/1); -parse_header(Name, Req, Default) when Name =:= <<"content-type">> -> +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 cowboy_http:cookie_list/1); -parse_header(Name, Req, Default) when Name =:= <<"expect">> -> +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) - when Name =:= <<"if-match">>; Name =:= <<"if-none-match">> -> + when Name =:= <<"if-match">>; + Name =:= <<"if-none-match">> -> parse_header(Name, Req, Default, fun cowboy_http:entity_tag_match/1); parse_header(Name, Req, Default) when Name =:= <<"if-modified-since">>; Name =:= <<"if-unmodified-since">> -> parse_header(Name, Req, Default, fun cowboy_http:http_date/1); -parse_header(Name, Req, Default) when Name =:= <<"sec-websocket-protocol">> -> +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); %% @todo Extension parameters. -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 =:= <<"transfer-encoding">>; + Name =:= <<"upgrade">> -> parse_header(Name, Req, Default, fun (Value) -> cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) @@ -566,7 +542,7 @@ set_meta(Name, Value, Req=#http_req{meta=Meta}) -> %% Request Body API. %% @doc Return whether the request message has a body. --spec has_body(cowboy_req:req()) -> boolean(). +-spec has_body(req()) -> boolean(). has_body(Req) -> case lists:keyfind(<<"content-length">>, 1, Req#http_req.headers) of {_, <<"0">>} -> @@ -613,11 +589,11 @@ init_stream(TransferDecode, TransferState, ContentDecode, Req) -> {ok, Req#http_req{body_state= {stream, 0, TransferDecode, TransferState, ContentDecode}}}. -%% @equiv stream_body(Req, 1000000) +%% @equiv stream_body(1000000, Req) -spec stream_body(Req) -> {ok, binary(), Req} | {done, Req} | {error, atom()} when Req::req(). stream_body(Req) -> - stream_body(Req, 1000000). + stream_body(1000000, Req). %% @doc Stream the request's body. %% @@ -633,10 +609,10 @@ stream_body(Req) -> %% %% You can limit the size of the chunks being returned by using the %% second argument which is the size in bytes. It defaults to 1000000 bytes. --spec stream_body(Req, non_neg_integer()) -> {ok, binary(), Req} +-spec stream_body(non_neg_integer(), Req) -> {ok, binary(), Req} | {done, Req} | {error, atom()} when Req::req(). -stream_body(Req=#http_req{body_state=waiting, version=Version, - transport=Transport, socket=Socket}, MaxLength) -> +stream_body(MaxLength, Req=#http_req{body_state=waiting, version=Version, + transport=Transport, socket=Socket}) -> {ok, ExpectHeader, Req1} = parse_header(<<"expect">>, Req), case ExpectHeader of [<<"100-continue">>] -> @@ -648,37 +624,35 @@ stream_body(Req=#http_req{body_state=waiting, version=Version, end, case parse_header(<<"transfer-encoding">>, Req1) of {ok, [<<"chunked">>], Req2} -> - stream_body(Req2#http_req{body_state= + stream_body(MaxLength, Req2#http_req{body_state= {stream, 0, fun cowboy_http:te_chunked/2, {0, 0}, - fun cowboy_http:ce_identity/1}}, - MaxLength); + fun cowboy_http:ce_identity/1}}); {ok, [<<"identity">>], Req2} -> {Length, Req3} = body_length(Req2), case Length of 0 -> {done, Req3#http_req{body_state=done}}; Length -> - stream_body(Req3#http_req{body_state= + stream_body(MaxLength, Req3#http_req{body_state= {stream, Length, fun cowboy_http:te_identity/2, {0, Length}, - fun cowboy_http:ce_identity/1}}, - MaxLength) + fun cowboy_http:ce_identity/1}}) end end; -stream_body(Req=#http_req{body_state=done}, _) -> +stream_body(_, Req=#http_req{body_state=done}) -> {done, Req}; -stream_body(Req=#http_req{buffer=Buffer}, _) +stream_body(_, Req=#http_req{buffer=Buffer}) when Buffer =/= <<>> -> transfer_decode(Buffer, Req#http_req{buffer= <<>>}); -stream_body(Req, MaxLength) -> - stream_body_recv(Req, MaxLength). +stream_body(MaxLength, Req) -> + stream_body_recv(MaxLength, Req). --spec stream_body_recv(Req, non_neg_integer()) +-spec stream_body_recv(non_neg_integer(), Req) -> {ok, binary(), Req} | {error, atom()} when Req::req(). -stream_body_recv(Req=#http_req{ +stream_body_recv(MaxLength, Req=#http_req{ transport=Transport, socket=Socket, buffer=Buffer, - body_state={stream, Length, _, _, _}}, MaxLength) -> + body_state={stream, Length, _, _, _}}) -> %% @todo Allow configuring the timeout. case Transport:recv(Socket, min(Length, MaxLength), 5000) of {ok, Data} -> transfer_decode(<< Buffer/binary, Data/binary >>, @@ -697,8 +671,8 @@ transfer_decode(Data, Req=#http_req{body_state={stream, _, TransferDecode, TransferState2, ContentDecode}}); %% @todo {header(s) for chunked more -> - stream_body_recv(Req#http_req{buffer=Data, body_state={stream, - 0, TransferDecode, TransferState, ContentDecode}}, 0); + stream_body_recv(0, Req#http_req{buffer=Data, body_state={stream, + 0, TransferDecode, TransferState, ContentDecode}}); {more, Length, Data2, TransferState2} -> content_decode(ContentDecode, Data2, Req#http_req{body_state={stream, Length, @@ -1015,8 +989,7 @@ reply(Status, Headers, Body, Req=#http_req{ reply_may_compress(Status, Headers, Body, Req, RespHeaders, HTTP11Headers, Method) -> BodySize = iolist_size(Body), - {ok, Encodings, Req2} - = cowboy_req:parse_header(<<"accept-encoding">>, Req), + {ok, Encodings, Req2} = parse_header(<<"accept-encoding">>, Req), CanGzip = (BodySize > 300) andalso (false =:= lists:keyfind(<<"content-encoding">>, 1, Headers)) diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index cb4fffb..526f102 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -96,9 +96,8 @@ 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">>; - Method =:= <<"PATCH">> -> + Method =:= <<"PATCH">>; Method =:= <<"DELETE">>; + Method =:= <<"OPTIONS">> -> next(Req, State, fun uri_too_long/2); no_call -> next(Req, State, 501); @@ -236,8 +235,8 @@ content_types_provided(Req, State) -> normalize_content_types({ContentType, Callback}) when is_binary(ContentType) -> {cowboy_http:content_type(ContentType), Callback}; -normalize_content_types(Provided) -> - Provided. +normalize_content_types(Normalized) -> + Normalized. prioritize_accept(Accept) -> lists:sort( @@ -728,16 +727,18 @@ is_post_to_missing_resource(Req, State, OnFalse) -> respond(Req, State, OnFalse). allow_missing_post(Req, State, OnFalse) -> - expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse). + expect(Req, State, allow_missing_post, true, fun post_resource/2, OnFalse). + +post_resource(Req, State) -> + accept_resource(Req, State, 204). method(Req, State=#state{method= <<"DELETE">>}) -> delete_resource(Req, State); -method(Req, State=#state{method= <<"POST">>}) -> - post_is_create(Req, State); method(Req, State=#state{method= <<"PUT">>}) -> is_conflict(Req, State); -method(Req, State=#state{method= <<"PATCH">>}) -> - patch_resource(Req, State); +method(Req, State=#state{method=Method}) + when Method =:= <<"POST">>; Method =:= <<"PATCH">> -> + accept_resource(Req, State, 204); method(Req, State=#state{method=Method}) when Method =:= <<"GET">>; Method =:= <<"HEAD">> -> set_resp_body_etag(Req, State); @@ -752,79 +753,21 @@ delete_resource(Req, State) -> delete_completed(Req, State) -> expect(Req, State, delete_completed, true, fun has_resp_body/2, 202). -%% post_is_create/2 indicates whether the POST method can create new resources. -post_is_create(Req, State) -> - expect(Req, State, post_is_create, false, fun process_post/2, fun create_path/2). - -%% When the POST method can create new resources, create_path/2 will be called -%% and is expected to return the full path to the new resource -%% (including the leading /). -create_path(Req, State) -> - case call(Req, State, create_path) of - no_call -> - put_resource(Req, State, fun created_path/2); - {halt, Req2, HandlerState} -> - terminate(Req2, State#state{handler_state=HandlerState}); - {Path, Req2, HandlerState} -> - {HostURL, Req3} = cowboy_req:host_url(Req2), - State2 = State#state{handler_state=HandlerState}, - Req4 = cowboy_req:set_resp_header( - <<"location">>, << HostURL/binary, Path/binary >>, Req3), - put_resource(cowboy_req:set_meta(put_path, Path, Req4), - State2, 303) - end. - -%% Called after content_types_accepted is called for POST methods -%% when create_path did not exist. Expects the full path to -%% be returned and MUST exist in the case that create_path -%% does not. -created_path(Req, State) -> - case call(Req, State, created_path) of - {halt, Req2, HandlerState} -> - terminate(Req2, State#state{handler_state=HandlerState}); - {Path, Req2, HandlerState} -> - {HostURL, Req3} = cowboy_req:host_url(Req2), - State2 = State#state{handler_state=HandlerState}, - Req4 = cowboy_req:set_resp_header( - <<"location">>, << HostURL/binary, Path/binary >>, Req3), - respond(cowboy_req:set_meta(put_path, Path, Req4), - State2, 303) - end. - -%% process_post should return true when the POST body could be processed -%% and false when it hasn't, in which case a 500 error is sent. -process_post(Req, State) -> - case call(Req, State, process_post) of - {halt, Req2, HandlerState} -> - terminate(Req2, State#state{handler_state=HandlerState}); - {true, Req2, HandlerState} -> - State2 = State#state{handler_state=HandlerState}, - next(Req2, State2, fun is_new_resource/2); - {false, Req2, HandlerState} -> - State2 = State#state{handler_state=HandlerState}, - respond(Req2, State2, 500) - end. - is_conflict(Req, State) -> expect(Req, State, is_conflict, false, fun put_resource/2, 409). put_resource(Req, State) -> - Path = cowboy_req:get(path, Req), - put_resource(cowboy_req:set_meta(put_path, Path, Req), - State, fun is_new_resource/2). + accept_resource(Req, State, fun is_new_resource/2). %% content_types_accepted should return a list of media types and their %% associated callback functions in the same format as content_types_provided. %% %% The callback will then be called and is expected to process the content -%% pushed to the resource in the request body. The path to the new resource -%% may be different from the request path, and is stored as request metadata. -%% It is always defined past this point. It can be retrieved as demonstrated: -%% {PutPath, Req2} = cowboy_req:meta(put_path, Req) +%% pushed to the resource in the request body. %% -%%content_types_accepted SHOULD return a different list +%% content_types_accepted SHOULD return a different list %% for each HTTP method. -put_resource(Req, State, OnTrue) -> +accept_resource(Req, State, OnTrue) -> case call(Req, State, content_types_accepted) of no_call -> respond(Req, State, 415); @@ -838,27 +781,6 @@ put_resource(Req, State, OnTrue) -> choose_content_type(Req3, State2, OnTrue, ContentType, CTA2) end. -%% content_types_accepted should return a list of media types and their -%% associated callback functions in the same format as content_types_provided. -%% -%% The callback will then be called and is expected to process the content -%% pushed to the resource in the request body. -%% -%% content_types_accepted SHOULD return a different list -%% for each HTTP method. -patch_resource(Req, State) -> - case call(Req, State, content_types_accepted) of - no_call -> - respond(Req, State, 415); - {halt, Req2, HandlerState} -> - terminate(Req2, State#state{handler_state=HandlerState}); - {CTM, Req2, HandlerState} -> - State2 = State#state{handler_state=HandlerState}, - {ok, ContentType, Req3} - = cowboy_req:parse_header(<<"content-type">>, Req2), - choose_content_type(Req3, State2, 204, ContentType, CTM) - end. - %% The special content type '*' will always match. It can be used as a %% catch-all content type for accepting any kind of request content. %% Note that because it will always match, it should be the last of the @@ -880,9 +802,8 @@ choose_content_type(Req, State, OnTrue, choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) -> choose_content_type(Req, State, OnTrue, ContentType, Tail). -process_content_type(Req, - State=#state{handler=Handler, handler_state=HandlerState}, - OnTrue, Fun) -> +process_content_type(Req, State=#state{method=Method, + handler=Handler, handler_state=HandlerState}, OnTrue, Fun) -> case call(Req, State, Fun) of no_call -> error_logger:error_msg( @@ -898,7 +819,12 @@ process_content_type(Req, next(Req2, State2, OnTrue); {false, Req2, HandlerState2} -> State2 = State#state{handler_state=HandlerState2}, - respond(Req2, State2, 422) + respond(Req2, State2, 422); + {ResURL, Req2, HandlerState2} when Method =:= <<"POST">> -> + State2 = State#state{handler_state=HandlerState2}, + Req3 = cowboy_req:set_resp_header( + <<"location">>, ResURL, Req2), + respond(Req3, State2, 303) end. %% Whether we created a new resource, either through PUT or POST. diff --git a/src/cowboy_router.erl b/src/cowboy_router.erl index 7c86653..91912d8 100644 --- a/src/cowboy_router.erl +++ b/src/cowboy_router.erl @@ -37,7 +37,7 @@ | {atom(), function, fun ((binary()) -> true | {true, any()} | false)}]. -export_type([constraints/0]). --type route_match() :: '_' | binary() | string(). +-type route_match() :: '_' | iodata(). -type route_path() :: {Path::route_match(), Handler::module(), Opts::any()} | {Path::route_match(), constraints(), Handler::module(), Opts::any()}. -type route_rule() :: {Host::route_match(), Paths::[route_path()]} @@ -88,7 +88,7 @@ compile_paths([{PathMatch, Handler, Opts}|Tail], Acc) -> compile_paths([{PathMatch, [], Handler, Opts}|Tail], Acc); compile_paths([{PathMatch, Constraints, Handler, Opts}|Tail], Acc) when is_list(PathMatch) -> - compile_paths([{list_to_binary(PathMatch), + compile_paths([{iolist_to_binary(PathMatch), Constraints, Handler, Opts}|Tail], Acc); compile_paths([{'_', Constraints, Handler, Opts}|Tail], Acc) -> compile_paths(Tail, [{'_', Constraints, Handler, Opts}] ++ Acc); diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl index d583fa9..fe6600c 100644 --- a/src/cowboy_static.erl +++ b/src/cowboy_static.erl @@ -324,7 +324,8 @@ file_contents(Req, #state{filepath=Filepath, %% if the connection is closed while sending the file. case Transport:sendfile(Socket, Filepath) of {ok, _} -> ok; - {error, closed} -> ok + {error, closed} -> ok; + {error, etimedout} -> ok end end, {{stream, Filesize, Writefile}, Req, State}. |