STOMP 协议规范 - 1.2 版

概要

STOMP 是一种简单的互操作协议,旨在通过中介服务器在客户端之间传递异步消息。 它为在这些客户端与服务端之间传递的消息定义了基于文本的连接格式。

STOMP 已活跃使用多年,并得到许多消息代理与客户端库的支持。 本规范定义了 STOMP 1.2 协议,并且是 STOMP 1.1 的更新。

请将反馈发送到 stomp-spec@googlegroups.com 邮件列表。

总览

背景

STOMP 源于需要通过脚本语言(例如:Ruby、Python 与 Perl)连接到企业消息代理的需求。 在这种环境中,通常会执行逻辑上简单的操作, 例如“可靠地发送单个消息并断开连接”或“消费给定目的地上的所有消息”。

它是其他开放消息协议(例如:AMQP)与 JMS 代理(例如:OpenWire)中使用的实现特定连接协议的替代。 它的独特之处是只涵盖了一小部分常用的消息传递操作,而不是提供一个全面的消息传递 API。

最近,STOMP 已经发展成为一种协议,就其现在提供的线路级特性而言, 可以通过这些简单的用例使用,但仍保持其简单性和互操作性的核心设计原则。

协议总览

STOMP 是基于帧的协议,其帧以 HTTP 为模型。 框架由命令,一组可选的标头与可选的正文组成。 STOMP 基于文本,但也允许传输二进制消息。 STOMP 的默认编码为 UTF-8,但是支持消息体的替代编码规范。

STOMP 服务端被建模为一组可以向其发送消息的目标。 STOMP 协议将目标视为不透明字符串,并且其语法是服务端实现特定的。 此外,STOMP 并未定义目的地的传递语义。 目的地的传递或“消息交换”的语义可能因服务端而异,甚至因目的地而异。 这使服务端可以利用 STOMP 支持的语义进行创新。

STOMP 客户端是一种用户代理,可以以两种(可能同时)模式运行:

协议的变化

STOMP 1.2 在很大程度上向后兼容 STOMP 1.1。 只有两个不兼容的更改:

除此之外,STOMP 1.2 没有引入任何新特性,而是着重于阐明规范的某些方面,例如:

设计理念

驱动 STOMP 设计的主要理念是简单性与互操作性。

STOMP 被设计为一种轻量级协议,易于在客户端和服务端以多种语言实现。 特别是,这意味着对服务端的体系结构没有太多限制, 并且诸如目标命名与可靠性语义之类的许多功能是特定于实现的。

在本规范中,将注意 STOMP 1.2 未明确定义的服务端特性。 应查阅 STOMP 服务端文档,了解这些特性的具体实现细节。

一致性

本文档中的关键字:

必须必需必要MUSTREQUIREDSHALL
意味着这是本规范的绝对要求。
绝不MUST NOTSHALL NOT
意味着这是本规范的绝对禁止。
应该建议SHOULDRECOMMENDED
表示在特定情况下可能存在忽略特定项目的正当理由,但是在选择不同的路线之前,必须理解并仔细权衡所有含义。
不该SHOULD NOT
这意味着在特定情况下,当特定的行为是可接受的甚至是有用的时,可能存在正当理由, 但在实施带有此标签描述的任何行为之前,应了解其全部含义并仔细权衡该案例。
可以可选MAYOPTIONAL
表示某项是真正可选的。
一个供应商可能选择包括该项目,因为某个特定的市场需要它,或者因为该供应商认为它增强了产品,而另一个供应商可能会忽略该产品。
一个不包含特定选项的实现 必须 准备与另一个包含该选项的实现互操作,尽管功能可能有所减少。
同样,一个确实包含特定选项的实现 必须 准备与另一个不包含该选项的实现互操作 (当然,该选项所提供的功能除外)。
按照 RFC 2119 中所述进行解释。

实现可能会对不受约束的输入施加特定于实现的限制, 例如:防止拒绝服务攻击、防止内存耗尽或绕过特定于平台的限制。

本规范定义的一致性类是 STOMP 客户端与 STOMP 服务端。

STOMP 帧

STOMP 是一种基于帧的协议,该协议假定底层具有可靠的双向流网络协议(例如:TCP)。 客户端与服务端将使用通过流发送的 STOMP 帧进行通信。 帧的结构看起来就像:

COMMAND
header1:value1
header2:value2

Body^@

帧以一个以行尾(EOL)结束的命令字符串开头, 该命令字符串由一个 可选 的回车符(octet 13)与一个 必须 的换行符(octet 10)组成。
该命令后是零个或多个 <key>:<value> 格式的标头项。
每个标头项都由 EOL 终止。
空行(即额外的 EOL)表示标头的结束与正文的开始。
然后,正文后跟 NULL octet。
本文档中的示例将使用 ASCII 中的 ^@,control-@ 来表示 NULL octet。
NULL octet 后面可以有多个 EOL。
有关如何解析 STOMP 帧的更多详细信息,请参见本文档的 BNF 增强 部分。

本文档中引用的所有命令与标头名均区分大小写。

值编码

命令与标头以 UTF-8 编码。 除 CONNECTCONNECTED 帧以外的所有帧都将转义在生成的 UTF-8 编码标头中发现的任何回车、换行或冒号。

需要转义以允许标头键与值包含那些帧标头定界 octet 作为值。 为了保持与 STOMP 1.0 的向后兼容性, CONNECTCONNECTED 帧不会对回车、换行或冒号字节进行转义。

C 语言风格的字符串字面转义用于编码任何在 UTF-8 编码的头文件中发现的回车、换行或冒号。 解码帧标头时,必须 应用以下转换:

必须 将未定义的转义序列(例如 \t(octet 92 与 116))视为致命协议错误。 相反,在编码帧标头头时,必须 应用反向转换。

STOMP 1.0 规范包含了许多在标头中填充的示例帧,许多服务端与客户端都实现了修剪或填充标头值。 如果应用程序要发送不应修剪的标头,这会导致问题。 在 STOMP 1.2 中,客户端与服务端 必须 禁止修剪标头或用空格填充标头。

正文

只有 SENDMESSAGEERROR 可以 有正文。 所有其他的帧都 绝不 能有正文。

标准标头

对于大多数帧,可以 使用某些标头,并具有特殊含义。

content-length 标头

所有帧都可以包含一个 content-length 的标头。 此标头是消息正文长度的 octet 数量。 如果包含 content-length 标头,那么无论正文中是否存在 NULL octet,都 必须 读取此 octet 数字。 该帧仍然需要以 NULL octet 终止。

如果存在帧正文,那么 SENDMESSAGEERROR 帧应包含 content-length 标头,以简化帧解析。 如果帧正文包含 NULL octet,那么该帧必须包含一个 content-length 标头。

content-type 标头

如果存在帧正文,那么 SENDMESSAGEERROR应该 包含 content-type 标头,以帮助帧的接收者解析正文。 如果设置了 content-type 标头,那么其值 必须 是描述正文格式的 MIME 类型。 否则,接收者 应该 将正文视为二进制对象。

text/ 开头的 MIME 类型的默认文本编码为 UTF-8。 如果使用的是带有不同编码的基于文本的 MIME 类型,那么 应该 在 MIME 类型后附加 ;charset=<encoding>。 例如,如果要以 UTF-16 编码发送 HTML 正文,那么 应该 使用 text/html;charset=utf-16;charset=<encoding>应该 附加到任何可以解释为文本的非 text/ MIME 类型。 一个很好的例子是以 UTF-8 编码的 XML。 其内容类型 应该 设置为 application/xml;charset=utf-8

所有 STOMP 客户端与服务端都 必须 支持 UTF-8 编码与解码。 因此,为了在异构计算环境中实现最大的互操作性,建议 使用 UTF-8 对基于文本的内容进行编码。

receipt 标头

CONNECT 以外的任何客户端帧都 可以 用任意值指定一个 receipt 标头。 这将导致服务端使用 RECEIPT 帧确认对客户端帧的处理(有关更多详细信息,请参见 RECEIPT 帧)。

SEND
destination:/queue/a
receipt:message-12345

hello queue a^@

重复的标头项

由于消息传递系统能按照类似于 SMTP 的存储与转发拓扑进行组织, 因此消息在到达使用者之前 可以 遍历多个消息传递服务端。 STOMP 服务端 可以 通过在消息前添加标头或在消息中就地修改标头来“更新”标头值。

如果客户端或服务端收到重复的帧标头项,只有第一个标头项的值 应该 被用作标头。 后续值仅用于维护标头状态更改的历史记录,可以 忽略。

例如,如果客户端收到:

MESSAGE
foo:World
foo:Hello

^@

foo 标头的值就是 World

大小限制

为了防止恶意客户端利用服务端中的内存分配,服务端 可以 在以下方面设置最大限制:

如果超出了这些限制,服务端 应该 向客户端发送一个 ERROR 帧,然后关闭连接。

链接徘徊

STOMP 服务端必须能够支持快速连接与断开连接的客户端。

这意味着服务端可能仅允许关闭的连接在重置连接之前短时间徘徊。

因此,在套接字重置之前,客户端可能未收到服务端发送的最后一个帧 (例如,ERROR 帧或 RECEIPT 帧以响应 DISCONNECT 帧)。

连接中

STOMP 客户端通过发送 CONNECT 帧来向服务端请求流连接或 TCP 连接:

CONNECT
accept-version:1.2
host:stomp.github.org

^@

如果服务端接受连接请求,将会响应 CONNECTED 帧:

CONNECTED
version:1.2

^@

服务端可以拒绝任何连接请求。 服务端 应该 以一个 ERROR 帧回应,说明连接请求被拒绝的原因,然后关闭连接。

CONNECTSTOMP

STOMP 服务端 必须 以与 CONNECT 帧相同的方式处理 STOMP 帧。 STOMP 1.2 客户端 应该 继续使用 CONNECT 命令以保持与 STOMP 1.0 服务端的向后兼容性。

使用 STOMP 帧而不是 CONNECT 帧的客户端只能连接到 STOMP 1.2 服务端(以及某些 STOMP 1.1 服务端), 但是优点是协议嗅探器/鉴别器将能够区分 STOMP 连接与 HTTP连接。

STOMP 1.2 客户端 必须 设置以下标头:

STOMP 1.2 客户端 可以 设置以下标头:

CONNECTED

STOMP 1.2 服务端 必须 设置以下标头:

STOMP 1.2 服务端 可以 设置以下标头:

协议版本协商

从 STOMP 1.1 开始,CONNECT必须 包含 accept-version 标头。 应该 将其设置为客户端支持的递增 STOMP 协议版本的逗号分隔列表。 如果缺少 accept-version 标头,那么表示客户端仅支持 STOMP 1.0。

其余会话将使用的协议将是客户端与服务端共同拥有的最高协议版本。

例如,如果客户端发送:

CONNECT
accept-version:1.0,1.1,2.0
host:stomp.github.org

^@

服务端将以与客户端相同的最高版本的协议进行响应:

CONNECTED
version:1.1

^@

如果客户端与服务端不共享任何通用协议版本, 那么服务端 必须 以类似于以下内容的 ERROR 帧响应,然后关闭连接:

ERROR
version:1.2,2.1
content-type:text/plain

Supported protocol versions are 1.2 2.1^@

心跳

心跳可以选择性地用于测试底层 TCP 连接的健康状况,并确保远端处于活动状态并正常运行。

为了启用心跳,双方都必须声明自己能做什么,以及声明希望对方做什么。 这是在 STOMP 会话开始时发生的, 方法是将 heart-beat 标头添加到 CONNECTCONNECTED 帧中。

使用时,heart-beat 报头 必须 包含两个用逗号分隔的正整数。

第一个数字表示帧发送者能做什么(发出心跳):

第二个数字代表帧的发送者希望对方做什么(传入的心跳):

heart-beat 标头是 可选 的。 丢失的 heart-beat 标头 必须 以“heart-beat:0,0”标头的方式对待, 即:对方无法发送并且不想接收心跳。

heart-beat 标头提供了足够的信息,以便双方可以确定是否可以使用心跳,心跳的方向和频率。

更正式地说,初始帧如下所示:

CONNECT
heart-beat:<cx>,<cy>

CONNECTED
heart-beat:<sx>,<sy>

对于从客户端到服务端的心跳:

在服务端上,<sx><cy> 的使用方式相同。

关于心跳本身,通过网络连接接收到的任何新数据都表明远端处于活动状态。 在给定的方向上,如果期望每 <n> 毫秒发送一次心跳:

客户端帧

客户端 可以 发送不在此列表中的帧, 但是对于这样的帧,STOMP 1.2 服务端可以用 ERROR 帧响应,然后关闭连接。

SEND

SEND 帧将消息发送到消息系统中的目的地。 它 必须 具有一个 destination 标头,即目的地,指示将消息发送到的位置。 SEND 帧的正文是要发送的消息。示例:

SEND
destination:/queue/a
content-type:text/plain

hello queue a
^@

这会将消息发送到名为 /queue/a 的目的地。 请注意,STOMP 将此目的地视为不透明字符串,并且目的地的名称没有假定传递语义。 应该查阅 STOMP 服务端的文档,以了解如何构造目标名称,该目标名称提供了应用程序所需的交付语义。

消息的可靠性语义也是服务端特定的,并且将取决于所使用的目标值与其他消息标头, 例如 transaction 标头或其他服务端特定的消息标头。

SEND 支持允许事务发送的 transaction 标头。

如果存在正文,那么 SEND应该 包含 content-length 标头与 content-type标头。

应用程序 可以 将任何用户定义的标头添加到 SEND 帧中。 用户定义的标头通常用于允许消费者使用 SUBSCRIBE 帧上的选择器基于应用程序定义的标头过滤消息。 用户定义的报头必须在 MESSAGE 帧中传递。

如果服务端由于某种原因不能成功处理 SEND 帧, 那么服务器必须向客户端发送一个 ERROR 帧,然后关闭连接。

SUBSCRIBE

SUBSCRIBE 帧用于注册以侦听给定的目的地。 像 SEND 帧一样,SUBSCRIBE 帧也需要一个 destination 标头,该头指示客户端要订阅的目标。 此后,在订阅目标上收到的任何消息将作为 MESSAGE 帧从服务器传递到客户端。 ack 头控制消息确认模式。

示例:

SUBSCRIBE
id:0
destination:/queue/foo
ack:client

^@

如果服务器无法成功创建订阅,则服务器 必须 向客户端发送一个 ERROR 帧,然后关闭连接。

STOMP 服务器 可以 支持附加的服务端特定报头,以自定义订阅的传递语义。 详见相关服务端文档。

SUBSCRIBE id 标头

由于单个连接可以在服务端开启的多个订阅,因此 id 标头 必须 包含在帧中以唯一标识订阅。 id 标头允许客户端与服务端将后续的 MESSAGEUNSUBSCRIBE 帧与原始订阅相关联。

在同一连接中,不同的订阅 必须 使用不同的订阅标识符。

SUBSCRIBE ack 标头

ack 标头的有效值为:autoclientclient-individual。 如果未设置标题,则默认为 auto

ack 模式为 auto 时,客户端不需要为收到的消息向服务端发送 ACK 帧。 服务端将假定消息发出后客户端一定能收到。 这种确认模式可能导致发送到客户端的消息丢失。

ack 模式为 client 时,客户端 必须 为处理的消息向服务端发送 ACK 帧。 如果在客户端发送该消息的 ACK 帧之前连接失败,那么服务器将假定该消息尚未处理, 可以 将消息重新传递给另一个客户端。 客户端发送的 ACK 帧将被视为累积确认。 这意味着将对 ACK 帧中指定的消息以及在 ACK 消息之前发送给订阅的所有消息进行确认操作。

如果客户端没有处理某些消息,它 应该 发送 NACK 帧来告诉服务端没有消费这些消息。

ack 模式为 client-individual 时, 确认的运行方式与 client 确认模式相同,只不过客户端发送的 ACKNACK 帧不是累积的。 这意味着后继消息的 ACKNACK绝不 能使前一条消息得到确认。

UNSUBSCRIBE

UNSUBSCRIBE 帧用于移除现有订阅。 移除订阅后,STOMP 连接将不再从该订阅中接收消息。

由于单个连接可以在服务端开启的多个订阅,因此 id 标头 必须 包含在帧中以唯一地标识要移除的订阅。 该标头 必须 匹配现有订阅的订阅标识符。

示例:

UNSUBSCRIBE
id:0

^@

ACK

ACK 通过使用 clientclient-individual 来确认订阅的消息是否被消费。 在通过 ACK 确认消息之前,从此类订阅收到的任何消息都不会被视为已消费。

ACK必须 包含一个与被确认 MESSAGEack 标头匹配的 id 标头。 可选地,可以 指定一个 transaction 标头, 指示消息确认 应该 是具名事务的一部分。

ACK
id:12345
transaction:tx1

^@

NACK

NACKACK 相反。 用于告诉服务端“客户端没有消费该消息”。 然后,服务端可以将消息发送到其他客户端、将其丢弃或将其放入死信队列。 确切的行为是特定于服务端的。

NACKACK 具有相同的标头: id必需) 与 transaction可选))。

NACK 适用于单一消息(如果订阅的 ack 模式为 client-individual) 或之前发送但尚未 ACKNACK 的所有消息(如果订阅的 ack 模式为 client)。

BEGIN

BEGIN 用于开启事务。 在这种情况下,事务适用于发送与确认——事务期间发送或确认的任何消息都将基于该事务进行原子处理。

BEGIN
transaction:tx1

^@

transaction 标头是 必需 的,并且事务标识符将用于 SENDCOMMITABORTACKNACK 帧,以将它们绑定到具名事务。 在同一连接内,不同的事务 必须 使用不同的事务标识符。

如果客户端发送 DISCONNECT 帧或 TCP 连接由于任何原因断开,那么任何尚未提交的已开启事务都将隐式中止。

COMMIT

COMMIT 用于提交正在进行的事务。

COMMIT
transaction:tx1

^@

transaction 标头是 必需 的,并且 必须 指定要提交的事务标识符。

ABORT

ABORT 用于回滚正在进行的事务。

ABORT
transaction:tx1

^@

transaction 标头是 必需的,必须 指定要中止的事务标识符

DISCONNECT

客户端可以随时通过关闭套接字来与服务端断开连接,但不能保证服务端已接收到先前发送的帧。 要进行正常关闭,确保客户端已经接收到所有先前的帧,客户端 应该

  1. 发送带有 receipt 标头的 DISCONNECT 帧。例如:

    DISCONNECT
    receipt:77
    ^@
  2. 等待 RECEIPT 帧对 DISCONNECT 的响应。例如:

    RECEIPT
    receipt-id:77
    ^@
  3. 关闭套接字。

请注意,如果服务端关闭套接字末端的速度过快,那么客户端可能永远不会收到预期的 RECEIPT 帧。 详见 连接徘徊

发送 DISCONNECT 帧后,客户端 绝不 再发送其他帧。

服务端帧

服务端有时会向客户端发送帧(除了初始的 CONNECTED 帧)。 这些帧 可以 是以下之一:

MESSAGE

MESSAGE 帧用于将消息从订阅传递到客户端。

MESSAGE必须 包含一个 destination 标头,指示消息发送到的目的地。 如果消息是使用 STOMP 发送的,那么该 destination 标头 应该 与对应的 SEND 帧中使用的相同。

MESSAGE 帧还 必须 包含一个带有该消息唯一标识符的 message-id 标头与一个与接收该消息的订阅标识符匹配的 subscription 标头。

如果从需要显式确认的订阅(clientclient-individual 模式)接收到消息, 那么 MESSAGE 帧还 必须 包含带有任意值的 ack 标头。 该标头将用于将消息与后续的 ACKNACK 帧相关。

帧正文包含消息的内容:

MESSAGE
subscription:0
message-id:007
destination:/queue/a
content-type:text/plain

hello queue a^@

如果存在正文,那么 MESSAGE应该 包含 content-length 标头与 content-type 标头。

MESSAGE 帧除了可能添加到该帧的服务端特定的标头之外, 还 可以 包含将消息发送到目标时存在的所有用户定义的标头。 请查阅服务端文档,以了解服务端添加到消息中的特定于服务端的标头。

RECEIPT

一旦服务端成功处理了请收据的客户端帧,就会将 RECEIPT 帧从服务端发送到客户端。 RECEIPT必须 包含 receipt-id 标头, 其值为收据帧中的 receipt 标头的值。

RECEIPT
receipt-id:message-12345

^@

RECEIPT 帧是对相应客户端帧已被服务器 处理 的确认。 由于 STOMP 是基于流的,所以收据帧也是一个累计确认,即服务器 已经接收了 之前的所有帧。 但是,这些先前的帧可能尚未完全 处理。 如果客户端断开连接,服务器 应该 继续处理之前收到的帧。

ERROR

如果出现问题,服务端 可以 发送 ERROR 帧。 在这种情况下,必须 在发送 ERROR 帧后立即关闭连接。 详见 连接徘徊 的下一节。

ERROR应该 包含带有错误的简短描述的 message 标头, 并且正文 可以 包含更详细的信息(也 可以 为空)。

ERROR
receipt-id:message-12345
content-type:text/plain
content-length:170
message:malformed frame received

The message:
-----
MESSAGE
destined:/queue/a
receipt:message-12345

Hello queue a!
-----
Did not contain a destination header, which is REQUIRED
for message propagation.
^@

如果错误与从客户端发送的特定帧有关,那么服务器 应该 添加其他报头,以帮助识别导致错误的原始帧。 例如,如果该帧包含一个收据标头,那么 ERROR应该receipt-id 标头设置为与相关错误帧的 receipt 标头值相匹配。

如果存在正文,那么 ERROR应该 包含 content-length 标头与 content-type 标头。

帧与标头

除上述 标准标头content-lengthcontent-typereceipt)外, 以下是此规范中定义的所有标头,每个帧 必须可以 使用:

另外,SENDMESSAGE可以 包括任意用户定义的报头, 应该 将其视为所携带消息的一部分。 同样,ERROR应该 包含附加的标头,以帮助识别引起错误的原始帧。

最后,STOMP 服务端 可以 使用其他标头来访问诸如持久化或过期之类的特性。 详见相关服务端文档

增加的 BNF

HTTP/1.1 RFC 2616 中使用的 Backus-Naur(BNF)格式语法可以更正式地描述 STOMP 会话。

NULL                = <US-ASCII null (octet 0)>
LF                  = <US-ASCII line feed (aka newline) (octet 10)>
CR                  = <US-ASCII carriage return (octet 13)>
EOL                 = [CR] LF 
OCTET               = <any 8-bit sequence of data>

frame-stream        = 1*frame

frame               = command EOL
                      *( header EOL )
                      EOL
                      *OCTET
                      NULL
                      *( EOL )

command             = client-command | server-command

client-command      = "SEND"
                      | "SUBSCRIBE"
                      | "UNSUBSCRIBE"
                      | "BEGIN"
                      | "COMMIT"
                      | "ABORT"
                      | "ACK"
                      | "NACK"
                      | "DISCONNECT"
                      | "CONNECT"
                      | "STOMP"

server-command      = "CONNECTED"
                      | "MESSAGE"
                      | "RECEIPT"
                      | "ERROR"

header              = header-name ":" header-value
header-name         = 1*<any OCTET except CR or LF or ":">
header-value        = *<any OCTET except CR or LF or ":">

许可证

本规范是根据知识共享 署名v3.0许可证进行许可的。

本站翻译遵循相同许可,并且在此感谢所有翻译贡献者:

欢迎补充或改善本站翻译,可以 发送 Issues 或直接 Fork 本站仓库,并提交拉取请求。