erps v0.7.0 Erps.Server behaviour View Source

Create an Erps server GenServer.

An Erps server is just a GenServer that has its call/2 and cast/2 callbacks connected to the external network over a transport portocol.

Basic Operation

Presuming you have set up TLS credentials, you can instantiate a server in basically the same way that you would instantiate a GenServer:

defmodule ErpsServer do
  use Erps.Server

  @port <...>

  def start_link(data, opts) do
    Erps.Server.start_link(__MODULE__, data, opts)
  end

  @impl true
  def init(init_state), do: {:ok, init_state}

  def handle_call(:some_remote_call, state) do
    {:reply, :some_remote_response, state}
  end
end

NB you must implement a start_link/2 in order to be properly launched by Erps.Daemon.

Now you may either access these values as a normal, local GenServer, or access them via Erps.Client. The Erps.Server module will not know the difference between a local or a remote call.

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

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

Module Options

  • :identifier a binary identifier for your Erps API endpoint. Maximum 12 bytes, suggested to be human-readable.
  • :versions client semvers which are accepted. See the "requirements" section in Version.
  • :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)
  • :transport - set the transport module, which must implement Transport behaviour. If you set it to false, the Erps server will use Erps.Transport.None and act similarly to a GenServer (with some overhead).

Example

defmodule MyServer do
  use Erps.Server, versions: "~> 0.2.4",
                   identifier: "my_api",
                   safe: false

  def start_link(iv) do
    Erps.Server.start_link(__MODULE__, init,
      port: ,
      tls_opts: [...])
  end

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

Magical features

The following functions are hoisted to your server module so that you can call them in your code with clarity and less boilerplate:

Link to this section Summary

Types

a from term that is either a local from, compatible with GenServer.from/0 or an opaque term that represents a connected remote client.

Functions

Returns a specification to start this module under a supervisor.

disconnects the server, causing it to close.

pushes a message to all connected clients. Causes client Erps.Client.handle_push/2 callbacks to be triggered.

sends a reply to either a local process or a remotely connected client.

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

starts a server GenServer, linked to the caller.

Callbacks

similar to GenServer.handle_call/3, but handles content from both local and remote clients.

similar to GenServer.handle_cast/2, but handles content from both local and remote clients (if the content has been successfully filtered).

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 set up the process.

Link to this section Types

a from term that is either a local from, compatible with GenServer.from/0 or an opaque term that represents a connected remote client.

This opaque term is completely compatible with the t:GenServer.from type. In other words, you may pass this term to Genserver.reply/2 from any process and it will correctly route it to the caller whether it be local or remote.

Link to this section Functions

Returns a specification to start this module under a supervisor.

See Supervisor.

Link to this function

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

disconnects the server, causing it to close.

Link to this function

push(srv, push) View Source
push(server(), push :: term()) :: :ok

pushes a message to all connected clients. Causes client Erps.Client.handle_push/2 callbacks to be triggered.

Link to this function

reply(from, reply) View Source
reply(GenServer.from(), term()) :: :ok

sends a reply to either a local process or a remotely connected client.

The Erps Server holds connection information, so you must supply the Erps Server pid; though you may use the arity-2 form if you call from within the action loop of the Erps Server itself, in which case it acts like GenServer.reply/2

naturally takes the from value passed in to the second parameter of handle_call/3.

Link to this function

start(module, param, opts \\ []) View Source
start(module(), term(), keyword()) :: GenServer.on_start()

starts a server 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, param, options! \\ []) View Source
start_link(module(), term(), keyword()) :: GenServer.on_start()

starts a server GenServer, linked to the caller.

options

  • :transport transport module (see Transport.Api)
  • :tls_opts options for TLS authorization and encryption. Should include:
    • :cacertfile path to the certificate of your signing authority.
    • :certfile path to the server certificate file.
    • :keyfile path to the signing key.
    • :customize_hostname_check see below.

customize_hostname_check

Erlang/OTP doesn't provide a simple mechanism for a server in a two way ssl connection to verify the identity of the client (which you may want in certain situations where you own both ends of the connection but not the intermediate transport layer). See Transport.Tls.handshake/2 and :public_key.pkix_verify_hostname/3 for more details.

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

Link to this section Callbacks

Link to this callback

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

similar to GenServer.handle_call/3, but handles content from both local and remote clients.

The from term might contain a opaque term which represents a return address for a remote client, but you may use this term as expected in reply/2.

Return codes

  • {:reply, reply, new_state} replies, then updates the state of the Server.
  • {:reply, reply, new_state, timeout} replies, then causes a :timeout message to be sent to handle_info/2 if no other message comes by
  • {:reply, reply, new_state, :hibernate} replies, then causes a hibernation event (see :erlang.hibernate/3)
  • {:reply, reply, new_state, {:continue, term}} replies, then causes a continuation to be triggered after the message is handled, it will be sent to c:handle_continue/3
  • and all return codes supported by GenServer.handle_cast/2
Link to this callback

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

similar to GenServer.handle_cast/2, but handles content from both local and remote clients (if the content has been successfully filtered).

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_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}}

see: GenServer.handle_continue/2.

Return codes

see return codes for handle_cast/2

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_cast/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()}

see: GenServer.terminate/2.