|
@@ -164,7 +164,8 @@
|
|
|
|
|
|
%% Response.
|
|
|
resp_compress = false :: boolean(),
|
|
|
- resp_state = waiting :: locked | waiting | chunks | done,
|
|
|
+ resp_state = waiting :: locked | waiting | waiting_stream
|
|
|
+ | chunks | stream | done,
|
|
|
resp_headers = [] :: cowboy:http_headers(),
|
|
|
resp_body = <<>> :: iodata() | resp_body_fun()
|
|
|
| {non_neg_integer(), resp_body_fun()}
|
|
@@ -946,7 +947,8 @@ reply(Status, Headers, Body, Req=#http_req{
|
|
|
socket=Socket, transport=Transport,
|
|
|
version=Version, connection=Connection,
|
|
|
method=Method, resp_compress=Compress,
|
|
|
- resp_state=waiting, resp_headers=RespHeaders}) ->
|
|
|
+ resp_state=RespState, resp_headers=RespHeaders})
|
|
|
+ when RespState =:= waiting; RespState =:= waiting_stream ->
|
|
|
HTTP11Headers = if
|
|
|
Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1' ->
|
|
|
[{<<"connection">>, atom_to_connection(Connection)}];
|
|
@@ -982,10 +984,12 @@ reply(Status, Headers, Body, Req=#http_req{
|
|
|
if RespType =/= hook, Method =/= <<"HEAD">> ->
|
|
|
ChunkFun = fun(IoData) -> chunk(IoData, Req2) end,
|
|
|
BodyFun(ChunkFun),
|
|
|
- %% Terminate the chunked body for HTTP/1.1 only.
|
|
|
- case Version of
|
|
|
- 'HTTP/1.0' -> Req2;
|
|
|
- _ -> last_chunk(Req2)
|
|
|
+ %% 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;
|
|
@@ -1086,7 +1090,7 @@ 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=chunks, version='HTTP/1.0'}) ->
|
|
|
+ resp_state=stream}) ->
|
|
|
Transport:send(Socket, Data);
|
|
|
chunk(Data, #http_req{socket=Socket, transport=Transport,
|
|
|
resp_state=chunks}) ->
|
|
@@ -1136,16 +1140,17 @@ ensure_response(#http_req{resp_state=done}, _) ->
|
|
|
ok;
|
|
|
%% No response has been sent but everything apparently went fine.
|
|
|
%% Reply with the status code found in the second argument.
|
|
|
-ensure_response(Req=#http_req{resp_state=waiting}, Status) ->
|
|
|
+ensure_response(Req=#http_req{resp_state=RespState}, Status)
|
|
|
+ when RespState =:= waiting; RespState =:= waiting_stream ->
|
|
|
_ = reply(Status, [], [], Req),
|
|
|
ok;
|
|
|
%% Terminate the chunked body for HTTP/1.1 only.
|
|
|
-ensure_response(#http_req{method= <<"HEAD">>, resp_state=chunks}, _) ->
|
|
|
- ok;
|
|
|
-ensure_response(#http_req{version='HTTP/1.0', resp_state=chunks}, _) ->
|
|
|
+ensure_response(#http_req{method= <<"HEAD">>}, _) ->
|
|
|
ok;
|
|
|
ensure_response(Req=#http_req{resp_state=chunks}, _) ->
|
|
|
_ = last_chunk(Req),
|
|
|
+ ok;
|
|
|
+ensure_response(#http_req{}, _) ->
|
|
|
ok.
|
|
|
|
|
|
%% Private setter/getter API.
|
|
@@ -1269,19 +1274,27 @@ chunked_response(Status, Headers, Req=#http_req{
|
|
|
resp_headers=[], resp_body= <<>>}};
|
|
|
chunked_response(Status, Headers, Req=#http_req{
|
|
|
version=Version, connection=Connection,
|
|
|
- resp_state=waiting, resp_headers=RespHeaders}) ->
|
|
|
+ resp_state=RespState, resp_headers=RespHeaders})
|
|
|
+ when RespState =:= waiting; RespState =:= waiting_stream ->
|
|
|
RespConn = response_connection(Headers, Connection),
|
|
|
- HTTP11Headers = case Version of
|
|
|
- 'HTTP/1.1' -> [
|
|
|
- {<<"connection">>, atom_to_connection(Connection)},
|
|
|
- {<<"transfer-encoding">>, <<"chunked">>}];
|
|
|
- _ -> []
|
|
|
+ HTTP11Headers = if
|
|
|
+ Version =:= 'HTTP/1.0' -> [];
|
|
|
+ true ->
|
|
|
+ MaybeTE = if
|
|
|
+ RespState =:= waiting_stream -> [];
|
|
|
+ true -> [{<<"transfer-encoding">>, <<"chunked">>}]
|
|
|
+ end,
|
|
|
+ [{<<"connection">>, atom_to_connection(Connection)}|MaybeTE]
|
|
|
+ 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=chunks,
|
|
|
+ {RespType, Req2#http_req{connection=RespConn, resp_state=RespState2,
|
|
|
resp_headers=[], resp_body= <<>>}}.
|
|
|
|
|
|
-spec response(cowboy:http_status(), cowboy:http_headers(),
|
|
@@ -1313,7 +1326,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
|
|
|
cowboy_spdy:reply(Socket, status(Status), FullHeaders, Body),
|
|
|
ReqPid ! {?MODULE, resp_sent},
|
|
|
normal;
|
|
|
- waiting ->
|
|
|
+ RespState when RespState =:= waiting; RespState =:= waiting_stream ->
|
|
|
HTTPVer = atom_to_binary(Version, latin1),
|
|
|
StatusLine = << HTTPVer/binary, " ",
|
|
|
(status(Status))/binary, "\r\n" >>,
|
|
@@ -1361,7 +1374,7 @@ response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
|
|
|
merge_headers(Headers, []) ->
|
|
|
Headers;
|
|
|
merge_headers(Headers, [{<<"set-cookie">>, Value}|Tail]) ->
|
|
|
- merge_headers([{<<"set-cookie">>, Value}|Headers], Tail);
|
|
|
+ merge_headers([{<<"set-cookie">>, Value}|Headers], Tail);
|
|
|
merge_headers(Headers, [{Name, Value}|Tail]) ->
|
|
|
Headers2 = case lists:keymember(Name, 1, Headers) of
|
|
|
true -> Headers;
|