Current section

Files

Jump to
phoenix lib phoenix endpoint cowboy_websocket.ex
Raw

lib/phoenix/endpoint/cowboy_websocket.ex

defmodule Phoenix.Endpoint.CowboyWebSocket do
# Implementation of the WebSocket transport for Cowboy.
@moduledoc false
@behaviour :cowboy_websocket_handler
@connection Plug.Adapters.Cowboy.Conn
@already_sent {:plug_conn, :sent}
def init({transport, :http}, req, {module, opts}) when transport in [:tcp, :ssl] do
conn = @connection.conn(req, transport)
try do
case module.init(conn, opts) do
{:ok, %{adapter: {@connection, req}}, args} ->
{:upgrade, :protocol, __MODULE__, req, args}
{:error, %{adapter: {@connection, req}}} ->
{:shutdown, req, :no_state}
end
catch
kind, reason ->
# Although we are not performing a call, we are using the call
# function for now so it is properly handled in error reports.
mfa = {module, :call, [conn, opts]}
{:upgrade, :protocol, __MODULE__, req, {:error, mfa, kind, reason, System.stacktrace}}
after
receive do
@already_sent -> :ok
after
0 -> :ok
end
end
end
def upgrade(req, env, __MODULE__, {handler, opts}) do
args = [req, env, __MODULE__, {handler, opts}]
resume(:cowboy_websocket, :upgrade, args)
end
def upgrade(_req, _env, __MODULE__, {:error, mfa, kind, reason, stack}) do
reason = format_reason(kind, reason, stack)
exit({reason, mfa})
end
def terminate(_reason, _req, _state) do
:ok
end
def resume(module, fun, args) do
try do
apply(module, fun, args)
catch
kind, [{:reason, reason}, {:mfa, _mfa}, {:stacktrace, stack} | _rest] ->
reason = format_reason(kind, reason, stack)
exit({reason, {__MODULE__, :resume, []}})
else
{:suspend, module, fun, args} ->
{:suspend, __MODULE__, :resume, [module, fun, args]}
_ ->
# We are forcing a shutdown exit because we want to make
# sure all transports exits with reason shutdown to guarantee
# all channels are closed.
exit(:shutdown)
end
end
defp format_reason(:exit, reason, _), do: reason
defp format_reason(:throw, reason, stack), do: {{:nocatch, reason}, stack}
defp format_reason(:error, reason, stack), do: {reason, stack}
## Websocket callbacks
def websocket_init(_transport, req, {handler, args}) do
{:ok, state, timeout} = handler.ws_init(args)
{:ok, :cowboy_req.compact(req), {handler, state}, timeout}
end
def websocket_handle({opcode = :text, payload}, req, {handler, state}) do
handle_reply req, handler, handler.ws_handle(opcode, payload, state)
end
def websocket_handle({opcode = :binary, payload}, req, {handler, state}) do
handle_reply req, handler, handler.ws_handle(opcode, payload, state)
end
def websocket_handle(_other, req, {handler, state}) do
{:ok, req, {handler, state}}
end
def websocket_info(message, req, {handler, state}) do
handle_reply req, handler, handler.ws_info(message, state)
end
def websocket_terminate({:error, :closed}, _req, {handler, state}) do
handler.ws_close(state)
:ok
end
def websocket_terminate({:remote, :closed}, _req, {handler, state}) do
handler.ws_close(state)
:ok
end
def websocket_terminate({:remote, code, _}, _req, {handler, state})
when code in 1000..1003 or code in 1005..1011 or code == 1015 do
handler.ws_close(state)
:ok
end
def websocket_terminate(reason, _req, {handler, state}) do
handler.ws_terminate(reason, state)
:ok
end
defp handle_reply(req, handler, {:shutdown, new_state}) do
{:shutdown, req, {handler, new_state}}
end
defp handle_reply(req, handler, {:ok, new_state}) do
{:ok, req, {handler, new_state}}
end
defp handle_reply(req, handler, {:reply, {opcode, payload}, new_state}) do
{:reply, {opcode, payload}, req, {handler, new_state}}
end
end