Skip to content

Instantly share code, notes, and snippets.

@alanpeabody
Last active March 24, 2024 13:28
Show Gist options
  • Save alanpeabody/4fae12b420fb50376af4 to your computer and use it in GitHub Desktop.
Save alanpeabody/4fae12b420fb50376af4 to your computer and use it in GitHub Desktop.
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
@brunoripa
Copy link

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
Copy link

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
Copy link

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