-
-
Save alanpeabody/4fae12b420fb50376af4 to your computer and use it in GitHub Desktop.
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 |
What version of Cowboy are you using? I'm running into an issue where websocket_init
is never called.
@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
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
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"},
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.
Found this tutorial that's more easily readable: https://medium.com/@loganbbres/elixir-websocket-chat-example-c72986ab5778
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 :)
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
Thanks for this Gist. Also, 👍 @kevinlang for further reductions/simplifications.
my_app_socket_handler.ex
missingdo
indefmodule