Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Websockets in Elixir with Cowboy and Plug
defmodule MyApp do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
Plug.Adapters.Cowboy.child_spec(:http, MyApp.Router, [], [
dispatch: dispatch
])
]
opts = [strategy: :one_for_one, name: Navis.Supervisor]
Supervisor.start_link(children, opts)
end
defp dispatch do
[
{:_, [
{"/ws", MyApp.SocketHandler, []},
{:_, Plug.Adapters.Cowboy.Handler, {MyApp.Router, []}}
]}
]
end
end
defmodule MyApp.Router do
use Plug.Router
plug :match
plug :dispatch
match _ do
send_resp(conn, 200, "Hello from plug")
end
end
defmodule MyApp.SocketHandler
@behaviour :cowboy_websocket_handler
def init(_, _req, _opts) do
{:upgrade, :protocol, :cowboy_websocket}
end
@timeout 60000 # terminate if no activity for one minute
#Called on websocket connection initialization.
def websocket_init(_type, req, _opts) do
state = %{}
{:ok, req, state, @timeout}
end
# Handle 'ping' messages from the browser - reply
def websocket_handle({:text, "ping"}, req, state) do
{:reply, {:text, "pong"}, req, state}
end
# Handle other messages from the browser - don't reply
def websocket_handle({:text, message}, req, state) do
IO.puts(message)
{:ok, req, state}
end
# Format and forward elixir messages to client
def websocket_info(message, req, state) do
{:reply, {:text, message}, req, state}
end
# No matter why we terminate, remove all of this pids subscriptions
def websocket_terminate(_reason, _req, _state) do
:ok
end
end
@CatTail

This comment has been minimized.

Copy link

@CatTail CatTail commented Aug 14, 2017

my_app_socket_handler.ex missing do in defmodule

@AquaGeek

This comment has been minimized.

Copy link

@AquaGeek AquaGeek commented Jan 11, 2018

What version of Cowboy are you using? I'm running into an issue where websocket_init is never called.

@MauricevanLeeuwen

This comment has been minimized.

Copy link

@MauricevanLeeuwen MauricevanLeeuwen commented May 31, 2018

@AquaGeek and others: the function definitions changed in Cowboy 2.0. If you use @behaviour :cowboy_websocket your compiler will warn you. Implement init/2 instead:

defmodule MySocket do
@behaviour :cowboy_websocket
    def init(req, state) do
        {:cowboy_websocket, req, state}
    end
    def websocket_init(state) do
        state = %{}
        {:ok, state}
    end

    def websocket_handle({:text, message}, state) do
        json = Poison.decode!(message)
        websocket_handle({:json, json}, state)
    end

    def websocket_handle({:json, _}, state) do
        {:reply, {:text, "hello world"}, state}
    end
    def websocket_info(info, state) do
        {:reply, state}
    end

    def terminate(_reason, _req, _state) do
        :ok
    end
end
@cybrox

This comment has been minimized.

Copy link

@cybrox cybrox commented Feb 11, 2019

For anyone finding this now, with plug_cowboy, the name of the handler module has changed.

  def dispatch do
    [
      {:_, [
        {"/socket", MyApp.Socket, []},
        {:_, Plug.Cowboy.Handler, {MyApp.Router, []}}
      ]}
    ]
  end
@pietro909

This comment has been minimized.

Copy link

@pietro909 pietro909 commented Jun 30, 2019

It looks like my_app.ex is not working. I'm getting this error:

** (Mix) Could not start application spb_api: exited in: MyApp.Application.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (UndefinedFunctionError) function Plug.Cowboy.child_spec/4 is undefined or private

Using {:plug_cowboy, "~> 2.0"},

@shijithkjayan

This comment has been minimized.

Copy link

@shijithkjayan shijithkjayan commented Apr 7, 2020

How can I reject a websocket connection from websocket_init/1 ?

I want to send an {:error, reason} tuple while rejecting so that I can pattern match the reason in the terminate/3 function and do some extra process there.

@KristerV

This comment has been minimized.

Copy link

@KristerV KristerV commented May 6, 2020

Found this tutorial that's more easily readable: https://medium.com/@loganbbres/elixir-websocket-chat-example-c72986ab5778

@brunoripa

This comment has been minimized.

Copy link

@brunoripa brunoripa commented May 15, 2021

For anyone finding this now, with plug_cowboy, the name of the handler module has changed.

  def dispatch do
    [
      {:_, [
        {"/socket", MyApp.Socket, []},
        {:_, Plug.Cowboy.Handler, {MyApp.Router, []}}
      ]}
    ]
  end

You solved my headache, buddy :)

@kevinlang

This comment has been minimized.

Copy link

@kevinlang kevinlang commented Aug 25, 2021

I created a library to make the Cowboy and Plug.Router integration much easier.

https://github.com/kevinlang/plug_socket

Essentially:

defmodule MyApp.Router
  use Plug.Router
  use PlugSocket

  socket "/ws", MyApp.SocketHandler

  plug :match
  plug :dispatch
  
  match _ do
    send_resp(conn, 200, "Hello from plug")
  end
end
defmodule MyApp do
  use Application

  def start(_type, _args) do
    children = [
      {Plug.Cowboy, scheme: :http, plug: MyApp.Router, options: [
        dispatch: PlugSocket.plug_cowboy_dispatch(MyApp.Router)
      ]}
    ]
  
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end

end
@gamecubate

This comment has been minimized.

Copy link

@gamecubate gamecubate commented Oct 16, 2021

Thanks for this Gist. Also, 👍 @kevinlang for further reductions/simplifications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment