aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-09-26 12:08:11 +0200
committerLoïc Hoguin <[email protected]>2018-09-26 12:08:11 +0200
commitf328916937c67b3c9679e4f11d4594c39d36f85d (patch)
tree53c8c3df47443227fc2800ffe450c94ab7e58478
parentbaf0e420917ca1cb2806f8594a6cdb4710d2793d (diff)
downloadgun-f328916937c67b3c9679e4f11d4594c39d36f85d.tar.gz
gun-f328916937c67b3c9679e4f11d4594c39d36f85d.tar.bz2
gun-f328916937c67b3c9679e4f11d4594c39d36f85d.zip
Keep track of the intermediaries the connection go through
Also augment the CONNECT tests to confirm that the intermediaries are accounted for.
-rw-r--r--src/gun.erl48
-rw-r--r--src/gun_http.erl16
-rw-r--r--src/gun_tcp.erl3
-rw-r--r--src/gun_tls.erl3
-rw-r--r--test/rfc7231_SUITE.erl61
5 files changed, 119 insertions, 12 deletions
diff --git a/src/gun.erl b/src/gun.erl
index 9c92281..daa5c62 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -128,6 +128,14 @@
}.
-export_type([connect_destination/0]).
+-type intermediary() :: #{
+ type := connect,
+ host := inet:hostname() | inet:ip_address(),
+ port := inet:port_number(),
+ transport := tcp | tls,
+ protocol := http | http2
+}.
+
%% @todo When/if HTTP/2 CONNECT gets implemented, we will want an option here
%% to indicate that the request must be sent on an existing CONNECT stream.
%% This is of course not required for HTTP/1.1 since the CONNECT takes over
@@ -163,6 +171,7 @@
port :: inet:port_number(),
origin_host :: inet:hostname() | inet:ip_address(),
origin_port :: inet:port_number(),
+ intermediaries = [] :: [intermediary()],
opts :: opts(),
keepalive_ref :: undefined | reference(),
socket :: undefined | inet:socket() | ssl:sslsocket(),
@@ -275,9 +284,25 @@ consider_tracing(_, _) ->
-spec info(pid()) -> map().
info(ServerPid) ->
- {_, #state{socket=Socket, transport=Transport}} = sys:get_state(ServerPid),
+ {_, #state{
+ socket=Socket,
+ transport=Transport,
+ protocol=Protocol,
+ origin_host=OriginHost,
+ origin_port=OriginPort,
+ intermediaries=Intermediaries
+ }} = sys:get_state(ServerPid),
{ok, {SockIP, SockPort}} = Transport:sockname(Socket),
- #{sock_ip => SockIP, sock_port => SockPort}.
+ #{
+ transport => Transport:name(),
+ protocol => Protocol:name(),
+ sock_ip => SockIP,
+ sock_port => SockPort,
+ origin_host => OriginHost,
+ origin_port => OriginPort,
+ %% Intermediaries are listed in the order data goes through them.
+ intermediaries => lists:reverse(Intermediaries)
+ }.
-spec close(pid()) -> ok.
close(ServerPid) ->
@@ -866,8 +891,23 @@ commands([Error={error, _}|_], State=#state{socket=Socket, transport=Transport})
commands([{state, ProtoState}|Tail], State) ->
commands(Tail, State#state{protocol_state=ProtoState});
%% @todo The scheme should probably not be ignored.
-commands([{origin, _Scheme, Host, Port}|Tail], State) ->
- commands(Tail, State#state{origin_host=Host, origin_port=Port});
+%%
+%% Order is important: the origin must be changed before
+%% the transport and/or protocol in order to keep track
+%% of the intermediaries properly.
+commands([{origin, _Scheme, Host, Port, Type}|Tail],
+ State=#state{transport=Transport, protocol=Protocol,
+ origin_host=IntermediateHost, origin_port=IntermediatePort,
+ intermediaries=Intermediaries}) ->
+ Info = #{
+ type => Type,
+ host => IntermediateHost,
+ port => IntermediatePort,
+ transport => Transport:name(),
+ protocol => Protocol:name()
+ },
+ 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});
%% @todo The two loops should be reunified and this clause generalized.
diff --git a/src/gun_http.erl b/src/gun_http.erl
index c2b0ed6..c4291bc 100644
--- a/src/gun_http.erl
+++ b/src/gun_http.erl
@@ -229,13 +229,13 @@ handle_head(Data, State=#http_state{socket=Socket, version=ClientVersion,
{ok, TLSSocket} ->
case ssl:negotiated_protocol(TLSSocket) of
{ok, <<"h2">>} ->
- [{switch_transport, gun_tls, TLSSocket},
- {switch_protocol, gun_http2, State2},
- {origin, <<"https">>, NewHost, NewPort}];
+ [{origin, <<"https">>, NewHost, NewPort, connect},
+ {switch_transport, gun_tls, TLSSocket},
+ {switch_protocol, gun_http2, State2}];
_ ->
[{state, State2#http_state{socket=TLSSocket, transport=gun_tls}},
- {switch_transport, gun_tls, TLSSocket},
- {origin, <<"https">>, NewHost, NewPort}]
+ {origin, <<"https">>, NewHost, NewPort, connect},
+ {switch_transport, gun_tls, TLSSocket}]
end;
Error ->
Error
@@ -244,10 +244,10 @@ handle_head(Data, State=#http_state{socket=Socket, version=ClientVersion,
case maps:get(protocols, Destination, [http]) of
[http] ->
[{state, State2},
- {origin, <<"http">>, NewHost, NewPort}];
+ {origin, <<"http">>, NewHost, NewPort, connect}];
[http2] ->
- [{switch_protocol, gun_http2, State2},
- {origin, <<"http">>, NewHost, NewPort}]
+ [{origin, <<"http">>, NewHost, NewPort, connect},
+ {switch_protocol, gun_http2, State2}]
end
end;
{_, _} when Status >= 100, Status =< 199 ->
diff --git a/src/gun_tcp.erl b/src/gun_tcp.erl
index 80bc45b..0f5addc 100644
--- a/src/gun_tcp.erl
+++ b/src/gun_tcp.erl
@@ -14,6 +14,7 @@
-module(gun_tcp).
+-export([name/0]).
-export([messages/0]).
-export([connect/4]).
-export([send/2]).
@@ -21,6 +22,8 @@
-export([sockname/1]).
-export([close/1]).
+name() -> tcp.
+
messages() -> {tcp, tcp_closed, tcp_error}.
-spec connect(inet:ip_address() | inet:hostname(),
diff --git a/src/gun_tls.erl b/src/gun_tls.erl
index f58620f..d377927 100644
--- a/src/gun_tls.erl
+++ b/src/gun_tls.erl
@@ -14,6 +14,7 @@
-module(gun_tls).
+-export([name/0]).
-export([messages/0]).
-export([connect/3]).
-export([connect/4]).
@@ -22,6 +23,8 @@
-export([sockname/1]).
-export([close/1]).
+name() -> tls.
+
messages() -> {ssl, ssl_closed, ssl_error}.
-spec connect(inet:socket(), any(), timeout())
diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl
index 88bc114..a42e06c 100644
--- a/test/rfc7231_SUITE.erl
+++ b/test/rfc7231_SUITE.erl
@@ -178,6 +178,18 @@ do_connect_http(Transport) ->
Len = byte_size(Authority),
<<"GET /proxied HTTP/1.1\r\nhost: ", Authority:Len/binary, "\r\n", _/bits>>
= do_receive(OriginPid),
+ #{
+ transport := Transport,
+ 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_h2c(_) ->
@@ -214,6 +226,18 @@ do_connect_h2(Transport) ->
<<_:Len/binary, _:24, 1:8, _/bits>> ->
ok
end,
+ #{
+ transport := Transport,
+ protocol := http2,
+ origin_host := "localhost",
+ origin_port := OriginPort,
+ intermediaries := [#{
+ type := connect,
+ host := "localhost",
+ port := ProxyPort,
+ transport := tcp,
+ protocol := http
+ }]} = gun:info(ConnPid),
gun:close(ConnPid).
connect_through_multiple_proxies(_) ->
@@ -243,6 +267,24 @@ connect_through_multiple_proxies(_) ->
Len = byte_size(Authority2),
<<"GET /proxied HTTP/1.1\r\nhost: ", Authority2:Len/binary, "\r\n", _/bits>>
= do_receive(OriginPid),
+ #{
+ transport := tcp,
+ protocol := http,
+ origin_host := "localhost",
+ origin_port := OriginPort,
+ intermediaries := [#{
+ type := connect,
+ host := "localhost",
+ port := Proxy1Port,
+ transport := tcp,
+ protocol := http
+ }, #{
+ type := connect,
+ host := "localhost",
+ port := Proxy2Port,
+ transport := tcp,
+ protocol := http
+ }]} = gun:info(ConnPid),
gun:close(ConnPid).
connect_response_201(_) ->
@@ -263,6 +305,18 @@ connect_response_201(_) ->
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_302(_) ->
@@ -295,6 +349,13 @@ do_connect_failure(Status) ->
{response, fin, Status, Headers} = gun:await(ConnPid, StreamRef),
FailedStreamRef = gun:get(ConnPid, "/proxied"),
{response, fin, 501, _} = gun:await(ConnPid, FailedStreamRef),
+ #{
+ transport := tcp,
+ protocol := http,
+ origin_host := "localhost",
+ origin_port := ProxyPort,
+ intermediaries := []
+ } = gun:info(ConnPid),
gun:close(ConnPid).
connect_authority_form(_) ->