From d8e91eefa6ea9973f60657db05c85ec594aa7d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 26 Sep 2018 13:51:13 +0200 Subject: Don't send keep-alive while waiting for CONNECT responses Otherwise this can mess up the underlying protocol we will switch to, like TLS or HTTP/2. --- src/gun_http.erl | 3 +++ test/rfc7231_SUITE.erl | 55 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/gun_http.erl b/src/gun_http.erl index c4291bc..e2b37d1 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -319,6 +319,9 @@ close_streams([#stream{ref=StreamRef, reply_to=ReplyTo}|Tail]) -> "The connection was lost."}}, close_streams(Tail). +%% We don't send a keep-alive when a CONNECT request was initiated. +keepalive(State=#http_state{streams=[#stream{ref={connect, _, _}}]}) -> + State; %% We can only keep-alive by sending an empty line in-between streams. keepalive(State=#http_state{socket=Socket, transport=Transport, out=head}) -> Transport:send(Socket, <<"\r\n">>), diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl index a42e06c..f985bd8 100644 --- a/test/rfc7231_SUITE.erl +++ b/test/rfc7231_SUITE.erl @@ -34,12 +34,15 @@ do_proxy_start(Status) -> do_proxy_start(Status, []). do_proxy_start(Status, ConnectRespHeaders) -> + do_proxy_start(Status, ConnectRespHeaders, 0). + +do_proxy_start(Status, ConnectRespHeaders, Delay) -> Self = self(), - Pid = spawn_link(fun() -> do_proxy_init(Self, Status, ConnectRespHeaders) end), + Pid = spawn_link(fun() -> do_proxy_init(Self, Status, ConnectRespHeaders, Delay) end), Port = do_receive(Pid), {ok, Pid, Port}. -do_proxy_init(Parent, Status, ConnectRespHeaders) -> +do_proxy_init(Parent, Status, ConnectRespHeaders, Delay) -> {ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]), {ok, {_, Port}} = inet:sockname(ListenSocket), Parent ! {self(), Port}, @@ -47,6 +50,7 @@ do_proxy_init(Parent, Status, ConnectRespHeaders) -> {ok, Data} = gen_tcp:recv(ClientSocket, 0, 1000), {Method= <<"CONNECT">>, Authority, Version, Rest} = cow_http:parse_request_line(Data), {Headers, <<>>} = cow_http:parse_headers(Rest), + timer:sleep(Delay), Parent ! {self(), {request, Method, Authority, Version, Headers}}, {OriginHost, OriginPort} = cow_http_hd:parse_host(Authority), ok = gen_tcp:send(ClientSocket, [ @@ -109,7 +113,7 @@ 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, 1000), + {ok, ClientSocket} = gen_tcp:accept(ListenSocket, 5000), do_origin_loop(Parent, ClientSocket, gen_tcp). do_origin_init_tls(Parent) -> @@ -117,8 +121,8 @@ do_origin_init_tls(Parent) -> {ok, ListenSocket} = ssl:listen(0, [binary, {active, false}|Opts]), {ok, {_, Port}} = ssl:sockname(ListenSocket), Parent ! {self(), Port}, - {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 1000), - ok = ssl:ssl_accept(ClientSocket, 1000), + {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) -> @@ -127,8 +131,8 @@ do_origin_init_tls_h2(Parent) -> {alpn_preferred_protocols, [<<"h2">>]}|Opts]), {ok, {_, Port}} = ssl:sockname(ListenSocket), Parent ! {self(), Port}, - {ok, ClientSocket} = ssl:transport_accept(ListenSocket, 1000), - ok = ssl:ssl_accept(ClientSocket, 1000), + {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). @@ -142,10 +146,13 @@ do_origin_loop(Parent, ClientSocket, ClientTransport) -> end. do_receive(Pid) -> + do_receive(Pid, 1000). + +do_receive(Pid, Timeout) -> receive {Pid, Msg} -> Msg - after 1000 -> + after Timeout -> error(timeout) end. @@ -287,6 +294,38 @@ connect_through_multiple_proxies(_) -> }]} = gun:info(ConnPid), gun:close(ConnPid). +connect_delay(_) -> + doc("The CONNECT response may not be immediate."), + {ok, OriginPid, OriginPort} = do_origin_start(tcp), + {ok, ProxyPid, ProxyPort} = do_proxy_start(201, [], 2000), + Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), + {ok, ConnPid} = gun:open("localhost", ProxyPort, + #{http_opts => #{keepalive => 1000}}), + {ok, http} = gun:await_up(ConnPid), + StreamRef = gun:connect(ConnPid, #{ + host => "localhost", + port => OriginPort + }), + {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = do_receive(ProxyPid, 3000), + {response, fin, 201, _} = gun:await(ConnPid, StreamRef), + _ = gun:get(ConnPid, "/proxied"), + Len = byte_size(Authority), + <<"GET /proxied HTTP/1.1\r\nhost: ", Authority:Len/binary, "\r\n", _/bits>> + = do_receive(OriginPid), + #{ + transport := tcp, + protocol := http, + origin_host := "localhost", + origin_port := OriginPort, + intermediaries := [#{ + type := connect, + host := "localhost", + port := ProxyPort, + transport := tcp, + protocol := http + }]} = gun:info(ConnPid), + gun:close(ConnPid). + connect_response_201(_) -> doc("2xx responses to CONNECT requests indicate " "the tunnel was set up successfully. (RFC7231 4.3.6)"), -- cgit v1.2.3