aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2020-07-27 17:24:16 +0200
committerLoïc Hoguin <[email protected]>2020-09-21 15:51:56 +0200
commit048224a888b3331796e66dd974c6d75234e09036 (patch)
tree05c8fb099619cd6d8a2cfd57d9dd87fed47686d0
parent323bd167fd33f322ab8747e398e54a8a36f5b753 (diff)
downloadgun-048224a888b3331796e66dd974c6d75234e09036.tar.gz
gun-048224a888b3331796e66dd974c6d75234e09036.tar.bz2
gun-048224a888b3331796e66dd974c6d75234e09036.zip
Add HTTP/2 CONNECT tests with Cowboy as origin
-rw-r--r--src/gun.erl1
-rw-r--r--src/gun_http.erl2
-rw-r--r--src/gun_http2.erl21
-rw-r--r--test/handlers/proxied_h.erl11
-rw-r--r--test/rfc7540_SUITE.erl69
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}.