Phoenix.Socket

Parsed documentation:
View on GitHub
A socket implementation that multiplexes messages over channels.

`Phoenix.Socket` is used as a module for establishing and maintaining
the socket state via the `Phoenix.Socket` struct.

Once connected to a socket, incoming and outgoing events are routed to
channels. The incoming client data is routed to channels via transports.
It is the responsibility of the socket to tie transports and channels
together.

By default, Phoenix supports both websockets and longpoll when invoking
`Phoenix.Endpoint.socket/3` in your endpoint:

    socket "/socket", MyApp.Socket, websocket: true, longpoll: false

The command above means incoming socket connections can be made via
a WebSocket connection. Events are routed by topic to channels:

    channel "room:lobby", MyApp.LobbyChannel

See `Phoenix.Channel` for more information on channels.

## Socket Behaviour

Socket handlers are mounted in Endpoints and must define two callbacks:

  * `connect/3` - receives the socket params, connection info if any, and
    authenticates the connection. Must return a `Phoenix.Socket` struct,
    often with custom assigns
  * `id/1` - receives the socket returned by `connect/3` and returns the
    id of this connection as a string. The `id` is used to identify socket
    connections, often to a particular user, allowing us to force disconnections.
    For sockets requiring no authentication, `nil` can be returned

## Examples

    defmodule MyApp.UserSocket do
      use Phoenix.Socket

      channel "room:*", MyApp.RoomChannel

      def connect(params, socket, _connect_info) do
        {:ok, assign(socket, :user_id, params["user_id"])}
      end

      def id(socket), do: "users_socket:#{socket.assigns.user_id}"
    end

    # Disconnect all user's socket connections and their multiplexed channels
    MyApp.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})

## Socket fields

  * `:id` - The string id of the socket
  * `:assigns` - The map of socket assigns, default: `%{}`
  * `:channel` - The current channel module
  * `:channel_pid` - The channel pid
  * `:endpoint` - The endpoint module where this socket originated, for example: `MyApp.Endpoint`
  * `:handler` - The socket module where this socket originated, for example: `MyApp.UserSocket`
  * `:joined` - If the socket has effectively joined the channel
  * `:join_ref` - The ref sent by the client when joining
  * `:ref` - The latest ref sent by the client
  * `:pubsub_server` - The registered name of the socket's pubsub server
  * `:topic` - The string topic, for example `"room:123"`
  * `:transport` - An identifier for the transport, used for logging
  * `:transport_pid` - The pid of the socket's transport process
  * `:serializer` - The serializer for socket messages

## Logging

Logging for socket connections is set via the `:log` option, for example:

    use Phoenix.Socket, log: :debug

Defaults to the `:info` log level. Pass `false` to disable logging.

## Garbage collection

It's possible to force garbage collection in the transport process after
processing large messages. For example, to trigger such from your channels,
run:

    send(socket.transport_pid, :garbage_collect)

## Client-server communication

The encoding of server data and the decoding of client data is done
according to a serializer, defined in `Phoenix.Socket.Serializer`.
By default, JSON encoding is used to broker messages to and from
clients with `Phoenix.Socket.V2.JSONSerializer`.

The serializer `decode!` function must return a `Phoenix.Socket.Message`
which is forwarded to channels except:

  * `"heartbeat"` events in the "phoenix" topic - should just emit an OK reply
  * `"phx_join"` on any topic - should join the topic
  * `"phx_leave"` on any topic - should leave the topic

Each message also has a `ref` field which is used to track responses.

The server may send messages or replies back. For messages, the
ref uniquely identifies the message. For replies, the ref matches
the original message. Both data-types also include a join_ref that
uniquely identifes the currently joined channel.

The `Phoenix.Socket` implementation may also sent special messages
and replies:

  * `"phx_error"` - in case of errors, such as a channel process
    crashing, or when attempting to join an already joined channel

  * `"phx_close"` - the channel was gracefully closed

Phoenix ships with a JavaScript implementation of both websocket
and long polling that interacts with Phoenix.Socket and can be
used as reference for those interested in implementing custom clients.

## Custom sockets and transports

See the `Phoenix.Socket.Transport` documentation for more information on
writing your own socket that does not leverage channels or for writing
your own transports that interacts with other sockets.

## Custom channels

You can list any module as a channel as long as it implements
a `start_link/1` function that receives a tuple with three elements:

    {auth_payload, from, socket}

A custom channel implementation MUST invoke
`GenServer.reply(from, reply_payload)` during its initialization
with a custom `reply_payload` that will be sent as a reply to the
client. Failing to do so will block the socket forever.

A custom channel receives `Phoenix.Socket.Message` structs as regular
messages from the transport. Replies to those messages and custom
messages can be sent to the socket at any moment by building an
appropriate `Phoenix.Socket.Reply` and `Phoenix.Socket.Message`
structs, encoding them with the serializer and dispatching the
serialized result to the transport.

For example, to handle "phx_leave" messages, which is recommended
to be handled by all channel implementations, one may do:

    def handle_info(
          %Message{topic: topic, event: "phx_leave"} = message,
          %{topic: topic, serializer: serializer, transport_pid: transport_pid} = socket
        ) do
      send transport_pid, serializer.encode!(build_leave_reply(message))
      {:stop, {:shutdown, :left}, socket}
    end

We also recommend all channels to monitor the `transport_pid`
on `init` and exit if the transport exits. We also advise to rewrite
`:normal` exit reasons (usually due to the socket being closed)
to the `{:shutdown, :closed}` to guarantee links are broken on
the channel exit (as a `:normal` exit does not break links):

    def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do
      reason = if reason == :normal, do: {:shutdown, :closed}, else: reason
      {:stop, reason, socket}
    end

Any process exit is treated as an error by the socket layer unless
a `{:socket_close, pid, reason}` message is sent to the socket before
shutdown.

Custom channel implementations cannot be tested with `Phoenix.ChannelTest`
and are currently considered experimental. The underlying API may be
changed at any moment.

**Note:** in future Phoenix versions we will require custom channels
to provide a custom `child_spec/1` function instead of `start_link/1`.
Since the default behaviour of `child_spec/1` is to invoke `start_link/1`,
this behaviour should be backwards compatible in almost all cases.
No suggestions.
Please help! Open an issue on GitHub if this assessment is incorrect.