diff options
Diffstat (limited to 'src/cowboy_req.erl')
-rw-r--r-- | src/cowboy_req.erl | 690 |
1 files changed, 371 insertions, 319 deletions
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 8f0a04b..16b1fd1 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -47,6 +47,9 @@ %% Request body API. -export([has_body/1]). -export([body_length/1]). +-export([read_body/1]). +-export([read_body/2]). + -export([body/1]). -export([body/2]). -export([body_qs/1]). @@ -70,10 +73,12 @@ -export([reply/2]). -export([reply/3]). -export([reply/4]). + +-export([send_body/3]). + -export([chunked_reply/2]). -export([chunked_reply/3]). -export([chunk/2]). --export([upgrade_reply/3]). -export([continue/1]). -export([maybe_reply/2]). -export([ensure_response/2]). @@ -93,12 +98,12 @@ -type transfer_decode_fun() :: fun((binary(), any()) -> cow_http_te:decode_ret()). --type body_opts() :: [{continue, boolean()} +-type body_opts() :: [{continue, boolean()} %% doesn't apply | {length, non_neg_integer()} - | {read_length, non_neg_integer()} - | {read_timeout, timeout()} - | {transfer_decode, transfer_decode_fun(), any()} - | {content_decode, content_decode_fun()}]. + | {read_length, non_neg_integer()} %% to be added back later as optimization + | {read_timeout, timeout()} %% same + | {transfer_decode, transfer_decode_fun(), any()} %% doesn't apply + | {content_decode, content_decode_fun()}]. %% does apply -export_type([body_opts/0]). -type resp_body_fun() :: fun((any(), module()) -> ok). @@ -182,43 +187,43 @@ new(Socket, Transport, Peer, Method, Path, Query, end. -spec method(req()) -> binary(). -method(Req) -> - Req#http_req.method. +method(#{method := Method}) -> + Method. -spec version(req()) -> cowboy:http_version(). -version(Req) -> - Req#http_req.version. +version(#{version := Version}) -> + Version. -spec peer(req()) -> {inet:ip_address(), inet:port_number()} | undefined. peer(Req) -> Req#http_req.peer. -spec host(req()) -> binary(). -host(Req) -> - Req#http_req.host. +host(#{host := Host}) -> + Host. -spec host_info(req()) -> cowboy_router:tokens() | undefined. -host_info(Req) -> - Req#http_req.host_info. +host_info(#{host_info := HostInfo}) -> + HostInfo. -spec port(req()) -> inet:port_number(). -port(Req) -> - Req#http_req.port. +port(#{port := Port}) -> + Port. -spec path(req()) -> binary(). -path(Req) -> - Req#http_req.path. +path(#{path := Path}) -> + Path. -spec path_info(req()) -> cowboy_router:tokens() | undefined. -path_info(Req) -> - Req#http_req.path_info. +path_info(#{path_info := PathInfo}) -> + PathInfo. -spec qs(req()) -> binary(). -qs(Req) -> - Req#http_req.qs. +qs(#{qs := Qs}) -> + Qs. -spec parse_qs(req()) -> [{binary(), binary() | true}]. -parse_qs(#http_req{qs=Qs}) -> +parse_qs(#{qs := Qs}) -> cow_qs:parse_qs(Qs). -spec match_qs(cowboy:fields(), req()) -> map(). @@ -227,30 +232,24 @@ match_qs(Fields, Req) -> %% The URL includes the scheme, host and port only. -spec host_url(req()) -> undefined | binary(). -host_url(#http_req{port=undefined}) -> +host_url(#{port := undefined}) -> undefined; -host_url(#http_req{transport=Transport, host=Host, port=Port}) -> - TransportName = Transport:name(), - Secure = case TransportName of - ssl -> <<"s">>; - _ -> <<>> - end, - PortBin = case {TransportName, Port} of - {ssl, 443} -> <<>>; - {tcp, 80} -> <<>>; +host_url(#{scheme := Scheme, host := Host, port := Port}) -> + PortBin = case {Scheme, Port} of + {<<"https">>, 443} -> <<>>; + {<<"http">>, 80} -> <<>>; _ -> << ":", (integer_to_binary(Port))/binary >> end, - << "http", Secure/binary, "://", Host/binary, PortBin/binary >>. + << Scheme/binary, "://", Host/binary, PortBin/binary >>. %% The URL includes the scheme, host, port, path and query string. -spec url(req()) -> undefined | binary(). -url(Req=#http_req{}) -> - HostURL = host_url(Req), - url(Req, HostURL). +url(Req) -> + url(Req, host_url(Req)). url(_, undefined) -> undefined; -url(#http_req{path=Path, qs=QS}, HostURL) -> +url(#{path := Path, qs := QS}, HostURL) -> QS2 = case QS of <<>> -> <<>>; _ -> << "?", QS/binary >> @@ -262,30 +261,31 @@ binding(Name, Req) -> binding(Name, Req, undefined). -spec binding(atom(), req(), Default) -> any() | Default when Default::any(). -binding(Name, Req, Default) when is_atom(Name) -> - case lists:keyfind(Name, 1, Req#http_req.bindings) of +binding(Name, #{bindings := Bindings}, Default) when is_atom(Name) -> + case lists:keyfind(Name, 1, Bindings) of {_, Value} -> Value; false -> Default - end. + end; +binding(Name, _, Default) when is_atom(Name) -> + Default. -spec bindings(req()) -> [{atom(), any()}]. -bindings(Req) -> - Req#http_req.bindings. +bindings(#{bindings := Bindings}) -> + Bindings; +bindings(_) -> + []. -spec header(binary(), req()) -> binary() | undefined. header(Name, Req) -> header(Name, Req, undefined). -spec header(binary(), req(), Default) -> binary() | Default when Default::any(). -header(Name, Req, Default) -> - case lists:keyfind(Name, 1, Req#http_req.headers) of - {Name, Value} -> Value; - false -> Default - end. +header(Name, #{headers := Headers}, Default) -> + maps:get(Name, Headers, Default). -spec headers(req()) -> cowboy:http_headers(). -headers(Req) -> - Req#http_req.headers. +headers(#{headers := Headers}) -> + Headers. -spec parse_header(binary(), Req) -> any() when Req::req(). parse_header(Name = <<"content-length">>, Req) -> @@ -354,31 +354,52 @@ set_meta(Name, Value, Req=#http_req{meta=Meta}) -> %% Request Body API. -spec has_body(req()) -> boolean(). -has_body(Req) -> - case lists:keyfind(<<"content-length">>, 1, Req#http_req.headers) of - {_, <<"0">>} -> - false; - {_, _} -> - true; - _ -> - lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers) - end. +has_body(#{has_body := HasBody}) -> + HasBody. %% The length may not be known if Transfer-Encoding is not identity, %% and the body hasn't been read at the time of the call. -spec body_length(req()) -> undefined | non_neg_integer(). -body_length(Req) -> - case parse_header(<<"transfer-encoding">>, Req) of - [<<"identity">>] -> - parse_header(<<"content-length">>, Req); - _ -> - undefined - end. +body_length(#{body_length := Length}) -> + Length. -spec body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). body(Req) -> body(Req, []). +-spec read_body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). +read_body(Req) -> + read_body(Req, []). + +-spec read_body(Req, body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). +read_body(Req=#{pid := Pid, streamid := StreamID}, Opts) -> + %% @todo Opts should be a map + Length = case lists:keyfind(length, 1, Opts) of + false -> 8000000; + {_, ChunkLen0} -> ChunkLen0 + end, + ReadTimeout = case lists:keyfind(read_timeout, 1, Opts) of + false -> 15000; + {_, ReadTimeout0} -> ReadTimeout0 + end, + Ref = make_ref(), + Pid ! {{Pid, StreamID}, {read_body, Ref, Length}}, +% io:format("READ_BODY ~p ~p ~p ~p~n", [Pid, StreamID, Ref, Length]), + receive + {request_body, Ref, nofin, Body} -> + {more, Body, Req}; + {request_body, Ref, {fin, BodyLength}, Body} -> + {ok, Body, set_body_length(Req, BodyLength)} + after ReadTimeout -> + exit(read_body_timeout) + end. + +set_body_length(Req=#{headers := Headers}, BodyLength) -> + Req#{ + headers => Headers#{<<"content-length">> => integer_to_binary(BodyLength)}, + body_length => BodyLength + }. + -spec body(Req, body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). body(Req=#http_req{body_state=waiting}, Opts) -> %% Send a 100 continue if needed (enabled by default). @@ -514,7 +535,7 @@ body_qs(Req) -> -spec body_qs(Req, body_opts()) -> {ok, [{binary(), binary() | true}], Req} | {badlength, Req} when Req::req(). body_qs(Req, Opts) -> - case body(Req, Opts) of + case read_body(Req, Opts) of {ok, Body, Req2} -> {ok, cow_qs:parse_qs(Body), Req2}; {more, _, Req2} -> @@ -535,13 +556,16 @@ part(Req) -> -spec part(Req, body_opts()) -> {ok, cow_multipart:headers(), Req} | {done, Req} when Req::req(). -part(Req=#http_req{multipart=undefined}, Opts) -> - part(init_multipart(Req), Opts); part(Req, Opts) -> - {Data, Req2} = stream_multipart(Req, Opts), - part(Data, Opts, Req2). + case maps:is_key(multipart, Req) of + true -> + {Data, Req2} = stream_multipart(Req, Opts), + part(Data, Opts, Req2); + false -> + part(init_multipart(Req), Opts) + end. -part(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}) -> +part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) -> case cow_multipart:parse_headers(Buffer, Boundary) of more -> {Data, Req2} = stream_multipart(Req, Opts), @@ -550,10 +574,10 @@ part(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}) -> {Data, Req2} = stream_multipart(Req, Opts), part(<< Buffer2/binary, Data/binary >>, Opts, Req2); {ok, Headers, Rest} -> - {ok, Headers, Req#http_req{multipart={Boundary, Rest}}}; + {ok, Headers, Req#{multipart => {Boundary, Rest}}}; %% Ignore epilogue. {done, _} -> - {done, Req#http_req{multipart=undefined}} + {done, Req#{multipart => done}} end. -spec part_body(Req) @@ -565,19 +589,22 @@ part_body(Req) -> -spec part_body(Req, body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). -part_body(Req=#http_req{multipart=undefined}, Opts) -> - part_body(init_multipart(Req), Opts); part_body(Req, Opts) -> - part_body(<<>>, Opts, Req, <<>>). + case maps:is_key(multipart, Req) of + true -> + part_body(<<>>, Opts, Req, <<>>); + false -> + part_body(init_multipart(Req), Opts) + end. -part_body(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}, Acc) -> +part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) -> ChunkLen = case lists:keyfind(length, 1, Opts) of false -> 8000000; {_, ChunkLen0} -> ChunkLen0 end, case byte_size(Acc) > ChunkLen of true -> - {more, Acc, Req#http_req{multipart={Boundary, Buffer}}}; + {more, Acc, Req#{multipart => {Boundary, Buffer}}}; false -> {Data, Req2} = stream_multipart(Req, Opts), case cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of @@ -591,21 +618,22 @@ part_body(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}, Acc) -> {ok, << Acc/binary, Body/binary >>, Req2}; {done, Body, Rest} -> {ok, << Acc/binary, Body/binary >>, - Req2#http_req{multipart={Boundary, Rest}}} + Req2#{multipart => {Boundary, Rest}}} end end. init_multipart(Req) -> {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req), {_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params), - Req#http_req{multipart={Boundary, <<>>}}. + Req#{multipart => {Boundary, <<>>}}. -stream_multipart(Req=#http_req{body_state=BodyState, multipart={_, <<>>}}, Opts) -> - true = BodyState =/= done, - {_, Data, Req2} = body(Req, Opts), +stream_multipart(Req=#{multipart := done}, _) -> + {<<>>, Req}; +stream_multipart(Req=#{multipart := {_, <<>>}}, Opts) -> + {_, Data, Req2} = read_body(Req, Opts), {Data, Req2}; -stream_multipart(Req=#http_req{multipart={Boundary, Buffer}}, _) -> - {Buffer, Req#http_req{multipart={Boundary, <<>>}}}. +stream_multipart(Req=#{multipart := {Boundary, Buffer}}, _) -> + {Buffer, Req#{multipart => {Boundary, <<>>}}}. %% Response API. @@ -618,16 +646,22 @@ stream_multipart(Req=#http_req{multipart={Boundary, Buffer}}, _) -> -> Req when Req::req(). set_resp_cookie(Name, Value, Opts, Req) -> Cookie = cow_cookie:setcookie(Name, Value, Opts), + %% @todo Nah, keep separate. set_resp_header(<<"set-cookie">>, Cookie, Req). -spec set_resp_header(binary(), iodata(), Req) -> Req when Req::req(). -set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) -> - Req#http_req{resp_headers=[{Name, Value}|RespHeaders]}. +set_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) -> + Req#{resp_headers => RespHeaders#{Name => Value}}; +set_resp_header(Name,Value, Req) -> + Req#{resp_headers => #{Name => Value}}. +%% @todo {sendfile, Offset, Bytes, Path} tuple -spec set_resp_body(iodata(), Req) -> Req when Req::req(). set_resp_body(Body, Req) -> - Req#http_req{resp_body=Body}. + Req#{resp_body => Body}. +%set_resp_body(Body, Req) -> +% Req#http_req{resp_body=Body}. -spec set_resp_body_fun(resp_body_fun(), Req) -> Req when Req::req(). set_resp_body_fun(StreamFun, Req) when is_function(StreamFun) -> @@ -647,189 +681,217 @@ set_resp_body_fun(chunked, StreamFun, Req) Req#http_req{resp_body={chunked, StreamFun}}. -spec has_resp_header(binary(), req()) -> boolean(). -has_resp_header(Name, #http_req{resp_headers=RespHeaders}) -> - lists:keymember(Name, 1, RespHeaders). +has_resp_header(Name, #{resp_headers := RespHeaders}) -> + maps:is_key(Name, RespHeaders); +has_resp_header(_, _) -> + false. -spec has_resp_body(req()) -> boolean(). -has_resp_body(#http_req{resp_body=RespBody}) when is_function(RespBody) -> - true; -has_resp_body(#http_req{resp_body={chunked, _}}) -> - true; -has_resp_body(#http_req{resp_body={Length, _}}) -> - Length > 0; -has_resp_body(#http_req{resp_body=RespBody}) -> - iolist_size(RespBody) > 0. +has_resp_body(#{resp_body := {sendfile, Len, _}}) -> + Len > 0; +has_resp_body(#{resp_body := RespBody}) -> + iolist_size(RespBody) > 0; +has_resp_body(_) -> + false. + +%has_resp_body(#http_req{resp_body=RespBody}) when is_function(RespBody) -> +% true; +%has_resp_body(#http_req{resp_body={chunked, _}}) -> +% true; +%has_resp_body(#http_req{resp_body={Length, _}}) -> +% Length > 0; +%has_resp_body(#http_req{resp_body=RespBody}) -> +% iolist_size(RespBody) > 0. -spec delete_resp_header(binary(), Req) -> Req when Req::req(). -delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) -> - RespHeaders2 = lists:keydelete(Name, 1, RespHeaders), - Req#http_req{resp_headers=RespHeaders2}. +delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) -> + Req#{resp_headers => maps:remove(Name, RespHeaders)}. -spec reply(cowboy:http_status(), Req) -> Req when Req::req(). -reply(Status, Req=#http_req{resp_body=Body}) -> - reply(Status, [], Body, Req). +reply(Status, Req) -> + reply(Status, #{}, Req). -spec reply(cowboy:http_status(), cowboy:http_headers(), Req) -> Req when Req::req(). -reply(Status, Headers, Req=#http_req{resp_body=Body}) -> - reply(Status, Headers, Body, Req). +reply(Status, Headers, Req=#{resp_body := Body}) -> + reply(Status, Headers, Body, Req); +reply(Status, Headers, Req) -> + reply(Status, Headers, <<>>, Req). -spec reply(cowboy:http_status(), cowboy:http_headers(), iodata() | resp_body_fun() | {non_neg_integer(), resp_body_fun()} | {chunked, resp_chunked_fun()}, Req) -> Req when Req::req(). -reply(Status, Headers, Body, Req=#http_req{ - socket=Socket, transport=Transport, - version=Version, connection=Connection, - method=Method, resp_compress=Compress, - resp_state=RespState, resp_headers=RespHeaders}) - when RespState =:= waiting; RespState =:= waiting_stream -> - HTTP11Headers = if - Transport =/= cowboy_spdy, Version =:= 'HTTP/1.0', Connection =:= keepalive -> - [{<<"connection">>, atom_to_connection(Connection)}]; - Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1', Connection =:= close -> - [{<<"connection">>, atom_to_connection(Connection)}]; - true -> - [] - end, - Req3 = case Body of - BodyFun when is_function(BodyFun) -> - %% We stream the response body until we close the connection. - RespConn = close, - {RespType, Req2} = if - Transport =:= cowboy_spdy -> - response(Status, Headers, RespHeaders, [ - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>} - ], stream, Req); - true -> - response(Status, Headers, RespHeaders, [ - {<<"connection">>, <<"close">>}, - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>}, - {<<"transfer-encoding">>, <<"identity">>} - ], <<>>, Req) - end, - if RespType =/= hook, Method =/= <<"HEAD">> -> - BodyFun(Socket, Transport); - true -> ok - end, - Req2#http_req{connection=RespConn}; - {chunked, BodyFun} -> - %% We stream the response body in chunks. - {RespType, Req2} = chunked_response(Status, Headers, Req), - if RespType =/= hook, Method =/= <<"HEAD">> -> - ChunkFun = fun(IoData) -> chunk(IoData, Req2) end, - BodyFun(ChunkFun), - %% Send the last chunk if chunked encoding was used. - if - Version =:= 'HTTP/1.0'; RespState =:= waiting_stream -> - Req2; - true -> - last_chunk(Req2) - end; - true -> Req2 - end; - {ContentLength, BodyFun} -> - %% We stream the response body for ContentLength bytes. - RespConn = response_connection(Headers, Connection), - {RespType, Req2} = response(Status, Headers, RespHeaders, [ - {<<"content-length">>, integer_to_list(ContentLength)}, - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>} - |HTTP11Headers], stream, Req), - if RespType =/= hook, Method =/= <<"HEAD">> -> - BodyFun(Socket, Transport); - true -> ok - end, - Req2#http_req{connection=RespConn}; - _ when Compress -> - RespConn = response_connection(Headers, Connection), - Req2 = reply_may_compress(Status, Headers, Body, Req, - RespHeaders, HTTP11Headers, Method), - Req2#http_req{connection=RespConn}; - _ -> - RespConn = response_connection(Headers, Connection), - Req2 = reply_no_compress(Status, Headers, Body, Req, - RespHeaders, HTTP11Headers, Method, iolist_size(Body)), - Req2#http_req{connection=RespConn} - end, - Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}. - -reply_may_compress(Status, Headers, Body, Req, - RespHeaders, HTTP11Headers, Method) -> - BodySize = iolist_size(Body), - try parse_header(<<"accept-encoding">>, Req) of - Encodings -> - CanGzip = (BodySize > 300) - andalso (false =:= lists:keyfind(<<"content-encoding">>, - 1, Headers)) - andalso (false =:= lists:keyfind(<<"content-encoding">>, - 1, RespHeaders)) - andalso (false =:= lists:keyfind(<<"transfer-encoding">>, - 1, Headers)) - andalso (false =:= lists:keyfind(<<"transfer-encoding">>, - 1, RespHeaders)) - andalso (Encodings =/= undefined) - andalso (false =/= lists:keyfind(<<"gzip">>, 1, Encodings)), - case CanGzip of - true -> - GzBody = zlib:gzip(Body), - {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"content-length">>, integer_to_list(byte_size(GzBody))}, - {<<"content-encoding">>, <<"gzip">>}, - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>} - |HTTP11Headers], - case Method of <<"HEAD">> -> <<>>; _ -> GzBody end, - Req), - Req2; - false -> - reply_no_compress(Status, Headers, Body, Req, - RespHeaders, HTTP11Headers, Method, BodySize) - end - catch _:_ -> - reply_no_compress(Status, Headers, Body, Req, - RespHeaders, HTTP11Headers, Method, BodySize) - end. +reply(Status, Headers, Stream = {stream, undefined, _}, Req) -> + do_stream_reply(Status, Headers, Stream, Req); +reply(Status, Headers, Stream = {stream, Len, _}, Req) -> + do_stream_reply(Status, Headers#{ + <<"content-length">> => integer_to_binary(Len) + }, Stream, Req); +reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req) -> + do_reply(Status, Headers#{ + <<"content-length">> => integer_to_binary(Len) + }, SendFile, Req); +reply(Status, Headers, Body, Req) -> + do_reply(Status, Headers#{ + <<"content-length">> => integer_to_binary(iolist_size(Body)) + }, Body, Req). + +do_stream_reply(Status, Headers, {stream, _, Fun}, Req=#{pid := Pid, streamid := StreamID}) -> + Pid ! {{Pid, StreamID}, {headers, Status, response_headers(Headers, Req)}}, + Fun(), + ok. + +do_reply(Status, Headers, Body, Req=#{pid := Pid, streamid := StreamID}) -> + Pid ! {{Pid, StreamID}, {response, Status, response_headers(Headers, Req), Body}}, + ok. + +send_body(Data, IsFin, #{pid := Pid, streamid := StreamID}) -> + Pid ! {{Pid, StreamID}, {data, IsFin, Data}}, + ok. -reply_no_compress(Status, Headers, Body, Req, - RespHeaders, HTTP11Headers, Method, BodySize) -> - {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"content-length">>, integer_to_list(BodySize)}, - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>} - |HTTP11Headers], - case Method of <<"HEAD">> -> <<>>; _ -> Body end, - Req), - Req2. +response_headers(Headers, Req) -> + RespHeaders = maps:get(resp_headers, Req, #{}), + maps:merge(#{ + <<"date">> => cowboy_clock:rfc1123(), + <<"server">> => <<"Cowboy">> + }, maps:merge(RespHeaders, Headers)). + +%reply(Status, Headers, Body, Req=#http_req{ +% socket=Socket, transport=Transport, +% version=Version, connection=Connection, +% method=Method, resp_compress=Compress, +% resp_state=RespState, resp_headers=RespHeaders}) +% when RespState =:= waiting; RespState =:= waiting_stream -> +% Req3 = case Body of +% BodyFun when is_function(BodyFun) -> +% %% We stream the response body until we close the connection. +% RespConn = close, +% {RespType, Req2} = if +% Transport =:= cowboy_spdy -> +% response(Status, Headers, RespHeaders, [ +% {<<"date">>, cowboy_clock:rfc1123()}, +% {<<"server">>, <<"Cowboy">>} +% ], stream, Req); +% true -> +% response(Status, Headers, RespHeaders, [ +% {<<"connection">>, <<"close">>}, +% {<<"date">>, cowboy_clock:rfc1123()}, +% {<<"server">>, <<"Cowboy">>}, +% {<<"transfer-encoding">>, <<"identity">>} +% ], <<>>, Req) +% end, +% if RespType =/= hook, Method =/= <<"HEAD">> -> +% BodyFun(Socket, Transport); +% true -> ok +% end, +% Req2#http_req{connection=RespConn}; +% {chunked, BodyFun} -> +% %% We stream the response body in chunks. +% {RespType, Req2} = chunked_response(Status, Headers, Req), +% if RespType =/= hook, Method =/= <<"HEAD">> -> +% ChunkFun = fun(IoData) -> chunk(IoData, Req2) end, +% BodyFun(ChunkFun), +% %% Send the last chunk if chunked encoding was used. +% if +% Version =:= 'HTTP/1.0'; RespState =:= waiting_stream -> +% Req2; +% true -> +% last_chunk(Req2) +% end; +% true -> Req2 +% end; +% {ContentLength, BodyFun} -> +% %% We stream the response body for ContentLength bytes. +% RespConn = response_connection(Headers, Connection), +% {RespType, Req2} = response(Status, Headers, RespHeaders, [ +% {<<"content-length">>, integer_to_list(ContentLength)}, +% {<<"date">>, cowboy_clock:rfc1123()}, +% {<<"server">>, <<"Cowboy">>} +% |HTTP11Headers], stream, Req), +% if RespType =/= hook, Method =/= <<"HEAD">> -> +% BodyFun(Socket, Transport); +% true -> ok +% end, +% Req2#http_req{connection=RespConn}; +% _ when Compress -> +% RespConn = response_connection(Headers, Connection), +% Req2 = reply_may_compress(Status, Headers, Body, Req, +% RespHeaders, HTTP11Headers, Method), +% Req2#http_req{connection=RespConn}; +% _ -> +% RespConn = response_connection(Headers, Connection), +% Req2 = reply_no_compress(Status, Headers, Body, Req, +% RespHeaders, HTTP11Headers, Method, iolist_size(Body)), +% Req2#http_req{connection=RespConn} +% end, +% Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}. + +%reply_may_compress(Status, Headers, Body, Req, +% RespHeaders, HTTP11Headers, Method) -> +% BodySize = iolist_size(Body), +% try parse_header(<<"accept-encoding">>, Req) of +% Encodings -> +% CanGzip = (BodySize > 300) +% andalso (false =:= lists:keyfind(<<"content-encoding">>, +% 1, Headers)) +% andalso (false =:= lists:keyfind(<<"content-encoding">>, +% 1, RespHeaders)) +% andalso (false =:= lists:keyfind(<<"transfer-encoding">>, +% 1, Headers)) +% andalso (false =:= lists:keyfind(<<"transfer-encoding">>, +% 1, RespHeaders)) +% andalso (Encodings =/= undefined) +% andalso (false =/= lists:keyfind(<<"gzip">>, 1, Encodings)), +% case CanGzip of +% true -> +% GzBody = zlib:gzip(Body), +% {_, Req2} = response(Status, Headers, RespHeaders, [ +% {<<"content-length">>, integer_to_list(byte_size(GzBody))}, +% {<<"content-encoding">>, <<"gzip">>}, +% |HTTP11Headers], +% case Method of <<"HEAD">> -> <<>>; _ -> GzBody end, +% Req), +% Req2; +% false -> +% reply_no_compress(Status, Headers, Body, Req, +% RespHeaders, HTTP11Headers, Method, BodySize) +% end +% catch _:_ -> +% reply_no_compress(Status, Headers, Body, Req, +% RespHeaders, HTTP11Headers, Method, BodySize) +% end. +% +%reply_no_compress(Status, Headers, Body, Req, +% RespHeaders, HTTP11Headers, Method, BodySize) -> +% {_, Req2} = response(Status, Headers, RespHeaders, [ +% {<<"content-length">>, integer_to_list(BodySize)}, +% |HTTP11Headers], +% case Method of <<"HEAD">> -> <<>>; _ -> Body end, +% Req), +% Req2. -spec chunked_reply(cowboy:http_status(), Req) -> Req when Req::req(). chunked_reply(Status, Req) -> - chunked_reply(Status, [], Req). + chunked_reply(Status, #{}, Req). -spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req) -> Req when Req::req(). -chunked_reply(Status, Headers, Req) -> - {_, Req2} = chunked_response(Status, Headers, Req), - Req2. +chunked_reply(Status, Headers, Req=#{pid := Pid, streamid := StreamID}) -> + Pid ! {{Pid, StreamID}, {headers, Status, response_headers(Headers, Req)}}, + Req. %% @todo return ok +% ok. -spec chunk(iodata(), req()) -> ok. -chunk(_Data, #http_req{method= <<"HEAD">>}) -> +chunk(_Data, #{method := <<"HEAD">>}) -> ok; -chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy, - resp_state=chunks}) -> - cowboy_spdy:stream_data(Socket, Data); -chunk(Data, #http_req{socket=Socket, transport=Transport, - resp_state=stream}) -> - ok = Transport:send(Socket, Data); -chunk(Data, #http_req{socket=Socket, transport=Transport, - resp_state=chunks}) -> +chunk(Data, #{pid := Pid, streamid := StreamID}) -> case iolist_size(Data) of 0 -> ok; - Size -> Transport:send(Socket, [integer_to_list(Size, 16), - <<"\r\n">>, Data, <<"\r\n">>]) + _ -> + Pid ! {{Pid, StreamID}, {data, nofin, Data}}, + ok end. %% If ever made public, need to send nothing if HEAD. @@ -841,16 +903,6 @@ last_chunk(Req=#http_req{socket=Socket, transport=Transport}) -> _ = Transport:send(Socket, <<"0\r\n\r\n">>), Req#http_req{resp_state=done}. --spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req) - -> Req when Req::req(). -upgrade_reply(Status, Headers, Req=#http_req{transport=Transport, - resp_state=waiting, resp_headers=RespHeaders}) - when Transport =/= cowboy_spdy -> - {_, Req2} = response(Status, Headers, RespHeaders, [ - {<<"connection">>, <<"Upgrade">>} - ], <<>>, Req), - Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}. - -spec continue(req()) -> ok. continue(#http_req{socket=Socket, transport=Transport, version=Version}) -> @@ -973,49 +1025,49 @@ to_list(Req) -> %% Internal. --spec chunked_response(cowboy:http_status(), cowboy:http_headers(), Req) -> - {normal | hook, Req} when Req::req(). -chunked_response(Status, Headers, Req=#http_req{ - transport=cowboy_spdy, resp_state=waiting, - resp_headers=RespHeaders}) -> - {RespType, Req2} = response(Status, Headers, RespHeaders, [ - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>} - ], stream, Req), - {RespType, Req2#http_req{resp_state=chunks, - resp_headers=[], resp_body= <<>>}}; -chunked_response(Status, Headers, Req=#http_req{ - version=Version, connection=Connection, - resp_state=RespState, resp_headers=RespHeaders}) - when RespState =:= waiting; RespState =:= waiting_stream -> - RespConn = response_connection(Headers, Connection), - HTTP11Headers = if - Version =:= 'HTTP/1.0', Connection =:= keepalive -> - [{<<"connection">>, atom_to_connection(Connection)}]; - Version =:= 'HTTP/1.0' -> []; - true -> - MaybeTE = if - RespState =:= waiting_stream -> []; - true -> [{<<"transfer-encoding">>, <<"chunked">>}] - end, - if - Connection =:= close -> - [{<<"connection">>, atom_to_connection(Connection)}|MaybeTE]; - true -> - MaybeTE - end - end, - RespState2 = if - Version =:= 'HTTP/1.1', RespState =:= 'waiting' -> chunks; - true -> stream - end, - {RespType, Req2} = response(Status, Headers, RespHeaders, [ - {<<"date">>, cowboy_clock:rfc1123()}, - {<<"server">>, <<"Cowboy">>} - |HTTP11Headers], <<>>, Req), - {RespType, Req2#http_req{connection=RespConn, resp_state=RespState2, - resp_headers=[], resp_body= <<>>}}. - +%-spec chunked_response(cowboy:http_status(), cowboy:http_headers(), Req) -> +% {normal | hook, Req} when Req::req(). +%chunked_response(Status, Headers, Req=#http_req{ +% transport=cowboy_spdy, resp_state=waiting, +% resp_headers=RespHeaders}) -> +% {RespType, Req2} = response(Status, Headers, RespHeaders, [ +% {<<"date">>, cowboy_clock:rfc1123()}, +% {<<"server">>, <<"Cowboy">>} +% ], stream, Req), +% {RespType, Req2#http_req{resp_state=chunks, +% resp_headers=[], resp_body= <<>>}}; +%chunked_response(Status, Headers, Req=#http_req{ +% version=Version, connection=Connection, +% resp_state=RespState, resp_headers=RespHeaders}) +% when RespState =:= waiting; RespState =:= waiting_stream -> +% RespConn = response_connection(Headers, Connection), +% HTTP11Headers = if +% Version =:= 'HTTP/1.0', Connection =:= keepalive -> +% [{<<"connection">>, atom_to_connection(Connection)}]; +% Version =:= 'HTTP/1.0' -> []; +% true -> +% MaybeTE = if +% RespState =:= waiting_stream -> []; +% true -> [{<<"transfer-encoding">>, <<"chunked">>}] +% end, +% if +% Connection =:= close -> +% [{<<"connection">>, atom_to_connection(Connection)}|MaybeTE]; +% true -> +% MaybeTE +% end +% end, +% RespState2 = if +% Version =:= 'HTTP/1.1', RespState =:= 'waiting' -> chunks; +% true -> stream +% end, +% {RespType, Req2} = response(Status, Headers, RespHeaders, [ +% {<<"date">>, cowboy_clock:rfc1123()}, +% {<<"server">>, <<"Cowboy">>} +% |HTTP11Headers], <<>>, Req), +% {RespType, Req2#http_req{connection=RespConn, resp_state=RespState2, +% resp_headers=[], resp_body= <<>>}}. +% -spec response(cowboy:http_status(), cowboy:http_headers(), cowboy:http_headers(), cowboy:http_headers(), stream | iodata(), Req) -> {normal | hook, Req} when Req::req(). @@ -1063,20 +1115,20 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{ 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">> -> - Tokens = cow_http_hd:parse_connection(Value), - connection_to_atom(Tokens); - _ -> - response_connection(Tail, Connection) - end. - +% +%-spec response_connection(cowboy:http_headers(), keepalive | close) +% -> keepalive | close. +%response_connection([], Connection) -> +% Connection; +%response_connection([{Name, Value}|Tail], Connection) -> +% case Name of +% <<"connection">> -> +% Tokens = cow_http_hd:parse_connection(Value), +% connection_to_atom(Tokens); +% _ -> +% response_connection(Tail, Connection) +% end. +% -spec response_merge_headers(cowboy:http_headers(), cowboy:http_headers(), cowboy:http_headers()) -> cowboy:http_headers(). response_merge_headers(Headers, RespHeaders, DefaultHeaders) -> @@ -1105,13 +1157,13 @@ merge_headers(Headers, [{Name, Value}|Tail]) -> false -> [{Name, Value}|Headers] 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 atom_to_connection(keepalive) -> <<_:80>>; +% (close) -> <<_:40>>. +%atom_to_connection(keepalive) -> +% <<"keep-alive">>; +%atom_to_connection(close) -> +% <<"close">>. %% We don't match on "keep-alive" since it is the default value. -spec connection_to_atom([binary()]) -> keepalive | close. |