aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorViktor Söderqvist <[email protected]>2020-10-21 16:58:22 +0200
committerLoïc Hoguin <[email protected]>2020-11-12 14:21:57 +0100
commit30a971a039c2725726080ce6d50ce90e1108cb5a (patch)
tree772af8cfd71eb420ba53ee7c56cce00ad69140f7 /test
parent7dabafe7a93582c71cdabc3c41a609f582afd7d1 (diff)
downloadgun-30a971a039c2725726080ce6d50ce90e1108cb5a.tar.gz
gun-30a971a039c2725726080ce6d50ce90e1108cb5a.tar.bz2
gun-30a971a039c2725726080ce6d50ce90e1108cb5a.zip
Fail fast while closing if reconnect is off
If a request/headers/connect/ws_upgrade is created when a connection is in state 'closing', such as after receiving an HTTP/2 GOAWAY frame or an HTTP/1.1 "Connection: close" header, an error message is sent back to the caller immediately, if reconnect is off (that is if the option retry is set to 0). This allows an application to retry the request on another connection without waiting for all streams on the current connection to complete.
Diffstat (limited to 'test')
-rw-r--r--test/shutdown_SUITE.erl73
1 files changed, 65 insertions, 8 deletions
diff --git a/test/shutdown_SUITE.erl b/test/shutdown_SUITE.erl
index 06fd81f..891aed8 100644
--- a/test/shutdown_SUITE.erl
+++ b/test/shutdown_SUITE.erl
@@ -218,9 +218,9 @@ http1_request_connection_close_pipeline(Config) ->
StreamRef3 = gun:get(ConnPid, "/"),
%% We get the response, pipelined streams get canceled, followed by Gun shutting down.
{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),
+ {error, {stream_error, closing}} = gun:await(ConnPid, StreamRef2),
+ {error, {stream_error, closing}} = gun:await(ConnPid, StreamRef3),
{ok, _} = gun:await_body(ConnPid, StreamRef1),
- {error, {stream_error, {closed, normal}}} = gun:await(ConnPid, StreamRef2),
- {error, {stream_error, {closed, normal}}} = gun:await(ConnPid, StreamRef3),
gun_is_down(ConnPid, ConnRef, normal).
http1_response_connection_close(_) ->
@@ -272,8 +272,8 @@ http1_response_connection_close_pipeline(_) ->
%% We get the response, pipelined streams get canceled, followed by Gun shutting down.
{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),
{ok, _} = gun:await_body(ConnPid, StreamRef1),
- {error, {stream_error, {closed, normal}}} = gun:await(ConnPid, StreamRef2),
- {error, {stream_error, {closed, normal}}} = gun:await(ConnPid, StreamRef3),
+ {error, {stream_error, closing}} = gun:await(ConnPid, StreamRef2),
+ {error, {stream_error, closing}} = gun:await(ConnPid, StreamRef3),
gun_is_down(ConnPid, ConnRef, normal)
after
cowboy:stop_listener(?FUNCTION_NAME)
@@ -297,6 +297,47 @@ http10_connection_close(Config) ->
{ok, _} = gun:await_body(ConnPid, StreamRef),
gun_is_down(ConnPid, ConnRef, normal).
+http1_response_connection_close_delayed_body(_) ->
+ doc("HTTP/1.1: Confirm that requests initiated when Gun has received a "
+ "connection: close response header fail immediately if retry "
+ "is disabled, without waiting for the response body."),
+ ServerFun = fun(_Parent, ClientSocket, gen_tcp) ->
+ try
+ {ok, Req} = gen_tcp:recv(ClientSocket, 0, 5000),
+ <<"GET / HTTP/1.1\r\n", _/binary>> = Req,
+ ok = gen_tcp:send(ClientSocket, <<"HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Content-Length: 12\r\n\r\nHello">>),
+ timer:sleep(500),
+ ok = gen_tcp:send(ClientSocket, " world!")
+ after
+ gen_tcp:close(ClientSocket)
+ end
+ end,
+ {ok, ServerPid, OriginPort} = gun_test:init_origin(tcp, http, ServerFun),
+ %% Client connects.
+ {ok, ConnPid} = gun:open("localhost", OriginPort, #{
+ protocols => [http],
+ retry => 0
+ }),
+ {ok, _Protocol} = gun:await_up(ConnPid),
+ receive {ServerPid, handshake_completed} -> ok end,
+ ConnRef = monitor(process, ConnPid),
+ StreamRef1 = gun:get(ConnPid, "/"),
+ StreamRef2 = gun:get(ConnPid, "/"),
+ %% We get the response headers with connection: close.
+ {response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),
+ %% Pipelined request fails immediately.
+ {gun_error, ConnPid, StreamRef2, closing} = receive E2 -> E2 end,
+ {gun_data, ConnPid, StreamRef1, nofin, <<"Hello">>} =
+ receive PartialBody -> PartialBody end,
+ %% Request initiated when Gun is in closing state fails immediately.
+ StreamRef3 = gun:get(ConnPid, "/"),
+ {gun_error, ConnPid, StreamRef3, closing} = receive E3 -> E3 end,
+ {gun_data, ConnPid, StreamRef1, fin, <<" world!">>} =
+ receive RestBody -> RestBody end,
+ gun_is_down(ConnPid, ConnRef, normal).
+
http2_gun_shutdown_no_streams(Config) ->
doc("HTTP/2: Confirm that the Gun process shuts down gracefully "
"when calling gun:shutdown/1 with no active streams."),
@@ -433,11 +474,20 @@ http2_server_goaway_many_streams(_) ->
{ok, <<SkipLen3:24, 1:8, _:8, 5:32>>} = Transport:recv(Socket, 9, 1000),
%% Skip the header.
{ok, _} = gen_tcp:recv(Socket, SkipLen3, 1000),
- %% Send a GOAWAY frame.
+ %% Stream 4.
+ %% Receive a HEADERS frame, but simulate that it is still
+ %% in-flight when the GOAWAY frame is sent.
+ {ok, <<SkipLen4:24, 1:8, _:8, 7:32>>} = Transport:recv(Socket, 9, 1000),
+ %% Skip the header.
+ {ok, _} = gen_tcp:recv(Socket, SkipLen4, 1000),
+ %% Send a GOAWAY frame. Simulate that GOAWAY was sent before
+ %% receiving stream 4 by including last stream ID of stream 3.
Transport:send(Socket, cow_http2:goaway(5, no_error, <<>>)),
- %% Wait before sending the responses back and closing the connection.
+ %% Gun replies with GOAWAY.
+ {ok, <<SkipLen5:24, 7:8, _:8, 0:32>>} = Transport:recv(Socket, 9, 1000),
+ {ok, _SkippedPayload} = gen_tcp:recv(Socket, SkipLen5, 1000),
timer:sleep(500),
- %% Send a HEADERS frame.
+ %% Send replies for streams 1-3.
{HeadersBlock1, State0} = cow_hpack:encode([
{<<":status">>, <<"200">>}
]),
@@ -456,7 +506,8 @@ http2_server_goaway_many_streams(_) ->
ok = Transport:send(Socket, [
cow_http2:headers(5, fin, HeadersBlock3)
]),
- timer:sleep(500)
+ %% Gun closes the connection.
+ {error, closed} = gen_tcp:recv(Socket, 9)
end),
Protocol = http2,
{ok, ConnPid} = gun:open("localhost", OriginPort, #{
@@ -468,7 +519,13 @@ http2_server_goaway_many_streams(_) ->
StreamRef1 = gun:get(ConnPid, "/"),
StreamRef2 = gun:get(ConnPid, "/"),
StreamRef3 = gun:get(ConnPid, "/"),
+ StreamRef4 = gun:get(ConnPid, "/"),
ConnRef = monitor(process, ConnPid),
+ %% GOAWAY received. Stream 4 is cancelled.
+ {gun_error, ConnPid, StreamRef4, Reason4} = receive E4 -> E4 end,
+ {goaway, no_error, _} = Reason4,
+ StreamRef5 = gun:get(ConnPid, "/"),
+ {gun_error, ConnPid, StreamRef5, closing} = receive E5 -> E5 end,
{response, fin, 200, _} = gun:await(ConnPid, StreamRef1),
{response, fin, 200, _} = gun:await(ConnPid, StreamRef2),
{response, fin, 200, _} = gun:await(ConnPid, StreamRef3),