Phoenix.Socket.Transport поведение
Исходный кодОписывает взаимодействие сокета и транспорта.
Каждый транспорт, например веб-сокеты и long polling, должен взаимодействовать с сокетом. Этот модуль определяет функции, которые транспорт будет вызывать в заданной реализации сокета.
Phoenix.Socket — лишь одна из возможных реализаций сокета, мультиплексирующая события между несколькими каналами. Если вы реализуете это поведение, существующие транспорты смогут использовать вашу новую реализацию сокета без передачи данных через каналы.
Этот модуль также содержит рекомендации и вспомогательные функции для реализации транспортов. Хотя его основная цель — помочь определить пользовательские сокеты.
Пример
Вот простая реализация эхо-сокета:
defmodule EchoSocket do
@behaviour Phoenix.Socket.Transport
def child_spec(opts) do
# We won't spawn any process, so let's ignore the child spec
:ignore
end
def connect(state) do
# Callback to retrieve relevant data from the connection.
# The map contains options, params, transport and endpoint keys.
{:ok, state}
end
def init(state) do
# Now we are effectively inside the process that maintains the socket.
{:ok, state}
end
def handle_in({text, _opts}, state) do
{:reply, :ok, {:text, text}, state}
end
def handle_info(_, state) do
{:ok, state}
end
def terminate(_reason, _state) do
:ok
end
end
Его можно подключить к вашей конечной точке так же, как любой другой сокет:
socket "/socket", EchoSocket, websocket: true, longpoll: true
Теперь вы можете взаимодействовать с сокетом по адресам /socket/websocket и /socket/longpoll.
Пользовательские транспорты
Работа сокетов обеспечивается транспортом. При определении транспорта ему обычно передаётся модуль сокета, который вызывается при возникновении определённых событий на уровне транспорта. Транспорт может вызывать обратные вызовы, определённые в этом модуле.
Когда транспорт получает новое соединение, он должен вызвать обратный вызов connect/1 с картой метаданных. Для разных сокетов могут требоваться разные метаданные.
Если соединение принято, транспорт может при желании передать его другому процессу или продолжить использовать тот же процесс. Затем процесс, отвечающий за управление сокетом, должен вызвать init/1.
Для каждого сообщения, полученного от клиента, транспорт должен вызвать для сокета handle_in/2. Для каждого информационного сообщения, полученного транспортом, он должен вызвать для сокета handle_info/2.
Транспорты могут дополнительно реализовать handle_control/2 для обработки управляющих кадров, таких как :ping и :pong.
При завершении работы необходимо вызвать terminate/2. Чтобы указать, что соединение завершил клиент, можно использовать специальный атом с причиной :closed.
Запуск
Когда вы указываете сокет в Phoenix.Endpoint.socket/3, Phoenix автоматически запускает модуль сокета в дереве супервизии, однако Phoenix не управляет транспортом.
При каждом запуске конечной точки Phoenix вызывает child_spec/1 для каждого указанного сокета и запускает эту спецификацию под супервизором конечной точки. Поскольку дерево супервизии сокета запускается конечной точкой, любой пользовательский транспорт необходимо запускать после конечной точки.
Сводка
Типы
- child_spec(keyword)
Возвращает спецификацию дочернего процесса для управления сокетом.
- connect(transport_info)
Подключается к сокету.
- drainer_spec(keyword)
Возвращает спецификацию дочернего процесса для завершения работы сокета.
- handle_control(tuple, state)
Обрабатывает входящие управляющие кадры.
- handle_in(tuple, state)
Обрабатывает входящие сообщения сокета.
- handle_info(message, state)
Обрабатывает информационные сообщения.
- init(state)
Инициализирует состояние сокета.
- terminate(reason, state)
Вызывается при завершении работы.
Обратные вызовы
- check_origin(conn, handler, endpoint, opts, sender \\ &Plug.Conn.send_resp/1)
Проверяет заголовок запроса origin на соответствие списку разрешённых источников.
- check_subprotocols(conn, subprotocols)
Проверяет заголовок запроса WebSocket subprotocols на соответствие разрешённым подпротоколам.
- code_reload(conn, endpoint, opts)
Запускает перезагрузчик кода, если он включён.
- connect_info(conn, endpoint, keys, opts \\ [])
Извлекает сведения о соединении из
connи возвращает карту.- transport_log(conn, level)
Записывает запрос транспорта в журнал.
Функции
Типы
state()Исходный код
@type state() :: term()
Обратные вызовы
child_spec(keyword)Исходный код
@callback child_spec(keyword()) :: :supervisor.child_spec() | :ignore
Возвращает спецификацию дочернего процесса для управления сокетом.
Этот обратный вызов вызывается для каждого сокета только один раз, независимо от количества транспортов. Он должен отвечать за настройку структуры процессов, используемой исключительно сокетом вне зависимости от транспорта.
Каждое соединение с сокетом запускается транспортом, и процесс, управляющий сокетом, скорее всего, принадлежит транспорту. Однако некоторые сокеты порождают новые процессы, например Phoenix.Socket, который порождает каналы. Это позволяет запустить дерево супервизии, связанное с сокетом.
Он получает параметры сокета от конечной точки, например:
socket "/my_app", MyApp.Socket, shutdown: 5000
означает, что будет вызван child_spec([shutdown: 5000]).
:ignore означает, что для этого сокета спецификация дочернего процесса не требуется.
connect(transport_info)Исходный код
@callback connect(transport_info :: map()) :: {:ok, state()} | {:error, term()} | :error Подключается к сокету.
Транспорт передаёт карту метаданных, а сокет возвращает {:ok, state}, {:error, reason} или :error. Транспорт должен сохранять состояние и возвращать его при всех последующих операциях. Если возвращено {:error, reason}, некоторые транспорты, например WebSocket, позволяют настроить ответ на основе reason с помощью пользовательского :error_handler.
Эта функция используется для авторизации и может быть вызвана не из процесса, который фактически обслуживает сокет.
В стандартной реализации Phoenix.Socket в метаданных ожидаются следующие ключи:
-
:endpoint— конечная точка приложения -
:transport— имя транспорта -
:params— параметры соединения -
:options— список ключевых слов с параметрами транспорта, обычно задаваемыми разработчиками при настройке транспорта. Он должен содержать поле:serializerсо списком сериализаторов и их требованиями
drainer_spec(keyword)Исходный код
@callback drainer_spec(keyword()) :: :supervisor.child_spec() | :ignore
Возвращает спецификацию дочернего процесса для завершения работы сокета.
Этот процесс запускается поздно в дереве супервизии специально для завершения соединений при остановке приложения.
Как и child_spec/1, он получает параметры сокета от конечной точки.
handle_control(tuple, state)Исходный код
@callback handle_control(
{message :: term(), opts :: keyword()},
state()
) ::
{:ok, state()}
| {:reply, :ok | :error, {opcode :: atom(), message :: term()}, state()}
| {:stop, reason :: term(), state()} Обрабатывает входящие управляющие кадры.
Сообщение представлено в виде {payload, options}. Функция должна вернуть одно из следующих значений:
-
{:ok, state}— продолжает работу сокета без ответа -
{:reply, status, reply, state}— продолжает работу сокета с ответом -
{:stop, reason, state}— останавливает сокет
Управляющие кадры поддерживаются только при использовании веб-сокетов.
В options содержится ключ opcode, значением которого будет либо :ping, либо :pong.
Если у управляющего кадра нет полезной нагрузки, её значение будет nil.
handle_in(tuple, state)Исходный код
@callback handle_in(
{message :: term(), opts :: keyword()},
state()
) ::
{:ok, state()}
| {:reply, :ok | :error, {opcode :: atom(), message :: term()}, state()}
| {:stop, reason :: term(), state()} Обрабатывает входящие сообщения сокета.
Сообщение представлено в виде {payload, options}. Функция должна вернуть одно из следующих значений:
-
{:ok, state}— продолжает работу сокета без ответа -
{:reply, status, reply, state}— продолжает работу сокета с ответом -
{:stop, reason, state}— останавливает сокет
reply — это кортеж, содержащий атом opcode и сообщение, которым может быть любой терм. Встроенный транспорт WebSocket поддерживает опкоды :text и :binary, а сообщение всегда должно быть iodata. Long polling поддерживает только текстовый опкод.
handle_info(message, state)Исходный код
@callback handle_info(message :: term(), state()) ::
{:ok, state()}
| {:push, {opcode :: atom(), message :: term()}, state()}
| {:stop, reason :: term(), state()} Обрабатывает информационные сообщения.
Сообщение является термом. Функция должна вернуть одно из следующих значений:
-
{:ok, state}— продолжает работу сокета без ответа -
{:push, reply, state}— продолжает работу сокета с ответом -
{:stop, reason, state}— останавливает сокет
reply — это кортеж, содержащий атом opcode и сообщение, которым может быть любой терм. Встроенный транспорт WebSocket поддерживает опкоды :text и :binary, а сообщение всегда должно быть iodata. Long polling поддерживает только текстовый опкод.
init(state)Исходный код
@callback init(state()) :: {:ok, state()} Инициализирует состояние сокета.
Функция должна выполняться в процессе, который фактически будет обслуживать сокет.
terminate(reason, state)Исходный код
@callback terminate(reason :: term(), state()) :: :ok
Вызывается при завершении работы.
Если reason — это :closed, значит, клиент закрыл сокет. Это считается сигналом завершения :normal, поэтому связанный процесс не завершится автоматически. Подробнее о сигналах завершения см. в разделе Process.exit/2.
Функции
check_origin(conn, handler, endpoint, opts, sender \\ &Plug.Conn.send_resp/1)Исходный код
Проверяет заголовок запроса origin на соответствие списку разрешённых источников.
При необходимости эту функцию следует вызывать в транспортах перед подключением. Если заголовок origin соответствует разрешённым источникам, не был отправлен или источник не был настроен, функция вернёт переданное соединение.
В противном случае будет отправлен ответ 403 Forbidden, а соединение будет прервано. Если соединение уже прервано, функция ничего не делает.
check_subprotocols(conn, subprotocols)Исходный код
Проверяет заголовок запроса WebSocket subprotocols на соответствие разрешённым подпротоколам.
При необходимости эту функцию следует вызывать в транспортах перед подключением. Если заголовок sec-websocket-protocol соответствует разрешённым подпротоколам, функция установит заголовок ответа sec-websocket-protocol и вернёт переданное соединение. Если заголовок sec-websocket-protocol не был отправлен, функция вернёт переданное соединение.
В противном случае будет отправлен ответ 403 Forbidden, а соединение будет прервано. Если соединение уже прервано, функция ничего не делает.
code_reload(conn, endpoint, opts)Исходный код
Запускает перезагрузчик кода, если он включён.
connect_info(conn, endpoint, keys, opts \\ [])Исходный код
Извлекает сведения о соединении из conn и возвращает карту.
Ключи извлекаются из необязательного параметра транспорта :connect_info. Эта возможность зависит от транспорта. Дополнительную информацию см. в документации используемого транспорта.
Поддерживаются следующие ключи:
:peer_data— результат вызоваPlug.Conn.get_peer_data/1:trace_context_headers— список всех заголовков контекста трассировки:x_headers— список всех заголовков запроса с префиксом "x-":uri— значение%URI{}, полученное из conn:user_agent— значение заголовка запроса "user-agent":sec_websocket_headers— список всех заголовков запроса с префиксом "sec-websocket-"
Проверку CSRF можно отключить, установив для параметра :check_csrf значение false.
transport_log(conn, level)Исходный код
Записывает запрос транспорта в журнал.
Доступна для транспортов, создающих соединение.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/Phoenix.Socket.Transport.html