We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel πŸš€.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Engineers who find a new job through Functional Works average a 15% increase in salary πŸš€

Blog hero image

Elixir Cowboy Websocket Handler

Eric Oestrich 20 August, 2018 (2 min read)

For my side project Gossip I wanted to have a websocket connection for non-Phoenix connections. I did this by going straight to Cowboy and using a handler at that level. This explains how I did this for Gossip.

Gossip is a cross game chat service for MUDs, check it out.

The Handler

The full websocket handler is here on GitHub.

defmodule Web.SocketHandler do
  @behaviour :cowboy_websocket_handler
  def init(_, _req, _opts) do
    {:upgrade, :protocol, :cowboy_websocket}
  end
  def websocket_init(_type, req, _opts) do
    Logger.info("Socket starting")
    {:ok, req, %State{status: "inactive"}}
  end
  def websocket_handle({:text, message}, req, state) do
    with {:ok, message} <- Poison.decode(message),
         {:ok, response, state} <- Implementation.receive(state, message) do
      {:reply, {:text, Poison.encode!(response)}, req, state}
    else
      {:ok, state} ->
        {:ok, req, state}
      _ ->
        {:reply, {:text, Poison.encode!(%{status: "unknown"})}, req, state}
    end
  end
end

This is a snipped version of the full file, but the basics are here. This shows the websocket upgrading, a cowboy and websockets requirement. The init function upgrades to websockets and the websocket_init function is called after the upgrade.

The other function shown is when a new message is received. The message is JSON (or should be), so it gets parsed and then run through an implementation module elsewhere. Depending on the response from that submodule, different responses will get send back.

There are a few other cool things in the real module, so I encourage you to check it out.

Ping/Pong

One thing I had to add that I thought was included as part of the cowboy handler was a pong response to a client side ping. This actually crashed the websocket process a few times so I needed to add this as per the websocket spec:

def websocket_handle({:ping, message}, req, state) do
  {:reply, {:pong, message}, req, state}
end

Phoenix Configuration

Since we're using a lower level websocket (than Phoenix) we have to manually set up the cowboy dispatcher. This configuration shows the cowboy websocket handler along with a separate Phoenix channel, since I want to have both options.

If you go this route, you need to manually specify any Phoenix channels from here on out.

config :gossip, Web.Endpoint,
  http: [dispatch: [
    {:_, [
      {"/socket", Web.SocketHandler, []},
      {"/chat/websocket", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, {Web.Endpoint, Web.UserSocket, :websocket}}},
      {:_, Plug.Adapters.Cowboy.Handler, {Web.Endpoint, []}}
    ]}
  ]]

Conclusion

Setting up your own lower level websocket in Elixir/Phoenix turned out to be pretty simple. I am happy I went this route so I didn't have to worry about forcing the higher level Phoenix channels protocol on top of external clients.

If you're curious about more of the events Gossip sends, the docs are available here.

Originally published on blog.oestrich.org