diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gun.erl | 80 | ||||
-rw-r--r-- | src/gun_default_event_h.erl | 29 | ||||
-rw-r--r-- | src/gun_event.erl | 72 |
3 files changed, 159 insertions, 22 deletions
diff --git a/src/gun.erl b/src/gun.erl index 7376a33..e958d2e 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -106,6 +106,7 @@ -type opts() :: #{ connect_timeout => timeout(), + event_handler => {module(), any()}, http_opts => http_opts(), http2_opts => http2_opts(), protocols => [http | http2], @@ -184,7 +185,9 @@ transport :: module(), messages :: {atom(), atom(), atom()}, protocol :: module(), - protocol_state :: any() + protocol_state :: any(), + event_handler :: module(), + event_handler_state :: any() }). %% Connection. @@ -233,6 +236,8 @@ 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([{event_handler, {Mod, _}}|Opts]) when is_atom(Mod) -> + check_options(Opts); check_options([{http_opts, ProtoOpts}|Opts]) when is_map(ProtoOpts) -> case gun_http:check_options(ProtoOpts) of ok -> @@ -724,18 +729,29 @@ init({Owner, Host, Port, Opts}) -> tls -> {<<"https">>, gun_tls} end, OwnerRef = monitor(process, Owner), + {EventHandler, EventHandlerState0} = maps:get(event_handler, Opts, + {gun_default_event_h, undefined}), + EventHandlerState = EventHandler:init(#{ + owner => Owner, + transport => OriginTransport, + origin_scheme => OriginScheme, + origin_host => Host, + origin_port => Port, + opts => Opts + }, EventHandlerState0), State = #state{owner=Owner, owner_ref=OwnerRef, host=Host, port=Port, origin_scheme=OriginScheme, origin_host=Host, origin_port=Port, opts=Opts, - transport=Transport, messages=Transport:messages()}, + transport=Transport, messages=Transport:messages(), + event_handler=EventHandler, event_handler_state=EventHandlerState}, {ok, not_connected, State, {next_event, internal, {retries, Retry}}}. default_transport(443) -> tls; default_transport(_) -> tcp. -not_connected(_, {retries, Retries}, - State=#state{host=Host, port=Port, opts=Opts, transport=Transport}) -> +not_connected(_, {retries, Retries}, State0=#state{host=Host, port=Port, opts=Opts, + transport=Transport, event_handler=EventHandler, event_handler_state=EventHandlerState0}) -> TransOpts0 = maps:get(transport_opts, Opts, []), TransOpts1 = case Transport of gun_tcp -> TransOpts0; @@ -743,27 +759,47 @@ not_connected(_, {retries, Retries}, end, TransOpts = [binary, {active, false}|TransOpts1], ConnectTimeout = maps:get(connect_timeout, Opts, infinity), + ConnectEvent = #{ + host => Host, + port => Port, + transport => Transport:name(), + transport_opts => TransOpts, + timeout => ConnectTimeout + }, + EventHandlerState1 = EventHandler:connect_start(ConnectEvent, EventHandlerState0), case Transport:connect(Host, Port, TransOpts, ConnectTimeout) of - {ok, Socket} when Transport =:= gun_tcp -> - Protocol = case maps:get(protocols, Opts, [http]) of - [http] -> gun_http; - [http2] -> gun_http2 + {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 end, - {next_state, connected, State, + EventHandlerState = EventHandler:connect_end(ConnectEvent#{ + socket => Socket, + protocol => Protocol:name() + }, EventHandlerState1), + {next_state, connected, State0#state{event_handler_state=EventHandlerState}, {next_event, internal, {connected, Socket, Protocol}}}; - {ok, Socket} when Transport =:= gun_tls -> - Protocol = case ssl:negotiated_protocol(Socket) of - {ok, <<"h2">>} -> gun_http2; - _ -> gun_http - end, - {next_state, connected, State, - {next_event, internal, {connected, Socket, Protocol}}}; - {error, Reason} when Retries =:= 0 -> - {stop, {shutdown, Reason}}; - {error, _Reason} -> - Timeout = maps:get(retry_timeout, Opts, 5000), - {keep_state, State, - {state_timeout, Timeout, {retries, Retries - 1}}} + {error, Reason} -> + EventHandlerState = EventHandler:connect_end(ConnectEvent#{ + error => Reason + }, EventHandlerState1), + State = State0#state{event_handler_state=EventHandlerState}, + 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 end; not_connected({call, From}, {stream_info, _}, _) -> {keep_state_and_data, {reply, From, {error, not_connected}}}; diff --git a/src/gun_default_event_h.erl b/src/gun_default_event_h.erl new file mode 100644 index 0000000..e242e84 --- /dev/null +++ b/src/gun_default_event_h.erl @@ -0,0 +1,29 @@ +%% Copyright (c) 2019, Loïc Hoguin <[email protected]> +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(gun_default_event_h). +-behavior(gun_event). + +-export([init/2]). +-export([connect_start/2]). +-export([connect_end/2]). + +init(_EventData, State) -> + State. + +connect_start(_EventData, State) -> + State. + +connect_end(_EventData, State) -> + State. diff --git a/src/gun_event.erl b/src/gun_event.erl new file mode 100644 index 0000000..fffcafe --- /dev/null +++ b/src/gun_event.erl @@ -0,0 +1,72 @@ +%% Copyright (c) 2019, Loïc Hoguin <[email protected]> +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(gun_event). + +%% init. + +-type init_event() :: #{ + owner := pid(), + transport := tcp | tls, + origin_scheme := binary(), + origin_host := inet:hostname() | inet:ip_address(), + origin_port := inet:port_number(), + opts := gun:opts() +}. + +-callback init(init_event(), State) -> State. + +%% connect_start/connect_end. + +-type connect_event() :: #{ + host := inet:hostname() | inet:ip_address(), + port := inet:port_number(), + transport := tcp | tls, + transport_opts := [gen_tcp:connect_option()] | [ssl:connect_option()], + timeout := timeout(), + socket => inet:socket() | ssl:sslsocket() | pid(), + protocol => http | http2, + error => any() +}. + +-callback connect_start(connect_event(), State) -> State. +-callback connect_end(connect_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 protocol_changed +%% @todo disconnected +%% @todo terminate +%% @todo stream_start +%% @todo stream_end +%% @todo request_start +%% @todo request_headers +%% @todo request_end +%% @todo response_start (call it once per inform + one for the response) +%% @todo response_inform +%% @todo response_headers +%% @todo response_end +%% @todo push_promise_start +%% @todo push_promise_end +%% @todo ws_upgrade_start +%% @todo ws_upgrade_end +%% @todo ws_frame_read_start +%% @todo ws_frame_read_header +%% @todo ws_frame_read_end +%% @todo ws_frame_write_start +%% @todo ws_frame_write_end |