diff options
Diffstat (limited to 'test/rfc7540_SUITE.erl')
-rw-r--r-- | test/rfc7540_SUITE.erl | 85 |
1 files changed, 56 insertions, 29 deletions
diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index aec0aa1..6d8aa91 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -18,6 +18,7 @@ -import(ct_helper, [config/2]). -import(ct_helper, [doc/1]). +-import(ct_helper, [get_remote_pid_tcp/1]). -import(cowboy_test, [gun_open/1]). -import(cowboy_test, [raw_open/1]). -import(cowboy_test, [raw_send/2]). @@ -52,6 +53,7 @@ init_routes(_) -> [ {"localhost", [ {"/", hello_h, []}, {"/echo/:key", echo_h, []}, + {"/delay_hello", delay_hello_h, 1200}, {"/long_polling", long_polling_h, []}, {"/loop_handler_abort", loop_handler_abort_h, []}, {"/resp/:key[/:arg]", resp_h, []} @@ -2955,39 +2957,64 @@ client_settings_disable_push(Config) -> %% (RFC7540 6.8) GOAWAY % @todo GOAWAY frames have a reserved bit in the payload that must be ignored. % -%% @todo We should eventually implement the mechanism for gracefully -%% shutting down of the connection. (Send the GOAWAY, finish processing -%% the current set of streams, give up after a certain timeout.) -% -%% @todo If we graceful shutdown and receive a GOAWAY, we give up too. % A GOAWAY frame might not immediately precede closing of the % connection; a receiver of a GOAWAY that has no more use for the % connection SHOULD still send a GOAWAY frame before terminating the % connection. -% -%% @todo And it gets more complex when you think about h1 to h2 proxies. -% A server that is attempting to gracefully shut down a -% connection SHOULD send an initial GOAWAY frame with the last stream -% identifier set to 2^31-1 and a NO_ERROR code. This signals to the -% client that a shutdown is imminent and that initiating further -% requests is prohibited. After allowing time for any in-flight stream -% creation (at least one round-trip time), the server can send another -% GOAWAY frame with an updated last stream identifier. This ensures -% that a connection can be cleanly shut down without losing requests. -% -%% @todo And of course even if we shutdown we need to be careful about -%% the connection state. -% After sending a GOAWAY frame, the sender can discard frames for -% streams initiated by the receiver with identifiers higher than the -% identified last stream. However, any frames that alter connection -% state cannot be completely ignored. For instance, HEADERS, -% PUSH_PROMISE, and CONTINUATION frames MUST be minimally processed to -% ensure the state maintained for header compression is consistent (see -% Section 4.3); similarly, DATA frames MUST be counted toward the -% connection flow-control window. Failure to process these frames can -% cause flow control or header compression state to become -% unsynchronized. -% + +graceful_shutdown_client_stays(Config) -> + doc("A server gracefully shutting down must send a GOAWAY frame with the " + "last stream identifier set to 2^31-1 and a NO_ERROR code. After allowing " + "time for any in-flight stream creation the server can send another GOAWAY " + "frame with an updated last stream identifier. (RFC7540 6.8)"), + {ok, Socket} = do_handshake(Config), + ServerConnPid = get_remote_pid_tcp(Socket), + ok = sys:terminate(ServerConnPid, whatever), + %% First GOAWAY frame. + {ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 16#7fffffff:31, 0:32>>} = gen_tcp:recv(Socket, 17, 500), + %% Second GOAWAY frame. + {ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 0:31, 0:32>>} = gen_tcp:recv(Socket, 17, 1500), + {error, closed} = gen_tcp:recv(Socket, 3, 1000), + ok. + +%% @todo We should add this test also for discarded DATA and CONTINUATION frames. +%% The test can be the same for CONTINUATION (just send headers differently) but +%% the DATA test should make sure the global window is not corrupted. +%% +%% @todo We should extend this test to have two requests: one initiated before +%% the second GOAWAY, but not terminated; another initiated after the GOAWAY, terminated. +%% Finally the first request is terminated by sending a body and a trailing +%% HEADERS frame. This way we know for sure that the connection state is not corrupt. +graceful_shutdown_race_condition(Config) -> + doc("A server in the process of gracefully shutting down must discard frames " + "for streams initiated by the receiver with identifiers higher than the " + "identified last stream. This may include frames that alter connection " + "state such as HEADERS frames. (RFC7540 6.8)"), + {ok, Socket} = do_handshake(Config), + ServerConnPid = get_remote_pid_tcp(Socket), + ok = sys:terminate(ServerConnPid, whatever), + %% First GOAWAY frame. + {ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 16#7fffffff:31, 0:32>>} = gen_tcp:recv(Socket, 17, 500), + %% Simulate an in-flight request, sent by the client before the + %% GOAWAY frame arrived to the client. + {HeadersBlock, _} = cow_hpack:encode([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/delay_hello">>} + ]), + ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)), + %% Second GOAWAY frame. + {ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 1:31, 0:32>>} = gen_tcp:recv(Socket, 17, 2000), + %% The client tries to send another request, ignoring the GOAWAY. + ok = gen_tcp:send(Socket, cow_http2:headers(3, fin, HeadersBlock)), + %% The server responds to the first request (streamid 1) and closes. + {ok, <<RespHeadersPayloadLength:24, 1, 4, 0:1, 1:31>>} = gen_tcp:recv(Socket, 9, 1000), + {ok, _RespHeaders} = gen_tcp:recv(Socket, RespHeadersPayloadLength, 1000), + {ok, <<12:24, 0, 1, 0:1, 1:31, "Hello world!">>} = gen_tcp:recv(Socket, 21, 1000), + {error, closed} = gen_tcp:recv(Socket, 3, 1000), + ok. + % The GOAWAY frame applies to the connection, not a specific stream. % An endpoint MUST treat a GOAWAY frame with a stream identifier other % than 0x0 as a connection error (Section 5.4.1) of type |