erps v0.7.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 sometimes you just don't want that (see Erps).

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 (optional) a binary identifier for your Erps API endpoint. Maximum 36 bytes, suggested to be human-readable. This must match the identifier on the server in order for there to be a successful connection.
  • :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",
                   safe: false

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

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

Important Notes

  • Calls are non-blocking for the Erps clients (and can be for the servers, if you so implement them). You may issue multiple, asynchronous calls across the network from different processes and they will be routed correctly and will only interfere with each other in terms of the connection arbitration overhead.
  • Calls reply immediately with the atom :disconnected if the erps client is not currently connected to its server.
  • handle_continue/2 is currently not supported. This is a limitation in the Connection library.

Link to this section Summary

Functions

triggers a connection.

triggers a disconnection.

returns the current socket in use by the server.

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

starts a client GenServer, linked to the caller.

Callbacks

called when a connection has been successfully established.

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

noreply_response() View Source
noreply_response() ::
  {:noreply, term()}
  | {:noreply, term(), timeout() | :hibernate}
  | {:disconnect, any(), term()}
  | {:stop, reason :: term(), term()}

Link to this type

reply_cache() View Source
reply_cache() :: %{optional(non_neg_integer()) => reply_ref()}

Link to this type

reply_ref() View Source
reply_ref() :: %{from: GenServer.from(), ttl: DateTime.t()}

Link to this section Functions

triggers a connection.

Only use this function after using disconnect/1

Link to this function

disconnect(server) View Source
disconnect(GenServer.server()) :: :ok

triggers a disconnection.

Useful to silence persistent Erps clients during maintenance phases. Note: this might cause upstream errors as consumers of the Erps service will emit errors on calls and casts.

returns the current socket in use by the server.

This may be a TCP socket or an SSL socket, or another interface depending on what transport strategy you're using.

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, options!) 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.
  • :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 Transport.Tls)
    • :keyfile path to the signing key. (required for Transport.Tls)
    • :customize_hostname_check it's very likely that you might get tls failures if you are relying on the OTP builtin hostname checks. This OTP ssl feature lets you override it for something custom. See :ssl.client_option/0
  • :reply_ttl the maximum amount of time that client should wait for call replies. Units in ms, defaults to 5000.
  • forward_callers: true causes the client to adopt the universe of the caller. see Multiverses for details

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

Link to this section Callbacks

Link to this callback

handle_connect(socket, state) View Source (optional)
handle_connect(socket :: Transport.socket(), state :: term()) ::
  noreply_response()

called when a connection has been successfully established.

Return codes

  • {:ok, state} update the state of the connection.
  • {:error, reason} stop the connection, with reason reason.
Link to this callback

handle_info(msg, state) View Source (optional)
handle_info(msg :: :timeout | term(), state :: term()) :: noreply_response()

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_push/2

Link to this callback

handle_push(push, state) View Source (optional)
handle_push(push :: term(), state :: term()) :: noreply_response()

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

  • {: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)
  • {:disconnect, reason, new_state}, causes a disconnection.
  • {:stop, reason, new_state} terminates the loop, passing new_state to terminate/2, if it's implemented.
Link to this callback

init(init_arg) View Source
init(init_arg :: term()) ::
  {:ok, state}
  | {:ok, state, timeout() | :hibernate}
  | :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)
  • :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.