Home

Awesome

reagent - socket acceptor pool

reagent is a socket acceptor pool for Elixir that leverages the socket library and its protocols to provide an easy way to implement servers.

Getting started

To define a reagent you first have to define a module using the reagent behaviour. This will define some basic functions you can extend and other helpers on the module and will make it startable as a reagent.

defmodule Test do
  use Reagent.Behaviour
end

When you want to start a server running the defined reagent, you have to call Reagent.start. It takes as first parameter the module implementing the behaviour and as second parameter a listener descriptor.

Listener descriptors contain the definition of the listener, including port, whether they're secure or not, other socket options and starting environment.

Reagent behaviour

A reagent to do anything useful has to either implement handle/1 or start/1.

handle/1 is called by the default start/1 and it gets called as a replacement for the acceptor process. It gets called with a Reagent.Connection record.

This is usually useful to implement simple protocols when you don't need a full blown gen_server or similar to handle a connection.

If you want more complex connection handling you can define start/1, it gets called with a Reagent.Connection record as well and must return { :ok, pid } or { :error, reason }. The returned process will be made owner of the socket and be used as reference for the connection itself.

You can also define accept/1 which gets called with the Reagent.Listener and allows you more fine grained socket acception.

Simple example

defmodule Echo do
  use Reagent

  def handle(conn) do
    case conn |> Socket.Stream.recv! do
      nil ->
        :closed

      data ->
        conn |> Socket.Stream.send! data

        handle(conn)
    end
  end
end

This is a simple implementation of an echo server.

To start it on port 8080 just run Reagent.start Echo, port: 8080.

Complex example

defmodule Echo do
  use Reagent

  def start(connection) do
    GenServer.start __MODULE__, connection, []
  end

  use GenServer

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

  # this message is sent when the socket has been completely accepted and the
  # process has been made owner of the socket, you don't need to wait for it
  # when implementing handle because it's internally handled
  def handle_info({ Reagent, :ack }, connection) do
    connection |> Socket.active!

    { :noreply, connection }
  end

  def handle_info({ :tcp, _, data }, connection) do
    connection |> Socket.Stream.send! data

    { :noreply, connection }
  end

  def handle_info({ :tcp_closed, _ }, connection) do
    { :stop, :normal, connection }
  end
end

This is the implementation of a full-blown gen_server based echo server (which is obviously overkill).

As with the simple example you just start it with Reagent.start Echo, port: 8080.