Using Ranch with Elixir

Posted on 12 Jul 2017 by Eric Oestrich

I started playing around with ranch yesterday and didn't see any examples on how to use it with Elixir, or an example with a GenServer. I finally managed to get it going, here is how I did it.

Start ranch

I have this module being started as a worker for my application. This will kick off the listener.

defmodule App do
  def start_link() do
    :ranch.start_listener(make_ref(), :ranch_tcp, [{:port, 5555}], App.GenProtocol, [])
  end
end

The first parameter is a reference, I couldn't find any explanation on how it's used so I went with make_ref. The other thing that matters is the App.GenProtocol part, which is the callback module that ranch will use for starting new connections.

Ranch Protocol

This part is fairly simple to get going as a non-GenServer process, but I really wanted to use GenServer since it handles a lot of extras for me. The tricky part was using :proc_lib to start the module and then entering the :gen_server.enter_loop after hooking up to the socket.

To note, the transport variable is :ranch_tcp which ends up calling into that erlang module.

defmodule App.GenProtocol do
  use GenServer

  @behaviour :ranch_protocol

  def start_link(ref, socket, transport, _opts) do
    pid = :proc_lib.spawn_link(__MODULE__, :init, [ref, socket, transport])
    {:ok, pid}
  end

  def init(ref, socket, transport) do
    IO.puts "Starting protocol"

    :ok = :ranch.accept_ack(ref)
    :ok = transport.setopts(socket, [{:active, true}])
    :gen_server.enter_loop(__MODULE__, [], %{socket: socket, transport: transport})
  end

  def handle_info({:tcp, socket, data}, state = %{socket: socket, transport: transport}) do
    IO.inspect data
    transport.send(socket, data)
    {:noreply, state}
  end
  def handle_info({:tcp_closed, socket}, state = %{socket: socket, transport: transport}) do
    IO.puts "Closing"
    transport.close(socket)
    {:stop, :normal, state}
  end
end

This will create a simple echo server. You can connect via telnet with telnet localhost 5555. It is very easy to extend this to perform more complex actions.

Example

Server
$ mix run --no-halt
Compiling 1 file (.ex)
Starting protocol
"Hi\r\n"
Closing
Client
$ telnet localhost 5555
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hi
Hi
^]
telnet> Connection closed.
comments powered by Disqus
Eric Oestrich
I am:
All posts
Creative Commons License
This site's content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License unless otherwise specified. Code on this site is licensed under the MIT License unless otherwise specified.