erps v0.2.0 Erps.Client behaviour View Source

Create an Erps client GenServer.

The best way to think of an Erps client is that it is a GenServer that forwards its call/2 and cast/2 callbacks to a remote GenServer over a LAN or WAN. This callbacks would normally be provided by standard GenServer.call/2 and GenServer.cast/2 semantics over erlang distribution but in some cases you may want to issue a remote protocol request over high-latency or unreliable network stretches, or in cases where you would like to have an OTP-supervised connection orthogonal to standard erlang distribution semantics.

Basic operation

Presuming you have set up an Erps server GenServer on some host at @hostname, you can connect the client and the server simply by instantiating the server module.

Example

defmodule ErpsClient do
  use Erps.Client

  @hostname <...>
  @port <...>

  def start_link, do: Erps.Client.start_link(__MODULE__, :ok,
    server: @hostname, port: @port, tls_opts: [...])

  def init(init_state), do: {:ok, init_state}
end

{:ok, client} = ErpsClient.start_link
GenServer.call(client, :some_remote_call)
# => :some_remote_response

Module options

  • :version the version of your Erps API messages. Should be a SemVer string. see Version for more information.
  • :identifier a binary identifier for your Erps API endpoint. Maximum 12 bytes, suggested to be human-readable.
  • :sign_with defines the cryptographic signing function for your Erps client/server pair. May take one of two forms:

    • function (where function is an atom) calls the signing function module.function/2 with the unsigned binary as the first parameter, and the hmac_key (see start_link/3 options) as the second parameter. The hmac_key is passed to the function in the event that the client has a KV store which should be queried to find the appropriate signing key for the requested connection instance. module.function/2 should emit a 32-byte signature in response.
    • {external_module, function} calls external_module.function/2 in the same fashion as above.
  • :safe (see :erlang.binary_to_term/2), for decoding terms. If set to false, then allows undefined atoms and lambdas to be passed via the protocol. This should be used with extreme caution, as disabling safe mode can be an attack vector. (defaults to true)

Example

defmodule MyClient do
  use Erps.Client, version: "0.2.4",
                   identifier: "my_api",
                   sign_with: :signing,
                   safe: false

  def signing(binary, hmac_key) do
    :crypto.mac(:hmac, :sha256, SecretProvider.secret_for(hmac_key), binary)
  end

  def start_link(iv) do
    Erps.Client.start_link(__MODULE__, init,
      hmac_key: "ABCDEFGHIJKLMNOP",
      server: "my_api-server.example.com",
      port: 4747,
      transport: Erps.Transport.Tls,
      tls_opts: [...])
  end

  def init(iv), do: {:ok, iv}
end

Link to this section Summary

Functions

starts a client GenServer, not linked to the caller. Most useful for tests.

starts a client GenServer, linked to the caller.

Callbacks

Invoked when an internal callback requests a continuation, using {:noreply, state, {:continue, continuation}}, or from init/1 using {:ok, state, {:continue, continuation}}

Invoked to handle general messages sent to the client process.

Invoked to handle Erps.Server.push/2 messages.

Invoked to set up the process.

Invoked when the client is about to exit.

Link to this section Types

Link to this type

hmac_function() View Source
hmac_function() :: (() -> String.t())

Link to this type

signing_function() View Source
signing_function() ::
  (content :: binary(), key :: binary() -> signature :: binary())

Link to this section Functions

Link to this function

start(module, state, opts) View Source

starts a client GenServer, not linked to the caller. Most useful for tests.

see start_link/3 for a description of avaliable options.

Link to this function

start_link(module, state, opts) View Source

starts a client GenServer, linked to the caller.

Will attempt to contact the server over the specified transport strategy. If the connection fails, the client will be placed in an invalid state until connection succeeds, with a reconnect interval specified in the module options.

options

  • :server IP address of the target server (required)
  • :port IP port of the target server (required)
  • :transport module for communication transport strategy
  • :keepalive time interval for sending a TCP/IP keepalive token.
  • :hmac_key one of two options:

    • function/0 a zero-arity function which can be used to fetch the key at runtime
    • binary a directly instrumented value (this could be fetched at vm startup time and pulled from System.get_env/1 or Application.get_env/2)
  • :tls_opts options for setting up a TLS connection.

    • :cacertfile path to the certificate of your signing authority. (required)
    • :certfile path to the server certificate file. (required for Erps.Transport.Tls)
    • :keyfile path to the signing key. (required for Erps.Transport.Tls)

see GenServer.start_link/3 for a description of further options.

Link to this section Callbacks

Link to this callback

handle_continue(continue, state) View Source (optional)
handle_continue(continue :: term(), state :: term()) ::
  {:noreply, new_state}
  | {:noreply, new_state, timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state}
when new_state: term()

Invoked when an internal callback requests a continuation, using {:noreply, state, {:continue, continuation}}, or from init/1 using {:ok, state, {:continue, continuation}}

The continuation is passed as the first argument of this callback. Most useful if init/1 functionality is long-running and needs to be broken up into separate parts so that the calling start_link/3 doesn't block.

see: GenServer.handle_continue/2.

Return codes

  • {:noreply, new_state} continues the loop with new state new_state
  • {:noreply, new_state, timeout} causes a :timeout message to be sent to handle_info/2 if no other message comes by
  • {:noreply, new_state, :hibernate}, causes a hibernation event (see :erlang.hibernate/3)
  • {:noreply, new_state, {:continue, term}} causes a continuation to be triggered after the message is handled, it will be sent to c:handle_continue/3
  • {:stop, reason, new_state} terminates the loop, passing new_state to terminate/2, if it's implemented.
Link to this callback

handle_info(msg, state) View Source (optional)
handle_info(msg :: :timeout | term(), state :: term()) ::
  {:noreply, new_state}
  | {:noreply, new_state, timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state}
when new_state: term()

Invoked to handle general messages sent to the client process.

Most useful if the client needs to be attentive to system messages, such as nodedown or monitored processes, but also useful for internal timeouts.

see: GenServer.handle_info/2.

Return codes

see return codes for handle_continue/2

Link to this callback

handle_push(push, state) View Source (optional)
handle_push(push :: term(), state :: term()) ::
  {:noreply, new_state}
  | {:noreply, new_state, timeout() | :hibernate | {:continue, term()}}
  | {:stop, reason :: term(), new_state}
when new_state: term()

Invoked to handle Erps.Server.push/2 messages.

push is the push message sent by a Erps.Server.push/2 and state is the current state of the Erps.Client.

Return codes

see return codes for handle_continue/2

Link to this callback

init(init_arg) View Source
init(init_arg :: term()) ::
  {:ok, state}
  | {:ok, state, timeout() | :hibernate | {:continue, term()}}
  | :ignore
  | {:stop, reason :: any()}
when state: term()

Invoked to set up the process.

Like GenServer.init/1, this function is called from inside the process immediately after start_link/3 or start/3.

Return codes

  • {:ok, state} a succesful startup of your intialization logic and sets the internal state of your server to state.
  • {:ok, state, timeout} the above, plus a :timeout atom will be sent to handle_info/2 if no other messages come by.
  • {:ok, state, :hibernate} successful startup, followed by a hibernation event (see :erlang.hibernate/3)
  • {:ok, state, {:continue, term}} successful startup, and causes a continuation to be triggered after the message is handled, sent to c:handle_continue/3
  • :ignore - Drop the gen_server creation request, because for some reason it shouldn't have started.
  • {:stop, reason} - a failure in creating the gen_server. Results in {:error, reason} being propagated as the result of the start_link
Link to this callback

terminate(reason, state) View Source (optional)
terminate(reason, state :: term()) :: term()
when reason: :normal | :shutdown | {:shutdown, term()}

Invoked when the client is about to exit.

This would usually occur due to handle_push/2 returning a {:stop, reason, new_state} tuple, but also if the TCP connection happens to go down.