From d3dcaf109b225b1384fad5b17dbae9c4888c40ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 27 Aug 2012 13:28:57 +0200 Subject: Rename cowboy_http_req to cowboy_req --- src/cowboy_http_req.erl | 1038 ----------------------------------------- src/cowboy_http_rest.erl | 54 +-- src/cowboy_http_static.erl | 4 +- src/cowboy_http_websocket.erl | 30 +- src/cowboy_protocol.erl | 14 +- src/cowboy_req.erl | 1038 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1089 insertions(+), 1089 deletions(-) delete mode 100644 src/cowboy_http_req.erl create mode 100644 src/cowboy_req.erl (limited to 'src') diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl deleted file mode 100644 index 985485a..0000000 --- a/src/cowboy_http_req.erl +++ /dev/null @@ -1,1038 +0,0 @@ -%% Copyright (c) 2011-2012, Loïc Hoguin -%% Copyright (c) 2011, Anthony Ramine -%% -%% 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. - -%% @doc HTTP request manipulation API. -%% -%% Almost all functions in this module return a new Req variable. -%% It should always be used instead of the one used in your function call -%% because it keeps the state of the request. It also allows Cowboy to do -%% some lazy evaluation and cache results where possible. --module(cowboy_http_req). - -%% Request API. --export([method/1]). --export([version/1]). --export([peer/1]). --export([peer_addr/1]). --export([host/1]). --export([host_info/1]). --export([raw_host/1]). --export([port/1]). --export([path/1]). --export([path_info/1]). --export([raw_path/1]). --export([qs_val/2]). --export([qs_val/3]). --export([qs_vals/1]). --export([raw_qs/1]). --export([binding/2]). --export([binding/3]). --export([bindings/1]). --export([header/2]). --export([header/3]). --export([headers/1]). --export([parse_header/2]). --export([parse_header/3]). --export([cookie/2]). --export([cookie/3]). --export([cookies/1]). --export([meta/2]). --export([meta/3]). - -%% Request body API. --export([has_body/1]). --export([body_length/1]). --export([init_stream/4]). --export([stream_body/1]). --export([skip_body/1]). --export([body/1]). --export([body/2]). --export([body_qs/1]). --export([multipart_data/1]). --export([multipart_skip/1]). - -%% Response API. --export([set_resp_cookie/4]). --export([set_resp_header/3]). --export([set_resp_body/2]). --export([set_resp_body_fun/3]). --export([has_resp_header/2]). --export([has_resp_body/1]). --export([reply/2]). --export([reply/3]). --export([reply/4]). --export([chunked_reply/2]). --export([chunked_reply/3]). --export([chunk/2]). --export([upgrade_reply/3]). - -%% Misc API. --export([compact/1]). --export([transport/1]). - --include("http.hrl"). - -%% Request API. - -%% @doc Return the HTTP method of the request. --spec method(#http_req{}) -> {cowboy_http:method(), #http_req{}}. -method(Req) -> - {Req#http_req.method, Req}. - -%% @doc Return the HTTP version used for the request. --spec version(#http_req{}) -> {cowboy_http:version(), #http_req{}}. -version(Req) -> - {Req#http_req.version, Req}. - -%% @doc Return the peer address and port number of the remote host. --spec peer(#http_req{}) - -> {{inet:ip_address(), inet:port_number()}, #http_req{}}. -peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) -> - {ok, Peer} = Transport:peername(Socket), - {Peer, Req#http_req{peer=Peer}}; -peer(Req) -> - {Req#http_req.peer, Req}. - -%% @doc Returns the peer address calculated from headers. --spec peer_addr(#http_req{}) -> {inet:ip_address(), #http_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, "^(?[^\\,]+)", - [{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 tokens for the hostname requested. --spec host(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. -host(Req) -> - {Req#http_req.host, Req}. - -%% @doc Return the extra host information obtained from partially matching -%% the hostname using '...'. --spec host_info(#http_req{}) - -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. -host_info(Req) -> - {Req#http_req.host_info, Req}. - -%% @doc Return the raw host directly taken from the request. --spec raw_host(#http_req{}) -> {binary(), #http_req{}}. -raw_host(Req) -> - {Req#http_req.raw_host, Req}. - -%% @doc Return the port used for this request. --spec port(#http_req{}) -> {inet:port_number(), #http_req{}}. -port(Req) -> - {Req#http_req.port, Req}. - -%% @doc Return the path segments for the path requested. -%% -%% Following RFC2396, this function may return path segments containing any -%% character, including / if, and only if, a / was escaped -%% and part of a path segment in the path requested. --spec path(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. -path(Req) -> - {Req#http_req.path, Req}. - -%% @doc Return the extra path information obtained from partially matching -%% the patch using '...'. --spec path_info(#http_req{}) - -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. -path_info(Req) -> - {Req#http_req.path_info, Req}. - -%% @doc Return the raw path directly taken from the request. --spec raw_path(#http_req{}) -> {binary(), #http_req{}}. -raw_path(Req) -> - {Req#http_req.raw_path, Req}. - -%% @equiv qs_val(Name, Req, undefined) --spec qs_val(binary(), #http_req{}) - -> {binary() | true | undefined, #http_req{}}. -qs_val(Name, Req) when is_binary(Name) -> - qs_val(Name, Req, undefined). - -%% @doc Return the query string value for the given key, or a default if -%% missing. --spec qs_val(binary(), #http_req{}, Default) - -> {binary() | true | Default, #http_req{}} when Default::any(). -qs_val(Name, Req=#http_req{raw_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_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. - -%% @doc Return the full list of query string values. --spec qs_vals(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. -qs_vals(Req=#http_req{raw_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_vals=QsVals}); -qs_vals(Req=#http_req{qs_vals=QsVals}) -> - {QsVals, Req}. - -%% @doc Return the raw query string directly taken from the request. --spec raw_qs(#http_req{}) -> {binary(), #http_req{}}. -raw_qs(Req) -> - {Req#http_req.raw_qs, Req}. - -%% @equiv binding(Name, Req, undefined) --spec binding(atom(), #http_req{}) -> {binary() | undefined, #http_req{}}. -binding(Name, Req) when is_atom(Name) -> - binding(Name, Req, undefined). - -%% @doc Return the binding value for the given key obtained when matching -%% the host and path against the dispatch list, or a default if missing. --spec binding(atom(), #http_req{}, Default) - -> {binary() | Default, #http_req{}} 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} - end. - -%% @doc Return the full list of binding values. --spec bindings(#http_req{}) -> {list({atom(), binary()}), #http_req{}}. -bindings(Req) -> - {Req#http_req.bindings, Req}. - -%% @equiv header(Name, Req, undefined) --spec header(atom() | binary(), #http_req{}) - -> {binary() | undefined, #http_req{}}. -header(Name, Req) when is_atom(Name) orelse is_binary(Name) -> - header(Name, Req, undefined). - -%% @doc Return the header value for the given key, or a default if missing. --spec header(atom() | binary(), #http_req{}, Default) - -> {binary() | Default, #http_req{}} when Default::any(). -header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) -> - case lists:keyfind(Name, 1, Req#http_req.headers) of - {Name, Value} -> {Value, Req}; - false -> {Default, Req} - end. - -%% @doc Return the full list of headers. --spec headers(#http_req{}) -> {cowboy_http:headers(), #http_req{}}. -headers(Req) -> - {Req#http_req.headers, Req}. - -%% @doc Semantically parse headers. -%% -%% 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(), #http_req{}) - -> {any(), #http_req{}} | {undefined, binary(), #http_req{}} - | {error, badarg}. -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} -> {Value, Req} - 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">>]; -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(), #http_req{}, any()) - -> {any(), #http_req{}} | {undefined, binary(), #http_req{}} - | {error, badarg}. -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, - 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, - 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, - 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, - fun (Value) -> - cowboy_http:digits(Value) - end); -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, - 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' -> - 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' -> - 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, - 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, - fun (Value) -> - cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) - end); -parse_header(Name, Req, Default) -> - {Value, Req2} = header(Name, Req, Default), - {undefined, Value, Req2}. - -parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> - case lists:keyfind(Name, 1, PHeaders) of - {Name, P} -> - {P, Req}; - false -> - parse_header_no_cache(Name, Req, Default, Fun) - end. - -parse_header_no_cache(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> - case header(Name, Req) of - {undefined, Req2} -> - {Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}}; - {Value, Req2} -> - case Fun(Value) of - {error, badarg} -> - {error, badarg}; - P -> - {P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}} - end - end. - -%% @equiv cookie(Name, Req, undefined) --spec cookie(binary(), #http_req{}) - -> {binary() | true | undefined, #http_req{}}. -cookie(Name, Req) when is_binary(Name) -> - cookie(Name, Req, undefined). - -%% @doc Return the cookie value for the given key, or a default if -%% missing. --spec cookie(binary(), #http_req{}, Default) - -> {binary() | true | Default, #http_req{}} when Default::any(). -cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> - case header('Cookie', Req) of - {undefined, Req2} -> - {Default, Req2#http_req{cookies=[]}}; - {RawCookie, Req2} -> - Cookies = cowboy_cookies:parse_cookie(RawCookie), - 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. - -%% @doc Return the full list of cookie values. --spec cookies(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. -cookies(Req=#http_req{cookies=undefined}) -> - case header('Cookie', Req) of - {undefined, Req2} -> - {[], Req2#http_req{cookies=[]}}; - {RawCookie, Req2} -> - Cookies = cowboy_cookies:parse_cookie(RawCookie), - cookies(Req2#http_req{cookies=Cookies}) - end; -cookies(Req=#http_req{cookies=Cookies}) -> - {Cookies, Req}. - -%% @equiv meta(Name, Req, undefined) --spec meta(atom(), #http_req{}) -> {any() | undefined, #http_req{}}. -meta(Name, Req) -> - meta(Name, Req, undefined). - -%% @doc Return metadata information about the request. -%% -%% Metadata information varies from one protocol to another. Websockets -%% would define the protocol version here, while REST would use it to -%% indicate which media type, language and charset were retained. --spec meta(atom(), #http_req{}, any()) -> {any(), #http_req{}}. -meta(Name, Req, Default) -> - case lists:keyfind(Name, 1, Req#http_req.meta) of - {Name, Value} -> {Value, Req}; - false -> {Default, Req} - end. - -%% Request Body API. - -%% @doc Return whether the request message has a body. --spec has_body(#http_req{}) -> {boolean(), #http_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, Req}. - -%% @doc Return the request message body length, if known. -%% -%% 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(#http_req{}) -> {undefined | non_neg_integer(), #http_req{}}. -body_length(Req) -> - case lists:keymember('Transfer-Encoding', 1, Req#http_req.headers) of - true -> {undefined, Req}; - false -> parse_header('Content-Length', Req, 0) - end. - -%% @doc Initialize body streaming and set custom decoding functions. -%% -%% Calling this function is optional. It should only be used if you -%% need to override the default behavior of Cowboy. Otherwise you -%% should call stream_body/1 directly. -%% -%% Two decodings happen. First a decoding function is applied to the -%% transferred data, and then another is applied to the actual content. -%% -%% Transfer encoding is generally used for chunked bodies. The decoding -%% function uses a state to keep track of how much it has read, which is -%% also initialized through this function. -%% -%% Content encoding is generally used for compression. -%% -%% Standard encodings can be found in cowboy_http. --spec init_stream(fun(), any(), fun(), #http_req{}) -> {ok, #http_req{}}. -init_stream(TransferDecode, TransferState, ContentDecode, Req) -> - {ok, Req#http_req{body_state= - {stream, TransferDecode, TransferState, ContentDecode}}}. - -%% @doc Stream the request's body. -%% -%% This is the most low level function to read the request body. -%% -%% In most cases, if they weren't defined before using stream_body/4, -%% this function will guess which transfer and content encodings were -%% used for building the request body, and configure the decoding -%% functions that will be used when streaming. -%% -%% It then starts streaming the body, returning {ok, Data, Req} -%% for each streamed part, and {done, Req} when it's finished streaming. --spec stream_body(#http_req{}) -> {ok, binary(), #http_req{}} - | {done, #http_req{}} | {error, atom()}. -stream_body(Req=#http_req{body_state=waiting, - version=Version, transport=Transport, socket=Socket}) -> - case parse_header(<<"Expect">>, Req) of - {[<<"100-continue">>], Req1} -> - HTTPVer = cowboy_http:version_to_binary(Version), - Transport:send(Socket, - << HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>); - {undefined, Req1} -> - ok; - {undefined, _, Req1} -> - ok - end, - case parse_header('Transfer-Encoding', Req1) of - {[<<"chunked">>], Req2} -> - stream_body(Req2#http_req{body_state= - {stream, fun cowboy_http:te_chunked/2, {0, 0}, - fun cowboy_http:ce_identity/1}}); - {[<<"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, fun cowboy_http:te_identity/2, {0, Length}, - fun cowboy_http:ce_identity/1}}) - end - end; -stream_body(Req=#http_req{buffer=Buffer, body_state={stream, _, _, _}}) - when Buffer =/= <<>> -> - transfer_decode(Buffer, Req#http_req{buffer= <<>>}); -stream_body(Req=#http_req{body_state={stream, _, _, _}}) -> - stream_body_recv(Req); -stream_body(Req=#http_req{body_state=done}) -> - {done, Req}; -stream_body(Req=#http_req{body_state={multipart, _N, _Fun}, - transport=Transport, socket=Socket}) -> - case Transport:recv(Socket, 0, 5000) of - {ok, Data} -> {ok, Data, Req}; - {error, Reason} -> {error, Reason} - end. - --spec stream_body_recv(#http_req{}) - -> {ok, binary(), #http_req{}} | {error, atom()}. -stream_body_recv(Req=#http_req{ - transport=Transport, socket=Socket, buffer=Buffer}) -> - %% @todo Allow configuring the timeout. - case Transport:recv(Socket, 0, 5000) of - {ok, Data} -> transfer_decode(<< Buffer/binary, Data/binary >>, Req); - {error, Reason} -> {error, Reason} - end. - --spec transfer_decode(binary(), #http_req{}) - -> {ok, binary(), #http_req{}} | {error, atom()}. -transfer_decode(Data, Req=#http_req{ - body_state={stream, TransferDecode, TransferState, ContentDecode}}) -> - case TransferDecode(Data, TransferState) of - {ok, Data2, TransferState2} -> - content_decode(ContentDecode, Data2, Req#http_req{body_state= - {stream, TransferDecode, TransferState2, ContentDecode}}); - {ok, Data2, Rest, TransferState2} -> - content_decode(ContentDecode, Data2, Req#http_req{ - buffer=Rest, body_state= - {stream, TransferDecode, TransferState2, ContentDecode}}); - %% @todo {header(s) for chunked - more -> - stream_body_recv(Req#http_req{buffer=Data}); - {done, Length, Rest} -> - Req2 = transfer_decode_done(Length, Rest, Req), - {done, Req2}; - {done, Data2, Length, Rest} -> - Req2 = transfer_decode_done(Length, Rest, Req), - content_decode(ContentDecode, Data2, Req2); - {error, Reason} -> - {error, Reason} - end. - --spec transfer_decode_done(non_neg_integer(), binary(), #http_req{}) - -> #http_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))}), - %% 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), - Req#http_req{buffer=Rest, body_state=done, - headers=Headers3, p_headers=PHeaders3}. - -%% @todo Probably needs a Rest. --spec content_decode(fun(), binary(), #http_req{}) - -> {ok, binary(), #http_req{}} | {error, atom()}. -content_decode(ContentDecode, Data, Req) -> - case ContentDecode(Data) of - {ok, Data2} -> {ok, Data2, Req}; - {error, Reason} -> {error, Reason} - end. - -%% @doc Return the full body sent with the request. --spec body(#http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}. -body(Req) -> - read_body(infinity, Req, <<>>). - -%% @doc Return the full body sent with the request as long as the body -%% length doesn't go over MaxLength. -%% -%% This is most useful to quickly be able to get the full body while -%% avoiding filling your memory with huge request bodies when you're -%% not expecting it. --spec body(non_neg_integer() | infinity, #http_req{}) - -> {ok, binary(), #http_req{}} | {error, atom()}. -body(MaxLength, Req) -> - read_body(MaxLength, Req, <<>>). - --spec read_body(non_neg_integer() | infinity, #http_req{}, binary()) - -> {ok, binary(), #http_req{}} | {error, atom()}. -read_body(MaxLength, Req, Acc) when MaxLength > byte_size(Acc) -> - case stream_body(Req) of - {ok, Data, Req2} -> - read_body(MaxLength, Req2, << Acc/binary, Data/binary >>); - {done, Req2} -> - {ok, Acc, Req2}; - {error, Reason} -> - {error, Reason} - end. - --spec skip_body(#http_req{}) -> {ok, #http_req{}} | {error, atom()}. -skip_body(Req) -> - case stream_body(Req) of - {ok, _, Req2} -> skip_body(Req2); - {done, Req2} -> {ok, Req2}; - {error, Reason} -> {error, Reason} - end. - -%% @doc Return the full body sent with the reqest, parsed as an -%% application/x-www-form-urlencoded string. Essentially a POST query string. -%% @todo We need an option to limit the size of the body for QS too. --spec body_qs(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. -body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) -> - {ok, Body, Req2} = body(Req), - {cowboy_http:x_www_form_urlencoded( - Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2}. - -%% Multipart Request API. - -%% @doc Return data from the multipart parser. -%% -%% Use this function for multipart streaming. For each part in the request, -%% this function returns {headers, Headers} followed by a sequence of -%% {body, Data} tuples and finally end_of_part. When there -%% is no part to parse anymore, eof is returned. -%% -%% If the request Content-Type is not a multipart one, {error, badarg} -%% is returned. --spec multipart_data(#http_req{}) - -> {{headers, cowboy_http:headers()} - | {body, binary()} | end_of_part | eof, - #http_req{}}. -multipart_data(Req=#http_req{body_state=waiting}) -> - {{<<"multipart">>, _SubType, Params}, Req2} = - parse_header('Content-Type', Req), - {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), - {Length, Req3} = parse_header('Content-Length', Req2), - multipart_data(Req3, Length, {more, cowboy_multipart:parser(Boundary)}); -multipart_data(Req=#http_req{body_state={multipart, Length, Cont}}) -> - multipart_data(Req, Length, Cont()); -multipart_data(Req=#http_req{body_state=done}) -> - {eof, Req}. - -multipart_data(Req, Length, {headers, Headers, Cont}) -> - {{headers, Headers}, Req#http_req{body_state={multipart, Length, Cont}}}; -multipart_data(Req, Length, {body, Data, Cont}) -> - {{body, Data}, Req#http_req{body_state={multipart, Length, Cont}}}; -multipart_data(Req, Length, {end_of_part, Cont}) -> - {end_of_part, Req#http_req{body_state={multipart, Length, Cont}}}; -multipart_data(Req, 0, eof) -> - {eof, Req#http_req{body_state=done}}; -multipart_data(Req=#http_req{socket=Socket, transport=Transport}, - Length, eof) -> - %% We just want to skip so no need to stream data here. - {ok, _Data} = Transport:recv(Socket, Length, 5000), - {eof, Req#http_req{body_state=done}}; -multipart_data(Req, Length, {more, Parser}) when Length > 0 -> - case stream_body(Req) of - {ok, << Data:Length/binary, Buffer/binary >>, Req2} -> - multipart_data(Req2#http_req{buffer=Buffer}, 0, Parser(Data)); - {ok, Data, Req2} -> - multipart_data(Req2, Length - byte_size(Data), Parser(Data)) - end. - -%% @doc Skip a part returned by the multipart parser. -%% -%% This function repeatedly calls multipart_data/1 until -%% end_of_part or eof is parsed. -multipart_skip(Req) -> - case multipart_data(Req) of - {end_of_part, Req2} -> {ok, Req2}; - {eof, Req2} -> {ok, Req2}; - {_Other, Req2} -> multipart_skip(Req2) - end. - -%% Response API. - -%% @doc Add a cookie header to the response. --spec set_resp_cookie(binary(), binary(), [cowboy_cookies:cookie_option()], - #http_req{}) -> {ok, #http_req{}}. -set_resp_cookie(Name, Value, Options, Req) -> - {HeaderName, HeaderValue} = cowboy_cookies:cookie(Name, Value, Options), - set_resp_header(HeaderName, HeaderValue, Req). - -%% @doc Add a header to the response. --spec set_resp_header(cowboy_http:header(), iodata(), #http_req{}) - -> {ok, #http_req{}}. -set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> - NameBin = header_to_binary(Name), - {ok, Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}}. - -%% @doc Add a body to the response. -%% -%% The body set here is ignored if the response is later sent using -%% anything other than reply/2 or reply/3. The response body is expected -%% to be a binary or an iolist. --spec set_resp_body(iodata(), #http_req{}) -> {ok, #http_req{}}. -set_resp_body(Body, Req) -> - {ok, Req#http_req{resp_body=Body}}. - - -%% @doc Add a body function to the response. -%% -%% The response body may also be set to a content-length - stream-function pair. -%% If the response body is of this type normal response headers will be sent. -%% After the response headers has been sent the body function is applied. -%% The body function is expected to write the response body directly to the -%% socket using the transport module. -%% -%% If the body function crashes while writing the response body or writes fewer -%% bytes than declared the behaviour is undefined. The body set here is ignored -%% if the response is later sent using anything other than `reply/2' or -%% `reply/3'. -%% -%% @see cowboy_http_req:transport/1. --spec set_resp_body_fun(non_neg_integer(), fun(() -> {sent, non_neg_integer()}), - #http_req{}) -> {ok, #http_req{}}. -set_resp_body_fun(StreamLen, StreamFun, Req) -> - {ok, 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(), #http_req{}) -> boolean(). -has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> - NameBin = header_to_binary(Name), - lists:keymember(NameBin, 1, RespHeaders). - -%% @doc Return whether a body has been set for the response. --spec has_resp_body(#http_req{}) -> boolean(). -has_resp_body(#http_req{resp_body={Length, _}}) -> - Length > 0; -has_resp_body(#http_req{resp_body=RespBody}) -> - iolist_size(RespBody) > 0. - -%% @equiv reply(Status, [], [], Req) --spec reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. -reply(Status, Req=#http_req{resp_body=Body}) -> - reply(Status, [], Body, Req). - -%% @equiv reply(Status, Headers, [], Req) --spec reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) - -> {ok, #http_req{}}. -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(), #http_req{}) - -> {ok, #http_req{}}. -reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport, - 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)}]; - _ -> [] - 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 - end, - {ok, Req2#http_req{connection=RespConn, resp_state=done, - resp_headers=[], resp_body= <<>>}}. - -%% @equiv chunked_reply(Status, [], Req) --spec chunked_reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. -chunked_reply(Status, Req) -> - chunked_reply(Status, [], Req). - -%% @doc Initiate the sending of a chunked reply to the client. -%% @see cowboy_http_req:chunk/2 --spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) - -> {ok, #http_req{}}. -chunked_reply(Status, Headers, Req=#http_req{ - version=Version, connection=Connection, - resp_state=waiting, resp_headers=RespHeaders}) -> - RespConn = response_connection(Headers, Connection), - HTTP11Headers = case Version of - {1, 1} -> [ - {<<"Connection">>, atom_to_connection(Connection)}, - {<<"Transfer-Encoding">>, <<"chunked">>}]; - _ -> [] - end, - {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"Date">>, cowboy_clock:rfc1123()}, - {<<"Server">>, <<"Cowboy">>} - |HTTP11Headers], Req), - {ok, Req2#http_req{connection=RespConn, resp_state=chunks, - resp_headers=[], resp_body= <<>>}}. - -%% @doc Send a chunk of data. -%% -%% A chunked reply must have been initiated before calling this function. --spec chunk(iodata(), #http_req{}) -> ok | {error, atom()}. -chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) -> - ok; -chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) -> - 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), - <<"\r\n">>, Data, <<"\r\n">>]). - -%% @doc Send an upgrade reply. -%% @private --spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) - -> {ok, #http_req{}}. -upgrade_reply(Status, Headers, Req=#http_req{ - resp_state=waiting, resp_headers=RespHeaders}) -> - {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"Connection">>, <<"Upgrade">>} - ], Req), - {ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. - -%% Misc API. - -%% @doc Compact the request data by removing all non-system information. -%% -%% This essentially removes the host, path, query string, bindings and headers. -%% Use it when you really need to save up memory, for example when having -%% many concurrent long-running connections. --spec compact(#http_req{}) -> #http_req{}. -compact(Req) -> - Req#http_req{host=undefined, host_info=undefined, path=undefined, - path_info=undefined, qs_vals=undefined, - bindings=undefined, headers=[], - p_headers=[], cookies=[]}. - -%% @doc Return the transport module and socket associated with a request. -%% -%% This exposes the same socket interface used internally by the HTTP protocol -%% implementation to developers that needs low level access to the socket. -%% -%% It is preferred to use this in conjuction with the stream function support -%% in `set_resp_body_fun/3' if this is used to write a response body directly -%% to the socket. This ensures that the response headers are set correctly. --spec transport(#http_req{}) -> {ok, module(), inet:socket()}. -transport(#http_req{transport=Transport, socket=Socket}) -> - {ok, Transport, Socket}. - -%% Internal. - --spec response(cowboy_http:status(), cowboy_http:headers(), - cowboy_http:headers(), cowboy_http:headers(), #http_req{}) - -> {normal | hook, #http_req{}}. -response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{ - socket=Socket, transport=Transport, version=Version, - pid=ReqPid, onresponse=OnResponse}) -> - FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders), - Req2 = case OnResponse of - undefined -> Req; - OnResponse -> OnResponse(Status, FullHeaders, - %% Don't call 'onresponse' from the hook itself. - Req#http_req{resp_headers=[], resp_body= <<>>, - onresponse=undefined}) - end, - ReplyType = case Req2#http_req.resp_state of - waiting -> - HTTPVer = cowboy_http:version_to_binary(Version), - StatusLine = << HTTPVer/binary, " ", - (status(Status))/binary, "\r\n" >>, - HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>] - || {Key, Value} <- FullHeaders], - Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]), - ReqPid ! {?MODULE, resp_sent}, - normal; - _ -> - hook - end, - {ReplyType, Req2}. - --spec response_connection(cowboy_http:headers(), keepalive | close) - -> keepalive | close. -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 - 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). - --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], - merge_headers( - merge_headers(Headers2, RespHeaders), - DefaultHeaders). - --spec merge_headers(cowboy_http:headers(), cowboy_http:headers()) - -> cowboy_http:headers(). -merge_headers(Headers, []) -> - Headers; -merge_headers(Headers, [{Name, Value}|Tail]) -> - Headers2 = case lists:keymember(Name, 1, Headers) of - true -> Headers; - false -> Headers ++ [{Name, Value}] - end, - merge_headers(Headers2, Tail). - --spec atom_to_connection(keepalive) -> <<_:80>>; - (close) -> <<_:40>>. -atom_to_connection(keepalive) -> - <<"keep-alive">>; -atom_to_connection(close) -> - <<"close">>. - --spec status(cowboy_http:status()) -> binary(). -status(100) -> <<"100 Continue">>; -status(101) -> <<"101 Switching Protocols">>; -status(102) -> <<"102 Processing">>; -status(200) -> <<"200 OK">>; -status(201) -> <<"201 Created">>; -status(202) -> <<"202 Accepted">>; -status(203) -> <<"203 Non-Authoritative Information">>; -status(204) -> <<"204 No Content">>; -status(205) -> <<"205 Reset Content">>; -status(206) -> <<"206 Partial Content">>; -status(207) -> <<"207 Multi-Status">>; -status(226) -> <<"226 IM Used">>; -status(300) -> <<"300 Multiple Choices">>; -status(301) -> <<"301 Moved Permanently">>; -status(302) -> <<"302 Found">>; -status(303) -> <<"303 See Other">>; -status(304) -> <<"304 Not Modified">>; -status(305) -> <<"305 Use Proxy">>; -status(306) -> <<"306 Switch Proxy">>; -status(307) -> <<"307 Temporary Redirect">>; -status(400) -> <<"400 Bad Request">>; -status(401) -> <<"401 Unauthorized">>; -status(402) -> <<"402 Payment Required">>; -status(403) -> <<"403 Forbidden">>; -status(404) -> <<"404 Not Found">>; -status(405) -> <<"405 Method Not Allowed">>; -status(406) -> <<"406 Not Acceptable">>; -status(407) -> <<"407 Proxy Authentication Required">>; -status(408) -> <<"408 Request Timeout">>; -status(409) -> <<"409 Conflict">>; -status(410) -> <<"410 Gone">>; -status(411) -> <<"411 Length Required">>; -status(412) -> <<"412 Precondition Failed">>; -status(413) -> <<"413 Request Entity Too Large">>; -status(414) -> <<"414 Request-URI Too Long">>; -status(415) -> <<"415 Unsupported Media Type">>; -status(416) -> <<"416 Requested Range Not Satisfiable">>; -status(417) -> <<"417 Expectation Failed">>; -status(418) -> <<"418 I'm a teapot">>; -status(422) -> <<"422 Unprocessable Entity">>; -status(423) -> <<"423 Locked">>; -status(424) -> <<"424 Failed Dependency">>; -status(425) -> <<"425 Unordered Collection">>; -status(426) -> <<"426 Upgrade Required">>; -status(428) -> <<"428 Precondition Required">>; -status(429) -> <<"429 Too Many Requests">>; -status(431) -> <<"431 Request Header Fields Too Large">>; -status(500) -> <<"500 Internal Server Error">>; -status(501) -> <<"501 Not Implemented">>; -status(502) -> <<"502 Bad Gateway">>; -status(503) -> <<"503 Service Unavailable">>; -status(504) -> <<"504 Gateway Timeout">>; -status(505) -> <<"505 HTTP Version Not Supported">>; -status(506) -> <<"506 Variant Also Negotiates">>; -status(507) -> <<"507 Insufficient Storage">>; -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. diff --git a/src/cowboy_http_rest.erl b/src/cowboy_http_rest.erl index 1f83d13..a9ff934 100644 --- a/src/cowboy_http_rest.erl +++ b/src/cowboy_http_rest.erl @@ -75,7 +75,7 @@ upgrade(_ListenerPid, Handler, Opts, Req) -> " 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()]), - {ok, _Req2} = cowboy_http_req:reply(500, Req), + {ok, _Req2} = cowboy_req:reply(500, Req), close end. @@ -122,7 +122,7 @@ allowed_methods(Req=#http_req{method=Method}, State) -> end. method_not_allowed(Req, State, Methods) -> - {ok, Req2} = cowboy_http_req:set_resp_header( + {ok, Req2} = cowboy_req:set_resp_header( <<"Allow">>, method_not_allowed_build(Methods, []), Req), respond(Req2, State, 405). @@ -149,7 +149,7 @@ is_authorized(Req, State) -> {true, Req2, HandlerState} -> forbidden(Req2, State#state{handler_state=HandlerState}); {{false, AuthHead}, Req2, HandlerState} -> - {ok, Req3} = cowboy_http_req:set_resp_header( + {ok, Req3} = cowboy_req:set_resp_header( <<"Www-Authenticate">>, AuthHead, Req2), respond(Req3, State#state{handler_state=HandlerState}, 401) end. @@ -207,7 +207,7 @@ content_types_provided(Req=#http_req{meta=Meta}, State) -> CTP2 = [normalize_content_types(P) || P <- CTP], State2 = State#state{ handler_state=HandlerState, content_types_p=CTP2}, - {Accept, Req3} = cowboy_http_req:parse_header('Accept', Req2), + {Accept, Req3} = cowboy_req:parse_header('Accept', Req2), case Accept of undefined -> {PMT, _Fun} = HeadCTP = hd(CTP2), @@ -302,7 +302,7 @@ languages_provided(Req, State) -> {LP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, languages_p=LP}, {AcceptLanguage, Req3} = - cowboy_http_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)}); @@ -348,7 +348,7 @@ match_language(Req, State, Accept, [Provided|Tail], end. set_language(Req=#http_req{meta=Meta}, State=#state{language_a=Language}) -> - {ok, Req2} = cowboy_http_req:set_resp_header( + {ok, Req2} = cowboy_req:set_resp_header( <<"Content-Language">>, Language, Req), charsets_provided(Req2#http_req{meta=[{language, Language}|Meta]}, State). @@ -365,7 +365,7 @@ charsets_provided(Req, State) -> {CP, Req2, HandlerState} -> State2 = State#state{handler_state=HandlerState, charsets_p=CP}, {AcceptCharset, Req3} = - cowboy_http_req:parse_header('Accept-Charset', Req2), + cowboy_req:parse_header('Accept-Charset', Req2), case AcceptCharset of undefined -> set_content_type(Req3, State2#state{ @@ -413,7 +413,7 @@ set_content_type(Req=#http_req{meta=Meta}, State=#state{ undefined -> ContentType; Charset -> [ContentType, <<"; charset=">>, Charset] end, - {ok, Req2} = cowboy_http_req:set_resp_header( + {ok, Req2} = cowboy_req:set_resp_header( <<"Content-Type">>, ContentType2, Req), encodings_provided(Req2#http_req{meta=[{charset, Charset}|Meta]}, State). @@ -468,7 +468,7 @@ variances(Req, State=#state{content_types_p=CTP, [] -> resource_exists(Req3, State2); [[<<", ">>, H]|Variances5] -> - {ok, Req4} = cowboy_http_req:set_resp_header( + {ok, Req4} = cowboy_req:set_resp_header( <<"Variances">>, [H|Variances5], Req3), resource_exists(Req4, State2) end. @@ -478,7 +478,7 @@ resource_exists(Req, State) -> fun if_match_exists/2, fun if_match_musnt_exist/2). if_match_exists(Req, State) -> - case cowboy_http_req:parse_header('If-Match', Req) of + case cowboy_req:parse_header('If-Match', Req) of {undefined, Req2} -> if_unmodified_since_exists(Req2, State); {'*', Req2} -> @@ -496,13 +496,13 @@ if_match(Req, State, EtagsList) -> end. if_match_musnt_exist(Req, State) -> - case cowboy_http_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_http_req:parse_header('If-Unmodified-Since', Req) of + case cowboy_req:parse_header('If-Unmodified-Since', Req) of {undefined, Req2} -> if_none_match_exists(Req2, State); {{error, badarg}, Req2} -> @@ -520,7 +520,7 @@ if_unmodified_since(Req, State, IfUnmodifiedSince) -> end. if_none_match_exists(Req, State) -> - case cowboy_http_req:parse_header('If-None-Match', Req) of + case cowboy_req:parse_header('If-None-Match', Req) of {undefined, Req2} -> if_modified_since_exists(Req2, State); {'*', Req2} -> @@ -548,7 +548,7 @@ precondition_is_head_get(Req, State) -> precondition_failed(Req, State). if_modified_since_exists(Req, State) -> - case cowboy_http_req:parse_header('If-Modified-Since', Req) of + case cowboy_req:parse_header('If-Modified-Since', Req) of {undefined, Req2} -> method(Req2, State); {{error, badarg}, Req2} -> @@ -595,7 +595,7 @@ is_put_to_missing_resource(Req, State) -> moved_permanently(Req, State, OnFalse) -> case call(Req, State, moved_permanently) of {{true, Location}, Req2, HandlerState} -> - {ok, Req3} = cowboy_http_req:set_resp_header( + {ok, Req3} = cowboy_req:set_resp_header( <<"Location">>, Location, Req2), respond(Req3, State#state{handler_state=HandlerState}, 301); {false, Req2, HandlerState} -> @@ -616,7 +616,7 @@ previously_existed(Req, State) -> moved_temporarily(Req, State) -> case call(Req, State, moved_temporarily) of {{true, Location}, Req2, HandlerState} -> - {ok, Req3} = cowboy_http_req:set_resp_header( + {ok, Req3} = cowboy_req:set_resp_header( <<"Location">>, Location, Req2), respond(Req3, State#state{handler_state=HandlerState}, 307); {false, Req2, HandlerState} -> @@ -666,7 +666,7 @@ create_path(Req=#http_req{meta=Meta}, State) -> {Path, Req2, HandlerState} -> Location = create_path_location(Req2, Path), State2 = State#state{handler_state=HandlerState}, - {ok, Req3} = cowboy_http_req:set_resp_header( + {ok, Req3} = cowboy_req:set_resp_header( <<"Location">>, Location, Req2), put_resource(Req3#http_req{meta=[{put_path, Path}|Meta]}, State2, 303) @@ -717,7 +717,7 @@ put_resource(Req=#http_req{raw_path=RawPath, meta=Meta}, State) -> %% 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_http_req:meta(put_path, Req) +%% {PutPath, Req2} = cowboy_req:meta(put_path, Req) put_resource(Req, State, OnTrue) -> case call(Req, State, content_types_accepted) of no_call -> @@ -728,7 +728,7 @@ put_resource(Req, State, OnTrue) -> CTA2 = [normalize_content_types(P) || P <- CTA], State2 = State#state{handler_state=HandlerState}, {ContentType, Req3} - = cowboy_http_req:parse_header('Content-Type', Req2), + = cowboy_req:parse_header('Content-Type', Req2), choose_content_type(Req3, State2, OnTrue, ContentType, CTA2) end. @@ -757,13 +757,13 @@ choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) -> %% This is easily testable because we would have set the Location %% header by this point if we did so. is_new_resource(Req, State) -> - case cowboy_http_req:has_resp_header(<<"Location">>, Req) of + case cowboy_req:has_resp_header(<<"Location">>, Req) of true -> respond(Req, State, 201); false -> has_resp_body(Req, State) end. has_resp_body(Req, State) -> - case cowboy_http_req:has_resp_body(Req) of + case cowboy_req:has_resp_body(Req) of true -> multiple_choices(Req, State); false -> respond(Req, State, 204) end. @@ -781,7 +781,7 @@ set_resp_body(Req=#http_req{method=Method}, Req4 = Req3; LastModified -> LastModifiedStr = httpd_util:rfc1123_date(LastModified), - {ok, Req4} = cowboy_http_req:set_resp_header( + {ok, Req4} = cowboy_req:set_resp_header( <<"Last-Modified">>, LastModifiedStr, Req3) end, {Req5, State4} = set_resp_expires(Req4, State3), @@ -792,9 +792,9 @@ set_resp_body(Req=#http_req{method=Method}, State5 = State4#state{handler_state=HandlerState}, {ok, Req7} = case Body of {stream, Len, Fun1} -> - cowboy_http_req:set_resp_body_fun(Len, Fun1, Req6); + cowboy_req:set_resp_body_fun(Len, Fun1, Req6); _Contents -> - cowboy_http_req:set_resp_body(Body, Req6) + cowboy_req:set_resp_body(Body, Req6) end, multiple_choices(Req7, State5) end; @@ -812,7 +812,7 @@ set_resp_etag(Req, State) -> undefined -> {Req2, State2}; Etag -> - {ok, Req3} = cowboy_http_req:set_resp_header( + {ok, Req3} = cowboy_req:set_resp_header( <<"ETag">>, encode_etag(Etag), Req2), {Req3, State2} end. @@ -828,7 +828,7 @@ set_resp_expires(Req, State) -> {Req2, State2}; Expires -> ExpiresStr = httpd_util:rfc1123_date(Expires), - {ok, Req3} = cowboy_http_req:set_resp_header( + {ok, Req3} = cowboy_req:set_resp_header( <<"Expires">>, ExpiresStr, Req2), {Req3, State2} end. @@ -908,7 +908,7 @@ next(Req, State, StatusCode) when is_integer(StatusCode) -> respond(Req, State, StatusCode). respond(Req, State, StatusCode) -> - {ok, Req2} = cowboy_http_req:reply(StatusCode, Req), + {ok, Req2} = cowboy_req:reply(StatusCode, Req), terminate(Req2, State). terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> diff --git a/src/cowboy_http_static.erl b/src/cowboy_http_static.erl index d204e5a..d8e10a0 100644 --- a/src/cowboy_http_static.erl +++ b/src/cowboy_http_static.erl @@ -231,7 +231,7 @@ rest_init(Req, Opts) -> end, {Filepath, Req1} = case lists:keyfind(file, 1, Opts) of {_, Filepath2} -> {filepath_path(Filepath2), Req}; - false -> cowboy_http_req:path_info(Req) + false -> cowboy_req:path_info(Req) end, State = case check_path(Filepath) of error -> @@ -320,7 +320,7 @@ content_types_provided(Req, #state{filepath=Filepath, -spec file_contents(#http_req{}, #state{}) -> tuple(). file_contents(Req, #state{filepath=Filepath, fileinfo={ok, #file_info{size=Filesize}}}=State) -> - {ok, Transport, Socket} = cowboy_http_req:transport(Req), + {ok, Transport, Socket} = cowboy_req:transport(Req), Writefile = content_function(Transport, Socket, Filepath), {{stream, Filesize, Writefile}, Req, State}. diff --git a/src/cowboy_http_websocket.erl b/src/cowboy_http_websocket.erl index 2078e04..f4a774d 100644 --- a/src/cowboy_http_websocket.erl +++ b/src/cowboy_http_websocket.erl @@ -69,11 +69,11 @@ upgrade(ListenerPid, Handler, Opts, Req) -> -spec websocket_upgrade(#state{}, #http_req{}) -> {ok, #state{}, #http_req{}}. websocket_upgrade(State, Req) -> {ConnTokens, Req2} - = cowboy_http_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. - {[<<"websocket">>], Req3} = cowboy_http_req:parse_header('Upgrade', Req2), - {Version, Req4} = cowboy_http_req:header(<<"Sec-Websocket-Version">>, Req3), + {[<<"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. @@ -87,9 +87,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=#http_req{meta=Meta}) -> - {Origin, Req2} = cowboy_http_req:header(<<"Origin">>, Req), - {Key1, Req3} = cowboy_http_req:header(<<"Sec-Websocket-Key1">>, Req2), - {Key2, Req4} = cowboy_http_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}, @@ -98,7 +98,7 @@ websocket_upgrade(undefined, State, Req=#http_req{meta=Meta}) -> websocket_upgrade(Version, State, Req=#http_req{meta=Meta}) when Version =:= <<"7">>; Version =:= <<"8">>; Version =:= <<"13">> -> - {Key, Req2} = cowboy_http_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)), @@ -134,7 +134,7 @@ handler_init(State=#state{handler=Handler, opts=Opts}, -spec upgrade_error(#http_req{}) -> closed. upgrade_error(Req) -> - {ok, _Req2} = cowboy_http_req:reply(400, [], [], + {ok, _Req2} = cowboy_req:reply(400, [], [], Req#http_req{resp_state=waiting}), closed. @@ -143,7 +143,7 @@ upgrade_error(Req) -> upgrade_denied(#http_req{resp_state=done}) -> closed; upgrade_denied(Req=#http_req{resp_state=waiting}) -> - {ok, _Req2} = cowboy_http_req:reply(400, [], [], Req), + {ok, _Req2} = cowboy_req:reply(400, [], [], Req), closed; upgrade_denied(#http_req{method='HEAD', resp_state=chunks}) -> closed; @@ -158,24 +158,24 @@ websocket_handshake(State=#state{version=0, origin=Origin, transport=Transport, raw_host=Host, port=Port, raw_path=Path, raw_qs=QS}, HandlerState) -> Location = hixie76_location(Transport:name(), Host, Port, Path, QS), - {ok, Req2} = cowboy_http_req:upgrade_reply( + {ok, Req2} = cowboy_req:upgrade_reply( <<"101 WebSocket Protocol Handshake">>, [{<<"Upgrade">>, <<"WebSocket">>}, {<<"Sec-Websocket-Location">>, Location}, {<<"Sec-Websocket-Origin">>, Origin}], Req#http_req{resp_state=waiting}), %% Flush the resp_sent message before moving on. - receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end, + receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, %% We replied with a proper response. Proxies should be happy enough, %% we can now read the 8 last bytes of the challenge keys and send %% the challenge response directly to the socket. %% %% We use a trick here to read exactly 8 bytes of the body regardless %% of what's in the buffer. - {ok, Req3} = cowboy_http_req:init_stream( + {ok, Req3} = cowboy_req:init_stream( fun cowboy_http:te_identity/2, {0, 8}, fun cowboy_http:ce_identity/1, Req2), - case cowboy_http_req:body(Req3) of + case cowboy_req:body(Req3) of {ok, Key3, Req4} -> Challenge = hixie76_challenge(Key1, Key2, Key3), Transport:send(Socket, Challenge), @@ -186,13 +186,13 @@ websocket_handshake(State=#state{version=0, origin=Origin, end; websocket_handshake(State=#state{challenge=Challenge}, Req=#http_req{transport=Transport}, HandlerState) -> - {ok, Req2} = cowboy_http_req:upgrade_reply( + {ok, Req2} = cowboy_req:upgrade_reply( 101, [{<<"Upgrade">>, <<"websocket">>}, {<<"Sec-Websocket-Accept">>, Challenge}], Req#http_req{resp_state=waiting}), %% Flush the resp_sent message before moving on. - receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end, + receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, handler_before_loop(State#state{messages=Transport:messages()}, Req2, HandlerState, <<>>). diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index 614f409..06f18bb 100644 --- a/src/cowboy_protocol.erl +++ b/src/cowboy_protocol.erl @@ -208,7 +208,7 @@ header({http_header, _I, 'Connection', _R, Connection}, when Keepalive < MaxKeepalive -> Req2 = Req#http_req{headers=[{'Connection', Connection}|Headers]}, {ConnTokens, Req3} - = cowboy_http_req:parse_header('Connection', Req2), + = cowboy_req:parse_header('Connection', Req2), ConnAtom = cowboy_http:connection_to_atom(ConnTokens), parse_header(Req3#http_req{connection=ConnAtom}, State); header({http_header, _I, Field, _R, Value}, Req, State) -> @@ -399,7 +399,7 @@ next_request(Req=#http_req{connection=Conn}, State=#state{ RespRes = ensure_response(Req), {BodyRes, Buffer} = ensure_body_processed(Req), %% Flush the resp_sent message before moving on. - receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end, + receive {cowboy_req, resp_sent} -> ok after 0 -> ok end, case {HandlerRes, BodyRes, RespRes, Conn} of {ok, ok, ok, keepalive} -> ?MODULE:parse_request(State#state{ @@ -413,12 +413,12 @@ next_request(Req=#http_req{connection=Conn}, State=#state{ ensure_body_processed(#http_req{body_state=done, buffer=Buffer}) -> {ok, Buffer}; ensure_body_processed(Req=#http_req{body_state=waiting}) -> - case cowboy_http_req:skip_body(Req) of + case cowboy_req:skip_body(Req) of {ok, Req2} -> {ok, Req2#http_req.buffer}; {error, _Reason} -> {close, <<>>} end; ensure_body_processed(Req=#http_req{body_state={multipart, _, _}}) -> - {ok, Req2} = cowboy_http_req:multipart_skip(Req), + {ok, Req2} = cowboy_req:multipart_skip(Req), ensure_body_processed(Req2). -spec ensure_response(#http_req{}) -> ok. @@ -428,7 +428,7 @@ ensure_response(#http_req{resp_state=done}) -> %% No response has been sent but everything apparently went fine. %% Reply with 204 No Content to indicate this. ensure_response(Req=#http_req{resp_state=waiting}) -> - _ = cowboy_http_req:reply(204, [], [], Req), + _ = cowboy_req:reply(204, [], [], Req), ok; %% Terminate the chunked body for HTTP/1.1 only. ensure_response(#http_req{method='HEAD', resp_state=chunks}) -> @@ -445,9 +445,9 @@ ensure_response(#http_req{socket=Socket, transport=Transport, error_terminate(Code, State=#state{socket=Socket, transport=Transport, onresponse=OnResponse}) -> receive - {cowboy_http_req, resp_sent} -> ok + {cowboy_req, resp_sent} -> ok after 0 -> - _ = cowboy_http_req:reply(Code, #http_req{ + _ = cowboy_req:reply(Code, #http_req{ socket=Socket, transport=Transport, onresponse=OnResponse, connection=close, pid=self(), resp_state=waiting}), ok diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl new file mode 100644 index 0000000..ad4a9a0 --- /dev/null +++ b/src/cowboy_req.erl @@ -0,0 +1,1038 @@ +%% Copyright (c) 2011-2012, Loïc Hoguin +%% Copyright (c) 2011, Anthony Ramine +%% +%% 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. + +%% @doc HTTP request manipulation API. +%% +%% Almost all functions in this module return a new Req variable. +%% It should always be used instead of the one used in your function call +%% because it keeps the state of the request. It also allows Cowboy to do +%% some lazy evaluation and cache results where possible. +-module(cowboy_req). + +%% Request API. +-export([method/1]). +-export([version/1]). +-export([peer/1]). +-export([peer_addr/1]). +-export([host/1]). +-export([host_info/1]). +-export([raw_host/1]). +-export([port/1]). +-export([path/1]). +-export([path_info/1]). +-export([raw_path/1]). +-export([qs_val/2]). +-export([qs_val/3]). +-export([qs_vals/1]). +-export([raw_qs/1]). +-export([binding/2]). +-export([binding/3]). +-export([bindings/1]). +-export([header/2]). +-export([header/3]). +-export([headers/1]). +-export([parse_header/2]). +-export([parse_header/3]). +-export([cookie/2]). +-export([cookie/3]). +-export([cookies/1]). +-export([meta/2]). +-export([meta/3]). + +%% Request body API. +-export([has_body/1]). +-export([body_length/1]). +-export([init_stream/4]). +-export([stream_body/1]). +-export([skip_body/1]). +-export([body/1]). +-export([body/2]). +-export([body_qs/1]). +-export([multipart_data/1]). +-export([multipart_skip/1]). + +%% Response API. +-export([set_resp_cookie/4]). +-export([set_resp_header/3]). +-export([set_resp_body/2]). +-export([set_resp_body_fun/3]). +-export([has_resp_header/2]). +-export([has_resp_body/1]). +-export([reply/2]). +-export([reply/3]). +-export([reply/4]). +-export([chunked_reply/2]). +-export([chunked_reply/3]). +-export([chunk/2]). +-export([upgrade_reply/3]). + +%% Misc API. +-export([compact/1]). +-export([transport/1]). + +-include("http.hrl"). + +%% Request API. + +%% @doc Return the HTTP method of the request. +-spec method(#http_req{}) -> {cowboy_http:method(), #http_req{}}. +method(Req) -> + {Req#http_req.method, Req}. + +%% @doc Return the HTTP version used for the request. +-spec version(#http_req{}) -> {cowboy_http:version(), #http_req{}}. +version(Req) -> + {Req#http_req.version, Req}. + +%% @doc Return the peer address and port number of the remote host. +-spec peer(#http_req{}) + -> {{inet:ip_address(), inet:port_number()}, #http_req{}}. +peer(Req=#http_req{socket=Socket, transport=Transport, peer=undefined}) -> + {ok, Peer} = Transport:peername(Socket), + {Peer, Req#http_req{peer=Peer}}; +peer(Req) -> + {Req#http_req.peer, Req}. + +%% @doc Returns the peer address calculated from headers. +-spec peer_addr(#http_req{}) -> {inet:ip_address(), #http_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, "^(?[^\\,]+)", + [{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 tokens for the hostname requested. +-spec host(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. +host(Req) -> + {Req#http_req.host, Req}. + +%% @doc Return the extra host information obtained from partially matching +%% the hostname using '...'. +-spec host_info(#http_req{}) + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. +host_info(Req) -> + {Req#http_req.host_info, Req}. + +%% @doc Return the raw host directly taken from the request. +-spec raw_host(#http_req{}) -> {binary(), #http_req{}}. +raw_host(Req) -> + {Req#http_req.raw_host, Req}. + +%% @doc Return the port used for this request. +-spec port(#http_req{}) -> {inet:port_number(), #http_req{}}. +port(Req) -> + {Req#http_req.port, Req}. + +%% @doc Return the path segments for the path requested. +%% +%% Following RFC2396, this function may return path segments containing any +%% character, including / if, and only if, a / was escaped +%% and part of a path segment in the path requested. +-spec path(#http_req{}) -> {cowboy_dispatcher:tokens(), #http_req{}}. +path(Req) -> + {Req#http_req.path, Req}. + +%% @doc Return the extra path information obtained from partially matching +%% the patch using '...'. +-spec path_info(#http_req{}) + -> {cowboy_dispatcher:tokens() | undefined, #http_req{}}. +path_info(Req) -> + {Req#http_req.path_info, Req}. + +%% @doc Return the raw path directly taken from the request. +-spec raw_path(#http_req{}) -> {binary(), #http_req{}}. +raw_path(Req) -> + {Req#http_req.raw_path, Req}. + +%% @equiv qs_val(Name, Req, undefined) +-spec qs_val(binary(), #http_req{}) + -> {binary() | true | undefined, #http_req{}}. +qs_val(Name, Req) when is_binary(Name) -> + qs_val(Name, Req, undefined). + +%% @doc Return the query string value for the given key, or a default if +%% missing. +-spec qs_val(binary(), #http_req{}, Default) + -> {binary() | true | Default, #http_req{}} when Default::any(). +qs_val(Name, Req=#http_req{raw_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_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. + +%% @doc Return the full list of query string values. +-spec qs_vals(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +qs_vals(Req=#http_req{raw_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_vals=QsVals}); +qs_vals(Req=#http_req{qs_vals=QsVals}) -> + {QsVals, Req}. + +%% @doc Return the raw query string directly taken from the request. +-spec raw_qs(#http_req{}) -> {binary(), #http_req{}}. +raw_qs(Req) -> + {Req#http_req.raw_qs, Req}. + +%% @equiv binding(Name, Req, undefined) +-spec binding(atom(), #http_req{}) -> {binary() | undefined, #http_req{}}. +binding(Name, Req) when is_atom(Name) -> + binding(Name, Req, undefined). + +%% @doc Return the binding value for the given key obtained when matching +%% the host and path against the dispatch list, or a default if missing. +-spec binding(atom(), #http_req{}, Default) + -> {binary() | Default, #http_req{}} 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} + end. + +%% @doc Return the full list of binding values. +-spec bindings(#http_req{}) -> {list({atom(), binary()}), #http_req{}}. +bindings(Req) -> + {Req#http_req.bindings, Req}. + +%% @equiv header(Name, Req, undefined) +-spec header(atom() | binary(), #http_req{}) + -> {binary() | undefined, #http_req{}}. +header(Name, Req) when is_atom(Name) orelse is_binary(Name) -> + header(Name, Req, undefined). + +%% @doc Return the header value for the given key, or a default if missing. +-spec header(atom() | binary(), #http_req{}, Default) + -> {binary() | Default, #http_req{}} when Default::any(). +header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) -> + case lists:keyfind(Name, 1, Req#http_req.headers) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% @doc Return the full list of headers. +-spec headers(#http_req{}) -> {cowboy_http:headers(), #http_req{}}. +headers(Req) -> + {Req#http_req.headers, Req}. + +%% @doc Semantically parse headers. +%% +%% 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(), #http_req{}) + -> {any(), #http_req{}} | {undefined, binary(), #http_req{}} + | {error, badarg}. +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} -> {Value, Req} + 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">>]; +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(), #http_req{}, any()) + -> {any(), #http_req{}} | {undefined, binary(), #http_req{}} + | {error, badarg}. +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, + 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, + 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, + 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, + fun (Value) -> + cowboy_http:digits(Value) + end); +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, + 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' -> + 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' -> + 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, + 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, + fun (Value) -> + cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2) + end); +parse_header(Name, Req, Default) -> + {Value, Req2} = header(Name, Req, Default), + {undefined, Value, Req2}. + +parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> + case lists:keyfind(Name, 1, PHeaders) of + {Name, P} -> + {P, Req}; + false -> + parse_header_no_cache(Name, Req, Default, Fun) + end. + +parse_header_no_cache(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) -> + case header(Name, Req) of + {undefined, Req2} -> + {Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}}; + {Value, Req2} -> + case Fun(Value) of + {error, badarg} -> + {error, badarg}; + P -> + {P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}} + end + end. + +%% @equiv cookie(Name, Req, undefined) +-spec cookie(binary(), #http_req{}) + -> {binary() | true | undefined, #http_req{}}. +cookie(Name, Req) when is_binary(Name) -> + cookie(Name, Req, undefined). + +%% @doc Return the cookie value for the given key, or a default if +%% missing. +-spec cookie(binary(), #http_req{}, Default) + -> {binary() | true | Default, #http_req{}} when Default::any(). +cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> + case header('Cookie', Req) of + {undefined, Req2} -> + {Default, Req2#http_req{cookies=[]}}; + {RawCookie, Req2} -> + Cookies = cowboy_cookies:parse_cookie(RawCookie), + 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. + +%% @doc Return the full list of cookie values. +-spec cookies(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +cookies(Req=#http_req{cookies=undefined}) -> + case header('Cookie', Req) of + {undefined, Req2} -> + {[], Req2#http_req{cookies=[]}}; + {RawCookie, Req2} -> + Cookies = cowboy_cookies:parse_cookie(RawCookie), + cookies(Req2#http_req{cookies=Cookies}) + end; +cookies(Req=#http_req{cookies=Cookies}) -> + {Cookies, Req}. + +%% @equiv meta(Name, Req, undefined) +-spec meta(atom(), #http_req{}) -> {any() | undefined, #http_req{}}. +meta(Name, Req) -> + meta(Name, Req, undefined). + +%% @doc Return metadata information about the request. +%% +%% Metadata information varies from one protocol to another. Websockets +%% would define the protocol version here, while REST would use it to +%% indicate which media type, language and charset were retained. +-spec meta(atom(), #http_req{}, any()) -> {any(), #http_req{}}. +meta(Name, Req, Default) -> + case lists:keyfind(Name, 1, Req#http_req.meta) of + {Name, Value} -> {Value, Req}; + false -> {Default, Req} + end. + +%% Request Body API. + +%% @doc Return whether the request message has a body. +-spec has_body(#http_req{}) -> {boolean(), #http_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, Req}. + +%% @doc Return the request message body length, if known. +%% +%% 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(#http_req{}) -> {undefined | non_neg_integer(), #http_req{}}. +body_length(Req) -> + case lists:keymember('Transfer-Encoding', 1, Req#http_req.headers) of + true -> {undefined, Req}; + false -> parse_header('Content-Length', Req, 0) + end. + +%% @doc Initialize body streaming and set custom decoding functions. +%% +%% Calling this function is optional. It should only be used if you +%% need to override the default behavior of Cowboy. Otherwise you +%% should call stream_body/1 directly. +%% +%% Two decodings happen. First a decoding function is applied to the +%% transferred data, and then another is applied to the actual content. +%% +%% Transfer encoding is generally used for chunked bodies. The decoding +%% function uses a state to keep track of how much it has read, which is +%% also initialized through this function. +%% +%% Content encoding is generally used for compression. +%% +%% Standard encodings can be found in cowboy_http. +-spec init_stream(fun(), any(), fun(), #http_req{}) -> {ok, #http_req{}}. +init_stream(TransferDecode, TransferState, ContentDecode, Req) -> + {ok, Req#http_req{body_state= + {stream, TransferDecode, TransferState, ContentDecode}}}. + +%% @doc Stream the request's body. +%% +%% This is the most low level function to read the request body. +%% +%% In most cases, if they weren't defined before using stream_body/4, +%% this function will guess which transfer and content encodings were +%% used for building the request body, and configure the decoding +%% functions that will be used when streaming. +%% +%% It then starts streaming the body, returning {ok, Data, Req} +%% for each streamed part, and {done, Req} when it's finished streaming. +-spec stream_body(#http_req{}) -> {ok, binary(), #http_req{}} + | {done, #http_req{}} | {error, atom()}. +stream_body(Req=#http_req{body_state=waiting, + version=Version, transport=Transport, socket=Socket}) -> + case parse_header(<<"Expect">>, Req) of + {[<<"100-continue">>], Req1} -> + HTTPVer = cowboy_http:version_to_binary(Version), + Transport:send(Socket, + << HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>); + {undefined, Req1} -> + ok; + {undefined, _, Req1} -> + ok + end, + case parse_header('Transfer-Encoding', Req1) of + {[<<"chunked">>], Req2} -> + stream_body(Req2#http_req{body_state= + {stream, fun cowboy_http:te_chunked/2, {0, 0}, + fun cowboy_http:ce_identity/1}}); + {[<<"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, fun cowboy_http:te_identity/2, {0, Length}, + fun cowboy_http:ce_identity/1}}) + end + end; +stream_body(Req=#http_req{buffer=Buffer, body_state={stream, _, _, _}}) + when Buffer =/= <<>> -> + transfer_decode(Buffer, Req#http_req{buffer= <<>>}); +stream_body(Req=#http_req{body_state={stream, _, _, _}}) -> + stream_body_recv(Req); +stream_body(Req=#http_req{body_state=done}) -> + {done, Req}; +stream_body(Req=#http_req{body_state={multipart, _N, _Fun}, + transport=Transport, socket=Socket}) -> + case Transport:recv(Socket, 0, 5000) of + {ok, Data} -> {ok, Data, Req}; + {error, Reason} -> {error, Reason} + end. + +-spec stream_body_recv(#http_req{}) + -> {ok, binary(), #http_req{}} | {error, atom()}. +stream_body_recv(Req=#http_req{ + transport=Transport, socket=Socket, buffer=Buffer}) -> + %% @todo Allow configuring the timeout. + case Transport:recv(Socket, 0, 5000) of + {ok, Data} -> transfer_decode(<< Buffer/binary, Data/binary >>, Req); + {error, Reason} -> {error, Reason} + end. + +-spec transfer_decode(binary(), #http_req{}) + -> {ok, binary(), #http_req{}} | {error, atom()}. +transfer_decode(Data, Req=#http_req{ + body_state={stream, TransferDecode, TransferState, ContentDecode}}) -> + case TransferDecode(Data, TransferState) of + {ok, Data2, TransferState2} -> + content_decode(ContentDecode, Data2, Req#http_req{body_state= + {stream, TransferDecode, TransferState2, ContentDecode}}); + {ok, Data2, Rest, TransferState2} -> + content_decode(ContentDecode, Data2, Req#http_req{ + buffer=Rest, body_state= + {stream, TransferDecode, TransferState2, ContentDecode}}); + %% @todo {header(s) for chunked + more -> + stream_body_recv(Req#http_req{buffer=Data}); + {done, Length, Rest} -> + Req2 = transfer_decode_done(Length, Rest, Req), + {done, Req2}; + {done, Data2, Length, Rest} -> + Req2 = transfer_decode_done(Length, Rest, Req), + content_decode(ContentDecode, Data2, Req2); + {error, Reason} -> + {error, Reason} + end. + +-spec transfer_decode_done(non_neg_integer(), binary(), #http_req{}) + -> #http_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))}), + %% 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), + Req#http_req{buffer=Rest, body_state=done, + headers=Headers3, p_headers=PHeaders3}. + +%% @todo Probably needs a Rest. +-spec content_decode(fun(), binary(), #http_req{}) + -> {ok, binary(), #http_req{}} | {error, atom()}. +content_decode(ContentDecode, Data, Req) -> + case ContentDecode(Data) of + {ok, Data2} -> {ok, Data2, Req}; + {error, Reason} -> {error, Reason} + end. + +%% @doc Return the full body sent with the request. +-spec body(#http_req{}) -> {ok, binary(), #http_req{}} | {error, atom()}. +body(Req) -> + read_body(infinity, Req, <<>>). + +%% @doc Return the full body sent with the request as long as the body +%% length doesn't go over MaxLength. +%% +%% This is most useful to quickly be able to get the full body while +%% avoiding filling your memory with huge request bodies when you're +%% not expecting it. +-spec body(non_neg_integer() | infinity, #http_req{}) + -> {ok, binary(), #http_req{}} | {error, atom()}. +body(MaxLength, Req) -> + read_body(MaxLength, Req, <<>>). + +-spec read_body(non_neg_integer() | infinity, #http_req{}, binary()) + -> {ok, binary(), #http_req{}} | {error, atom()}. +read_body(MaxLength, Req, Acc) when MaxLength > byte_size(Acc) -> + case stream_body(Req) of + {ok, Data, Req2} -> + read_body(MaxLength, Req2, << Acc/binary, Data/binary >>); + {done, Req2} -> + {ok, Acc, Req2}; + {error, Reason} -> + {error, Reason} + end. + +-spec skip_body(#http_req{}) -> {ok, #http_req{}} | {error, atom()}. +skip_body(Req) -> + case stream_body(Req) of + {ok, _, Req2} -> skip_body(Req2); + {done, Req2} -> {ok, Req2}; + {error, Reason} -> {error, Reason} + end. + +%% @doc Return the full body sent with the reqest, parsed as an +%% application/x-www-form-urlencoded string. Essentially a POST query string. +%% @todo We need an option to limit the size of the body for QS too. +-spec body_qs(#http_req{}) -> {list({binary(), binary() | true}), #http_req{}}. +body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) -> + {ok, Body, Req2} = body(Req), + {cowboy_http:x_www_form_urlencoded( + Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2}. + +%% Multipart Request API. + +%% @doc Return data from the multipart parser. +%% +%% Use this function for multipart streaming. For each part in the request, +%% this function returns {headers, Headers} followed by a sequence of +%% {body, Data} tuples and finally end_of_part. When there +%% is no part to parse anymore, eof is returned. +%% +%% If the request Content-Type is not a multipart one, {error, badarg} +%% is returned. +-spec multipart_data(#http_req{}) + -> {{headers, cowboy_http:headers()} + | {body, binary()} | end_of_part | eof, + #http_req{}}. +multipart_data(Req=#http_req{body_state=waiting}) -> + {{<<"multipart">>, _SubType, Params}, Req2} = + parse_header('Content-Type', Req), + {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), + {Length, Req3} = parse_header('Content-Length', Req2), + multipart_data(Req3, Length, {more, cowboy_multipart:parser(Boundary)}); +multipart_data(Req=#http_req{body_state={multipart, Length, Cont}}) -> + multipart_data(Req, Length, Cont()); +multipart_data(Req=#http_req{body_state=done}) -> + {eof, Req}. + +multipart_data(Req, Length, {headers, Headers, Cont}) -> + {{headers, Headers}, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, Length, {body, Data, Cont}) -> + {{body, Data}, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, Length, {end_of_part, Cont}) -> + {end_of_part, Req#http_req{body_state={multipart, Length, Cont}}}; +multipart_data(Req, 0, eof) -> + {eof, Req#http_req{body_state=done}}; +multipart_data(Req=#http_req{socket=Socket, transport=Transport}, + Length, eof) -> + %% We just want to skip so no need to stream data here. + {ok, _Data} = Transport:recv(Socket, Length, 5000), + {eof, Req#http_req{body_state=done}}; +multipart_data(Req, Length, {more, Parser}) when Length > 0 -> + case stream_body(Req) of + {ok, << Data:Length/binary, Buffer/binary >>, Req2} -> + multipart_data(Req2#http_req{buffer=Buffer}, 0, Parser(Data)); + {ok, Data, Req2} -> + multipart_data(Req2, Length - byte_size(Data), Parser(Data)) + end. + +%% @doc Skip a part returned by the multipart parser. +%% +%% This function repeatedly calls multipart_data/1 until +%% end_of_part or eof is parsed. +multipart_skip(Req) -> + case multipart_data(Req) of + {end_of_part, Req2} -> {ok, Req2}; + {eof, Req2} -> {ok, Req2}; + {_Other, Req2} -> multipart_skip(Req2) + end. + +%% Response API. + +%% @doc Add a cookie header to the response. +-spec set_resp_cookie(binary(), binary(), [cowboy_cookies:cookie_option()], + #http_req{}) -> {ok, #http_req{}}. +set_resp_cookie(Name, Value, Options, Req) -> + {HeaderName, HeaderValue} = cowboy_cookies:cookie(Name, Value, Options), + set_resp_header(HeaderName, HeaderValue, Req). + +%% @doc Add a header to the response. +-spec set_resp_header(cowboy_http:header(), iodata(), #http_req{}) + -> {ok, #http_req{}}. +set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + {ok, Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}}. + +%% @doc Add a body to the response. +%% +%% The body set here is ignored if the response is later sent using +%% anything other than reply/2 or reply/3. The response body is expected +%% to be a binary or an iolist. +-spec set_resp_body(iodata(), #http_req{}) -> {ok, #http_req{}}. +set_resp_body(Body, Req) -> + {ok, Req#http_req{resp_body=Body}}. + + +%% @doc Add a body function to the response. +%% +%% The response body may also be set to a content-length - stream-function pair. +%% If the response body is of this type normal response headers will be sent. +%% After the response headers has been sent the body function is applied. +%% The body function is expected to write the response body directly to the +%% socket using the transport module. +%% +%% If the body function crashes while writing the response body or writes fewer +%% bytes than declared the behaviour is undefined. The body set here is ignored +%% if the response is later sent using anything other than `reply/2' or +%% `reply/3'. +%% +%% @see cowboy_req:transport/1. +-spec set_resp_body_fun(non_neg_integer(), fun(() -> {sent, non_neg_integer()}), + #http_req{}) -> {ok, #http_req{}}. +set_resp_body_fun(StreamLen, StreamFun, Req) -> + {ok, 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(), #http_req{}) -> boolean(). +has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> + NameBin = header_to_binary(Name), + lists:keymember(NameBin, 1, RespHeaders). + +%% @doc Return whether a body has been set for the response. +-spec has_resp_body(#http_req{}) -> boolean(). +has_resp_body(#http_req{resp_body={Length, _}}) -> + Length > 0; +has_resp_body(#http_req{resp_body=RespBody}) -> + iolist_size(RespBody) > 0. + +%% @equiv reply(Status, [], [], Req) +-spec reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. +reply(Status, Req=#http_req{resp_body=Body}) -> + reply(Status, [], Body, Req). + +%% @equiv reply(Status, Headers, [], Req) +-spec reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +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(), #http_req{}) + -> {ok, #http_req{}}. +reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport, + 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)}]; + _ -> [] + 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 + end, + {ok, Req2#http_req{connection=RespConn, resp_state=done, + resp_headers=[], resp_body= <<>>}}. + +%% @equiv chunked_reply(Status, [], Req) +-spec chunked_reply(cowboy_http:status(), #http_req{}) -> {ok, #http_req{}}. +chunked_reply(Status, Req) -> + chunked_reply(Status, [], Req). + +%% @doc Initiate the sending of a chunked reply to the client. +%% @see cowboy_req:chunk/2 +-spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +chunked_reply(Status, Headers, Req=#http_req{ + version=Version, connection=Connection, + resp_state=waiting, resp_headers=RespHeaders}) -> + RespConn = response_connection(Headers, Connection), + HTTP11Headers = case Version of + {1, 1} -> [ + {<<"Connection">>, atom_to_connection(Connection)}, + {<<"Transfer-Encoding">>, <<"chunked">>}]; + _ -> [] + end, + {_, Req2} = response(Status, Headers, RespHeaders, [ + {<<"Date">>, cowboy_clock:rfc1123()}, + {<<"Server">>, <<"Cowboy">>} + |HTTP11Headers], Req), + {ok, Req2#http_req{connection=RespConn, resp_state=chunks, + resp_headers=[], resp_body= <<>>}}. + +%% @doc Send a chunk of data. +%% +%% A chunked reply must have been initiated before calling this function. +-spec chunk(iodata(), #http_req{}) -> ok | {error, atom()}. +chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) -> + ok; +chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) -> + 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), + <<"\r\n">>, Data, <<"\r\n">>]). + +%% @doc Send an upgrade reply. +%% @private +-spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), #http_req{}) + -> {ok, #http_req{}}. +upgrade_reply(Status, Headers, Req=#http_req{ + resp_state=waiting, resp_headers=RespHeaders}) -> + {_, Req2} = response(Status, Headers, RespHeaders, [ + {<<"Connection">>, <<"Upgrade">>} + ], Req), + {ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. + +%% Misc API. + +%% @doc Compact the request data by removing all non-system information. +%% +%% This essentially removes the host, path, query string, bindings and headers. +%% Use it when you really need to save up memory, for example when having +%% many concurrent long-running connections. +-spec compact(#http_req{}) -> #http_req{}. +compact(Req) -> + Req#http_req{host=undefined, host_info=undefined, path=undefined, + path_info=undefined, qs_vals=undefined, + bindings=undefined, headers=[], + p_headers=[], cookies=[]}. + +%% @doc Return the transport module and socket associated with a request. +%% +%% This exposes the same socket interface used internally by the HTTP protocol +%% implementation to developers that needs low level access to the socket. +%% +%% It is preferred to use this in conjuction with the stream function support +%% in `set_resp_body_fun/3' if this is used to write a response body directly +%% to the socket. This ensures that the response headers are set correctly. +-spec transport(#http_req{}) -> {ok, module(), inet:socket()}. +transport(#http_req{transport=Transport, socket=Socket}) -> + {ok, Transport, Socket}. + +%% Internal. + +-spec response(cowboy_http:status(), cowboy_http:headers(), + cowboy_http:headers(), cowboy_http:headers(), #http_req{}) + -> {normal | hook, #http_req{}}. +response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{ + socket=Socket, transport=Transport, version=Version, + pid=ReqPid, onresponse=OnResponse}) -> + FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders), + Req2 = case OnResponse of + undefined -> Req; + OnResponse -> OnResponse(Status, FullHeaders, + %% Don't call 'onresponse' from the hook itself. + Req#http_req{resp_headers=[], resp_body= <<>>, + onresponse=undefined}) + end, + ReplyType = case Req2#http_req.resp_state of + waiting -> + HTTPVer = cowboy_http:version_to_binary(Version), + StatusLine = << HTTPVer/binary, " ", + (status(Status))/binary, "\r\n" >>, + HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>] + || {Key, Value} <- FullHeaders], + Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]), + ReqPid ! {?MODULE, resp_sent}, + normal; + _ -> + hook + end, + {ReplyType, Req2}. + +-spec response_connection(cowboy_http:headers(), keepalive | close) + -> keepalive | close. +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 + 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). + +-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], + merge_headers( + merge_headers(Headers2, RespHeaders), + DefaultHeaders). + +-spec merge_headers(cowboy_http:headers(), cowboy_http:headers()) + -> cowboy_http:headers(). +merge_headers(Headers, []) -> + Headers; +merge_headers(Headers, [{Name, Value}|Tail]) -> + Headers2 = case lists:keymember(Name, 1, Headers) of + true -> Headers; + false -> Headers ++ [{Name, Value}] + end, + merge_headers(Headers2, Tail). + +-spec atom_to_connection(keepalive) -> <<_:80>>; + (close) -> <<_:40>>. +atom_to_connection(keepalive) -> + <<"keep-alive">>; +atom_to_connection(close) -> + <<"close">>. + +-spec status(cowboy_http:status()) -> binary(). +status(100) -> <<"100 Continue">>; +status(101) -> <<"101 Switching Protocols">>; +status(102) -> <<"102 Processing">>; +status(200) -> <<"200 OK">>; +status(201) -> <<"201 Created">>; +status(202) -> <<"202 Accepted">>; +status(203) -> <<"203 Non-Authoritative Information">>; +status(204) -> <<"204 No Content">>; +status(205) -> <<"205 Reset Content">>; +status(206) -> <<"206 Partial Content">>; +status(207) -> <<"207 Multi-Status">>; +status(226) -> <<"226 IM Used">>; +status(300) -> <<"300 Multiple Choices">>; +status(301) -> <<"301 Moved Permanently">>; +status(302) -> <<"302 Found">>; +status(303) -> <<"303 See Other">>; +status(304) -> <<"304 Not Modified">>; +status(305) -> <<"305 Use Proxy">>; +status(306) -> <<"306 Switch Proxy">>; +status(307) -> <<"307 Temporary Redirect">>; +status(400) -> <<"400 Bad Request">>; +status(401) -> <<"401 Unauthorized">>; +status(402) -> <<"402 Payment Required">>; +status(403) -> <<"403 Forbidden">>; +status(404) -> <<"404 Not Found">>; +status(405) -> <<"405 Method Not Allowed">>; +status(406) -> <<"406 Not Acceptable">>; +status(407) -> <<"407 Proxy Authentication Required">>; +status(408) -> <<"408 Request Timeout">>; +status(409) -> <<"409 Conflict">>; +status(410) -> <<"410 Gone">>; +status(411) -> <<"411 Length Required">>; +status(412) -> <<"412 Precondition Failed">>; +status(413) -> <<"413 Request Entity Too Large">>; +status(414) -> <<"414 Request-URI Too Long">>; +status(415) -> <<"415 Unsupported Media Type">>; +status(416) -> <<"416 Requested Range Not Satisfiable">>; +status(417) -> <<"417 Expectation Failed">>; +status(418) -> <<"418 I'm a teapot">>; +status(422) -> <<"422 Unprocessable Entity">>; +status(423) -> <<"423 Locked">>; +status(424) -> <<"424 Failed Dependency">>; +status(425) -> <<"425 Unordered Collection">>; +status(426) -> <<"426 Upgrade Required">>; +status(428) -> <<"428 Precondition Required">>; +status(429) -> <<"429 Too Many Requests">>; +status(431) -> <<"431 Request Header Fields Too Large">>; +status(500) -> <<"500 Internal Server Error">>; +status(501) -> <<"501 Not Implemented">>; +status(502) -> <<"502 Bad Gateway">>; +status(503) -> <<"503 Service Unavailable">>; +status(504) -> <<"504 Gateway Timeout">>; +status(505) -> <<"505 HTTP Version Not Supported">>; +status(506) -> <<"506 Variant Also Negotiates">>; +status(507) -> <<"507 Insufficient Storage">>; +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. -- cgit v1.2.3