aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/manual/gun.asciidoc52
-rw-r--r--src/gun.erl179
-rw-r--r--src/gun_default_event_h.erl16
-rw-r--r--src/gun_event.erl42
-rw-r--r--src/gun_tcp.erl73
-rw-r--r--src/gun_tls.erl9
-rw-r--r--test/event_SUITE.erl183
-rw-r--r--test/gun_SUITE.erl30
8 files changed, 459 insertions, 125 deletions
diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc
index 3f8d43f..cd34537 100644
--- a/doc/src/manual/gun.asciidoc
+++ b/doc/src/manual/gun.asciidoc
@@ -202,17 +202,20 @@ Time between pings in milliseconds.
[source,erlang]
----
opts() :: #{
- connect_timeout => timeout(),
- http_opts => http_opts(),
- http2_opts => http2_opts(),
- protocols => [http | http2],
- retry => non_neg_integer(),
- retry_timeout => pos_integer(),
- supervise => boolean(),
- trace => boolean(),
- transport => tcp | tls,
- transport_opts => [gen_tcp:connect_option()] | [ssl:connect_option()],
- ws_opts => ws_opts()
+ connect_timeout => timeout(),
+ domain_lookup_timeout => timeout(),
+ http_opts => http_opts(),
+ http2_opts => http2_opts(),
+ protocols => [http | http2],
+ retry => non_neg_integer(),
+ retry_timeout => pos_integer(),
+ supervise => boolean(),
+ tcp_opts => [gen_tcp:connect_option()],
+ tls_handshake_timeout => timeout(),
+ tls_opts => [ssl:connect_option()],
+ trace => boolean(),
+ transport => tcp | tls,
+ ws_opts => ws_opts()
}
----
@@ -224,6 +227,10 @@ connect_timeout (infinity)::
Connection timeout.
+domain_lookup_timeout (infinity)::
+
+Domain lookup timeout.
+
http_opts (#{})::
Options specific to the HTTP protocol.
@@ -255,6 +262,19 @@ supervise (true)::
Whether the Gun process should be started under the `gun_sup`
supervisor. Set to `false` to use your own supervisor.
+tcp_opts ([])::
+
+TCP options used when establishing the connection.
+
+tls_handshake_timeout (infinity)::
+
+TLS handshake timeout.
+
+tls_opts ([])::
+
+TLS options used for the TLS handshake after the connection
+has been established, when the transport is set to `tls`.
+
trace (false)::
Whether to enable `dbg` tracing of the connection process. Should
@@ -265,11 +285,6 @@ transport - see below::
Whether to use TLS or plain TCP. The default varies depending on the
port used. Port 443 defaults to `tls`. All other ports default to `tcp`.
-transport_opts ([])::
-
-Transport options. They are TCP options or TLS options depending on
-the selected transport.
-
ws_opts (#{})::
Options specific to the Websocket protocol.
@@ -332,6 +347,11 @@ undocumented and must be set to `gun_ws_h`.
== Changelog
+* *2.0*: The `connect_timeout` option has been split into
+ three options: `domain_lookup_timeout`, `connect_timeout`
+ and when applicable `tls_handshake_timeout`.
+* *2.0*: The `transport_opts` option has been split into
+ two options: `tcp_opts` and `tls_opts`.
* *2.0*: Introduce the type `req_headers()` and extend the
types accepted for header names for greater
interoperability. Header names are automatically
diff --git a/src/gun.erl b/src/gun.erl
index 12e4ae6..b55d3a1 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -93,6 +93,9 @@
-export([callback_mode/0]).
-export([init/1]).
-export([not_connected/3]).
+-export([domain_lookup/3]).
+-export([connecting/3]).
+-export([tls_handshake/3]).
-export([connected/3]).
-export([terminate/3]).
@@ -107,17 +110,20 @@
-type opts() :: #{
connect_timeout => timeout(),
- event_handler => {module(), any()},
- http_opts => http_opts(),
- http2_opts => http2_opts(),
- protocols => [http | http2],
- retry => non_neg_integer(),
- retry_timeout => pos_integer(),
- supervise => boolean(),
- trace => boolean(),
- transport => tcp | tls | ssl,
- transport_opts => [gen_tcp:connect_option()] | [ssl:connect_option()],
- ws_opts => ws_opts()
+ domain_lookup_timeout => timeout(),
+ event_handler => {module(), any()},
+ http_opts => http_opts(),
+ http2_opts => http2_opts(),
+ protocols => [http | http2],
+ retry => non_neg_integer(),
+ retry_timeout => pos_integer(),
+ supervise => boolean(),
+ tcp_opts => [gen_tcp:connect_option()],
+ tls_handshake_timeout => timeout(),
+ tls_opts => [ssl:connect_option()],
+ trace => boolean(),
+ transport => tcp | tls | ssl,
+ ws_opts => ws_opts()
}.
-export_type([opts/0]).
%% @todo Add an option to disable/enable the notowner behavior.
@@ -237,6 +243,10 @@ check_options([{connect_timeout, infinity}|Opts]) ->
check_options(Opts);
check_options([{connect_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
check_options(Opts);
+check_options([{domain_lookup_timeout, infinity}|Opts]) ->
+ check_options(Opts);
+check_options([{domain_lookup_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
+ check_options(Opts);
check_options([{event_handler, {Mod, _}}|Opts]) when is_atom(Mod) ->
check_options(Opts);
check_options([{http_opts, ProtoOpts}|Opts]) when is_map(ProtoOpts) ->
@@ -273,12 +283,18 @@ check_options([{retry_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
check_options(Opts);
check_options([{supervise, B}|Opts]) when B =:= true; B =:= false ->
check_options(Opts);
+check_options([{tcp_opts, L}|Opts]) when is_list(L) ->
+ check_options(Opts);
+check_options([{tls_handshake_timeout, infinity}|Opts]) ->
+ check_options(Opts);
+check_options([{tls_handshake_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
+ check_options(Opts);
+check_options([{tls_opts, L}|Opts]) when is_list(L) ->
+ check_options(Opts);
check_options([{trace, B}|Opts]) when B =:= true; B =:= false ->
check_options(Opts);
check_options([{transport, T}|Opts]) when T =:= tcp; T =:= tls ->
check_options(Opts);
-check_options([{transport_opts, L}|Opts]) when is_list(L) ->
- check_options(Opts);
check_options([{ws_opts, ProtoOpts}|Opts]) when is_map(ProtoOpts) ->
case gun_ws:check_options(ProtoOpts) of
ok ->
@@ -745,66 +761,125 @@ init({Owner, Host, Port, Opts}) ->
origin_host=Host, origin_port=Port, opts=Opts,
transport=Transport, messages=Transport:messages(),
event_handler=EvHandler, event_handler_state=EvHandlerState},
- {ok, not_connected, State,
- {next_event, internal, {retries, Retry}}}.
+ {ok, domain_lookup, State,
+ {next_event, internal, {retries, Retry, not_connected}}}.
default_transport(443) -> tls;
default_transport(_) -> tcp.
-not_connected(_, {retries, Retries}, State0=#state{host=Host, port=Port, opts=Opts,
+%% @todo This is where we would implement the backoff mechanism presumably.
+not_connected(_, {retries, 0, Reason}, State) ->
+ {stop, {shutdown, Reason}, State};
+not_connected(_, {retries, Retries, _}, State=#state{opts=Opts}) ->
+ Timeout = maps:get(retry_timeout, Opts, 5000),
+ {next_state, domain_lookup, State,
+ {state_timeout, Timeout, {retries, Retries - 1, not_connected}}};
+not_connected({call, From}, {stream_info, _}, _) ->
+ {keep_state_and_data, {reply, From, {error, not_connected}}};
+not_connected(Type, Event, State) ->
+ handle_common(Type, Event, ?FUNCTION_NAME, State).
+
+domain_lookup(_, {retries, Retries, _}, State=#state{host=Host, port=Port, opts=Opts,
+ event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
+ TransOpts = maps:get(tcp_opts, Opts, []),
+ DomainLookupTimeout = maps:get(domain_lookup_timeout, Opts, infinity),
+ DomainLookupEvent = #{
+ host => Host,
+ port => Port,
+ tcp_opts => TransOpts,
+ timeout => DomainLookupTimeout
+ },
+ EvHandlerState1 = EvHandler:domain_lookup_start(DomainLookupEvent, EvHandlerState0),
+ case gun_tcp:domain_lookup(Host, Port, TransOpts, DomainLookupTimeout) of
+ {ok, LookupInfo} ->
+ EvHandlerState = EvHandler:domain_lookup_end(DomainLookupEvent#{
+ lookup_info => LookupInfo
+ }, EvHandlerState1),
+ {next_state, connecting, State#state{event_handler_state=EvHandlerState},
+ {next_event, internal, {retries, Retries, LookupInfo}}};
+ {error, Reason} ->
+ EvHandlerState = EvHandler:domain_lookup_end(DomainLookupEvent#{
+ error => Reason
+ }, EvHandlerState1),
+ {next_state, not_connected, State#state{event_handler_state=EvHandlerState},
+ {next_event, internal, {retries, Retries, Reason}}}
+ end;
+domain_lookup({call, From}, {stream_info, _}, _) ->
+ {keep_state_and_data, {reply, From, {error, not_connected}}};
+domain_lookup(Type, Event, State) ->
+ handle_common(Type, Event, ?FUNCTION_NAME, State).
+
+connecting(_, {retries, Retries, LookupInfo}, State=#state{opts=Opts,
transport=Transport, event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
- TransOpts0 = maps:get(transport_opts, Opts, []),
- TransOpts1 = case Transport of
- gun_tcp -> TransOpts0;
- gun_tls -> ensure_alpn(maps:get(protocols, Opts, [http2, http]), TransOpts0)
- end,
- TransOpts = [binary, {active, false}|TransOpts1],
ConnectTimeout = maps:get(connect_timeout, Opts, infinity),
ConnectEvent = #{
- host => Host,
- port => Port,
- transport => Transport:name(),
- transport_opts => TransOpts,
+ lookup_info => LookupInfo,
timeout => ConnectTimeout
},
EvHandlerState1 = EvHandler:connect_start(ConnectEvent, EvHandlerState0),
- case Transport:connect(Host, Port, TransOpts, ConnectTimeout) of
- {ok, Socket} ->
- Protocol = case Transport of
- gun_tcp ->
- case maps:get(protocols, Opts, [http]) of
- [http] -> gun_http;
- [http2] -> gun_http2
- end;
- gun_tls ->
- case ssl:negotiated_protocol(Socket) of
- {ok, <<"h2">>} -> gun_http2;
- _ -> gun_http
- end
+ case gun_tcp:connect(LookupInfo, ConnectTimeout) of
+ {ok, Socket} when Transport =:= gun_tcp ->
+ Protocol = case maps:get(protocols, Opts, [http]) of
+ [http] -> gun_http;
+ [http2] -> gun_http2
end,
EvHandlerState = EvHandler:connect_end(ConnectEvent#{
socket => Socket,
protocol => Protocol:name()
}, EvHandlerState1),
- {next_state, connected, State0#state{event_handler_state=EvHandlerState},
+ {next_state, connected, State#state{event_handler_state=EvHandlerState},
{next_event, internal, {connected, Socket, Protocol}}};
+ {ok, Socket} when Transport =:= gun_tls ->
+ EvHandlerState = EvHandler:connect_end(ConnectEvent#{
+ socket => Socket
+ }, EvHandlerState1),
+ {next_state, tls_handshake, State#state{event_handler_state=EvHandlerState},
+ {next_event, internal, {retries, Retries, Socket}}};
{error, Reason} ->
EvHandlerState = EvHandler:connect_end(ConnectEvent#{
error => Reason
}, EvHandlerState1),
- State = State0#state{event_handler_state=EvHandlerState},
- case Retries of
- 0 ->
- {stop, {shutdown, Reason}, State};
- _ ->
- Timeout = maps:get(retry_timeout, Opts, 5000),
- {keep_state, State,
- {state_timeout, Timeout, {retries, Retries - 1}}}
- end
+ {next_state, not_connected, State#state{event_handler_state=EvHandlerState},
+ {next_event, internal, {retries, Retries, Reason}}}
end;
-not_connected({call, From}, {stream_info, _}, _) ->
+connecting({call, From}, {stream_info, _}, _) ->
{keep_state_and_data, {reply, From, {error, not_connected}}};
-not_connected(Type, Event, State) ->
+connecting(Type, Event, State) ->
+ handle_common(Type, Event, ?FUNCTION_NAME, State).
+
+tls_handshake(_, {retries, Retries, Socket0}, State=#state{opts=Opts,
+ event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
+ TransOpts0 = maps:get(tls_opts, Opts, []),
+ TransOpts = ensure_alpn(maps:get(protocols, Opts, [http2, http]), TransOpts0),
+ HandshakeTimeout = maps:get(tls_handshake_timeout, Opts, infinity),
+ HandshakeEvent = #{
+ socket => Socket0,
+ tls_opts => TransOpts,
+ timeout => HandshakeTimeout
+ },
+ EvHandlerState1 = EvHandler:tls_handshake_start(HandshakeEvent, EvHandlerState0),
+ case gun_tls:connect(Socket0, TransOpts, HandshakeTimeout) of
+ {ok, Socket} ->
+ Protocol = case ssl:negotiated_protocol(Socket) of
+ {ok, <<"h2">>} -> gun_http2;
+ _ -> gun_http
+ end,
+ EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
+ socket => Socket,
+ protocol => Protocol:name()
+ }, EvHandlerState1),
+ {next_state, connected, State#state{event_handler_state=EvHandlerState},
+ {next_event, internal, {connected, Socket, Protocol}}};
+ {error, Reason} ->
+ EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
+ error => Reason
+ }, EvHandlerState1),
+ {next_state, not_connected, State#state{event_handler_state=EvHandlerState},
+ {next_event, internal, {retries, Retries, Reason}}}
+ end;
+tls_handshake({call, From}, {stream_info, _}, _) ->
+ {keep_state_and_data, {reply, From, {error, not_connected}}};
+tls_handshake(Type, Event, State) ->
handle_common(Type, Event, ?FUNCTION_NAME, State).
ensure_alpn(Protocols0, TransOpts) ->
@@ -1048,7 +1123,7 @@ disconnect(State=#state{owner=Owner, opts=Opts,
keepalive_cancel(State#state{socket=undefined,
protocol=undefined, protocol_state=undefined,
event_handler_state=EvHandlerState}),
- {next_event, internal, {retries, Retry - 1}}}
+ {next_event, internal, {retries, Retry - 1, Reason}}}
end.
disconnect_flush(State=#state{socket=Socket, messages={OK, Closed, Error}}) ->
diff --git a/src/gun_default_event_h.erl b/src/gun_default_event_h.erl
index de63f17..9b56e71 100644
--- a/src/gun_default_event_h.erl
+++ b/src/gun_default_event_h.erl
@@ -16,8 +16,12 @@
-behavior(gun_event).
-export([init/2]).
+-export([domain_lookup_start/2]).
+-export([domain_lookup_end/2]).
-export([connect_start/2]).
-export([connect_end/2]).
+-export([tls_handshake_start/2]).
+-export([tls_handshake_end/2]).
-export([request_start/2]).
-export([request_headers/2]).
-export([request_end/2]).
@@ -39,12 +43,24 @@
init(_EventData, State) ->
State.
+domain_lookup_start(_EventData, State) ->
+ State.
+
+domain_lookup_end(_EventData, State) ->
+ State.
+
connect_start(_EventData, State) ->
State.
connect_end(_EventData, State) ->
State.
+tls_handshake_start(_EventData, State) ->
+ State.
+
+tls_handshake_end(_EventData, State) ->
+ State.
+
request_start(_EventData, State) ->
State.
diff --git a/src/gun_event.erl b/src/gun_event.erl
index 5332568..4716f47 100644
--- a/src/gun_event.erl
+++ b/src/gun_event.erl
@@ -27,22 +27,46 @@
-callback init(init_event(), State) -> State.
-%% connect_start/connect_end.
+%% domain_lookup_start/domain_lookup_end.
--type connect_event() :: #{
+-type domain_lookup_event() :: #{
host := inet:hostname() | inet:ip_address(),
port := inet:port_number(),
- transport := tcp | tls,
- transport_opts := [gen_tcp:connect_option()] | [ssl:connect_option()],
+ tcp_opts := [gen_tcp:connect_option()],
timeout := timeout(),
- socket => inet:socket() | ssl:sslsocket() | pid(),
- protocol => http | http2,
+ lookup_info => gun_tcp:lookup_info(),
+ error => any()
+}.
+
+-callback domain_lookup_start(domain_lookup_event(), State) -> State.
+-callback domain_lookup_end(domain_lookup_event(), State) -> State.
+
+%% connect_start/connect_end.
+
+-type connect_event() :: #{
+ lookup_info := gun_tcp:lookup_info(),
+ timeout := timeout(),
+ socket => inet:socket(),
+ protocol => http | http2, %% Only when transport is tcp.
error => any()
}.
-callback connect_start(connect_event(), State) -> State.
-callback connect_end(connect_event(), State) -> State.
+%% tls_handshake_start/tls_handshake_end.
+
+-type tls_handshake_event() :: #{
+ socket := inet:socket() | ssl:sslsocket(), %% The socket before/after will be different.
+ tls_opts := [ssl:connect_option()],
+ timeout := timeout(),
+ protocol => http | http2,
+ error => any()
+}.
+
+-callback tls_handshake_start(tls_handshake_event(), State) -> State.
+-callback tls_handshake_end(tls_handshake_event(), State) -> State.
+
%% request_start/request_headers.
-type request_start_event() :: #{
@@ -201,16 +225,12 @@
%% terminate.
-type terminate_event() :: #{
- state := not_connected | connected,
+ state := not_connected | domain_lookup | connecting | tls_handshake | connected,
reason := normal | shutdown | {shutdown, any()} | any()
}.
-callback terminate(terminate_event(), State) -> State.
-%% @todo domain_lookup_start
-%% @todo domain_lookup_end
-%% @todo tls_handshake_start
-%% @todo tls_handshake_end
%% @todo origin_changed
%% @todo transport_changed
%% @todo push_promise_start
diff --git a/src/gun_tcp.erl b/src/gun_tcp.erl
index 72e5681..2d091a0 100644
--- a/src/gun_tcp.erl
+++ b/src/gun_tcp.erl
@@ -16,23 +16,82 @@
-export([name/0]).
-export([messages/0]).
--export([connect/4]).
+-export([domain_lookup/4]).
+-export([connect/2]).
-export([send/2]).
-export([setopts/2]).
-export([sockname/1]).
-export([close/1]).
+-type lookup_info() :: #{
+ ip_addresses := [inet:ip_address()],
+ port := inet:port_number(),
+ tcp_module := module(),
+ tcp_opts := [gen_tcp:connect_option()] | [ssl:connect_option()]
+}.
+-export_type([lookup_info/0]).
+
name() -> tcp.
messages() -> {tcp, tcp_closed, tcp_error}.
--spec connect(inet:ip_address() | inet:hostname(),
- inet:port_number(), any(), timeout())
+%% The functions domain_lookup/4 and connect/2 are very similar
+%% to gen_tcp:connect/4 except the logic is split in order to
+%% be able to trigger events between the domain lookup step
+%% and the actual connect step.
+
+-spec domain_lookup(inet:ip_address() | inet:hostname(),
+ inet:port_number(), [gen_tcp:connect_option()] | [ssl:connect_option()], timeout())
+ -> {ok, lookup_info()} | {error, atom()}.
+domain_lookup(Address, Port0, Opts0, Timeout) ->
+ {Mod, Opts} = inet:tcp_module(Opts0, Address),
+ Timer = inet:start_timer(Timeout),
+ try Mod:getaddrs(Address, Timer) of
+ {ok, IPs} ->
+ case Mod:getserv(Port0) of
+ {ok, Port} ->
+ {ok, #{
+ ip_addresses => IPs,
+ port => Port,
+ tcp_module => Mod,
+ tcp_opts => Opts ++ [binary, {active, false}, {packet, raw}]
+ }};
+ Error ->
+ maybe_exit(Error)
+ end;
+ Error ->
+ maybe_exit(Error)
+ after
+ _ = inet:stop_timer(Timer)
+ end.
+
+-spec connect(lookup_info(), timeout())
-> {ok, inet:socket()} | {error, atom()}.
-connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
- gen_tcp:connect(Host, Port,
- Opts ++ [binary, {active, false}, {packet, raw}],
- Timeout).
+connect(#{ip_addresses := IPs, port := Port, tcp_module := Mod, tcp_opts := Opts}, Timeout) ->
+ Timer = inet:start_timer(Timeout),
+ Res = try
+ try_connect(IPs, Port, Opts, Timer, Mod, {error, einval})
+ after
+ _ = inet:stop_timer(Timer)
+ end,
+ case Res of
+ {ok, S} -> {ok, S};
+ Error -> maybe_exit(Error)
+ end.
+
+try_connect([IP|IPs], Port, Opts, Timer, Mod, _) ->
+ Timeout = inet:timeout(Timer),
+ case Mod:connect(IP, Port, Opts, Timeout) of
+ {ok, S} -> {ok, S};
+ {error, einval} -> {error, einval};
+ {error, timeout} -> {error, timeout};
+ Error -> try_connect(IPs, Port, Opts, Timer, Mod, Error)
+ end;
+try_connect([], _, _, _, _, Error) ->
+ Error.
+
+maybe_exit({error, einval}) -> exit(badarg);
+maybe_exit(Error) -> Error.
-spec send(inet:socket(), iodata()) -> ok | {error, atom()}.
send(Socket, Packet) ->
diff --git a/src/gun_tls.erl b/src/gun_tls.erl
index 256b881..3f8d1d6 100644
--- a/src/gun_tls.erl
+++ b/src/gun_tls.erl
@@ -17,7 +17,6 @@
-export([name/0]).
-export([messages/0]).
-export([connect/3]).
--export([connect/4]).
-export([send/2]).
-export([setopts/2]).
-export([sockname/1]).
@@ -32,14 +31,6 @@ messages() -> {ssl, ssl_closed, ssl_error}.
connect(Socket, Opts, Timeout) ->
ssl:connect(Socket, Opts, Timeout).
--spec connect(inet:ip_address() | inet:hostname(),
- inet:port_number(), any(), timeout())
- -> {ok, ssl:sslsocket()} | {error, atom()}.
-connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
- ssl:connect(Host, Port,
- Opts ++ [binary, {active, false}, {packet, raw}],
- Timeout).
-
-spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}.
send(Socket, Packet) ->
ssl:send(Socket, Packet).
diff --git a/test/event_SUITE.erl b/test/event_SUITE.erl
index 5f56b27..f7cae4e 100644
--- a/test/event_SUITE.erl
+++ b/test/event_SUITE.erl
@@ -38,7 +38,7 @@ groups() ->
].
init_per_suite(Config) ->
- {ok, _} = cowboy:start_clear(?MODULE, [], #{env => #{
+ ProtoOpts = #{env => #{
dispatch => cowboy_router:compile([{'_', [
{"/", hello_h, []},
{"/empty", empty_h, []},
@@ -47,12 +47,16 @@ init_per_suite(Config) ->
{"/trailers", trailers_h, []},
{"/ws", ws_echo, []}
]}])
- }}),
- OriginPort = ranch:get_port(?MODULE),
- [{origin_port, OriginPort}|Config].
+ }},
+ {ok, _} = cowboy:start_clear({?MODULE, tcp}, [], ProtoOpts),
+ TCPOriginPort = ranch:get_port({?MODULE, tcp}),
+ {ok, _} = cowboy:start_tls({?MODULE, tls}, ct_helper:get_certs_from_ets(), ProtoOpts),
+ TLSOriginPort = ranch:get_port({?MODULE, tls}),
+ [{tcp_origin_port, TCPOriginPort}, {tls_origin_port, TLSOriginPort}|Config].
end_per_suite(_) ->
- ok = cowboy:stop_listener(?MODULE).
+ ok = cowboy:stop_listener({?MODULE, tls}),
+ ok = cowboy:stop_listener({?MODULE, tcp}).
%% init.
@@ -74,16 +78,64 @@ init(Config) ->
} = do_receive_event(?FUNCTION_NAME),
gun:close(Pid).
+%% domain_lookup_start/domain_lookup_end.
+
+domain_lookup_start(Config) ->
+ doc("Confirm that the domain_lookup_start event callback is called."),
+ {ok, Pid, _} = do_gun_open(12345, Config),
+ #{
+ host := "localhost",
+ port := 12345,
+ tcp_opts := _,
+ timeout := _
+ } = do_receive_event(?FUNCTION_NAME),
+ gun:close(Pid).
+
+domain_lookup_end_error(Config) ->
+ doc("Confirm that the domain_lookup_end event callback is called on lookup failure."),
+ Opts = #{
+ event_handler => {?MODULE, self()},
+ protocols => [config(name, config(tc_group_properties, Config))]
+ },
+ {ok, Pid} = gun:open("this.should.not.exist", 12345, Opts),
+ #{
+ host := "this.should.not.exist",
+ port := 12345,
+ tcp_opts := _,
+ timeout := _,
+ error := nxdomain
+ } = do_receive_event(domain_lookup_end),
+ gun:close(Pid).
+
+domain_lookup_end_ok(Config) ->
+ doc("Confirm that the domain_lookup_end event callback is called on lookup success."),
+ {ok, Pid, _} = do_gun_open(12345, Config),
+ #{
+ host := "localhost",
+ port := 12345,
+ tcp_opts := _,
+ timeout := _,
+ lookup_info := #{
+ ip_addresses := [_|_],
+ port := 12345,
+ tcp_module := _,
+ tcp_opts := _
+ }
+ } = do_receive_event(domain_lookup_end),
+ gun:close(Pid).
+
%% connect_start/connect_end.
connect_start(Config) ->
doc("Confirm that the connect_start event callback is called."),
{ok, Pid, _} = do_gun_open(12345, Config),
#{
- host := "localhost",
- port := 12345,
- transport := tcp,
- transport_opts := _,
+ lookup_info := #{
+ ip_addresses := [_|_],
+ port := 12345,
+ tcp_module := _,
+ tcp_opts := _
+ },
timeout := _
} = do_receive_event(?FUNCTION_NAME),
gun:close(Pid).
@@ -92,30 +144,93 @@ connect_end_error(Config) ->
doc("Confirm that the connect_end event callback is called on connect failure."),
{ok, Pid, _} = do_gun_open(12345, Config),
#{
- host := "localhost",
- port := 12345,
- transport := tcp,
- transport_opts := _,
+ lookup_info := #{
+ ip_addresses := [_|_],
+ port := 12345,
+ tcp_module := _,
+ tcp_opts := _
+ },
timeout := _,
error := _
} = do_receive_event(connect_end),
gun:close(Pid).
-connect_end_ok(Config) ->
- doc("Confirm that the connect_end event callback is called on connect success."),
+connect_end_ok_tcp(Config) ->
+ doc("Confirm that the connect_end event callback is called on connect success with TCP."),
{ok, Pid, OriginPort} = do_gun_open(Config),
{ok, Protocol} = gun:await_up(Pid),
#{
- host := "localhost",
- port := OriginPort,
- transport := tcp,
- transport_opts := _,
+ lookup_info := #{
+ ip_addresses := [_|_],
+ port := OriginPort,
+ tcp_module := _,
+ tcp_opts := _
+ },
timeout := _,
socket := _,
protocol := Protocol
} = do_receive_event(connect_end),
gun:close(Pid).
+connect_end_ok_tls(Config) ->
+ doc("Confirm that the connect_end event callback is called on connect success with TLS."),
+ {ok, Pid, OriginPort} = do_gun_open_tls(Config),
+ Event = #{
+ lookup_info := #{
+ ip_addresses := [_|_],
+ port := OriginPort,
+ tcp_module := _,
+ tcp_opts := _
+ },
+ timeout := _,
+ socket := _
+ } = do_receive_event(connect_end),
+ false = maps:is_key(protocol, Event),
+ gun:close(Pid).
+
+tls_handshake_start(Config) ->
+ doc("Confirm that the tls_handshake_start event callback is called."),
+ {ok, Pid, _} = do_gun_open_tls(Config),
+ #{
+ socket := Socket,
+ tls_opts := _,
+ timeout := _
+ } = do_receive_event(?FUNCTION_NAME),
+ true = is_port(Socket),
+ gun:close(Pid).
+
+tls_handshake_end_error(Config) ->
+ doc("Confirm that the tls_handshake_end event callback is called on TLS handshake error."),
+ %% We use the wrong port on purpose to trigger a handshake error.
+ OriginPort = config(tcp_origin_port, Config),
+ Opts = #{
+ event_handler => {?MODULE, self()},
+ protocols => [config(name, config(tc_group_properties, Config))],
+ transport => tls
+ },
+ {ok, Pid} = gun:open("localhost", OriginPort, Opts),
+ #{
+ socket := Socket,
+ tls_opts := _,
+ timeout := _,
+ error := {tls_alert, _}
+ } = do_receive_event(tls_handshake_end),
+ true = is_port(Socket),
+ gun:close(Pid).
+
+tls_handshake_end_ok(Config) ->
+ doc("Confirm that the tls_handshake_end event callback is called on TLS handshake success."),
+ {ok, Pid, _} = do_gun_open_tls(Config),
+ {ok, Protocol} = gun:await_up(Pid),
+ #{
+ socket := Socket,
+ tls_opts := _,
+ timeout := _,
+ protocol := Protocol
+ } = do_receive_event(tls_handshake_end),
+ false = is_port(Socket),
+ gun:close(Pid).
+
request_start(Config) ->
doc("Confirm that the request_start event callback is called."),
do_request_event(Config, ?FUNCTION_NAME),
@@ -477,7 +592,7 @@ terminate(Config) ->
{ok, Pid, _} = do_gun_open(12345, Config),
gun:close(Pid),
#{
- state := not_connected,
+ state := _,
reason := shutdown
} = do_receive_event(terminate),
ok.
@@ -485,7 +600,7 @@ terminate(Config) ->
%% Internal.
do_gun_open(Config) ->
- OriginPort = config(origin_port, Config),
+ OriginPort = config(tcp_origin_port, Config),
do_gun_open(OriginPort, Config).
do_gun_open(OriginPort, Config) ->
@@ -496,6 +611,16 @@ do_gun_open(OriginPort, Config) ->
{ok, Pid} = gun:open("localhost", OriginPort, Opts),
{ok, Pid, OriginPort}.
+do_gun_open_tls(Config) ->
+ OriginPort = config(tls_origin_port, Config),
+ Opts = #{
+ event_handler => {?MODULE, self()},
+ protocols => [config(name, config(tc_group_properties, Config))],
+ transport => tls
+ },
+ {ok, Pid} = gun:open("localhost", OriginPort, Opts),
+ {ok, Pid, OriginPort}.
+
do_receive_event(Event) ->
receive
{Event, EventData} ->
@@ -513,6 +638,14 @@ init(EventData, Pid) ->
Pid ! {?FUNCTION_NAME, EventData},
Pid.
+domain_lookup_start(EventData, Pid) ->
+ Pid ! {?FUNCTION_NAME, EventData},
+ Pid.
+
+domain_lookup_end(EventData, Pid) ->
+ Pid ! {?FUNCTION_NAME, EventData},
+ Pid.
+
connect_start(EventData, Pid) ->
Pid ! {?FUNCTION_NAME, EventData},
Pid.
@@ -521,6 +654,14 @@ connect_end(EventData, Pid) ->
Pid ! {?FUNCTION_NAME, EventData},
Pid.
+tls_handshake_start(EventData, Pid) ->
+ Pid ! {?FUNCTION_NAME, EventData},
+ Pid.
+
+tls_handshake_end(EventData, Pid) ->
+ Pid ! {?FUNCTION_NAME, EventData},
+ Pid.
+
request_start(EventData, Pid) ->
Pid ! {?FUNCTION_NAME, EventData},
Pid.
diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl
index 6afaaf5..6a01cf6 100644
--- a/test/gun_SUITE.erl
+++ b/test/gun_SUITE.erl
@@ -54,18 +54,22 @@ atom_hostname(_) ->
connect_timeout(_) ->
doc("Ensure an integer value for connect_timeout is accepted."),
- {ok, Pid} = gun:open("localhost", 12345, #{connect_timeout => 1000, retry => 0}),
- Ref = monitor(process, Pid),
- receive
- {'DOWN', Ref, process, Pid, {shutdown, _}} ->
- ok
- after 5000 ->
- error(timeout)
- end.
+ do_timeout(connect_timeout, 1000).
connect_timeout_infinity(_) ->
doc("Ensure infinity for connect_timeout is accepted."),
- {ok, Pid} = gun:open("localhost", 12345, #{connect_timeout => infinity, retry => 0}),
+ do_timeout(connect_timeout, infinity).
+
+domain_lookup_timeout(_) ->
+ doc("Ensure an integer value for domain_lookup_timeout is accepted."),
+ do_timeout(domain_lookup_timeout, 1000).
+
+domain_lookup_timeout_infinity(_) ->
+ doc("Ensure infinity for domain_lookup_timeout is accepted."),
+ do_timeout(domain_lookup_timeout, infinity).
+
+do_timeout(Opt, Timeout) ->
+ {ok, Pid} = gun:open("localhost", 12345, #{Opt => Timeout, retry => 0}),
Ref = monitor(process, Pid),
receive
{'DOWN', Ref, process, Pid, {shutdown, _}} ->
@@ -496,6 +500,14 @@ supervise_false(_) ->
[] = [P || {_, P, _, _} <- supervisor:which_children(gun_sup), P =:= Pid],
ok.
+tls_handshake_timeout(_) ->
+ doc("Ensure an integer value for tls_handshake_timeout is accepted."),
+ do_timeout(tls_handshake_timeout, 1000).
+
+tls_handshake_timeout_infinity(_) ->
+ doc("Ensure infinity for tls_handshake_timeout is accepted."),
+ do_timeout(tls_handshake_timeout, infinity).
+
transform_header_name(_) ->
doc("The transform_header_name option allows changing the case of header names."),
{ok, ListenSocket} = gen_tcp:listen(0, [binary, {active, false}]),