Phoenix.Socket поведение
Исходный кодРеализация сокета, которая мультиплексирует сообщения через каналы.
Phoenix.Socket используется как модуль для установки соединения между клиентом и сервером. После установки соединения начальное состояние сохраняется в структуре Phoenix.Socket.
Один и тот же сокет можно использовать для получения событий от разных транспортов. Phoenix поддерживает параметры websocket и longpoll при вызове Phoenix.Endpoint.socket/3 в эндпоинте. По умолчанию задано websocket, а longpoll также можно настроить явно.
socket "/socket", MyAppWeb.Socket, websocket: true, longpoll: false
Приведённая выше команда позволяет устанавливать входящие соединения с сокетом через WebSocket-соединение. Входящие и исходящие события направляются в каналы по топику:
channel "room:lobby", MyAppWeb.LobbyChannel
Дополнительную информацию о каналах см. в разделе Phoenix.Channel.
Поведение сокета
Обработчики сокетов подключаются в эндпоинтах и должны определять два обратных вызова:
connect/3— получает параметры сокета и, если есть, сведения о соединении, а также аутентифицирует соединение. Должен возвращать структуруPhoenix.Socket, часто с пользовательскими assignsid/1— получает сокет, возвращённыйconnect/3, и возвращает идентификатор этого соединения в виде строки.idиспользуется для идентификации соединений сокетов, часто принадлежащих определённому пользователю, что позволяет принудительно разрывать соединения. Для сокетов, не требующих аутентификации, можно вернутьnil
Примеры
defmodule MyAppWeb.UserSocket do
use Phoenix.Socket
channel "room:*", MyAppWeb.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
MyAppWeb.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
Поля сокета
-
:id— строковый идентификатор сокета -
:assigns— map с assigns сокета, значение по умолчанию:%{} -
:channel— текущий модуль канала -
:channel_pid— pid канала -
:endpoint— модуль эндпоинта, из которого был создан этот сокет, например:MyAppWeb.Endpoint -
:handler— модуль сокета, из которого был создан этот сокет, например:MyAppWeb.UserSocket -
:joined— присоединился ли сокет к каналу -
:join_ref— ref, отправленный клиентом при присоединении -
:ref— последний ref, отправленный клиентом -
:pubsub_server— зарегистрированное имя pubsub-сервера сокета -
:topic— строковый топик, например"room:123" -
:transport— идентификатор транспорта, используемый для ведения журнала -
:transport_pid— pid процесса транспорта сокета -
:serializer— сериализатор сообщений сокета
Использование параметров
Для use Phoenix.Socket принимаются следующие параметры:
:log— уровень ведения журнала по умолчанию для действий сокета. По умолчанию —:info. Можно задатьfalse, чтобы отключить ведение журнала:partitions— каждый канал запускается под супервизором. Этот параметр задаёт количество супервизоров, запускаемых для обработки каналов. По умолчанию используется количество ядер.
Сборка мусора
Можно принудительно запустить сборку мусора в процессе транспорта после обработки больших сообщений. Например, чтобы запустить её из каналов, выполните:
send(socket.transport_pid, :garbage_collect)
Также можно настроить сокет эндпоинта на более частый запуск полной сборки мусора, задав параметр :fullsweep_after для WebSocket. Дополнительную информацию см. в разделе Phoenix.Endpoint.socket/3.
Обмен данными между клиентом и сервером
Кодирование данных сервера и декодирование данных клиента выполняются согласно сериализатору, определённому в Phoenix.Socket.Serializer. По умолчанию для передачи сообщений клиентам и от них используется кодирование JSON.
Функция сериализатора decode! должна возвращать Phoenix.Socket.Message, который передаётся каналам, за исключением следующих случаев:
-
"heartbeat"— события в топике "phoenix": следует просто отправить ответ OK -
"phx_join"для любого топика — следует присоединиться к топику -
"phx_leave"для любого топика — следует покинуть топик
У каждого сообщения также есть поле ref, используемое для отслеживания ответов.
Сервер может отправлять сообщения или ответы. Для сообщений ref уникально идентифицирует сообщение. Для ответов ref совпадает с ref исходного сообщения. Оба типа данных также содержат join_ref, который уникально идентифицирует текущий канал, к которому выполнено присоединение.
Реализация Phoenix.Socket также может отправлять специальные сообщения и ответы:
"phx_error"— в случае ошибок, например при аварийном завершении процесса канала или попытке присоединиться к каналу, к которому уже выполнено присоединение"phx_close"— канал был корректно закрыт
В Phoenix входят реализации WebSocket и long polling на JavaScript, взаимодействующие с Phoenix.Socket. Их можно использовать как образец при создании собственных клиентов.
Пользовательские сокеты и транспорты
Дополнительную информацию о создании собственного сокета, не использующего каналы, или собственных транспортов для взаимодействия с другими сокетами см. в документации Phoenix.Socket.Transport.
Пользовательские каналы
Любой модуль можно указать как канал, если он реализует функцию child_spec/1. Функция child_spec/1 получает вызывающий процесс в качестве аргумента и должна возвращать спецификацию дочернего процесса, которая запускает процесс.
После инициализации процесс получит следующее сообщение:
{Phoenix.Channel, auth_payload, from, socket}
Пользовательская реализация канала ОБЯЗАНА вызвать GenServer.reply(from, {:ok | :error, reply_payload}) во время инициализации и передать пользовательский reply_payload, который будет отправлен клиенту в качестве ответа. Если этого не сделать, сокет будет заблокирован навсегда.
Пользовательский канал получает структуры Phoenix.Socket.Message в виде обычных сообщений от транспорта. Ответы на эти сообщения и пользовательские сообщения можно в любой момент отправить сокету, создав соответствующие структуры Phoenix.Socket.Reply и Phoenix.Socket.Message, закодировав их с помощью сериализатора и передав сериализованный результат транспорту.
Например, для обработки сообщений "phx_leave", которые рекомендуется обрабатывать во всех реализациях каналов, можно использовать следующий код:
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
Особое сообщение, доставляемое всем каналам, — это Broadcast с событием "phx_drain", отправляемое при остановке сокета во время завершения работы приложения. Обычно его обрабатывают, отправляя транспорту сообщение drain, которое приводит к его остановке:
def handle_info(
%Broadcast{event: "phx_drain"},
%{transport_pid: transport_pid} = socket
) do
send(transport_pid, :socket_drain)
{:stop, {:shutdown, :draining}, socket}
end
Кроме того, мы рекомендуем всем каналам отслеживать transport_pid на init и завершаться, если завершается транспорт. Мы также рекомендуем преобразовывать причины завершения :normal (обычно возникающие из-за закрытия сокета) в {:shutdown, :closed}, чтобы гарантировать разрыв связей при завершении канала (завершение :normal не разрывает связи):
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
Уровень сокета рассматривает завершение любого процесса как ошибку, если перед остановкой сокету не было отправлено сообщение {:socket_close, pid, reason}.
Пользовательские реализации каналов нельзя тестировать с помощью Phoenix.ChannelTest.
Краткое описание
Типы
- connect(params, t)
Упрощённый вариант
connect/3, который не получаетconnect_info.- connect(params, t, connect_info)
Получает параметры сокета и аутентифицирует соединение.
- id(t)
Идентифицирует соединение сокета.
Обратные вызовы
- assign(socket, keyword_or_map)
Добавляет пары ключ/значение в assigns сокета. Принимает список ключевых слов, map или функцию с одним аргументом.
- assign(socket, key, value)
Добавляет пару
key/valueв assigns объектаsocket.- channel(topic_pattern, module, opts \\ [])
Определяет канал, соответствующий заданному топику и транспортам.
Функции
Типы
t()Исходный код
@type t() :: %Phoenix.Socket{
assigns: map(),
channel: atom(),
channel_pid: pid(),
endpoint: atom(),
handler: atom(),
id: String.t() | nil,
join_ref: term(),
joined: boolean(),
private: map(),
pubsub_server: atom(),
ref: term(),
serializer: atom(),
topic: String.t(),
transport: atom(),
transport_pid: pid()
} Обратные вызовы
connect(params, t)Исходный код
@callback connect(params :: map(), t()) :: {:ok, t()} | {:error, term()} | :error Упрощённый вариант connect/3, который не получает connect_info.
Предоставлен для обратной совместимости.
connect(params, t, connect_info)Исходный код
@callback connect(params :: map(), t(), connect_info :: map()) ::
{:ok, t()} | {:error, term()} | :error Получает параметры сокета и аутентифицирует соединение.
Параметры и assigns сокета
Параметры сокета передаются клиентом и могут использоваться для проверки и аутентификации пользователя. После проверки в сокет можно добавить assigns по умолчанию, которые будут заданы для всех каналов, например:
{:ok, assign(socket, :user_id, verified_user_id)}
Чтобы отклонить соединение, верните :error или {:error, term}. Чтобы управлять ответом, который в таком случае получит клиент, задайте обработчик ошибок в конфигурации WebSocket.
Примеры проверки токена при подключении см. в документации Phoenix.Token.
id(t)Исходный код
@callback id(t()) :: String.t() | nil
Идентифицирует соединение сокета.
Идентификаторы сокетов — это топики, позволяющие идентифицировать все сокеты заданного пользователя:
def id(socket), do: "users_socket:#{socket.assigns.user_id}"
Это позволит отправить событие "disconnect" и завершить все активные сокеты и каналы заданного пользователя:
MyAppWeb.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
Возврат nil делает этот сокет анонимным.
Функции
assign(socket, keyword_or_map)Исходный код
Добавляет пары ключ/значение в assigns сокета. Принимает список ключевых слов, map или функцию с одним аргументом.
Если передан список ключевых слов или map, он будет объединён с существующими assigns.
Если передана функция, она получает текущие assigns в качестве аргумента, а её возвращаемое значение объединяется с текущими assigns.
Примеры
iex> assign(socket, name: "Elixir", logo: "💧")
iex> assign(socket, %{name: "Elixir"})
iex> assign(socket, fn %{name: name, logo: logo} -> %{title: Enum.join([name, logo], " | ")} end) assign(socket, key, value)Исходный код
Добавляет пару key/value в assigns объекта socket.
Чтобы добавить несколько пар ключ/значение, см. также assign/2.
Примеры
iex> assign(socket, :name, "Elixir")
channel(topic_pattern, module, opts \\ [])Исходный код
Определяет канал, соответствующий заданному топику и транспортам.
-
topic_pattern— строковый шаблон, например"room:*","users:*"или"system" -
module— модуль-обработчик канала, напримерMyAppWeb.RoomChannel -
opts— необязательный список параметров, см. ниже
Параметры
-
:assigns— map с assigns сокета, объединяемый с сокетом при присоединении
Примеры
channel "topic1:*", MyChannel
Шаблоны топиков
Макрос channel принимает шаблоны топиков двух видов. В качестве последнего символа можно указать звёздочку (символ *), чтобы обозначить совпадение "topic:subtopic". Если указана обычная строка, обработчику канала будет соответствовать только этот топик. В большинстве случаев используется шаблон "topic:*", позволяющий гибче задавать область действия топиков.
Дополнительную информацию см. в разделе Phoenix.Channel.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/Phoenix.Socket.html