aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/manual/gun.asciidoc111
-rw-r--r--doc/src/manual/gun.await_up.asciidoc3
-rw-r--r--doc/src/manual/gun.info.asciidoc6
-rw-r--r--doc/src/manual/gun.open.asciidoc5
-rw-r--r--doc/src/manual/gun.open_unix.asciidoc5
-rw-r--r--doc/src/manual/gun_down.asciidoc3
-rw-r--r--doc/src/manual/gun_error.asciidoc1
-rw-r--r--doc/src/manual/gun_socks_up.asciidoc66
-rw-r--r--doc/src/manual/gun_up.asciidoc13
-rw-r--r--src/gun.erl31
-rw-r--r--src/gun_event.erl6
-rw-r--r--src/gun_http.erl4
-rw-r--r--test/event_SUITE.erl4
-rw-r--r--test/socks_SUITE.erl6
14 files changed, 216 insertions, 48 deletions
diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc
index b9fbbc2..7b54666 100644
--- a/doc/src/manual/gun.asciidoc
+++ b/doc/src/manual/gun.asciidoc
@@ -63,6 +63,7 @@ by sending any of the following messages:
Connection:
* link:man:gun_up(3)[gun_up(3)] - The connection is up
+* link:man:gun_socks_up(3)[gun_socks_up(3)] - The Socks connection is up
* link:man:gun_down(3)[gun_down(3)] - The connection is down
* link:man:gun_upgrade(3)[gun_upgrade(3)] - Successful protocol upgrade
* link:man:gun_error(3)[gun_error(3)] - Stream or connection-wide error
@@ -96,7 +97,7 @@ connect_destination() :: #{
username => iodata(),
password => iodata(),
- protocols => [http | http2],
+ protocols => protocols(),
transport => tcp | tls,
tls_opts => [ssl:tls_client_option()],
@@ -121,15 +122,17 @@ username, password::
Proxy authorization credentials. They are only sent when
both options are provided.
-protocol (http)::
+protocols - see below::
-Protocol that will be used for tunneled requests.
+Ordered list of preferred protocols. Please refer to the
+`protocols()` type documentation for details.
++
+Defaults to `[http]` when the transport is `tcp`,
+and `[http2, http]` when the transport is `tls`.
transport (tcp)::
-Transport that will be used for tunneled requests. Note that
-due to Erlang/OTP limitations it is not possible to tunnel
-a TLS connection inside a TLS tunnel.
+Transport that will be used for tunneled requests.
tls_opts ([])::
@@ -233,7 +236,7 @@ opts() :: #{
domain_lookup_timeout => timeout(),
http_opts => http_opts(),
http2_opts => http2_opts(),
- protocols => [http | http2],
+ protocols => protocols(),
retry => non_neg_integer(),
retry_fun => fun(),
retry_timeout => pos_integer(),
@@ -269,13 +272,11 @@ Options specific to the HTTP/2 protocol.
protocols - see below::
-Ordered list of preferred protocols. When the transport is `tcp`,
-this list must contain exactly one protocol. When the transport
-is `tls`, this list must contain at least one protocol and will be
-used to negotiate a protocol via ALPN. When the server does not
-support ALPN then `http` will always be used. Defaults to
-`[http]` when the transport is `tcp`, and `[http2, http]` when the
-transport is `tls`.
+Ordered list of preferred protocols. Please refer to the
+`protocols()` type documentation for details.
++
+Defaults to `[http]` when the transport is `tcp`,
+and `[http2, http]` when the transport is `tls`.
retry (5)::
@@ -340,6 +341,25 @@ ws_opts (#{})::
Options specific to the Websocket protocol.
+=== protocols()
+
+[source,erlang]
+----
+protocols() :: http | {http, http_opts()}
+ | http2 | {http2, http2_opts()}
+ | socks | {socks, socks_opts()}
+----
+
+Ordered list of preferred protocols. When the transport is `tcp`,
+this list must contain exactly one protocol. When the transport
+is `tls`, this list must contain at least one protocol and will be
+used to negotiate a protocol via ALPN. When the server does not
+support ALPN then `http` will be used, except when the list of
+preferred protocols is set to only accept `socks`.
+
+Defaults to `[http]` when the transport is `tcp`,
+and `[http2, http]` when the transport is `tls`.
+
=== req_headers()
[source,erlang]
@@ -373,6 +393,64 @@ reply_to (`self()`)::
The pid of the process that will receive the response messages.
+=== socks_opts()
+
+[source,erlang]
+----
+socks_opts() :: #{
+ host := inet:hostname() | inet:ip_address(),
+ port := inet:port_number(),
+
+ auth => [{username_password, binary(), binary()} | none],
+ protocols => protocols(),
+ transport => tcp | tls,
+ version => 5,
+
+ tls_opts => [ssl:tls_client_option()],
+ tls_handshake_timeout => timeout()
+}
+----
+
+Configuration for the Socks protocol.
+
+The default value, if any, is given next to the option name:
+
+host, port::
+
+Destination hostname and port number. Mandatory.
++
+Upon successful completion of the Socks connection, Gun will
+begin using these as the host and port of the origin server
+for subsequent requests.
+
+auth ([none])::
+
+Authentication methods Gun advertises to the Socks proxy.
+
+protocols - see below::
+
+Ordered list of preferred protocols. Please refer to the
+`protocols()` type documentation for details.
++
+Defaults to `[http]` when the transport is `tcp`,
+and `[http2, http]` when the transport is `tls`.
+
+transport (tcp)::
+
+Transport that will be used for tunneled requests.
+
+tls_opts ([])::
+
+Options to use for tunneled TLS connections.
+
+tls_handshake_timeout (infinity)::
+
+Handshake timeout for tunneled TLS connections.
+
+version (5)::
+
+Version of the Socks protocol to use.
+
=== ws_opts()
[source,erlang]
@@ -417,6 +495,11 @@ undocumented and must be set to `gun_ws_h`.
== Changelog
+* *2.0*: The types `protocols()` and `socks_opts()` have been
+ added. Support for the Socks protocol has been added
+ in every places where protocol selection is available.
+ In addition it is now possible to specify separate
+ HTTP options for the CONNECT proxy and the origin server.
* *2.0*: The `connect_timeout` option has been split into
three options: `domain_lookup_timeout`, `connect_timeout`
and when applicable `tls_handshake_timeout`.
diff --git a/doc/src/manual/gun.await_up.asciidoc b/doc/src/manual/gun.await_up.asciidoc
index dfd4da9..d9934b5 100644
--- a/doc/src/manual/gun.await_up.asciidoc
+++ b/doc/src/manual/gun.await_up.asciidoc
@@ -23,7 +23,7 @@ await_up(ConnPid, Timeout, MonitorRef)
ConnPid :: pid()
MonitorRef :: reference()
Timeout :: timeout()
-Protocol :: http | http2
+Protocol :: http | http2 | socks
Reason :: {down, any()} | timeout
----
@@ -71,4 +71,5 @@ may also be returned when a timeout or an error occur.
link:man:gun(3)[gun(3)],
link:man:gun:open(3)[gun:open(3)],
link:man:gun:open_unix(3)[gun:open_unix(3)],
+link:man:gun_socks_up(3)[gun_socks_up(3)],
link:man:gun_up(3)[gun_up(3)]
diff --git a/doc/src/manual/gun.info.asciidoc b/doc/src/manual/gun.info.asciidoc
index afb2333..cf861c9 100644
--- a/doc/src/manual/gun.info.asciidoc
+++ b/doc/src/manual/gun.info.asciidoc
@@ -14,7 +14,7 @@ ConnPid :: pid()
Info :: #{
socket => inet:socket() | ssl:sslsocket(),
transport => tcp | tls,
- protocol => http | http2 | ws,
+ protocol => http | http2 | socks | ws,
sock_ip => inet:ip_address(),
sock_port => inet:port_number(),
origin_host => inet:hostname() | inet:ip_address(),
@@ -22,11 +22,11 @@ Info :: #{
intermediaries => [Intermediary]
}
Intermediary :: #{
- type => connect,
+ type => connect | socks5,
host => inet:hostname() | inet:ip_address(),
port => inet:port_number(),
transport => tcp | tls,
- protocol => http | http2
+ protocol => http | http2 | socks
}
----
diff --git a/doc/src/manual/gun.open.asciidoc b/doc/src/manual/gun.open.asciidoc
index 79a9d54..63bb3f8 100644
--- a/doc/src/manual/gun.open.asciidoc
+++ b/doc/src/manual/gun.open.asciidoc
@@ -14,7 +14,9 @@ open(Host, Port, Opts) -> {ok, pid()} | {error, Reason}
Host :: inet:hostname() | inet:ip_address()
Port :: inet:port_number()
Opts :: gun:opts()
-Reason :: {options, OptName} | {options, {http | http2, OptName}} | any()
+Reason :: {options, OptName}
+ | {options, {http | http2 | socks | ws, OptName}}
+ | any()
OptName :: atom()
----
@@ -71,4 +73,5 @@ message will be sent for that.
link:man:gun(3)[gun(3)],
link:man:gun:open_unix(3)[gun:open_unix(3)],
link:man:gun:await_up(3)[gun:await_up(3)],
+link:man:gun_socks_up(3)[gun_socks_up(3)],
link:man:gun_up(3)[gun_up(3)]
diff --git a/doc/src/manual/gun.open_unix.asciidoc b/doc/src/manual/gun.open_unix.asciidoc
index 4ea8d2b..441bfa8 100644
--- a/doc/src/manual/gun.open_unix.asciidoc
+++ b/doc/src/manual/gun.open_unix.asciidoc
@@ -12,7 +12,9 @@ open_unix(SocketPath, Opts) -> {ok, pid()} | {error, Reason}
SocketPath :: string()
Opts :: gun:opts()
-Reason :: {options, OptName} | {options, {http | http2, OptName}} | any()
+Reason :: {options, OptName}
+ | {options, {http | http2 | socks | ws, OptName}}
+ | any()
OptName :: atom()
----
@@ -59,4 +61,5 @@ message will be sent for that.
link:man:gun(3)[gun(3)],
link:man:gun:open(3)[gun:open(3)],
link:man:gun:await_up(3)[gun:await_up(3)],
+link:man:gun_socks_up(3)[gun_socks_up(3)],
link:man:gun_up(3)[gun_up(3)]
diff --git a/doc/src/manual/gun_down.asciidoc b/doc/src/manual/gun_down.asciidoc
index 67c7796..6c72898 100644
--- a/doc/src/manual/gun_down.asciidoc
+++ b/doc/src/manual/gun_down.asciidoc
@@ -11,7 +11,7 @@ gun_down - The connection is down
{gun_down, ConnPid, Protocol, Reason, KilledStreams, UnprocessedStreams}
ConnPid :: pid()
-Protocol :: http | http2 | ws
+Protocol :: http | http2 | socks | ws
Reason :: any()
KilledStreams :: [reference()]
UnprocessedStreams :: [reference()]
@@ -83,4 +83,5 @@ link:man:gun(3)[gun(3)],
link:man:gun:open(3)[gun:open(3)],
link:man:gun:open_unix(3)[gun:open_unix(3)],
link:man:gun_up(3)[gun_up(3)],
+link:man:gun_socks_up(3)[gun_socks_up(3)],
link:man:gun_error(3)[gun_error(3)]
diff --git a/doc/src/manual/gun_error.asciidoc b/doc/src/manual/gun_error.asciidoc
index a43d32b..ac278fd 100644
--- a/doc/src/manual/gun_error.asciidoc
+++ b/doc/src/manual/gun_error.asciidoc
@@ -64,4 +64,5 @@ handle_info({gun_error, ConnPid, _StreamRef, _Reason},
link:man:gun(3)[gun(3)],
link:man:gun_up(3)[gun_up(3)],
+link:man:gun_socks_up(3)[gun_socks_up(3)],
link:man:gun_down(3)[gun_down(3)]
diff --git a/doc/src/manual/gun_socks_up.asciidoc b/doc/src/manual/gun_socks_up.asciidoc
new file mode 100644
index 0000000..e74f1a9
--- /dev/null
+++ b/doc/src/manual/gun_socks_up.asciidoc
@@ -0,0 +1,66 @@
+= gun_socks_up(3)
+
+== Name
+
+gun_socks_up - The Socks connection is up
+
+== Description
+
+[source,erlang]
+----
+{gun_socks_up, ConnPid, Protocol}
+
+ConnPid :: pid()
+Protocol :: http | http2 | socks
+----
+
+The Socks connection is up.
+
+This message informs the owner process that the connection
+completed through the configured Socks proxy.
+
+If Gun is configured to connect to another Socks server, then the
+connection is not usable yet. One or more
+link:man:gun_socks_up(3)[gun_socks_up(3)] messages will follow.
+
+Otherwise, Gun will start processing the messages it received while
+waiting for the connection to be up. If this is a reconnection,
+then this may not be desirable for all requests. Those requests
+should be cancelled when the connection goes down, and any
+subsequent messages ignored.
+
+== Elements
+
+ConnPid::
+
+The pid of the Gun connection process.
+
+Protocol::
+
+The protocol selected for this connection. It can be used
+to determine the capabilities of the server.
+
+== Changelog
+
+* *2.0*: Message introduced.
+
+== Examples
+
+.Receive a gun_socks_up message in a gen_server
+[source,erlang]
+----
+handle_info({gun_socks_up, ConnPid, _Protocol},
+ State=#state{conn_pid=ConnPid}) ->
+ %% Do something.
+ {noreply, State}.
+----
+
+== See also
+
+link:man:gun(3)[gun(3)],
+link:man:gun:open(3)[gun:open(3)],
+link:man:gun:open_unix(3)[gun:open_unix(3)],
+link:man:gun:await_up(3)[gun:await_up(3)],
+link:man:gun_up(3)[gun_up(3)],
+link:man:gun_down(3)[gun_down(3)],
+link:man:gun_error(3)[gun_error(3)]
diff --git a/doc/src/manual/gun_up.asciidoc b/doc/src/manual/gun_up.asciidoc
index a103594..db09fca 100644
--- a/doc/src/manual/gun_up.asciidoc
+++ b/doc/src/manual/gun_up.asciidoc
@@ -11,7 +11,7 @@ gun_up - The connection is up
{gun_up, ConnPid, Protocol}
ConnPid :: pid()
-Protocol :: http | http2
+Protocol :: http | http2 | socks
----
The connection is up.
@@ -19,16 +19,16 @@ The connection is up.
This message informs the owner process that the connection or
reconnection completed.
-Gun will now start processing the messages it received while
+If Gun is configured to connect to a Socks server, then the
+connection is not usable yet. One or more
+link:man:gun_socks_up(3)[gun_socks_up(3)] messages will follow.
+
+Otherwise, Gun will start processing the messages it received while
waiting for the connection to be up. If this is a reconnection,
then this may not be desirable for all requests. Those requests
should be cancelled when the connection goes down, and any
subsequent messages ignored.
-// @todo Gun doesn't process messages immediately if it
-// is using the socks protocol, there are gun_socks_connected
-// messages coming up before reaching HTTP.
-
== Elements
ConnPid::
@@ -61,5 +61,6 @@ link:man:gun(3)[gun(3)],
link:man:gun:open(3)[gun:open(3)],
link:man:gun:open_unix(3)[gun:open_unix(3)],
link:man:gun:await_up(3)[gun:await_up(3)],
+link:man:gun_socks_up(3)[gun_socks_up(3)],
link:man:gun_down(3)[gun_down(3)],
link:man:gun_error(3)[gun_error(3)]
diff --git a/src/gun.erl b/src/gun.erl
index e576b61..6efca0b 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -114,19 +114,22 @@
| {close, ws_close_code(), iodata()}.
-export_type([ws_frame/0]).
+-type protocols() :: [http | http2 | socks
+ | {http, http_opts()} | {http2, http2_opts()} | {socks, socks_opts()}].
+-export_type([protocols/0]).
+
-type opts() :: #{
connect_timeout => timeout(),
domain_lookup_timeout => timeout(),
event_handler => {module(), any()},
http_opts => http_opts(),
http2_opts => http2_opts(),
- protocols => [http | http2 | {socks, socks_opts()}],
+ protocols => protocols(),
retry => non_neg_integer(),
retry_fun => fun((non_neg_integer(), opts())
-> #{retries => non_neg_integer(), timeout => pos_integer()}),
retry_timeout => pos_integer(),
- %% @todo Not sure this should be allowed, there could be loops.
-% socks_opts => socks_opts(),
+ socks_opts => socks_opts(),
supervise => boolean(),
tcp_opts => [gen_tcp:connect_option()],
tls_handshake_timeout => timeout(),
@@ -144,9 +147,7 @@
username => iodata(),
password => iodata(),
protocol => http | http2, %% @todo Remove in Gun 2.0.
- %% @todo It could be interesting to accept {http, http_opts()}
- %% as well since we may want different options for proxy and origin.
- protocols => [http | http2 | {socks, socks_opts()}],
+ protocols => protocols(),
transport => tcp | tls,
tls_opts => [ssl:tls_client_option()],
tls_handshake_timeout => timeout()
@@ -192,7 +193,7 @@
auth => [{username_password, binary(), binary()} | none],
host := inet:hostname() | inet:ip_address(),
port := inet:port_number(),
- protocols => [http | http2 | {socks, socks_opts()}],
+ protocols => protocols(),
transport => tcp | tls,
tls_opts => [ssl:tls_client_option()],
tls_handshake_timeout => timeout()
@@ -306,6 +307,13 @@ check_options([{retry_fun, F}|Opts]) when is_function(F, 2) ->
check_options(Opts);
check_options([{retry_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
check_options(Opts);
+check_options([{socks_opts, ProtoOpts}|Opts]) when is_map(ProtoOpts) ->
+ case gun_socks:check_options(ProtoOpts) of
+ ok ->
+ check_options(Opts);
+ Error ->
+ Error
+ end;
check_options([{supervise, B}|Opts]) when B =:= true; B =:= false ->
check_options(Opts);
check_options([{tcp_opts, L}|Opts]) when is_list(L) ->
@@ -340,8 +348,9 @@ check_protocols_opt(Protocols) ->
true ->
%% When options are given alongside a protocol, they
%% must be checked as well.
- %% @todo It may be interesting to allow more than just socks here.
TupleCheck = [case P of
+ {http, Opts} -> gun_http:check_options(Opts);
+ {http2, Opts} -> gun_http2:check_options(Opts);
{socks, Opts} -> gun_socks:check_options(Opts)
end || P <- Protocols, is_tuple(P)],
case lists:usort(TupleCheck) of
@@ -683,7 +692,7 @@ await_up(ServerPid, Timeout, MRef) ->
receive
{gun_up, ServerPid, Protocol} ->
{ok, Protocol};
- {gun_socks_connected, ServerPid, Protocol} ->
+ {gun_socks_up, ServerPid, Protocol} ->
{ok, Protocol};
{'DOWN', MRef, process, ServerPid, Reason} ->
{error, {down, Reason}}
@@ -1289,9 +1298,9 @@ commands([{switch_protocol, Protocol0}], State0=#state{
Protocol1 = protocol_handler(P),
{Protocol1, maps:get(Protocol1:opts_name(), Opts, #{})}
end,
- %% When we switch_protocol from socks we must send a gun_socks_connected message.
+ %% When we switch_protocol from socks we must send a gun_socks_up message.
_ = case CurrentProtocol of
- gun_socks -> Owner ! {gun_socks_connected, self(), Protocol:name()};
+ gun_socks -> Owner ! {gun_socks_up, self(), Protocol:name()};
_ -> ok
end,
{StateName, ProtoState} = Protocol:init(Owner, Socket, Transport, ProtoOpts),
diff --git a/src/gun_event.erl b/src/gun_event.erl
index a984796..0c1326d 100644
--- a/src/gun_event.erl
+++ b/src/gun_event.erl
@@ -47,7 +47,7 @@
lookup_info := gun_tcp:lookup_info(),
timeout := timeout(),
socket => inet:socket(),
- protocol => http | http2, %% Only when transport is tcp.
+ protocol => http | http2 | socks, %% Only when transport is tcp.
error => any()
}.
@@ -67,7 +67,7 @@
socket := inet:socket() | ssl:sslsocket() | pid(), %% The socket before/after will be different.
tls_opts := [ssl:tls_client_option()],
timeout := timeout(),
- protocol => http | http2,
+ protocol => http | http2 | socks,
error => any()
}.
@@ -241,7 +241,7 @@
%% support CONNECT and Websocket over HTTP/2.
-type protocol_changed_event() :: #{
- protocol := http2 | ws
+ protocol := http | http2 | socks | ws
}.
-callback protocol_changed(protocol_changed_event(), State) -> State.
diff --git a/src/gun_http.erl b/src/gun_http.erl
index ce174ce..8860baa 100644
--- a/src/gun_http.erl
+++ b/src/gun_http.erl
@@ -301,7 +301,6 @@ handle_head(Data, State=#http_state{version=ClientVersion, content_handlers=Hand
_ = end_stream(State#http_state{streams=[Stream|Tail]}),
NewHost = maps:get(host, Destination),
NewPort = maps:get(port, Destination),
- Protocols = maps:get(protocols, Destination, [http]),
case Destination of
#{transport := tls} ->
HandshakeEvent = #{
@@ -310,10 +309,11 @@ handle_head(Data, State=#http_state{version=ClientVersion, content_handlers=Hand
tls_opts => maps:get(tls_opts, Destination, []),
timeout => maps:get(tls_handshake_timeout, Destination, infinity)
},
+ Protocols = maps:get(protocols, Destination, [http2, http]),
{[{origin, <<"https">>, NewHost, NewPort, connect},
{tls_handshake, HandshakeEvent, Protocols}], EvHandlerState1};
_ ->
- [Protocol] = Protocols,
+ [Protocol] = maps:get(protocols, Destination, [http]),
{[{origin, <<"http">>, NewHost, NewPort, connect},
{switch_protocol, Protocol}], EvHandlerState1}
end;
diff --git a/test/event_SUITE.erl b/test/event_SUITE.erl
index 977eec4..5510e8c 100644
--- a/test/event_SUITE.erl
+++ b/test/event_SUITE.erl
@@ -315,7 +315,7 @@ http1_tls_handshake_end_ok_connect(Config) ->
socket := Socket,
tls_opts := _,
timeout := _,
- protocol := http
+ protocol := http2
} = do_receive_event(tls_handshake_end),
true = is_tuple(Socket),
gun:close(ConnPid).
@@ -405,7 +405,7 @@ http1_tls_handshake_end_ok_connect_over_https_proxy(Config) ->
socket := Socket,
tls_opts := _,
timeout := _,
- protocol := http
+ protocol := http2
} = do_receive_event(tls_handshake_end),
true = is_pid(Socket),
gun:close(ConnPid).
diff --git a/test/socks_SUITE.erl b/test/socks_SUITE.erl
index 060c89f..ec184a6 100644
--- a/test/socks_SUITE.erl
+++ b/test/socks_SUITE.erl
@@ -222,7 +222,7 @@ do_socks5(OriginScheme, OriginTransport, OriginProtocol, ProxyTransport, SocksAu
protocols => [OriginProtocol]
}}]
}),
- %% We receive a gun_up and a gun_socks_connected.
+ %% We receive a gun_up and a gun_socks_up.
{ok, socks} = gun:await_up(ConnPid),
{ok, OriginProtocol} = gun:await_up(ConnPid),
%% The proxy received two packets.
@@ -300,7 +300,7 @@ do_socks5_through_multiple_proxies(OriginScheme, OriginTransport, ProxyTransport
}}]
}}]
}),
- %% We receive a gun_up and two gun_socks_connected.
+ %% We receive a gun_up and two gun_socks_up.
{ok, socks} = gun:await_up(ConnPid),
{ok, socks} = gun:await_up(ConnPid),
{ok, http} = gun:await_up(ConnPid),
@@ -386,7 +386,7 @@ do_socks5_through_connect_proxy(OriginScheme, OriginTransport, ProxyTransport) -
}),
{request, <<"CONNECT">>, Authority1, 'HTTP/1.1', _} = receive_from(Proxy1Pid),
{response, fin, 200, _} = gun:await(ConnPid, StreamRef),
- %% We receive a gun_socks_connected afterwards. This is the origin HTTP server.
+ %% We receive a gun_socks_up afterwards. This is the origin HTTP server.
{ok, http} = gun:await_up(ConnPid),
%% The second proxy receives a Socks5 auth/connect request.
{auth_methods, 1, [none]} = receive_from(Proxy2Pid),