diff options
author | Loïc Hoguin <[email protected]> | 2020-07-27 17:24:16 +0200 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2020-09-21 15:51:56 +0200 |
commit | 048224a888b3331796e66dd974c6d75234e09036 (patch) | |
tree | 05c8fb099619cd6d8a2cfd57d9dd87fed47686d0 | |
parent | 323bd167fd33f322ab8747e398e54a8a36f5b753 (diff) | |
download | gun-048224a888b3331796e66dd974c6d75234e09036.tar.gz gun-048224a888b3331796e66dd974c6d75234e09036.tar.bz2 gun-048224a888b3331796e66dd974c6d75234e09036.zip |
Add HTTP/2 CONNECT tests with Cowboy as origin
-rw-r--r-- | src/gun.erl | 1 | ||||
-rw-r--r-- | src/gun_http.erl | 2 | ||||
-rw-r--r-- | src/gun_http2.erl | 21 | ||||
-rw-r--r-- | test/handlers/proxied_h.erl | 11 | ||||
-rw-r--r-- | test/rfc7540_SUITE.erl | 69 |
5 files changed, 97 insertions, 7 deletions
diff --git a/src/gun.erl b/src/gun.erl index 00e2d82..8b0b78d 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -1464,6 +1464,7 @@ commands([{set_cookie, _, _, _, _}|Tail], State=#state{cookie_store=undefined}) commands([{set_cookie, _, _, Status, _}|Tail], State=#state{opts=#{cookie_ignore_informational := true}}) when Status >= 100, Status =< 199 -> commands(Tail, State); +%% @todo Make sure this works for proxied requests too. commands([{set_cookie, Authority, PathWithQs, _, Headers}|Tail], State=#state{ transport=Transport, cookie_store=Store0}) -> Scheme = case Transport of diff --git a/src/gun_http.erl b/src/gun_http.erl index 982df82..6b511a3 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -515,6 +515,8 @@ close_reason(closed) -> closed; close_reason(Reason) -> {closed, Reason}. %% @todo Do we want an event for this? +%% +%% @todo Need to propagate stream closing to tunneled streams. close_streams(_, [], _) -> ok; close_streams(State, [#stream{is_alive=false}|Tail], Reason) -> diff --git a/src/gun_http2.erl b/src/gun_http2.erl index 7b6d1eb..b1dc1e6 100644 --- a/src/gun_http2.erl +++ b/src/gun_http2.erl @@ -330,13 +330,24 @@ data_frame(State, StreamID, IsFin, Data, EvHandler, EvHandlerState0) -> %% This means that #stream{} must contain both the user-facing StreamRef and the reference. - %% @todo Commands. - {{state, ProtoState}, EvHandlerState} = Protocol:handle(Data, ProtoState0, - EvHandler, EvHandlerState0), - {store_stream(State, Stream#stream{tunnel={Protocol, ProtoState, TunnelInfo}}), - EvHandlerState} + {Commands, EvHandlerState} = Protocol:handle(Data, ProtoState0, EvHandler, EvHandlerState0), + {tunnel_commands(Commands, Stream, Protocol, TunnelInfo, State), EvHandlerState} end. +tunnel_commands(Command, Stream, Protocol, TunnelInfo, State) when not is_list(Command) -> + tunnel_commands([Command], Stream, Protocol, TunnelInfo, State); +tunnel_commands([], Stream, _, _, State) -> + store_stream(State, Stream); +tunnel_commands([{state, ProtoState}|Tail], Stream, Protocol, TunnelInfo, State) -> + tunnel_commands(Tail, Stream#stream{tunnel={Protocol, ProtoState, TunnelInfo}}, + Protocol, TunnelInfo, State); +tunnel_commands([SetCookie={set_cookie, _, _, _, _}|Tail], Stream, Protocol, TunnelInfo, + State=#http2_state{commands_queue=Queue}) -> + tunnel_commands(Tail, Stream, Protocol, TunnelInfo, + State#http2_state{commands_queue=[SetCookie|Queue]}); +tunnel_commands([{active, true}|Tail], Stream, Protocol, TunnelInfo, State) -> + tunnel_commands(Tail, Stream, Protocol, TunnelInfo, State). + data_frame(State0, StreamID, IsFin, Data, EvHandler, EvHandlerState0, Stream=#stream{ref=StreamRef, reply_to=ReplyTo, flow=Flow0, handler_state=Handlers0}) -> {ok, Dec, Handlers} = gun_content_handler:handle(IsFin, Data, Handlers0), diff --git a/test/handlers/proxied_h.erl b/test/handlers/proxied_h.erl new file mode 100644 index 0000000..d5d4690 --- /dev/null +++ b/test/handlers/proxied_h.erl @@ -0,0 +1,11 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(proxied_h). + +-export([init/2]). + +init(Req, State) -> + {ok, cowboy_req:reply(200, + #{<<"content-type">> => <<"text/plain">>}, + <<"TODO">>, + Req), State}. diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index 0991b1b..879c342 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -535,5 +535,70 @@ do_connect_http(OriginScheme, OriginTransport, OriginProtocol, ProxyScheme, Prox }} = gun:stream_info(ConnPid, ProxiedStreamRef), gun:close(ConnPid). -%% @todo Have a test with a Cowboy origin that confirms that tunneled requests -%% work as intended. +connect_cowboy_http_via_h2c(_) -> + doc("CONNECT can be used to establish a TCP connection " + "to an HTTP/1.1 server via a TCP HTTP/2 proxy. (RFC7540 8.3)"), + do_connect_cowboy(<<"http">>, tcp, http, <<"http">>, tcp). + +connect_cowboy_http_via_h2(_) -> + doc("CONNECT can be used to establish a TCP connection " + "to an HTTP/1.1 server via a TLS HTTP/2 proxy. (RFC7540 8.3)"), + do_connect_cowboy(<<"http">>, tcp, http, <<"https">>, tls). + +connect_cowboy_h2c_via_h2c(_) -> + doc("CONNECT can be used to establish a TCP connection " + "to an HTTP/2 server via a TCP HTTP/2 proxy. (RFC7540 8.3)"), + do_connect_cowboy(<<"http">>, tcp, http2, <<"http">>, tcp). + +connect_cowboy_h2c_via_h2(_) -> + doc("CONNECT can be used to establish a TCP connection " + "to an HTTP/2 server via a TLS HTTP/2 proxy. (RFC7540 8.3)"), + do_connect_cowboy(<<"http">>, tcp, http2, <<"https">>, tls). + +do_connect_cowboy(_OriginScheme, OriginTransport, OriginProtocol, _ProxyScheme, ProxyTransport) -> + {ok, Ref, OriginPort} = do_cowboy_origin(OriginTransport, OriginProtocol), + try + {ok, ProxyPid, ProxyPort} = do_proxy_start(ProxyTransport, [ + #proxy_stream{id=1, status=200} + ]), + Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), + {ok, ConnPid} = gun:open("localhost", ProxyPort, #{ + transport => ProxyTransport, + protocols => [http2] + }), + {ok, http2} = gun:await_up(ConnPid), + handshake_completed = receive_from(ProxyPid), + StreamRef = gun:connect(ConnPid, #{ + host => "localhost", + port => OriginPort, + transport => OriginTransport, + protocols => [OriginProtocol] + }), + {request, #{ + <<":method">> := <<"CONNECT">>, + <<":authority">> := Authority + }} = receive_from(ProxyPid), + {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), + ProxiedStreamRef = gun:get(ConnPid, "/proxied", #{}, #{tunnel => StreamRef}), + {response, nofin, 200, _} = gun:await(ConnPid, ProxiedStreamRef), + gun:close(ConnPid) + after + cowboy:stop_listener(Ref) + end. + +do_cowboy_origin(OriginTransport, OriginProtocol) -> + Ref = make_ref(), + ProtoOpts0 = case OriginTransport of + tcp -> #{protocols => [OriginProtocol]}; + tls -> #{} + end, + ProtoOpts = ProtoOpts0#{ + env => #{dispatch => cowboy_router:compile([{'_', [ + {"/proxied/[...]", proxied_h, []} + ]}])} + }, + [{ref, _}, {port, Port}] = case OriginTransport of + tcp -> gun_test:init_cowboy_tcp(Ref, ProtoOpts, []); + tls -> gun_test:init_cowboy_tls(Ref, ProtoOpts, []) + end, + {ok, Ref, Port}. |