33/ZHTTP

33/ZHTTP

基于 ZeroMQ 的 HTTP 请求和响应

本文档描述了一种基于 ZeroMQ 执行 HTTP 请求和响应的协议。它部分借鉴了 Mongrel2 和 ZeroGW 的协议模型,但增加了流式传输能力和基于信用额度的流量控制。

许可证

版权所有 (c) 2013 Fanout, Inc.

本规范是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证条款重新分发和/或修改它;可以选择许可证的第 3 版或(由您决定)任何更高版本。

本规范的发布是希望它有用,但不提供任何保证;甚至不提供适销性或特定用途适用性的默示保证。有关更多详细信息,请参阅 GNU 通用公共许可证。

您应该已随本程序收到一份 GNU 通用公共许可证的副本;如果未收到,请参阅 https://gnu.ac.cn/licenses

语言

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释(参见“RFC 中用于指示要求级别的关键词”)。

目标

ZHTTP 的目标是

  • 为基于 ZeroMQ 的 HTTP 交互提供抽象接口。传输层的问题,如连接、HTTP 版本控制或传输编码,不应是协议用户关注的问题。
  • 支持请求和响应数据的流式传输,并带有流量控制。
  • 支持长生命周期会话。
  • 具备将 ZHTTP 会话“移交”给另一个 ZeroMQ 实体的能力。

协议

描述

有两种可能的安排,基本和高级。

基本

  • DEALER <-> 请求/响应 <-> ROUTER

在基本安排中,发起者在一个消息中包含整个 HTTP 请求,而响应者在一个消息中回复整个 HTTP 响应。发起者用其返回地址包裹消息,响应者使用该地址进行回传路由。这也意味着该安排兼容 REQ 和 REP。

高级

  • PUSH -> 请求 -> PULL
  • ROUTER -> 请求 -> DEALER (仅接收)
  • SUB <- 响应 <- PUB

在高级安排中,发起者可以发送一个或多个 HTTP 请求片段,响应者可以回复一个或多个 HTTP 响应片段。使用基于信用额度的流量控制来确保任何一方都不会过载。发起者的第一条消息使用 PUSH 套接字发送。响应者的所有消息都使用 PUB 套接字发送。一旦发起者从响应者收到至少一条消息,它就可以使用 ROUTER 套接字发送后续消息。这种设计使得拥有多个独立的响应工作者成为可能。第一条消息通过 PUSH 发送给任意工作者,接收消息的工作者回复其自己的地址,然后所有后续消息都通过 ROUTER 直接发送给同一个工作者。响应者用于接收后续数据包的套接字类型可以是任何与 ROUTER 兼容的类型,因为响应者实际上从不使用此套接字发送消息。

消息格式主要为 tnetstrings,并根据套接字类型有一些细微的帧结构差异。

基本 HTTP 请求包含以下字段

  • id - 发起者发送的请求中的唯一标识符

  • method - HTTP 方法

  • uri - 完整 URI,例如 scheme://domain.com/path?query

  • headers - 项目列表,其中每个项目是一个包含两个字符串的列表:头部名称,头部值

  • body - 正文内容

  • user-data - 要回传的数据

消息被编码为一个 tnetstring 字典。发起者通过其套接字标识,该套接字通常是一个随机值或在套接字上明确设置的值。然后发送一个带信封的请求。

基本 HTTP 响应包含以下字段

  • id - 请求的标识符

  • type - data(省略)或“error”

  • condition - 错误条件

  • code - HTTP 状态码

  • reason - HTTP 状态原因

  • headers - 同请求头部格式

  • body - 正文内容

  • user-data - 如果请求中存在 user-data,则复制此处

消息被编码为一个 tnetstring 字典。响应者必须读取信封以确定发起者的地址,然后在回复时在其响应上放置一个正确的信封。

高级 HTTP 请求包含以下字段

  • from - 发起者的地址

  • id - 发起者发送的请求中的唯一标识符

  • type - data(省略)、“credit”、“cancel”、“keep-alive”、“handoff-start”或“handoff-proceed”

  • condition - 错误条件

  • seq - 请求流的序列号,从 0 开始

  • credits - 提供信用额度给响应者

  • more - 表示此后还有更多数据包

  • stream - 优先使用流模式。如果为 false,响应者应以单个消息回复

  • max-size - 不接受大于此值的响应(仅限非流模式)

  • method - HTTP 方法

  • uri - 完整 URI,例如 scheme://domain.com/path?query

  • headers - 项目列表,其中每个项目是一个包含两个字符串的列表:头部名称,头部值

  • body - 正文内容

  • user-data - 要回传的数据

第一条消息被编码为一个用于 PUSH 套接字的 tnetstring 字典。任何后续消息都被编码为一个 tnetstring 字典,并且额外包裹响应者的地址。

高级 HTTP 响应包含以下字段

  • from - 响应者的地址

  • id - 请求的标识符

  • type - data(省略)、“credits”、“cancel”、“keep-alive”、“handoff-start”或“handoff-proceed”

  • condition - 错误条件

  • seq - 响应流的序列号,从 0 开始

  • credits - 提供信用额度给发起者

  • more - 表示此后还有更多数据包

  • code - HTTP 状态码

  • reason - HTTP 状态原因

  • headers - 同请求头部格式

  • body - 正文内容

  • user-data - 如果请求中存在 user-data,则复制此处

消息被编码为一个 tnetstring 字典,并以发起者的地址和一个空格作为前缀,例如“{发起者地址} {tnetstring}”。

注意事项

  • 发起者的第一条消息必须是“data”消息
  • method、uri 和 headers 必须出现在发起者的第一条“data”消息中
  • code、reason、headers 和 body 必须出现在响应者的第一条“data”消息中
  • 除“cancel”类型外,所有消息必须按序发送和处理,而“cancel”类型可以乱序发送和处理
  • “credits”可以在“data”或“credit”类型中提供
  • “more”只能在“data”类型中提供。如果不存在,则流已完成
  • “stream”只能在第一条消息中提供
  • 如果响应者不支持流式输入(通过发起者的第一条消息中存在“more”检测到),则响应者可以取消
  • 如果在后续请求消息中提供了 user-data,它将替换响应者已知的先前值

假定双方都从 0 个信用额度开始,并且不应发送数据,除非它们认为自己有信用额度。信用额度仅适用于正文数据。作为一个特殊例外,发起者可以在未获得任何信用额度的情况下提供正文数据。此后,它应该假定自己拥有 0 个信用额度。此外,如果未请求流模式,则无需向响应者提供信用额度。在这种情况下,无论如何响应者都可以回复完整的 HTTP 响应。

根据使用场景,还有一些额外的字段

仅入站字段,由 Web 服务器发送

  • peer-address - 请求客户端的远程地址
  • peer-port - 请求客户端的远程端口

仅出站标志,发送给 HTTP 客户端工作者

  • connect-host - 覆盖要连接的主机
  • connect-port - 覆盖要连接的端口
  • ignore-policies - 忽略关于允许哪些请求的任何规则
  • ignore-tls-errors - 忽略 HTTP 服务器证书

这些额外的字段仅在第一个请求消息中有效。

移交

可以通过移交将会话状态从一个工作者传递给另一个工作者。其工作方式是发送一个“handoff-start”消息。在此之前不能发送任何后续消息,直到对方回复预期的“handoff-proceed”,这意味着对方也同样不会发送任何后续消息。此时,请求移交的实体可以将状态转移到另一个工作者。该工作者随后可以通过向对方发送消息来恢复会话。如果该实体是发起者,则该消息应包含一个额外字段“old-from”,其值设置为前一个工作者的地址。这是必需的,因为响应者通过 address+id 对来标识未完成的会话。

如果双方同时尝试开始移交,则视为冲突,响应者获胜。这意味着如果响应者发送“handoff-start”并进入 WaitForHandoffProceed 状态,它应该忽略在此状态下收到的任何“handoff-start”消息。如果发起者发送“handoff-start”并进入 WaitForHandoffProceed 状态,它应该停止(并可能排队以便将来处理)其移交意图,发送一个“handoff-proceed”,并立即认为响应者正在进行移交。

参考实现

Zurl(参见“Zurl”)是 ZHTTP 的主要首选实现。它将传入的 ZHTTP 请求转换为传出的 HTTP 请求。