diff options
-rw-r--r-- | src/gun.erl | 3 | ||||
-rw-r--r-- | test/gun_test.erl | 84 | ||||
-rw-r--r-- | test/rfc7230_SUITE.erl | 77 | ||||
-rw-r--r-- | test/rfc7231_SUITE.erl | 146 | ||||
-rw-r--r-- | test/rfc7540_SUITE.erl | 73 |
5 files changed, 132 insertions, 251 deletions
diff --git a/src/gun.erl b/src/gun.erl index f27e7f4..e74c43f 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -888,7 +888,8 @@ commands([{origin, _Scheme, Host, Port, Type}|Tail], commands(Tail, State#state{origin_host=Host, origin_port=Port, intermediaries=[Info|Intermediaries]}); commands([{switch_transport, Transport, Socket}|Tail], State) -> - commands(Tail, State#state{socket=Socket, transport=Transport}); + commands(Tail, active(State#state{socket=Socket, transport=Transport, + messages=Transport:messages()})); %% @todo The two loops should be reunified and this clause generalized. commands([{switch_protocol, Protocol=gun_ws, ProtoState}], State) -> {keep_state, keepalive_cancel(State#state{protocol=Protocol, protocol_state=ProtoState})}; diff --git a/test/gun_test.erl b/test/gun_test.erl index 4e9aedd..11a04e3 100644 --- a/test/gun_test.erl +++ b/test/gun_test.erl @@ -16,9 +16,93 @@ -compile(export_all). -compile(nowarn_export_all). +-ifdef(OTP_RELEASE). +-compile({nowarn_deprecated_function, [{ssl, ssl_accept, 2}]}). +-endif. + %% Cowboy listeners. init_cowboy_tls(Ref, ProtoOpts, Config) -> Opts = ct_helper:get_certs_from_ets(), {ok, _} = cowboy:start_tls(Ref, Opts ++ [{port, 0}], ProtoOpts), [{ref, Ref}, {port, ranch:get_port(Ref)}|Config]. + +%% Origin server helpers. + +init_origin(Transport) -> + init_origin(Transport, http). + +init_origin(Transport, Protocol) -> + init_origin(Transport, Protocol, fun loop_origin/3). + +init_origin(Transport, Protocol, Fun) -> + Pid = spawn_link(?MODULE, init_origin, [self(), Transport, Protocol, Fun]), + Port = receive_from(Pid), + {ok, Pid, Port}. + +init_origin(Parent, tcp, Protocol, Fun) -> + {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), + {ok, {_, Port}} = inet:sockname(ListenSocket), + Parent ! {self(), Port}, + {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), + case Protocol of + http2 -> http2_handshake(ClientSocket, gen_tcp); + _ -> ok + end, + Fun(Parent, ClientSocket, gen_tcp); +init_origin(Parent, tls, Protocol, Fun) -> + Opts0 = ct_helper:get_certs_from_ets(), + Opts = case Protocol of + http2 -> [{alpn_preferred_protocols, [<<"h2">>]}|Opts0]; + _ -> Opts0 + end, + {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}|Opts]), + {ok, {_, Port}} = ssl:sockname(ListenSocket), + Parent ! {self(), Port}, + {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), + ok = ssl:ssl_accept(ClientSocket, 5000), + case Protocol of + http2 -> + {ok, <<"h2">>} = ssl:negotiated_protocol(ClientSocket), + http2_handshake(ClientSocket, ssl); + _ -> + ok + end, + Fun(Parent, ClientSocket, ssl). + +http2_handshake(Socket, Transport) -> + %% Send a valid preface. + ok = Transport:send(Socket, cow_http2:settings(#{})), + %% Receive the fixed sequence from the preface. + Preface = <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>, + {ok, Preface} = Transport:recv(Socket, byte_size(Preface), 5000), + %% Receive the SETTINGS from the preface. + {ok, <<Len:24>>} = Transport:recv(Socket, 3, 5000), + {ok, <<4:8, 0:40, _:Len/binary>>} = Transport:recv(Socket, 6 + Len, 5000), + %% Send the SETTINGS ack. + ok = Transport:send(Socket, cow_http2:settings_ack()), + %% Receive the SETTINGS ack. + {ok, <<0:24, 4:8, 1:8, 0:32>>} = Transport:recv(Socket, 9, 5000), + ok. + +loop_origin(Parent, ClientSocket, ClientTransport) -> + case ClientTransport:recv(ClientSocket, 0, 5000) of + {ok, Data} -> + Parent ! {self(), Data}, + loop_origin(Parent, ClientSocket, ClientTransport); + {error, closed} -> + ok + end. + +%% Common helpers. + +receive_from(Pid) -> + receive_from(Pid, 5000). + +receive_from(Pid, Timeout) -> + receive + {Pid, Msg} -> + Msg + after Timeout -> + error(timeout) + end. diff --git a/test/rfc7230_SUITE.erl b/test/rfc7230_SUITE.erl index f6e077b..c7aa63d 100644 --- a/test/rfc7230_SUITE.erl +++ b/test/rfc7230_SUITE.erl @@ -16,82 +16,13 @@ -compile(export_all). -compile(nowarn_export_all). --ifdef(OTP_RELEASE). --compile({nowarn_deprecated_function, [{ssl, ssl_accept, 2}]}). --endif. - -import(ct_helper, [doc/1]). +-import(gun_test, [init_origin/2]). +-import(gun_test, [receive_from/1]). all() -> ct_helper:all(?MODULE). -%% Server helpers. (Taken from rfc7231_SUITE.) - -do_origin_start(Transport) -> - do_origin_start(Transport, http). - -do_origin_start(Transport, Protocol) -> - Self = self(), - Pid = spawn_link(fun() -> - case Transport of - tcp -> - do_origin_init_tcp(Self); - tls when Protocol =:= http -> - do_origin_init_tls(Self); - tls when Protocol =:= http2 -> - do_origin_init_tls_h2(Self) - end - end), - Port = do_receive(Pid), - {ok, Pid, Port}. - -do_origin_init_tcp(Parent) -> - {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), - {ok, {_, Port}} = inet:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), - do_origin_loop(Parent, ClientSocket, gen_tcp). - -do_origin_init_tls(Parent) -> - Opts = ct_helper:get_certs_from_ets(), - {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}|Opts]), - {ok, {_, Port}} = ssl:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), - ok = ssl:ssl_accept(ClientSocket, 5000), - do_origin_loop(Parent, ClientSocket, ssl). - -do_origin_init_tls_h2(Parent) -> - Opts = ct_helper:get_certs_from_ets(), - {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}, - {alpn_preferred_protocols, [<<"h2">>]}|Opts]), - {ok, {_, Port}} = ssl:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), - ok = ssl:ssl_accept(ClientSocket, 5000), - {ok, <<"h2">>} = ssl:negotiated_protocol(ClientSocket), - do_origin_loop(Parent, ClientSocket, ssl). - -do_origin_loop(Parent, ClientSocket, ClientTransport) -> - case ClientTransport:recv(ClientSocket, 0, 1000) of - {ok, Data} -> - Parent ! {self(), Data}, - do_origin_loop(Parent, ClientSocket, ClientTransport); - {error, closed} -> - ok - end. - -do_receive(Pid) -> - do_receive(Pid, 1000). - -do_receive(Pid, Timeout) -> - receive - {Pid, Msg} -> - Msg - after Timeout -> - error(timeout) - end. - %% Tests. host_default_port_http(_) -> @@ -111,7 +42,7 @@ host_other_port_https(_) -> do_host_port(tls, 80, <<":80">>). do_host_port(Transport, DefaultPort, HostHeaderPort) -> - {ok, OriginPid, OriginPort} = do_origin_start(Transport, http), + {ok, OriginPid, OriginPort} = init_origin(Transport, http), {ok, ConnPid} = gun:open("localhost", OriginPort, #{transport => Transport}), {ok, http} = gun:await_up(ConnPid), %% Change the origin's port in the state to trigger the default port behavior. @@ -120,7 +51,7 @@ do_host_port(Transport, DefaultPort, HostHeaderPort) -> end, 5000), %% Confirm the default port is not sent in the request. _ = gun:get(ConnPid, "/"), - Data = do_receive(OriginPid), + Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: localhost", Rest/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], HostHeaderPort = Rest, diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl index 5f7bd6f..9a112a8 100644 --- a/test/rfc7231_SUITE.erl +++ b/test/rfc7231_SUITE.erl @@ -16,11 +16,11 @@ -compile(export_all). -compile(nowarn_export_all). --ifdef(OTP_RELEASE). --compile({nowarn_deprecated_function, [{ssl, ssl_accept, 2}]}). --endif. - -import(ct_helper, [doc/1]). +-import(gun_test, [init_origin/1]). +-import(gun_test, [init_origin/2]). +-import(gun_test, [receive_from/1]). +-import(gun_test, [receive_from/2]). all() -> ct_helper:all(?MODULE). @@ -39,7 +39,7 @@ do_proxy_start(Status, ConnectRespHeaders) -> do_proxy_start(Status, ConnectRespHeaders, Delay) -> Self = self(), Pid = spawn_link(fun() -> do_proxy_init(Self, Status, ConnectRespHeaders, Delay) end), - Port = do_receive(Pid), + Port = receive_from(Pid), {ok, Pid, Port}. do_proxy_init(Parent, Status, ConnectRespHeaders, Delay) -> @@ -91,71 +91,6 @@ do_proxy_loop(ClientSocket, OriginSocket) -> error(Msg) end. -do_origin_start(Transport) -> - do_origin_start(Transport, http). - -do_origin_start(Transport, Protocol) -> - Self = self(), - Pid = spawn_link(fun() -> - case Transport of - tcp -> - do_origin_init_tcp(Self); - tls when Protocol =:= http -> - do_origin_init_tls(Self); - tls when Protocol =:= http2 -> - do_origin_init_tls_h2(Self) - end - end), - Port = do_receive(Pid), - {ok, Pid, Port}. - -do_origin_init_tcp(Parent) -> - {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), - {ok, {_, Port}} = inet:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), - do_origin_loop(Parent, ClientSocket, gen_tcp). - -do_origin_init_tls(Parent) -> - Opts = ct_helper:get_certs_from_ets(), - {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}|Opts]), - {ok, {_, Port}} = ssl:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), - ok = ssl:ssl_accept(ClientSocket, 5000), - do_origin_loop(Parent, ClientSocket, ssl). - -do_origin_init_tls_h2(Parent) -> - Opts = ct_helper:get_certs_from_ets(), - {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}, - {alpn_preferred_protocols, [<<"h2">>]}|Opts]), - {ok, {_, Port}} = ssl:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), - ok = ssl:ssl_accept(ClientSocket, 5000), - {ok, <<"h2">>} = ssl:negotiated_protocol(ClientSocket), - do_origin_loop(Parent, ClientSocket, ssl). - -do_origin_loop(Parent, ClientSocket, ClientTransport) -> - case ClientTransport:recv(ClientSocket, 0, 1000) of - {ok, Data} -> - Parent ! {self(), Data}, - do_origin_loop(Parent, ClientSocket, ClientTransport); - {error, closed} -> - ok - end. - -do_receive(Pid) -> - do_receive(Pid, 1000). - -do_receive(Pid, Timeout) -> - receive - {Pid, Msg} -> - Msg - after Timeout -> - error(timeout) - end. - %% Tests. connect_http(_) -> @@ -169,7 +104,7 @@ connect_https(_) -> do_connect_http(tls). do_connect_http(Transport) -> - {ok, OriginPid, OriginPort} = do_origin_start(Transport, http), + {ok, OriginPid, OriginPort} = init_origin(Transport, http), {ok, ProxyPid, ProxyPort} = do_proxy_start(), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), @@ -179,10 +114,10 @@ do_connect_http(Transport) -> port => OriginPort, transport => Transport }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied"), - Data = do_receive(OriginPid), + Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ @@ -210,7 +145,7 @@ connect_h2(_) -> do_connect_h2(tls). do_connect_h2(Transport) -> - {ok, OriginPid, OriginPort} = do_origin_start(Transport, http2), + {ok, OriginPid, OriginPort} = init_origin(Transport, http2), {ok, ProxyPid, ProxyPort} = do_proxy_start(), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), @@ -221,18 +156,11 @@ do_connect_h2(Transport) -> transport => Transport, protocols => [http2] }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef), + timer:sleep(100), %% Give enough time for the handshake to fully complete. _ = gun:get(ConnPid, "/proxied"), - <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", - Len:24, 4:8, 0:40, %% SETTINGS - Rest/bits>> = do_receive(OriginPid), - _ = case Rest of - <<_:Len/binary>> -> - <<_:24, 1:8, _/bits>> = do_receive(OriginPid); - <<_:Len/binary, _:24, 1:8, _/bits>> -> - ok - end, + <<_:24, 1:8, _/bits>> = receive_from(OriginPid), #{ transport := Transport, protocol := http2, @@ -251,7 +179,7 @@ connect_through_multiple_proxies(_) -> doc("CONNECT can be used to establish a TCP connection " "to an HTTP/1.1 server via a tunnel going through " "two separate HTTP proxies. (RFC7231 4.3.6)"), - {ok, OriginPid, OriginPort} = do_origin_start(tcp), + {ok, OriginPid, OriginPort} = init_origin(tcp), {ok, Proxy1Pid, Proxy1Port} = do_proxy_start(), {ok, Proxy2Pid, Proxy2Port} = do_proxy_start(), {ok, ConnPid} = gun:open("localhost", Proxy1Port), @@ -261,17 +189,17 @@ connect_through_multiple_proxies(_) -> host => "localhost", port => Proxy2Port }), - {request, <<"CONNECT">>, Authority1, 'HTTP/1.1', _} = do_receive(Proxy1Pid), + {request, <<"CONNECT">>, Authority1, 'HTTP/1.1', _} = receive_from(Proxy1Pid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef1), Authority2 = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), StreamRef2 = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority2, 'HTTP/1.1', _} = do_receive(Proxy2Pid), + {request, <<"CONNECT">>, Authority2, 'HTTP/1.1', _} = receive_from(Proxy2Pid), {response, fin, 200, _} = gun:await(ConnPid, StreamRef2), _ = gun:get(ConnPid, "/proxied"), - Data = do_receive(OriginPid), + Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority2/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ @@ -296,7 +224,7 @@ connect_through_multiple_proxies(_) -> connect_delay(_) -> doc("The CONNECT response may not be immediate."), - {ok, OriginPid, OriginPort} = do_origin_start(tcp), + {ok, OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(201, [], 2000), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort, @@ -306,10 +234,10 @@ connect_delay(_) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid, 3000), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid, 3000), {response, fin, 201, _} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied"), - Data = do_receive(OriginPid), + Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ @@ -329,7 +257,7 @@ connect_delay(_) -> connect_response_201(_) -> doc("2xx responses to CONNECT requests indicate " "the tunnel was set up successfully. (RFC7231 4.3.6)"), - {ok, OriginPid, OriginPort} = do_origin_start(tcp), + {ok, OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(201), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), @@ -338,10 +266,10 @@ connect_response_201(_) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 201, _} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied"), - Data = do_receive(OriginPid), + Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], #{ @@ -384,7 +312,7 @@ do_connect_failure(Status) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, Status, Headers} = gun:await(ConnPid, StreamRef), FailedStreamRef = gun:get(ConnPid, "/proxied"), {response, fin, 501, _} = gun:await(ConnPid, FailedStreamRef), @@ -399,7 +327,7 @@ do_connect_failure(Status) -> connect_authority_form(_) -> doc("CONNECT requests must use the authority-form. (RFC7231 4.3.6)"), - {ok, _OriginPid, OriginPort} = do_origin_start(tcp), + {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), @@ -408,13 +336,13 @@ connect_authority_form(_) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {<<"localhost">>, OriginPort} = cow_http_hd:parse_host(Authority), gun:close(ConnPid). connect_proxy_authorization(_) -> doc("CONNECT requests may include a proxy-authorization header. (RFC7231 4.3.6)"), - {ok, _OriginPid, OriginPort} = do_origin_start(tcp), + {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), @@ -425,7 +353,7 @@ connect_proxy_authorization(_) -> username => "essen", password => "myrealpasswordis" }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = receive_from(ProxyPid), {_, ProxyAuthorization} = lists:keyfind(<<"proxy-authorization">>, 1, Headers), {basic, <<"essen">>, <<"myrealpasswordis">>} = cow_http_hd:parse_proxy_authorization(ProxyAuthorization), @@ -434,7 +362,7 @@ connect_proxy_authorization(_) -> connect_request_no_transfer_encoding(_) -> doc("The payload for CONNECT requests has no defined semantics. " "The transfer-encoding header should not be sent. (RFC7231 4.3.6)"), - {ok, _OriginPid, OriginPort} = do_origin_start(tcp), + {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), @@ -443,14 +371,14 @@ connect_request_no_transfer_encoding(_) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = receive_from(ProxyPid), false = lists:keyfind(<<"transfer-encoding">>, 1, Headers), gun:close(ConnPid). connect_request_no_content_length(_) -> doc("The payload for CONNECT requests has no defined semantics. " "The content-length header should not be sent. (RFC7231 4.3.6)"), - {ok, _OriginPid, OriginPort} = do_origin_start(tcp), + {ok, _OriginPid, OriginPort} = init_origin(tcp), {ok, ProxyPid, ProxyPort} = do_proxy_start(), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), {ok, ConnPid} = gun:open("localhost", ProxyPort), @@ -459,14 +387,14 @@ connect_request_no_content_length(_) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', Headers} = receive_from(ProxyPid), false = lists:keyfind(<<"content-length">>, 1, Headers), gun:close(ConnPid). connect_response_ignore_transfer_encoding(_) -> doc("Clients must ignore transfer-encoding headers in responses " "to CONNECT requests. (RFC7231 4.3.6)"), - {ok, OriginPid, OriginPort} = do_origin_start(tcp), + {ok, OriginPid, OriginPort} = init_origin(tcp), Headers = [{<<"transfer-encoding">>, <<"chunked">>}], {ok, ProxyPid, ProxyPort} = do_proxy_start(200, Headers), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), @@ -476,10 +404,10 @@ connect_response_ignore_transfer_encoding(_) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, Headers} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied"), - Data = do_receive(OriginPid), + Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], gun:close(ConnPid). @@ -487,7 +415,7 @@ connect_response_ignore_transfer_encoding(_) -> connect_response_ignore_content_length(_) -> doc("Clients must ignore content-length headers in responses " "to CONNECT requests. (RFC7231 4.3.6)"), - {ok, OriginPid, OriginPort} = do_origin_start(tcp), + {ok, OriginPid, OriginPort} = init_origin(tcp), Headers = [{<<"content-length">>, <<"1000">>}], {ok, ProxyPid, ProxyPort} = do_proxy_start(200, Headers), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), @@ -497,10 +425,10 @@ connect_response_ignore_content_length(_) -> host => "localhost", port => OriginPort }), - {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), {response, fin, 200, Headers} = gun:await(ConnPid, StreamRef), _ = gun:get(ConnPid, "/proxied"), - Data = do_receive(OriginPid), + Data = receive_from(OriginPid), Lines = binary:split(Data, <<"\r\n">>, [global]), [<<"host: ", Authority/bits>>] = [L || <<"host: ", _/bits>> = L <- Lines], gun:close(ConnPid). diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index 54667b7..be70002 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -16,76 +16,13 @@ -compile(export_all). -compile(nowarn_export_all). --ifdef(OTP_RELEASE). --compile({nowarn_deprecated_function, [{ssl, ssl_accept, 2}]}). --endif. - -import(ct_helper, [doc/1]). +-import(gun_test, [init_origin/3]). +-import(gun_test, [receive_from/1]). all() -> ct_helper:all(?MODULE). -%% Server helpers. - -do_origin_start(Transport, Fun) -> - Self = self(), - Pid = spawn_link(fun() -> - case Transport of - tcp -> - do_origin_init_tcp(Self, Fun); - tls -> - do_origin_init_tls_h2(Self, Fun) - end - end), - Port = do_receive(Pid), - {ok, Pid, Port}. - -do_origin_init_tcp(Parent, Fun) -> - {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), - {ok, {_, Port}} = inet:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), - do_handshake(ClientSocket, gen_tcp), - Fun(Parent, ClientSocket, gen_tcp). - -do_origin_init_tls_h2(Parent, Fun) -> - Opts = ct_helper:get_certs_from_ets(), - {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}, - {alpn_preferred_protocols, [<<"h2">>]}|Opts]), - {ok, {_, Port}} = ssl:sockname(ListenSocket), - Parent ! {self(), Port}, - {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 5000), - ok = ssl:ssl_accept(ClientSocket, 5000), - {ok, <<"h2">>} = ssl:negotiated_protocol(ClientSocket), - do_handshake(ClientSocket, ssl), - Fun(Parent, ClientSocket, ssl). - -do_handshake(Socket, Transport) -> - %% Send a valid preface. - ok = Transport:send(Socket, cow_http2:settings(#{})), - %% Receive the fixed sequence from the preface. - Preface = <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>, - {ok, Preface} = Transport:recv(Socket, byte_size(Preface), 5000), - %% Receive the SETTINGS from the preface. - {ok, <<Len:24>>} = Transport:recv(Socket, 3, 1000), - {ok, <<4:8, 0:40, _:Len/binary>>} = Transport:recv(Socket, 6 + Len, 1000), - %% Send the SETTINGS ack. - ok = Transport:send(Socket, cow_http2:settings_ack()), - %% Receive the SETTINGS ack. - {ok, <<0:24, 4:8, 1:8, 0:32>>} = Transport:recv(Socket, 9, 1000), - ok. - -do_receive(Pid) -> - do_receive(Pid, 1000). - -do_receive(Pid, Timeout) -> - receive - {Pid, Msg} -> - Msg - after Timeout -> - error(timeout) - end. - %% Tests. authority_default_port_http(_) -> @@ -109,7 +46,7 @@ authority_other_port_https(_) -> do_authority_port(tls, 80, <<":80">>). do_authority_port(Transport0, DefaultPort, AuthorityHeaderPort) -> - {ok, OriginPid, OriginPort} = do_origin_start(Transport0, fun(Parent, Socket, Transport) -> + {ok, OriginPid, OriginPort} = init_origin(Transport0, http2, fun(Parent, Socket, Transport) -> %% Receive the HEADERS frame and send the headers decoded. {ok, <<Len:24, 1:8, _:8, 1:32>>} = Transport:recv(Socket, 9, 1000), {ok, ReqHeadersBlock} = Transport:recv(Socket, Len, 1000), @@ -128,7 +65,7 @@ do_authority_port(Transport0, DefaultPort, AuthorityHeaderPort) -> %% Confirm the default port is not sent in the request. timer:sleep(100), %% Give enough time for the handshake to fully complete. _ = gun:get(ConnPid, "/"), - ReqHeaders = do_receive(OriginPid), + ReqHeaders = receive_from(OriginPid), {_, <<"localhost", Rest/bits>>} = lists:keyfind(<<":authority">>, 1, ReqHeaders), AuthorityHeaderPort = Rest, gun:close(ConnPid). @@ -136,7 +73,7 @@ do_authority_port(Transport0, DefaultPort, AuthorityHeaderPort) -> headers_priority_flag(_) -> doc("HEADERS frames may include a PRIORITY flag indicating " "that stream dependency information is attached. (RFC7540 6.2)"), - {ok, _, Port} = do_origin_start(tcp, fun(_, Socket, Transport) -> + {ok, _, Port} = init_origin(tcp, http2, fun(_, Socket, Transport) -> %% Receive a HEADERS frame. {ok, <<_:24, 1:8, _:8, 1:32>>} = Transport:recv(Socket, 9, 1000), %% Send a HEADERS frame with PRIORITY back. |