erps v0.3.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, do: Erps.Server.start_link(__MODULE__, :ok,
    port: @port,
    tls_opts: [...])

  @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

Now you may either access these values as a normal, local GenServer, or access them via Erps.Client (see documentation) for the implementation.

{: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)
  • :port - sets the TCP/IP port that the server will listen to. If you don't set it or set it to 0 it will pick a random port, which is useful for testing purposes.
  • :transport - set the transport module, which must implement Erps.Transport.Api behaviour. If you set it to false, the Erps server will 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

queries the server to retrieve a list of all clients that are connected to the server.

instruct the server to drop one of its connections.

queries the server to retrieve the TCP port that it's bound to to receive protocol messages.

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

used to filter messages from remote clients, allowing you to restrict your api.

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.

Link to this section Functions

Link to this function

connections(srv) View Source
connections(server()) :: [Erps.socket()]

queries the server to retrieve a list of all clients that are connected to the server.

Link to this function

disconnect(srv, socket) View Source
disconnect(server(), Erps.socket()) :: :ok | {:error, :enoent}

instruct the server to drop one of its connections.

returns {:error, :enoent} if the connection is not in its list of active connections.

Link to this function

port(srv) View Source
port(server()) :: {:ok, :inet.port_number()} | {:error, any()}

queries the server to retrieve the TCP port that it's bound to to receive protocol messages.

Useful if you have initiated your server with port: 0, especially in tests.

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(from(), term()) :: :ok

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

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, opts \\ []) View Source
start_link(module(), term(), keyword()) :: GenServer.on_start()

starts a server GenServer, linked to the caller.

options

  • :port tcp port that the server should listen to. Use 0 to pick an unused port and port/1 to retrieve that port number (useful for testing). You may also set it to false if you want the server to act as a normal GenServer (this is useful if you need a configuration-dependent behaviour)
  • :transport transport module (see Erps.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.

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

Link to this section Callbacks

Link to this callback

filter(mode, payload) View Source (optional)
filter(mode :: Erps.Packet.type(), payload :: term()) :: boolean()

used to filter messages from remote clients, allowing you to restrict your api.

The first term is either :call or :cast, indicating which type of api protocol the filter applies to, followed by the term to be checked for filtering. A true response means allow, and a false response means drop.

Defaults to fn _, _ -> true end (which is permissive).

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.