Browse Source

Fix streaming HTTP/2 responses

Error reporting for connection processes has been improved,
using a similar proc_lib hack as was done for the stream
processes.
Loïc Hoguin 9 years ago
parent
commit
b82bb92f7e
3 changed files with 41 additions and 6 deletions
  1. 13 2
      src/cowboy_clear.erl
  2. 15 2
      src/cowboy_http2.erl
  3. 13 2
      src/cowboy_tls.erl

+ 13 - 2
src/cowboy_clear.erl

@@ -16,13 +16,24 @@
 -behavior(ranch_protocol).
 
 -export([start_link/4]).
--export([init/5]).
+-export([proc_lib_hack/5]).
 
 -spec start_link(ranch:ref(), inet:socket(), module(), cowboy:opts()) -> {ok, pid()}.
 start_link(Ref, Socket, Transport, Opts) ->
-	Pid = proc_lib:spawn_link(?MODULE, init, [self(), Ref, Socket, Transport, Opts]),
+	Pid = proc_lib:spawn_link(?MODULE, proc_lib_hack, [self(), Ref, Socket, Transport, Opts]),
 	{ok, Pid}.
 
+-spec proc_lib_hack(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts()) -> ok.
+proc_lib_hack(Parent, Ref, Socket, Transport, Opts) ->
+	try
+		init(Parent, Ref, Socket, Transport, Opts)
+	catch
+		_:normal -> exit(normal);
+		_:shutdown -> exit(shutdown);
+		_:Reason = {shutdown, _} -> exit(Reason);
+		_:Reason -> exit({Reason, erlang:get_stacktrace()})
+	end.
+
 -spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts()) -> ok.
 init(Parent, Ref, Socket, Transport, Opts) ->
 	ok = ranch:accept_ack(Ref),

+ 15 - 2
src/cowboy_http2.erl

@@ -385,6 +385,13 @@ commands(State=#state{socket=Socket, transport=Transport, encode_state=EncodeSta
 			]),
 			commands(State#state{encode_state=EncodeState}, StreamID, Tail)
 	end;
+%% Send response headers and initiate chunked encoding.
+commands(State=#state{socket=Socket, transport=Transport, encode_state=EncodeState0}, StreamID,
+		[{headers, StatusCode, Headers0}|Tail]) ->
+	Headers = Headers0#{<<":status">> => integer_to_binary(StatusCode)},
+	{HeaderBlock, EncodeState} = headers_encode(Headers, EncodeState0),
+	Transport:send(Socket, cow_http2:headers(StreamID, nofin, HeaderBlock)),
+	commands(State#state{encode_state=EncodeState}, StreamID, Tail);
 %% Send a response body chunk.
 %%
 %% @todo WINDOW_UPDATE stuff require us to buffer some data.
@@ -455,7 +462,7 @@ commands(State, StreamID, [{upgrade, _Mod, _ModState}|Tail]) ->
 	commands(State, StreamID, Tail);
 commands(State, StreamID, [stop|_Tail]) ->
 	%% @todo Do we want to run the commands after a stop?
-	stream_terminate(State, StreamID, stop).
+	stream_terminate(State, StreamID, normal).
 
 terminate(#state{socket=Socket, transport=Transport, handler=Handler,
 		streams=Streams, children=Children}, Reason) ->
@@ -550,8 +557,14 @@ stream_reset(State=#state{socket=Socket, transport=Transport}, StreamID,
 	Transport:send(Socket, cow_http2:rst_stream(StreamID, Reason)),
 	stream_terminate(State, StreamID, StreamError).
 
-stream_terminate(State=#state{handler=Handler, streams=Streams0, children=Children0}, StreamID, Reason) ->
+stream_terminate(State=#state{socket=Socket, transport=Transport,
+		handler=Handler, streams=Streams0, children=Children0}, StreamID, Reason) ->
 	case lists:keytake(StreamID, #stream.id, Streams0) of
+		{value, #stream{state=StreamState, local=nofin}, Streams} when Reason =:= normal ->
+			Transport:send(Socket, cow_http2:data(StreamID, fin, <<>>)),
+			stream_call_terminate(StreamID, Reason, Handler, StreamState),
+			Children = stream_terminate_children(Children0, StreamID, []),
+			State#state{streams=Streams, children=Children};
 		{value, #stream{state=StreamState}, Streams} ->
 			stream_call_terminate(StreamID, Reason, Handler, StreamState),
 			Children = stream_terminate_children(Children0, StreamID, []),

+ 13 - 2
src/cowboy_tls.erl

@@ -16,13 +16,24 @@
 -behavior(ranch_protocol).
 
 -export([start_link/4]).
--export([init/5]).
+-export([proc_lib_hack/5]).
 
 -spec start_link(ranch:ref(), inet:socket(), module(), cowboy:opts()) -> {ok, pid()}.
 start_link(Ref, Socket, Transport, Opts) ->
-	Pid = proc_lib:spawn_link(?MODULE, init, [self(), Ref, Socket, Transport, Opts]),
+	Pid = proc_lib:spawn_link(?MODULE, proc_lib_hack, [self(), Ref, Socket, Transport, Opts]),
 	{ok, Pid}.
 
+-spec proc_lib_hack(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts()) -> ok.
+proc_lib_hack(Parent, Ref, Socket, Transport, Opts) ->
+	try
+		init(Parent, Ref, Socket, Transport, Opts)
+	catch
+		_:normal -> exit(normal);
+		_:shutdown -> exit(shutdown);
+		_:Reason = {shutdown, _} -> exit(Reason);
+		_:Reason -> exit({Reason, erlang:get_stacktrace()})
+	end.
+
 -spec init(pid(), ranch:ref(), inet:socket(), module(), cowboy:opts()) -> ok.
 init(Parent, Ref, Socket, Transport, Opts) ->
 	ok = ranch:accept_ack(Ref),