查看源代码 Phoenix.Socket.Transport 行为 (Phoenix v1.7.14)

概述 Socket <-> Transport 通信。

每个传输,例如 WebSockets 和长轮询,都必须与套接字交互。该模块定义了这种行为。

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 的特殊原子来指定客户端终止了连接。

启动

每当你的端点启动时,它将自动调用每个列出的套接字上的 child_spec/1,并在端点主管下启动该规范。

由于套接字监管树由端点启动,因此任何自定义传输都必须在监管树中的端点之后启动。

摘要

回调

返回用于套接字管理的子规范。

连接到套接字。

返回用于终止套接字的子规范。

处理传入的控制帧。

处理传入的套接字消息。

处理信息消息。

初始化套接字状态。

在终止时调用。

函数

检查 origin 请求头是否与允许的来源列表匹配。

检查 Websocket 子协议请求头是否与允许的子协议匹配。

如果启用了代码重新加载,则运行代码重新加载器。

conn 中提取连接信息并返回一个映射。

记录传输请求。

类型

@type state() :: term()

回调

@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} 时,一些传输(例如 WebSockets)允许通过自定义 :error_handler 根据 reason 自定义响应。

此函数用于授权目的,它可能在有效运行套接字的进程之外调用。

在默认的 Phoenix.Socket 实现中,元数据期望以下键

  • :endpoint - 应用程序端点
  • :transport - 传输名称
  • :params - 连接参数
  • :options - 传输选项的关键字列表,通常由开发人员在配置传输时提供。它必须包含一个带有序列化程序列表及其要求的 :serializer 字段
链接到此回调

drainer_spec(keyword)

查看源代码 (可选)
@callback drainer_spec(keyword()) :: :supervisor.child_spec() | :ignore

返回用于终止套接字的子规范。

这是一个在监管树中较晚启动的进程,其特定目标是在应用程序关闭时排空连接。

child_spec/1 类似,它从端点接收套接字选项。

链接到此回调

handle_control({}, 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} - 停止套接字

仅在使用 WebSockets 时支持控制帧。

options 包含一个 opcode 键,它将是 :ping:pong

如果控制帧没有有效负载,则有效负载值将为 nil

@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 原子和一个消息,该消息可以是任何项。内置的 WebSockets 传输支持 :text:binary opcode,并且消息必须始终是 iodata。长轮询仅支持文本 opcode。

链接到此回调

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 原子和一个消息,该消息可以是任何项。内置的 WebSockets 传输支持 :text:binary opcode,并且消息必须始终是 iodata。长轮询仅支持文本 opcode。

@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 标头与允许的来源匹配,则没有发送 origin 标头或没有配置 origin,它将返回给定的连接。

否则,将发送 403 禁止响应,并将连接停止。如果连接已停止,它将是一个空操作。

链接到此函数

check_subprotocols(conn, subprotocols)

查看源代码

检查 Websocket 子协议请求头是否与允许的子协议匹配。

应该在适当的情况下由传输在连接之前调用。如果 sec-websocket-protocol 标头与允许的子协议匹配,它将设置 sec-websocket-protocol 响应标头并返回给定的连接。如果没有发送 sec-websocket-protocol 标头,它将返回给定的连接。

否则,将发送 403 禁止响应,并将连接停止。如果连接已停止,它将是一个空操作。

链接到此函数

code_reload(conn, endpoint, opts)

查看源代码

如果启用了代码重新加载,则运行代码重新加载器。

链接到此函数

connect_info(conn, endpoint, keys)

查看源代码

conn 中提取连接信息并返回一个映射。

键从可选的传输选项 :connect_info 中检索。此功能是特定于传输的。有关更多信息,请参阅你的传输文档。

支持的键为

  • :peer_data - Plug.Conn.get_peer_data/1 的结果

  • :trace_context_headers - 所有跟踪上下文标头的列表

  • :x_headers - 所有以“x-”为前缀的请求标头的列表

  • :uri - 从 conn 派生的 %URI{}

  • :user_agent - “user-agent” 请求标头的值

链接到此函数

transport_log(conn, level)

查看源代码

记录传输请求。

适用于生成连接的传输。