aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cowboy_http.erl15
-rw-r--r--src/cowboy_http2.erl10
-rw-r--r--test/handlers/resp_h.erl20
-rw-r--r--test/req_SUITE.erl10
4 files changed, 41 insertions, 14 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index 281e815..c681154 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -871,7 +871,14 @@ commands(State0=#state{socket=Socket, transport=Transport, streams=Streams}, Str
%% data frame, as that would break the protocol.
Size = iolist_size(Data),
case Size of
- 0 -> ok;
+ 0 ->
+ %% We send the last chunk only if version is HTTP/1.1 and IsFin=fin.
+ case lists:keyfind(StreamID, #stream.id, Streams) of
+ #stream{version='HTTP/1.1'} when IsFin =:= fin ->
+ Transport:send(Socket, <<"0\r\n\r\n">>);
+ _ ->
+ ok
+ end;
_ ->
%% @todo We need to kill the stream if it tries to send data before headers.
%% @todo Same as above.
@@ -961,8 +968,7 @@ stream_reset(State, StreamID, StreamError={internal_error, _, _}) ->
% stream_terminate(State#state{out_state=done}, StreamID, StreamError).
stream_terminate(State, StreamID, StreamError).
-stream_terminate(State0=#state{socket=Socket, transport=Transport,
- out_streamid=OutStreamID, out_state=OutState,
+stream_terminate(State0=#state{out_streamid=OutStreamID, out_state=OutState,
streams=Streams0, children=Children0}, StreamID, Reason) ->
#stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams0),
State1 = #state{streams=Streams1} = case OutState of
@@ -971,8 +977,7 @@ stream_terminate(State0=#state{socket=Socket, transport=Transport,
wait ->
info(State0, StreamID, {response, 204, #{}, <<>>});
chunked when Version =:= 'HTTP/1.1' ->
- _ = Transport:send(Socket, <<"0\r\n\r\n">>),
- State0;
+ info(State0, StreamID, {data, fin, <<>>});
_ -> %% done or Version =:= 'HTTP/1.0'
State0
end,
diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl
index 21f0aeb..9e81957 100644
--- a/src/cowboy_http2.erl
+++ b/src/cowboy_http2.erl
@@ -830,8 +830,7 @@ stream_linger(State=#state{lingering_streams=Lingering0}, StreamID) ->
Lingering = [StreamID|lists:sublist(Lingering0, 100 - 1)],
State#state{lingering_streams=Lingering}.
-stream_terminate(State0=#state{socket=Socket, transport=Transport,
- streams=Streams0, children=Children0}, StreamID, Reason) ->
+stream_terminate(State0=#state{streams=Streams0, children=Children0}, StreamID, Reason) ->
case lists:keytake(StreamID, #stream.id, Streams0) of
%% When the stream terminates normally (without sending RST_STREAM)
%% and no response was sent, we need to send a proper response back to the client.
@@ -843,10 +842,11 @@ stream_terminate(State0=#state{socket=Socket, transport=Transport,
Children = cowboy_children:shutdown(Children0, StreamID),
State#state{streams=Streams, children=Children};
%% When a response was sent but not terminated, we need to close the stream.
- {value, Stream=#stream{state=StreamState, local=nofin, local_buffer_size=0}, Streams}
+ {value, Stream=#stream{local=nofin, local_buffer_size=0}, Streams}
when Reason =:= normal ->
- Transport:send(Socket, cow_http2:data(StreamID, fin, <<>>)),
- State = maybe_skip_body(State0, Stream, Reason),
+ State1 = #state{streams=Streams1} = info(State0, StreamID, {data, fin, <<>>}),
+ State = maybe_skip_body(State1, Stream, Reason),
+ #stream{state=StreamState} = lists:keyfind(StreamID, #stream.id, Streams1),
stream_call_terminate(StreamID, Reason, StreamState),
Children = cowboy_children:shutdown(Children0, StreamID),
State#state{streams=Streams, children=Children};
diff --git a/test/handlers/resp_h.erl b/test/handlers/resp_h.erl
index 94c7f60..add90ad 100644
--- a/test/handlers/resp_h.erl
+++ b/test/handlers/resp_h.erl
@@ -188,10 +188,22 @@ do(<<"stream_reply3">>, Req0, Opts) ->
end,
stream_body(Req),
{ok, Req, Opts};
-do(<<"stream_body">>, Req, Opts) ->
- %% Call stream_body without initiating streaming.
- cowboy_req:stream_body(<<0:800000>>, fin, Req),
- {ok, Req, Opts};
+do(<<"stream_body">>, Req0, Opts) ->
+ case cowboy_req:binding(arg, Req0) of
+ <<"fin0">> ->
+ Req = cowboy_req:stream_reply(200, Req0),
+ cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
+ cowboy_req:stream_body(<<>>, fin, Req),
+ {ok, Req, Opts};
+ <<"nofin">> ->
+ Req = cowboy_req:stream_reply(200, Req0),
+ cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
+ {ok, Req, Opts};
+ _ ->
+ %% Call stream_body without initiating streaming.
+ cowboy_req:stream_body(<<0:800000>>, fin, Req0),
+ {ok, Req0, Opts}
+ end;
do(<<"push">>, Req, Opts) ->
case cowboy_req:binding(arg, Req) of
<<"method">> ->
diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl
index 107cdd8..862ee53 100644
--- a/test/req_SUITE.erl
+++ b/test/req_SUITE.erl
@@ -827,6 +827,16 @@ stream_reply3(Config) ->
{500, _, _} = do_get("/resp/stream_reply3/error", Config),
ok.
+stream_body_fin0(Config) ->
+ doc("Streamed body with last chunk of size 0."),
+ {200, _, <<"Hello world!">>} = do_get("/resp/stream_body/fin0", Config),
+ ok.
+
+stream_body_nofin(Config) ->
+ doc("Unfinished streamed body."),
+ {200, _, <<"Hello world!">>} = do_get("/resp/stream_body/nofin", Config),
+ ok.
+
%% @todo Crash when calling stream_body after the fin flag has been set.
%% @todo Crash when calling stream_body after calling reply.
%% @todo Crash when calling stream_body before calling stream_reply.