diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/http2_SUITE.erl | 216 | ||||
-rw-r--r-- | test/http_SUITE.erl | 277 | ||||
-rw-r--r-- | test/misc_SUITE.erl | 42 | ||||
-rw-r--r-- | test/rfc7230_SUITE.erl | 21 | ||||
-rw-r--r-- | test/rfc7540_SUITE.erl | 599 |
5 files changed, 631 insertions, 524 deletions
diff --git a/test/http2_SUITE.erl b/test/http2_SUITE.erl index ffed530..54a5c98 100644 --- a/test/http2_SUITE.erl +++ b/test/http2_SUITE.erl @@ -58,11 +58,14 @@ idle_timeout(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = do_handshake([{port, Port}|Config]), - timer:sleep(1000), - %% Receive a GOAWAY frame back with NO_ERROR. - {ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 1000), - ok. + try + {ok, Socket} = do_handshake([{port, Port}|Config]), + timer:sleep(1000), + %% Receive a GOAWAY frame back with NO_ERROR. + {ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. idle_timeout_infinity(Config) -> doc("Ensure the idle_timeout option accepts the infinity value."), @@ -72,11 +75,14 @@ idle_timeout_infinity(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = do_handshake([{port, Port}|Config]), - timer:sleep(1000), - %% Don't receive a GOAWAY frame. - {error, timeout} = gen_tcp:recv(Socket, 17, 1000), - ok. + try + {ok, Socket} = do_handshake([{port, Port}|Config]), + timer:sleep(1000), + %% Don't receive a GOAWAY frame. + {error, timeout} = gen_tcp:recv(Socket, 17, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. idle_timeout_reset_on_data(Config) -> doc("Terminate when the idle timeout is reached."), @@ -86,23 +92,26 @@ idle_timeout_reset_on_data(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = do_handshake([{port, Port}|Config]), - %% We wait a little, send a PING, receive a PING ack. - {error, timeout} = gen_tcp:recv(Socket, 17, 500), - ok = gen_tcp:send(Socket, cow_http2:ping(0)), - {ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000), - %% Again. - {error, timeout} = gen_tcp:recv(Socket, 17, 500), - ok = gen_tcp:send(Socket, cow_http2:ping(0)), - {ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000), - %% And one more time. - {error, timeout} = gen_tcp:recv(Socket, 17, 500), - ok = gen_tcp:send(Socket, cow_http2:ping(0)), - {ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000), - %% The connection goes away soon after we stop sending data. - timer:sleep(1000), - {ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 1000), - ok. + try + {ok, Socket} = do_handshake([{port, Port}|Config]), + %% We wait a little, send a PING, receive a PING ack. + {error, timeout} = gen_tcp:recv(Socket, 17, 500), + ok = gen_tcp:send(Socket, cow_http2:ping(0)), + {ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000), + %% Again. + {error, timeout} = gen_tcp:recv(Socket, 17, 500), + ok = gen_tcp:send(Socket, cow_http2:ping(0)), + {ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000), + %% And one more time. + {error, timeout} = gen_tcp:recv(Socket, 17, 500), + ok = gen_tcp:send(Socket, cow_http2:ping(0)), + {ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000), + %% The connection goes away soon after we stop sending data. + timer:sleep(1000), + {ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. inactivity_timeout(Config) -> doc("Terminate when the inactivity timeout is reached."), @@ -112,11 +121,14 @@ inactivity_timeout(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = do_handshake([{port, Port}|Config]), - receive after 1000 -> ok end, - %% Receive a GOAWAY frame back with an INTERNAL_ERROR. - {ok, << _:24, 7:8, _:72, 2:32 >>} = gen_tcp:recv(Socket, 17, 1000), - ok. + try + {ok, Socket} = do_handshake([{port, Port}|Config]), + receive after 1000 -> ok end, + %% Receive a GOAWAY frame back with an INTERNAL_ERROR. + {ok, << _:24, 7:8, _:72, 2:32 >>} = gen_tcp:recv(Socket, 17, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. initial_connection_window_size(Config) -> doc("Confirm a WINDOW_UPDATE frame is sent when the configured " @@ -128,16 +140,19 @@ initial_connection_window_size(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}]), - %% Send a valid preface. - ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), - %% Receive the server preface. - {ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000), - {ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000), - %% Receive a WINDOW_UPDATE frame incrementing the connection window to 100000. - {ok, <<4:24, 8:8, 0:41, Size:31>>} = gen_tcp:recv(Socket, 13, 1000), - ConfiguredSize = Size + 65535, - ok. + try + {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}]), + %% Send a valid preface. + ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), + %% Receive the server preface. + {ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000), + {ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000), + %% Receive a WINDOW_UPDATE frame incrementing the connection window to 100000. + {ok, <<4:24, 8:8, 0:41, Size:31>>} = gen_tcp:recv(Socket, 13, 1000), + ConfiguredSize = Size + 65535 + after + cowboy:stop_listener(?FUNCTION_NAME) + end. max_frame_size_sent(Config) -> doc("Confirm that frames sent by Cowboy are limited in size " @@ -149,35 +164,38 @@ max_frame_size_sent(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = do_handshake(#{max_frame_size => MaxFrameSize + 10000}, [{port, Port}|Config]), - %% Send a request with a 30000 bytes body. - {HeadersBlock, _} = cow_hpack:encode([ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/echo/read_body">>} - ]), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, nofin, <<0:16384/unit:8>>), - cow_http2:data(1, fin, <<0:13616/unit:8>>) - ]), - %% Receive a HEADERS frame as a response. - {ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = case gen_tcp:recv(Socket, 9, 1000) of - %% We received a WINDOW_UPDATE first. Skip it and the next. - {ok, <<4:24, 8:8, 0:40>>} -> - {ok, _} = gen_tcp:recv(Socket, 4 + 13, 1000), - gen_tcp:recv(Socket, 9, 1000); - Res -> - Res - end, - {ok, _} = gen_tcp:recv(Socket, SkipLen, 6000), - %% The DATA frames following must have lengths of 20000 - %% and then 10000 due to the limit. - {ok, <<20000:24, 0:8, _:40, _:20000/unit:8>>} = gen_tcp:recv(Socket, 20009, 6000), - {ok, <<10000:24, 0:8, _:40, _:10000/unit:8>>} = gen_tcp:recv(Socket, 10009, 6000), - %% Stop the listener. - cowboy:stop_listener(?FUNCTION_NAME). + try + {ok, Socket} = do_handshake(#{max_frame_size => MaxFrameSize + 10000}, + [{port, Port}|Config]), + %% Send a request with a 30000 bytes body. + {HeadersBlock, _} = cow_hpack:encode([ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/echo/read_body">>} + ]), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, nofin, <<0:16384/unit:8>>), + cow_http2:data(1, fin, <<0:13616/unit:8>>) + ]), + %% Receive a HEADERS frame as a response. + {ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = case gen_tcp:recv(Socket, 9, 1000) of + %% We received a WINDOW_UPDATE first. Skip it and the next. + {ok, <<4:24, 8:8, 0:40>>} -> + {ok, _} = gen_tcp:recv(Socket, 4 + 13, 1000), + gen_tcp:recv(Socket, 9, 1000); + Res -> + Res + end, + {ok, _} = gen_tcp:recv(Socket, SkipLen, 6000), + %% The DATA frames following must have lengths of 20000 + %% and then 10000 due to the limit. + {ok, <<20000:24, 0:8, _:40, _:20000/unit:8>>} = gen_tcp:recv(Socket, 20009, 6000), + {ok, <<10000:24, 0:8, _:40, _:10000/unit:8>>} = gen_tcp:recv(Socket, 10009, 6000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. preface_timeout_infinity(Config) -> doc("Ensure infinity for preface_timeout is accepted."), @@ -187,13 +205,17 @@ preface_timeout_infinity(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = do_handshake([{port, Port}|Config]), - Pid = get_remote_pid_tcp(Socket), - Ref = erlang:monitor(process, Pid), - receive - {'DOWN', Ref, process, Pid, Reason} -> - error(Reason) - after 1000 -> + try + {ok, Socket} = do_handshake([{port, Port}|Config]), + Pid = get_remote_pid_tcp(Socket), + Ref = erlang:monitor(process, Pid), + receive + {'DOWN', Ref, process, Pid, Reason} -> + error(Reason) + after 1000 -> + ok + end + after cowboy:stop_listener(?FUNCTION_NAME) end. @@ -206,14 +228,18 @@ resp_iolist_body(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - ConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]), - Ref = gun:get(ConnPid, "/resp_iolist_body"), - {response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref), - {_, BinLen} = lists:keyfind(<<"content-length">>, 1, RespHeaders), - Len = binary_to_integer(BinLen), - {ok, RespBody} = gun:await_body(ConnPid, Ref), - Len = iolist_size(RespBody), - gun:close(ConnPid). + try + ConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]), + Ref = gun:get(ConnPid, "/resp_iolist_body"), + {response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref), + {_, BinLen} = lists:keyfind(<<"content-length">>, 1, RespHeaders), + Len = binary_to_integer(BinLen), + {ok, RespBody} = gun:await_body(ConnPid, Ref), + Len = iolist_size(RespBody), + gun:close(ConnPid) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. settings_timeout_infinity(Config) -> doc("Ensure infinity for settings_timeout is accepted."), @@ -223,12 +249,16 @@ settings_timeout_infinity(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - {ok, Socket} = do_handshake([{port, Port}|Config]), - Pid = get_remote_pid_tcp(Socket), - Ref = erlang:monitor(process, Pid), - receive - {'DOWN', Ref, process, Pid, Reason} -> - error(Reason) - after 1000 -> + try + {ok, Socket} = do_handshake([{port, Port}|Config]), + Pid = get_remote_pid_tcp(Socket), + Ref = erlang:monitor(process, Pid), + receive + {'DOWN', Ref, process, Pid, Reason} -> + error(Reason) + after 1000 -> + ok + end + after cowboy:stop_listener(?FUNCTION_NAME) end. diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index c16215e..4828747 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -47,23 +47,26 @@ chunked_false(Config) -> chunked => false }), Port = ranch:get_port(?FUNCTION_NAME), - Request = "GET /resp/stream_reply2/200 HTTP/1.1\r\nhost: localhost\r\n\r\n", - Client = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]), - ok = raw_send(Client, Request), - Rest = case catch raw_recv_head(Client) of - {'EXIT', _} -> error(closed); - Data -> - %% Cowboy always advertises itself as HTTP/1.1. - {'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data), - {Headers, Rest1} = cow_http:parse_headers(Rest0), - false = lists:keyfind(<<"content-length">>, 1, Headers), - false = lists:keyfind(<<"transfer-encoding">>, 1, Headers), - Rest1 - end, - Bits = 8000000 - bit_size(Rest), - raw_expect_recv(Client, <<0:Bits>>), - {error, closed} = raw_recv(Client, 1, 1000), - ok. + try + Request = "GET /resp/stream_reply2/200 HTTP/1.1\r\nhost: localhost\r\n\r\n", + Client = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]), + ok = raw_send(Client, Request), + Rest = case catch raw_recv_head(Client) of + {'EXIT', _} -> error(closed); + Data -> + %% Cowboy always advertises itself as HTTP/1.1. + {'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data), + {Headers, Rest1} = cow_http:parse_headers(Rest0), + false = lists:keyfind(<<"content-length">>, 1, Headers), + false = lists:keyfind(<<"transfer-encoding">>, 1, Headers), + Rest1 + end, + Bits = 8000000 - bit_size(Rest), + raw_expect_recv(Client, <<0:Bits>>), + {error, closed} = raw_recv(Client, 1, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. http10_keepalive_false(Config) -> doc("Confirm the option http10_keepalive => false disables keep-alive " @@ -73,21 +76,25 @@ http10_keepalive_false(Config) -> http10_keepalive => false }), Port = ranch:get_port(?FUNCTION_NAME), - Keepalive = "GET / HTTP/1.0\r\nhost: localhost\r\nConnection: keep-alive\r\n\r\n", - Client = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]), - ok = raw_send(Client, Keepalive), - _ = case catch raw_recv_head(Client) of - {'EXIT', _} -> error(closed); - Data -> - %% Cowboy always advertises itself as HTTP/1.1. - {'HTTP/1.1', 200, _, Rest} = cow_http:parse_status_line(Data), - {Headers, _} = cow_http:parse_headers(Rest), - {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers) - end, - ok = raw_send(Client, Keepalive), - case catch raw_recv_head(Client) of - {'EXIT', _} -> closed; - _ -> error(not_closed) + try + Keepalive = "GET / HTTP/1.0\r\nhost: localhost\r\nConnection: keep-alive\r\n\r\n", + Client = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]), + ok = raw_send(Client, Keepalive), + _ = case catch raw_recv_head(Client) of + {'EXIT', _} -> error(closed); + Data -> + %% Cowboy always advertises itself as HTTP/1.1. + {'HTTP/1.1', 200, _, Rest} = cow_http:parse_status_line(Data), + {Headers, _} = cow_http:parse_headers(Rest), + {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers) + end, + ok = raw_send(Client, Keepalive), + case catch raw_recv_head(Client) of + {'EXIT', _} -> closed; + _ -> error(not_closed) + end + after + cowboy:stop_listener(?FUNCTION_NAME) end. idle_timeout_infinity(Config) -> @@ -98,17 +105,21 @@ idle_timeout_infinity(Config) -> idle_timeout => infinity }), Port = ranch:get_port(?FUNCTION_NAME), - ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), - _ = gun:post(ConnPid, "/echo/read_body", - [{<<"content-type">>, <<"text/plain">>}]), - #{socket := Socket} = gun:info(ConnPid), - Pid = get_remote_pid_tcp(Socket), - Ref = erlang:monitor(process, Pid), - receive - {'DOWN', Ref, process, Pid, Reason} -> - error(Reason) - after 1000 -> - ok + try + ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), + _ = gun:post(ConnPid, "/echo/read_body", + [{<<"content-type">>, <<"text/plain">>}]), + #{socket := Socket} = gun:info(ConnPid), + Pid = get_remote_pid_tcp(Socket), + Ref = erlang:monitor(process, Pid), + receive + {'DOWN', Ref, process, Pid, Reason} -> + error(Reason) + after 1000 -> + ok + end + after + cowboy:stop_listener(?FUNCTION_NAME) end. request_timeout_infinity(Config) -> @@ -118,15 +129,19 @@ request_timeout_infinity(Config) -> idle_timeout => infinity }), Port = ranch:get_port(?FUNCTION_NAME), - ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), - #{socket := Socket} = gun:info(ConnPid), - Pid = get_remote_pid_tcp(Socket), - Ref = erlang:monitor(process, Pid), - receive - {'DOWN', Ref, process, Pid, Reason} -> - error(Reason) - after 1000 -> - ok + try + ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), + #{socket := Socket} = gun:info(ConnPid), + Pid = get_remote_pid_tcp(Socket), + Ref = erlang:monitor(process, Pid), + receive + {'DOWN', Ref, process, Pid, Reason} -> + error(Reason) + after 1000 -> + ok + end + after + cowboy:stop_listener(?FUNCTION_NAME) end. set_options_chunked_false(Config) -> @@ -138,21 +153,24 @@ set_options_chunked_false(Config) -> chunked => true }), Port = ranch:get_port(?FUNCTION_NAME), - Request = "GET /set_options/chunked_false HTTP/1.1\r\nhost: localhost\r\n\r\n", - Client = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]), - ok = raw_send(Client, Request), - _ = case catch raw_recv_head(Client) of - {'EXIT', _} -> error(closed); - Data -> - %% Cowboy always advertises itself as HTTP/1.1. - {'HTTP/1.1', 200, _, Rest} = cow_http:parse_status_line(Data), - {Headers, <<>>} = cow_http:parse_headers(Rest), - false = lists:keyfind(<<"content-length">>, 1, Headers), - false = lists:keyfind(<<"transfer-encoding">>, 1, Headers) - end, - raw_expect_recv(Client, <<0:8000000>>), - {error, closed} = raw_recv(Client, 1, 1000), - ok. + try + Request = "GET /set_options/chunked_false HTTP/1.1\r\nhost: localhost\r\n\r\n", + Client = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]), + ok = raw_send(Client, Request), + _ = case catch raw_recv_head(Client) of + {'EXIT', _} -> error(closed); + Data -> + %% Cowboy always advertises itself as HTTP/1.1. + {'HTTP/1.1', 200, _, Rest} = cow_http:parse_status_line(Data), + {Headers, <<>>} = cow_http:parse_headers(Rest), + false = lists:keyfind(<<"content-length">>, 1, Headers), + false = lists:keyfind(<<"transfer-encoding">>, 1, Headers) + end, + raw_expect_recv(Client, <<0:8000000>>), + {error, closed} = raw_recv(Client, 1, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. set_options_chunked_false_ignored(Config) -> doc("Confirm the option chunked can be dynamically set to disable " @@ -163,18 +181,21 @@ set_options_chunked_false_ignored(Config) -> chunked => true }), Port = ranch:get_port(?FUNCTION_NAME), - ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), - %% We do a first request setting the option but not - %% using chunked transfer-encoding in the response. - StreamRef1 = gun:get(ConnPid, "/set_options/chunked_false_ignored"), - {response, nofin, 200, _} = gun:await(ConnPid, StreamRef1), - {ok, <<"Hello world!">>} = gun:await_body(ConnPid, StreamRef1), - %% We then do a second request to confirm that chunked - %% is not disabled for that second request. - StreamRef2 = gun:get(ConnPid, "/resp/stream_reply2/200"), - {response, nofin, 200, Headers} = gun:await(ConnPid, StreamRef2), - {_, <<"chunked">>} = lists:keyfind(<<"transfer-encoding">>, 1, Headers), - ok. + try + ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), + %% We do a first request setting the option but not + %% using chunked transfer-encoding in the response. + StreamRef1 = gun:get(ConnPid, "/set_options/chunked_false_ignored"), + {response, nofin, 200, _} = gun:await(ConnPid, StreamRef1), + {ok, <<"Hello world!">>} = gun:await_body(ConnPid, StreamRef1), + %% We then do a second request to confirm that chunked + %% is not disabled for that second request. + StreamRef2 = gun:get(ConnPid, "/resp/stream_reply2/200"), + {response, nofin, 200, Headers} = gun:await(ConnPid, StreamRef2), + {_, <<"chunked">>} = lists:keyfind(<<"transfer-encoding">>, 1, Headers) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. set_options_idle_timeout(Config) -> doc("Confirm that the idle_timeout option can be dynamically " @@ -185,17 +206,21 @@ set_options_idle_timeout(Config) -> idle_timeout => 60000 }), Port = ranch:get_port(?FUNCTION_NAME), - ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), - _ = gun:post(ConnPid, "/set_options/idle_timeout_short", - [{<<"content-type">>, <<"text/plain">>}]), - #{socket := Socket} = gun:info(ConnPid), - Pid = get_remote_pid_tcp(Socket), - Ref = erlang:monitor(process, Pid), - receive - {'DOWN', Ref, process, Pid, _} -> - ok - after 2000 -> - error(timeout) + try + ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), + _ = gun:post(ConnPid, "/set_options/idle_timeout_short", + [{<<"content-type">>, <<"text/plain">>}]), + #{socket := Socket} = gun:info(ConnPid), + Pid = get_remote_pid_tcp(Socket), + Ref = erlang:monitor(process, Pid), + receive + {'DOWN', Ref, process, Pid, _} -> + ok + after 2000 -> + error(timeout) + end + after + cowboy:stop_listener(?FUNCTION_NAME) end. set_options_idle_timeout_only_applies_to_current_request(Config) -> @@ -206,30 +231,34 @@ set_options_idle_timeout_only_applies_to_current_request(Config) -> idle_timeout => 500 }), Port = ranch:get_port(?FUNCTION_NAME), - ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), - StreamRef = gun:post(ConnPid, "/set_options/idle_timeout_long", - [{<<"content-type">>, <<"text/plain">>}]), - #{socket := Socket} = gun:info(ConnPid), - Pid = get_remote_pid_tcp(Socket), - Ref = erlang:monitor(process, Pid), - receive - {'DOWN', Ref, process, Pid, Reason} -> - error(Reason) - after 2000 -> - ok - end, - %% Finish the first request and start a second one to confirm - %% the idle_timeout option is back to normal. - gun:data(ConnPid, StreamRef, fin, <<"Hello!">>), - {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), - {ok, <<"Hello!">>} = gun:await_body(ConnPid, StreamRef), - _ = gun:post(ConnPid, "/echo/read_body", - [{<<"content-type">>, <<"text/plain">>}]), - receive - {'DOWN', Ref, process, Pid, _} -> + try + ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), + StreamRef = gun:post(ConnPid, "/set_options/idle_timeout_long", + [{<<"content-type">>, <<"text/plain">>}]), + #{socket := Socket} = gun:info(ConnPid), + Pid = get_remote_pid_tcp(Socket), + Ref = erlang:monitor(process, Pid), + receive + {'DOWN', Ref, process, Pid, Reason} -> + error(Reason) + after 2000 -> ok - after 2000 -> - error(timeout) + end, + %% Finish the first request and start a second one to confirm + %% the idle_timeout option is back to normal. + gun:data(ConnPid, StreamRef, fin, <<"Hello!">>), + {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), + {ok, <<"Hello!">>} = gun:await_body(ConnPid, StreamRef), + _ = gun:post(ConnPid, "/echo/read_body", + [{<<"content-type">>, <<"text/plain">>}]), + receive + {'DOWN', Ref, process, Pid, _} -> + ok + after 2000 -> + error(timeout) + end + after + cowboy:stop_listener(?FUNCTION_NAME) end. switch_protocol_flush(Config) -> @@ -240,12 +269,18 @@ switch_protocol_flush(Config) -> }, {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts), Port = ranch:get_port(?FUNCTION_NAME), - Self = self(), - ConnPid = gun_open([{port, Port}, {type, tcp}, {protocol, http}|Config]), - _ = gun:get(ConnPid, "/", [ - {<<"x-test-pid">>, pid_to_list(Self)} - ]), - receive - {Self, Events} -> - switch_protocol_flush_h:validate(Events) + try + Self = self(), + ConnPid = gun_open([{port, Port}, {type, tcp}, {protocol, http}|Config]), + _ = gun:get(ConnPid, "/", [ + {<<"x-test-pid">>, pid_to_list(Self)} + ]), + receive + {Self, Events} -> + switch_protocol_flush_h:validate(Events) + after 5000 -> + error(timeout) + end + after + cowboy:stop_listener(?FUNCTION_NAME) end. diff --git a/test/misc_SUITE.erl b/test/misc_SUITE.erl index 4ad2325..45fb04e 100644 --- a/test/misc_SUITE.erl +++ b/test/misc_SUITE.erl @@ -82,25 +82,31 @@ set_env(Config0) -> Config = cowboy_test:init_http(?FUNCTION_NAME, #{ env => #{dispatch => []} }, Config0), - ConnPid1 = gun_open(Config), - Ref1 = gun:get(ConnPid1, "/"), - {response, _, 400, _} = gun:await(ConnPid1, Ref1), - cowboy:set_env(?FUNCTION_NAME, dispatch, init_dispatch(Config)), - %% Only new connections get the updated environment. - ConnPid2 = gun_open(Config), - Ref2 = gun:get(ConnPid2, "/"), - {response, _, 200, _} = gun:await(ConnPid2, Ref2), - ok. + try + ConnPid1 = gun_open(Config), + Ref1 = gun:get(ConnPid1, "/"), + {response, _, 400, _} = gun:await(ConnPid1, Ref1), + cowboy:set_env(?FUNCTION_NAME, dispatch, init_dispatch(Config)), + %% Only new connections get the updated environment. + ConnPid2 = gun_open(Config), + Ref2 = gun:get(ConnPid2, "/"), + {response, _, 200, _} = gun:await(ConnPid2, Ref2) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. set_env_missing(Config0) -> doc("Live replace a middleware environment value when env was not provided."), Config = cowboy_test:init_http(?FUNCTION_NAME, #{}, Config0), - ConnPid1 = gun_open(Config), - Ref1 = gun:get(ConnPid1, "/"), - {response, _, 500, _} = gun:await(ConnPid1, Ref1), - cowboy:set_env(?FUNCTION_NAME, dispatch, []), - %% Only new connections get the updated environment. - ConnPid2 = gun_open(Config), - Ref2 = gun:get(ConnPid2, "/"), - {response, _, 400, _} = gun:await(ConnPid2, Ref2), - ok. + try + ConnPid1 = gun_open(Config), + Ref1 = gun:get(ConnPid1, "/"), + {response, _, 500, _} = gun:await(ConnPid1, Ref1), + cowboy:set_env(?FUNCTION_NAME, dispatch, []), + %% Only new connections get the updated environment. + ConnPid2 = gun_open(Config), + Ref2 = gun:get(ConnPid2, "/"), + {response, _, 400, _} = gun:await(ConnPid2, Ref2) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. diff --git a/test/rfc7230_SUITE.erl b/test/rfc7230_SUITE.erl index d42a454..17ff452 100644 --- a/test/rfc7230_SUITE.erl +++ b/test/rfc7230_SUITE.erl @@ -1593,15 +1593,18 @@ empty_host(Config0) -> Config = cowboy_test:init_http(?FUNCTION_NAME, #{ env => #{dispatch => cowboy_router:compile(Routes)} }, Config0), - #{code := 200, body := <<>>} = do_raw(Config, [ - "GET /echo/host HTTP/1.1\r\n" - "Host:\r\n" - "\r\n"]), - #{code := 200, body := <<>>} = do_raw(Config, [ - "GET /echo/host HTTP/1.1\r\n" - "Host: \r\n" - "\r\n"]), - cowboy:stop_listener(?FUNCTION_NAME). + try + #{code := 200, body := <<>>} = do_raw(Config, [ + "GET /echo/host HTTP/1.1\r\n" + "Host:\r\n" + "\r\n"]), + #{code := 200, body := <<>>} = do_raw(Config, [ + "GET /echo/host HTTP/1.1\r\n" + "Host: \r\n" + "\r\n"]) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. %% The effective request URI can be rebuilt by concatenating scheme, %% "://", authority, path and query components. (RFC7230 5.5) diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index 2f20a59..be30b71 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -1327,27 +1327,30 @@ max_frame_size_allow_exactly_custom(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, max_frame_size_received => 30000 }, Config0), - %% Do the handshake. - {ok, Socket} = do_handshake(Config), - %% Send a HEADERS frame initiating a stream followed by - %% a single 30000 bytes DATA frame. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, fin, <<0:30000/unit:8>>) - ]), - %% Receive a proper response. - {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), - {ok, _} = gen_tcp:recv(Socket, Len2, 6000), - %% No errors follow due to our sending of a 25000 bytes frame. - {error, timeout} = gen_tcp:recv(Socket, 0, 1000), - cowboy:stop_listener(?FUNCTION_NAME). + try + %% Do the handshake. + {ok, Socket} = do_handshake(Config), + %% Send a HEADERS frame initiating a stream followed by + %% a single 30000 bytes DATA frame. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, fin, <<0:30000/unit:8>>) + ]), + %% Receive a proper response. + {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), + {ok, _} = gen_tcp:recv(Socket, Len2, 6000), + %% No errors follow due to our sending of a 25000 bytes frame. + {error, timeout} = gen_tcp:recv(Socket, 0, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. max_frame_size_reject_larger_than_custom(Config0) -> doc("An endpoint that sets SETTINGS_MAX_FRAME_SIZE must reject frames " @@ -1357,24 +1360,27 @@ max_frame_size_reject_larger_than_custom(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, max_frame_size_received => 30000 }, Config0), - %% Do the handshake. - {ok, Socket} = do_handshake(Config), - %% Send a HEADERS frame initiating a stream followed by - %% a single DATA frame larger than 30000 bytes. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, fin, <<0:30001/unit:8>>) - ]), - %% Receive a FRAME_SIZE_ERROR connection error. - {ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000), - cowboy:stop_listener(?FUNCTION_NAME). + try + %% Do the handshake. + {ok, Socket} = do_handshake(Config), + %% Send a HEADERS frame initiating a stream followed by + %% a single DATA frame larger than 30000 bytes. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, fin, <<0:30001/unit:8>>) + ]), + %% Receive a FRAME_SIZE_ERROR connection error. + {ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. %% I am using FRAME_SIZE_ERROR here because the information in the %% frame header tells us this frame is at least 1 byte long, while @@ -2555,40 +2561,43 @@ settings_header_table_size_server(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, max_decode_table_size => HeaderTableSize }, Config0), - %% Do the handhsake. - {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), - %% Send a valid preface. - ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", - cow_http2:settings(#{header_table_size => HeaderTableSize})]), - %% Receive the server preface. - {ok, << Len0:24 >>} = gen_tcp:recv(Socket, 3, 1000), - {ok, Data = <<_:48, _:Len0/binary>>} = gen_tcp:recv(Socket, 6 + Len0, 1000), - %% Confirm the server's SETTINGS_HEADERS_TABLE_SIZE uses HeaderTableSize. - {ok, {settings, #{header_table_size := HeaderTableSize}}, <<>>} - = cow_http2:parse(<<Len0:24, Data/binary>>), - %% Send the SETTINGS ack. - ok = gen_tcp:send(Socket, cow_http2:settings_ack()), - %% Receive the SETTINGS ack. - {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), - %% Initialize decoding/encoding states. - DecodeState = cow_hpack:init(), - EncodeState = cow_hpack:set_max_size(HeaderTableSize, cow_hpack:init()), - %% Send a HEADERS frame as a request. - {ReqHeadersBlock1, _} = cow_hpack:encode([ - {<<":method">>, <<"GET">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/">>} - ], EncodeState), - ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, ReqHeadersBlock1)), - %% Receive a HEADERS frame as a response. - {ok, << Len1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), - {ok, RespHeadersBlock1} = gen_tcp:recv(Socket, Len1, 6000), - {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock1, DecodeState), - {_, <<"200">>} = lists:keyfind(<<":status">>, 1, RespHeaders), - %% The decoding succeeded on the server, confirming that - %% the table size was updated to HeaderTableSize. - cowboy:stop_listener(?FUNCTION_NAME). + try + %% Do the handhsake. + {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), + %% Send a valid preface. + ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", + cow_http2:settings(#{header_table_size => HeaderTableSize})]), + %% Receive the server preface. + {ok, << Len0:24 >>} = gen_tcp:recv(Socket, 3, 1000), + {ok, Data = <<_:48, _:Len0/binary>>} = gen_tcp:recv(Socket, 6 + Len0, 1000), + %% Confirm the server's SETTINGS_HEADERS_TABLE_SIZE uses HeaderTableSize. + {ok, {settings, #{header_table_size := HeaderTableSize}}, <<>>} + = cow_http2:parse(<<Len0:24, Data/binary>>), + %% Send the SETTINGS ack. + ok = gen_tcp:send(Socket, cow_http2:settings_ack()), + %% Receive the SETTINGS ack. + {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), + %% Initialize decoding/encoding states. + DecodeState = cow_hpack:init(), + EncodeState = cow_hpack:set_max_size(HeaderTableSize, cow_hpack:init()), + %% Send a HEADERS frame as a request. + {ReqHeadersBlock1, _} = cow_hpack:encode([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/">>} + ], EncodeState), + ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, ReqHeadersBlock1)), + %% Receive a HEADERS frame as a response. + {ok, << Len1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), + {ok, RespHeadersBlock1} = gen_tcp:recv(Socket, Len1, 6000), + {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock1, DecodeState), + {_, <<"200">>} = lists:keyfind(<<":status">>, 1, RespHeaders) + %% The decoding succeeded on the server, confirming that + %% the table size was updated to HeaderTableSize. + after + cowboy:stop_listener(?FUNCTION_NAME) + end. settings_max_concurrent_streams(Config0) -> doc("The SETTINGS_MAX_CONCURRENT_STREAMS setting can be used to " @@ -2598,23 +2607,26 @@ settings_max_concurrent_streams(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, max_concurrent_streams => 1 }, Config0), - {ok, Socket} = do_handshake(Config), - %% Send two HEADERS frames as two separate streams. - Headers = [ - {<<":method">>, <<"GET">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {ReqHeadersBlock1, EncodeState} = cow_hpack:encode(Headers), - {ReqHeadersBlock2, _} = cow_hpack:encode(Headers, EncodeState), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, fin, ReqHeadersBlock1), - cow_http2:headers(3, fin, ReqHeadersBlock2) - ]), - %% Receive a REFUSED_STREAM stream error. - {ok, << _:24, 3:8, _:8, 3:32, 7:32 >>} = gen_tcp:recv(Socket, 13, 6000), - cowboy:stop_listener(?FUNCTION_NAME). + try + {ok, Socket} = do_handshake(Config), + %% Send two HEADERS frames as two separate streams. + Headers = [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {ReqHeadersBlock1, EncodeState} = cow_hpack:encode(Headers), + {ReqHeadersBlock2, _} = cow_hpack:encode(Headers, EncodeState), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, fin, ReqHeadersBlock1), + cow_http2:headers(3, fin, ReqHeadersBlock2) + ]), + %% Receive a REFUSED_STREAM stream error. + {ok, << _:24, 3:8, _:8, 3:32, 7:32 >>} = gen_tcp:recv(Socket, 13, 6000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. settings_max_concurrent_streams_0(Config0) -> doc("The SETTINGS_MAX_CONCURRENT_STREAMS setting can be set to " @@ -2624,18 +2636,21 @@ settings_max_concurrent_streams_0(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, max_concurrent_streams => 0 }, Config0), - {ok, Socket} = do_handshake(Config), - %% Send a HEADERS frame. - {HeadersBlock, _} = cow_hpack:encode([ - {<<":method">>, <<"GET">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ]), - ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)), - %% Receive a REFUSED_STREAM stream error. - {ok, << _:24, 3:8, _:8, 1:32, 7:32 >>} = gen_tcp:recv(Socket, 13, 6000), - cowboy:stop_listener(?FUNCTION_NAME). + try + {ok, Socket} = do_handshake(Config), + %% Send a HEADERS frame. + {HeadersBlock, _} = cow_hpack:encode([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ]), + ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)), + %% Receive a REFUSED_STREAM stream error. + {ok, << _:24, 3:8, _:8, 1:32, 7:32 >>} = gen_tcp:recv(Socket, 13, 6000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. %% @todo The client can limit the number of concurrent streams too. (RFC7540 5.1.2) % @@ -2663,44 +2678,47 @@ settings_initial_window_size(Config0) -> initial_connection_window_size => 100000, initial_stream_window_size => 100000 }, Config0), - %% We need to do the handshake manually because a WINDOW_UPDATE - %% frame will be sent to update the connection window. - {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), - %% Send a valid preface. - ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), - %% Receive the server preface. - {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), - {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), - %% Send the SETTINGS ack. - ok = gen_tcp:send(Socket, cow_http2:settings_ack()), - %% Receive the WINDOW_UPDATE for the connection. - {ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000), - %% Receive the SETTINGS ack. - {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), - %% Send a HEADERS frame initiating a stream followed by - %% DATA frames totaling 90000 bytes of body. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, fin, <<0:15000/unit:8>>) - ]), - %% Receive a proper response. - {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), - {ok, _} = gen_tcp:recv(Socket, Len2, 6000), - %% No errors follow due to our sending of more than 65535 bytes of data. - {error, timeout} = gen_tcp:recv(Socket, 0, 1000), - cowboy:stop_listener(?FUNCTION_NAME). + try + %% We need to do the handshake manually because a WINDOW_UPDATE + %% frame will be sent to update the connection window. + {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), + %% Send a valid preface. + ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), + %% Receive the server preface. + {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), + {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), + %% Send the SETTINGS ack. + ok = gen_tcp:send(Socket, cow_http2:settings_ack()), + %% Receive the WINDOW_UPDATE for the connection. + {ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000), + %% Receive the SETTINGS ack. + {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), + %% Send a HEADERS frame initiating a stream followed by + %% DATA frames totaling 90000 bytes of body. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, fin, <<0:15000/unit:8>>) + ]), + %% Receive a proper response. + {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), + {ok, _} = gen_tcp:recv(Socket, Len2, 6000), + %% No errors follow due to our sending of more than 65535 bytes of data. + {error, timeout} = gen_tcp:recv(Socket, 0, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. settings_initial_window_size_after_ack(Config0) -> doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to " @@ -2711,36 +2729,39 @@ settings_initial_window_size_after_ack(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, initial_stream_window_size => 0 }, Config0), - %% We need to do the handshake manually because we don't - %% want to send the SETTINGS ack immediately. - {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), - %% Send a valid preface. - ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), - %% Receive the server preface. - {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), - {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), - %% - %% Don't send the SETTINGS ack yet! We want to create a stream first. - %% - %% Receive the SETTINGS ack. - {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), - %% Send a HEADERS frame initiating a stream, a SETTINGS ack - %% and a small DATA frame despite no window available in the stream. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:settings_ack(), - cow_http2:data(1, fin, <<0:32/unit:8>>) - ]), - %% Receive a FLOW_CONTROL_ERROR stream error. - {ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000), - cowboy:stop_listener(?FUNCTION_NAME). + try + %% We need to do the handshake manually because we don't + %% want to send the SETTINGS ack immediately. + {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), + %% Send a valid preface. + ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), + %% Receive the server preface. + {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), + {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), + %% + %% Don't send the SETTINGS ack yet! We want to create a stream first. + %% + %% Receive the SETTINGS ack. + {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), + %% Send a HEADERS frame initiating a stream, a SETTINGS ack + %% and a small DATA frame despite no window available in the stream. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:settings_ack(), + cow_http2:data(1, fin, <<0:32/unit:8>>) + ]), + %% Receive a FLOW_CONTROL_ERROR stream error. + {ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. settings_initial_window_size_before_ack(Config0) -> doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to " @@ -2751,41 +2772,44 @@ settings_initial_window_size_before_ack(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, initial_stream_window_size => 0 }, Config0), - %% We need to do the handshake manually because we don't - %% want to send the SETTINGS ack. - {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), - %% Send a valid preface. - ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), - %% Receive the server preface. - {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), - {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), - %% - %% Don't send the SETTINGS ack! We want the server to keep the original settings. - %% - %% Receive the SETTINGS ack. - {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), - %% Send a HEADERS frame initiating a stream followed by - %% DATA frames totaling 60000 bytes of body. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, fin, <<0:15000/unit:8>>) - ]), - %% Receive a proper response. - {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), - {ok, _} = gen_tcp:recv(Socket, Len2, 6000), - %% No errors follow due to our sending of more than 0 bytes of data. - {error, timeout} = gen_tcp:recv(Socket, 0, 1000), - cowboy:stop_listener(?FUNCTION_NAME). + try + %% We need to do the handshake manually because we don't + %% want to send the SETTINGS ack. + {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), + %% Send a valid preface. + ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), + %% Receive the server preface. + {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), + {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), + %% + %% Don't send the SETTINGS ack! We want the server to keep the original settings. + %% + %% Receive the SETTINGS ack. + {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), + %% Send a HEADERS frame initiating a stream followed by + %% DATA frames totaling 60000 bytes of body. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, fin, <<0:15000/unit:8>>) + ]), + %% Receive a proper response. + {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), + {ok, _} = gen_tcp:recv(Socket, Len2, 6000), + %% No errors follow due to our sending of more than 0 bytes of data. + {error, timeout} = gen_tcp:recv(Socket, 0, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. settings_max_frame_size(Config0) -> doc("The SETTINGS_MAX_FRAME_SIZE setting can be used to " @@ -2795,27 +2819,30 @@ settings_max_frame_size(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, max_frame_size_received => 30000 }, Config0), - %% Do the handshake. - {ok, Socket} = do_handshake(Config), - %% Send a HEADERS frame initiating a stream followed by - %% a single 25000 bytes DATA frame. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, fin, <<0:25000/unit:8>>) - ]), - %% Receive a proper response. - {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), - {ok, _} = gen_tcp:recv(Socket, Len2, 6000), - %% No errors follow due to our sending of a 25000 bytes frame. - {error, timeout} = gen_tcp:recv(Socket, 0, 1000), - cowboy:stop_listener(?FUNCTION_NAME). + try + %% Do the handshake. + {ok, Socket} = do_handshake(Config), + %% Send a HEADERS frame initiating a stream followed by + %% a single 25000 bytes DATA frame. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, fin, <<0:25000/unit:8>>) + ]), + %% Receive a proper response. + {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000), + {ok, _} = gen_tcp:recv(Socket, Len2, 6000), + %% No errors follow due to our sending of a 25000 bytes frame. + {error, timeout} = gen_tcp:recv(Socket, 0, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. settings_max_frame_size_reject_too_small(Config) -> doc("A SETTINGS_MAX_FRAME_SIZE smaller than 16384 must be rejected " @@ -3015,28 +3042,31 @@ data_reject_overflow(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, initial_stream_window_size => 100000 }, Config0), - {ok, Socket} = do_handshake(Config), - %% Send a HEADERS frame initiating a stream followed by - %% DATA frames totaling 90000 bytes of body. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, fin, <<0:15000/unit:8>>) - ]), - %% Receive a FLOW_CONTROL_ERROR connection error. - {ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000), - cowboy:stop_listener(?FUNCTION_NAME). + try + {ok, Socket} = do_handshake(Config), + %% Send a HEADERS frame initiating a stream followed by + %% DATA frames totaling 90000 bytes of body. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, fin, <<0:15000/unit:8>>) + ]), + %% Receive a FLOW_CONTROL_ERROR connection error. + {ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. data_reject_overflow_stream(Config0) -> doc("DATA frames that cause the stream flow control window " @@ -3047,41 +3077,44 @@ data_reject_overflow_stream(Config0) -> env => #{dispatch => cowboy_router:compile(init_routes(Config0))}, initial_connection_window_size => 100000 }, Config0), - %% We need to do the handshake manually because a WINDOW_UPDATE - %% frame will be sent to update the connection window. - {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), - %% Send a valid preface. - ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), - %% Receive the server preface. - {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), - {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), - %% Send the SETTINGS ack. - ok = gen_tcp:send(Socket, cow_http2:settings_ack()), - %% Receive the WINDOW_UPDATE for the connection. - {ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000), - %% Receive the SETTINGS ack. - {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), - %% Send a HEADERS frame initiating a stream followed by - %% DATA frames totaling 90000 bytes of body. - Headers = [ - {<<":method">>, <<"POST">>}, - {<<":scheme">>, <<"http">>}, - {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. - {<<":path">>, <<"/long_polling">>} - ], - {HeadersBlock, _} = cow_hpack:encode(Headers), - ok = gen_tcp:send(Socket, [ - cow_http2:headers(1, nofin, HeadersBlock), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, nofin, <<0:15000/unit:8>>), - cow_http2:data(1, fin, <<0:15000/unit:8>>) - ]), - %% Receive a FLOW_CONTROL_ERROR stream error. - {ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000), - cowboy:stop_listener(?FUNCTION_NAME). + try + %% We need to do the handshake manually because a WINDOW_UPDATE + %% frame will be sent to update the connection window. + {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]), + %% Send a valid preface. + ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]), + %% Receive the server preface. + {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000), + {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000), + %% Send the SETTINGS ack. + ok = gen_tcp:send(Socket, cow_http2:settings_ack()), + %% Receive the WINDOW_UPDATE for the connection. + {ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000), + %% Receive the SETTINGS ack. + {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000), + %% Send a HEADERS frame initiating a stream followed by + %% DATA frames totaling 90000 bytes of body. + Headers = [ + {<<":method">>, <<"POST">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/long_polling">>} + ], + {HeadersBlock, _} = cow_hpack:encode(Headers), + ok = gen_tcp:send(Socket, [ + cow_http2:headers(1, nofin, HeadersBlock), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, nofin, <<0:15000/unit:8>>), + cow_http2:data(1, fin, <<0:15000/unit:8>>) + ]), + %% Receive a FLOW_CONTROL_ERROR stream error. + {ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. %% (RFC7540 6.9.1) % Frames with zero length with the END_STREAM flag set (that |