diff --git a/README.md b/README.md index 038cfa1..92ee7ca 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ The other way is send all that info as a parameter to `apns:connect/1` function , keyfile => path() , timeout => integer() , type := type() + , gun => gun:opts() }. ``` @@ -137,6 +138,13 @@ certfile => "priv/cert2.pem", keyfile => "priv/key2-noenc.pem", type => cert}). {ok,<0.132.0>} ``` +## Passing options specific to Gun + +The actual connection is handled by the [gun](https://github.com/ninenines/gun) library, +which supports some options of its own. You can pass options specific to `gun` in +the `connection` struct using `gun => GunOpts`. See the `gun` documentation for +examples. + ## Push Notifications over `Provider Certificate` connections In order to send Notifications over `Provider Certificate` connection we will use `apns:push_notification/3,4`. diff --git a/elvis.config b/elvis.config index d122bc6..12a3675 100644 --- a/elvis.config +++ b/elvis.config @@ -9,6 +9,7 @@ rules => [ {elvis_style, line_length, #{limit => 100}} , {elvis_style, god_modules, #{limit => 25, ignore => [apns_connection]}} + , {elvis_style, dont_repeat_yourself, #{min_complexity => 12}} ] }, #{dirs => ["."], diff --git a/src/apns_connection.erl b/src/apns_connection.erl index 2a7767b..24a428d 100644 --- a/src/apns_connection.erl +++ b/src/apns_connection.erl @@ -68,6 +68,7 @@ -type path() :: string(). -type notification() :: binary(). -type type() :: certdata | cert | token. +-type http_opts() :: #{ keepalive => non_neg_integer() }. -type keydata() :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo' , binary()}. @@ -87,6 +88,8 @@ , timeout => integer() , type := type() , proxy_info => proxy_info() + , http_opts => http_opts() + , gun => gun:opts() }. -type state() :: #{ connection := connection() @@ -227,25 +230,30 @@ open_origin(internal, _, #{connection := Connection} = StateData) -> Host = host(Connection), Port = port(Connection), TransportOpts = transport_opts(Connection), + Opts0 = #{ protocols => [http2] + , transport_opts => TransportOpts + , retry => 0 + }, + Opts = add_gun_opts(Connection, Opts0), {next_state, open_common, StateData, {next_event, internal, { Host , Port - , #{ protocols => [http2] - , transport_opts => TransportOpts - , retry => 0 - }}}}. + , Opts}}}. -spec open_proxy(_, _, _) -> _. open_proxy(internal, _, StateData) -> #{connection := Connection} = StateData, #{type := connect, host := ProxyHost, port := ProxyPort} = proxy(Connection), + Opts0 = #{ protocols => [http] + , transport => tcp + , retry => 0 + }, + Opts = add_gun_opts(Connection, Opts0), {next_state, open_common, StateData, {next_event, internal, { ProxyHost , ProxyPort - , #{ protocols => [http] - , transport => tcp - , retry => 0 - }}}}. + , Opts + }}}. %% This function exists only to make Elvis happy. %% I do not think it makes things any easier to read. @@ -492,3 +500,7 @@ backoff(N, Ceiling) -> NString = float_to_list(NextN, [{decimals, 0}]), list_to_integer(NString) end. + +add_gun_opts(Connection, Opts) -> + GunOpts = maps:get(gun, Connection, #{}), + maps:merge(GunOpts, Opts). diff --git a/test/connection_SUITE.erl b/test/connection_SUITE.erl index 40be5a8..81e51a5 100644 --- a/test/connection_SUITE.erl +++ b/test/connection_SUITE.erl @@ -10,6 +10,7 @@ , certdata_keydata_connection/1 , connect/1 , connect_without_name/1 + , connect_with_gun_params/1 , gun_connection_lost/1 , gun_connection_lost_timeout/1 , gun_connection_killed/1 @@ -32,6 +33,7 @@ all() -> [ default_connection , certdata_keydata_connection , connect , connect_without_name + , connect_with_gun_params , gun_connection_lost , gun_connection_lost_timeout , gun_connection_killed @@ -122,6 +124,27 @@ connect_without_name(_Config) -> [_] = meck:unload(), ok. +-spec connect_with_gun_params(config()) -> ok. +connect_with_gun_params(_Config) -> + connect_with_gun_param(connect_timeout, 10000), + connect_with_gun_param(http_opts, #{keepalive => 10000}), + connect_with_gun_param(http2_opts, #{keepalive => 10000}), + connect_with_gun_param(retry_timeout, 10000), + connect_with_gun_param(trace, true), + connect_with_gun_param(transport, tls). + +-spec connect_with_gun_param(atom(), any()) -> ok. +connect_with_gun_param(K, V) -> + ConnectionName = ?FUNCTION_NAME, + Connection = apns_connection:default_connection(cert, ConnectionName), + ok = mock_gun_open_param(K), + {ok, ServerPid} = apns:connect(Connection#{gun => #{K => V}}), + true = is_process_alive(ServerPid), + ok = close_connection(ServerPid), + [_] = meck:unload(), + ct:log("Gun param ~p => ~p passed correctly", [K, V]), + ok. + -spec gun_connection_lost(config()) -> ok. gun_connection_lost(_Config) -> ok = mock_gun_open(), @@ -394,10 +417,27 @@ test_function() -> -spec mock_gun_open() -> ok. mock_gun_open() -> meck:expect(gun, open, fun(_, _, _) -> + mimick_gun_open() + end). + +-spec mimick_gun_open() -> {ok, pid()}. +mimick_gun_open() -> GunPid = spawn(fun test_function/0), self() ! {gun_up, GunPid, http2}, - {ok, GunPid} - end). + {ok, GunPid}. + +-spec mock_gun_open_param(atom()) -> ok. +mock_gun_open_param(Key) -> + meck:expect( + gun, open, + fun(_, _, Opts) -> + case maps:is_key(Key, Opts) of + true -> + mimick_gun_open(); + false -> + error({missing_key, Key}) + end + end). -spec mock_gun_post() -> ok. mock_gun_post() ->