XEP-0124: 基于同步HTTP的双向流
摘要: |
本协议定义了一个传输协议来模拟两个实体 (例如一个客户端和一个服务器) 之间的长连双向TCP连接的语义,它有效地运用多个同步的HTTP"请求/应答"对,而不需要使用频繁的轮询或者分块响应. |
作者: |
Ian Paterson, Dave Smith, Peter Saint-Andre, Jack Moffitt |
版权: |
© 1999 - 2011 XMPP标准化基金会(XSF). 参见法律通告. |
状态: |
草案 |
类型: |
标准跟踪 |
版本: |
1.10 |
最后更新日期: |
2010-07-02 |
注意: 这里定义的协议是XMPP标准化基金会的一个
草案标准 .对本协议的执行是被鼓励的,也适于部署到生产系统,但是在它成为最终标准之前可能还会有一些变动.
简介
传输控制协议 (TCP;
RFC 793 1 ) 经常用来建立两个实体之间的面向流的连接. 这些连接可以保持常连,使得实体之间的 "会话" 能够交互式的进行. 无论如何, 有时候设备或网络的性质可能会阻止一个应用程序去维护一个常连的TCP连接到一个服务器或对端. 在这种情况下, 需要使用一个替代的连接方法,使用排序的一系列请求和应答在短连的连接上交换信息来模拟常连的TCP连接. 通过
RFC1945 2 和
RFC2616 3定义的超文本传输协议(HTTP)可以获得很多可用的合适的 请求-应答 语义.
BOSH, 本文定义的这种技术, 实质上为常连的双向TCP连接提供了一个 "drop-in" 的替代品. 它是一个成熟的, 全功能的技术,自2004年以来已经被广泛实现和布署. 据我们所知它是众多类似技术中的第一个, 它现在包含了正规化的
Bayeux协议 4 的 彗星(Comet) 方法(译者注:Comet就是Ajax web应用程序的服务器推送技术的结合),以及
Web Socket协议 5 和
反向HTTP 6.
BOSH被设计成有效地传输任何数据并且在两个方向上都保持最小延迟. 对应用程序来讲那同时需要 "推" 和 "拉" 语义, BOSH显然比其他大多数双向的基于HTTP的传输协议更节省带宽和响应更迅速,这个技术现在通常称为 "Ajax". BOSH通过对多个同步的"HTTP请求/响应对"使用所谓"长轮询"来达到高效. 此外, 通过使用不需要"cookies"而全兼容HTTP 1.0 (见
RFC 2965 7 )
8 或甚至访问HTTP头,BOSH可以解决受限客户端的需要 .
BOSH原本是由 Jabber/XMPP 社区开发出来替代更早的基于HTTP的技术
Jabber HTTP轮询 9 . 尽管BOSH假定HTTP请求和应答的 "载荷" 是XML, 载荷的格式未受XMPP节 (见
XMPP Core 10 ) 的限制,并且可以包含不同协议定义的命名空间的元素的混合物 (例如, 同时包含 XMPP 和 JSON). 这种混合是必要的,因为一些连接管理者可能不支持多重流,而受限的客户端经常无法访问HTTP流水线 (这限制同一时间只能拥有一个BOSH会话). BOSH连接管理者通常不需要理解它们所传输的XML的任何内容,除了确保每个XML载荷被正确的命名空间所限定.
注意:
XMPP Over BOSH 11 记录了一些以前包含在本协议中的XMPP特有的扩展.
需求
以下的设计需求反映了提供接近标准TCP连接的性能的需要.
- 和受约束的运行时环境兼容* (例如, 手机和基于浏览器的客户端).
- 和缓冲了部分HTTP应答的代理兼容.
- 高效的通过那些限制HTTP响应时间的代理.
- 完全兼容HTTP/1.0.
- 兼容受限的网络连接 (例如, 防火墙, 代理, 以及网关).
- 容错 (例如, 在HTTP请求的任何阶段,底层TCP连接中断之后,会话恢复).
- 可扩展.
- 带宽消耗显著低于基于轮询的协议.
- 比基于轮询的协议显著快速响应 (低延迟).
- 支持轮询 (支持那些限制同一时间只有一个HTTP连接的客户端轮询).
- 顺序递送数据.
- 防范未经授权的用户会话注入HTTP请求.
- 防止拒绝服务攻击.
- 复用数据流.
- 客户端不需要编程访问每个HTTP请求和应答的头 (例如, cookies 或 状态码).
- 每个HTTP请求和应答的 body 是一个可解析的根元素的XML.
- 客户端可以指定它们接收的HTTP应答的 Content-Type.
架构假设
本文假定大部分实现使用专门的连接管理者 ("CM") 来处理HTTP连接,而非有关应用的本地连接类型 (例如, XMPP里的TCP连接). 为了效率, 这样一个连接管理者是一个专门的HTTP服务器,用来把这里定义的HTTP请求和应答和与之通讯的服务器实现的数据流(或API)之间进行翻译, 因此使得客户端能通过HTTP的80或443端口连接到一个服务器而不是一个特定应用的端口. 我们可以图形说明如下:
服务器
|
| [未封装的数据流]
|
HTTP连接管理器
|
| [HTTP + <body/> 封装器]
|
客户端
本协议仅涵盖了客户端和连接管理器的通讯. 没有涉及连接管理器和服务器之间的通讯, 因为这类通讯是和特定实现相关的 (例如, 服务器可能原生支持HTTP绑定, 在这种情况下连接管理器是一个逻辑实体而不是物理实体; 另外连接管理器可以是独立翻译代理,这样该服务器认为它在直接和客户端通过TCP交谈; 或者连接管理器和服务器可能使用组件协议或服务器定义的API).
此外, 本协议并没有限定只用于客户端和服务器之间的通讯. 例如, 它可以用于服务器之间的通讯,如果有关的应用(例如, 在XMPP里)发生了这样的通讯. 无论如何, 本文只专注于那些无法和服务器随时维护持久的TCP连接的客户端传输的使用. 我们假定服务器和组件不受那些限制,并且因此相关的应用将使用原生的连接传输. (无论如何, 在一些不可靠的网络中, BOSH可以让服务器之间的通讯更加稳定.)
BOSH技术
因为HTTP是一个同步请求/应答协议, 传统的通过HTTP模拟双向流的解决方案是让客户端HTTP间歇性地轮询连接管理器来查询是否有任何等待发送给客户端的数据. 当没有数据需要传输的时候,这种幼稚的做法在轮询的时候浪费了很多网络带宽. 它也降低了应用程序的响应,因为数据要花时间排队直到连接管理器从客户端接收下一次轮询 (HTTP 请求) . 这导致了响应速度和带宽之间难免顾此失彼, 因为增加轮询频率将在减少延迟的同时增加带宽消耗 (如果轮询频率降低的话,反之亦然).
BOSH使用的技术通过鼓励连接管理器在它确实有数据发送给客户端之前不去应答请求,同时达到低延迟和低带宽消耗的目的. 一旦客户端从连接管理器接收到一个应答,它发送另一个请求, 从而确保连接管理器 (几乎) 总是持有一个可以用来 "推送" 数据给客户端的请求.
如果客户端需要发送一些数据给连接管理器,那么它只要简单地发送第二个包含数据的请求. 不幸的是大部分受约束的客户端不支持HTTP流水线 (通过单个的连接的并发请求), 所以客户端通常需要通过第二个HTTP连接发送这个数据. 连接管理器总是应答它持有的第一个连接的请求,一旦从客户端接收到一个新的请求 -- 甚至即使没有数据发送给客户端. 它这么做可以确保客户端能在必要的时候立刻发送更多数据. (客户端同一时间不应该打开超过两个HTTP连接到连接管理器,
12 ,否则它不得不等待连接管理器应答请求中的一个.)
即使网络情况迫使每个HTTP请求都要通过不同的TCP连接,BOSH也会可靠地工作. 无论如何, 如果经常发生这种情况, 客户端可以使用HTTP/1.1, 然后 (假定网络情况稳定) 一个会话中的所有请求将在同样的两个持久TCP连接上通过. 几乎任何时候 (见下文) 客户端都可以推送数据到这两个连接中的一个, 而连接管理器能推送数据到另一个连接 (所以延迟和一个标准的TCP连接一样低). 有意思的是,注意这两个连接的角色每当客户端发送数据到连接管理器的时候就会互换.
如果在一个规定时间内(通常是几分钟)两个方向上都没有流量, 那么连接管理器以无数据来应答客户端, 并且那个应答立即触发一个新的客户端请求. 连接管理器这么做来确认是否网络连接已经中断,并且双方都在一个合理的时间内明白到这一点. 这一交换类似大部分持久TCP连接中的通用的 "keep-alive" 或 "ping" . 因为BOSH技术未使用轮询, 带宽消耗不会比标准TCP连接大很多.
13
大部分时候数据可以被立刻推送. 无论如何, 如果其中一个端点刚推送了一些数据,在它再次推送之前将不得不等待网络往返. 如果客户端可以使用HTTP流水线,那么就可以拥有多个并发的请求了. 这样客户端就总能立刻推送数据了. 它也能保证连接管理器总是持有足够的请求,这样甚至在连续发送的时候它也绝不需要在发送数据之前等待. 此外, 如果连接管理器持有的请求池太大, 那么客户端从连接管理器接收数据之后在压力之下将不能立刻发送一个新的空请求. 它只能等待,除非需要发送数据. 所以, 如果随着时间的推移,客户端接收和发出的流量平衡了, 带宽消耗将和使用标准的TCP连接相同.
连接管理器推送的每一个数据块都是一个完整的HTTP应答. 所以, 和Comet技术不像, BOSH技术是通过中间代理缓冲部分HTTP请求来工作的. 它也完全兼容HTTP/1.0 -- 不提供分块传输编码.
HTTP概述
所有信息被编码到标准的HTTP POST请求和应答的body中. 每个HTTP body包含一个单独的 <body/> 封装器用来封装被传输的XML元素 (见
XEP-0124#<body/>封装器元素 ).
客户端应该使用HTTP流水线通过一个持久的HTTP/1.1连接发送所有的HTTP请求. 不过, 客户端也可以用任何
RFC 1945 或
RFC 2616 允许的方式递送这些POST请求. 例如, 受约束的客户端可能期望打开不止多个持久连接而不是使用HTTP流水线, 或者在某些情况下打开一个新的HTTP/1.0连接来发送每个请求. 无论如何, 客户端和连接管理器不应该使用分块传输编码, 因为中间代理可能缓冲每部分的HTTP请求或应答并且在可以发送的时候只传输完整的请求或应答.
客户端可以在任何请求中包含一个HTTP Accept-Encoding头. 如果连接管理器接收到一个拥有Accept-Encoding头的请求, 它可以在应答中包含一个HTTP Content-Encoding头 (指定请求中说明的编码之一) 并据此压缩应答body.
请求和应答可以包含未在本文提到的HTTP头. 接收者应该忽略那些HTTP头.
每个BOSH会话可以分享其他用途的HTTP流量的HTTP连接, 包括其他BOSH会话以及和本协议完全无关的HTTP请求和应答 (例如, web页面下载). 无论如何, 使用HTTP流水线通过同一个连接对不是本会话一部分的请求的应答(或同一个连接中的发送队列)可能会导致对于本会话的一部分的请求的应答的延迟 (因为连接管理器必须以和接收到的请求相同的顺序来发送它的应答, 并且连接管理器通常会延迟它的某些应答).
所有客户端请求的HTTP Content-Type头应该是 "text/xml; charset=utf-8". 无论如何, 如果客户端受到某些约束,它们也可以指定其他的值 (例如, "application/x-www-form-urlencoded" 或 "text/plain"). 客户端和连接管理器应该忽略所有接收到的HTTP Content-Type头.
<body/>封装器元素
每个HTTP请求和应答的body包含一个单独的由 '
http://jabber.org/protocol/httpbind' 命名空间限定的<body/>封装器元素. 这个封装器的内容就是被传输的数据. <body/>元素和它的内容都必须符合
XML 1.0 14 的一系列规格. 它们也应该符合
Namespaces in XML 15. 内容必须不包含任何以下的东西 (都定义于
XML 1.0 ):
- 部分XML元素
- XML注释
- XML处理指令
- 内部或外部DTD亚群
- 内部或外部实体引用 (预定义的实体除外)
<body/>封装器必须不包含任何XML字符串数据, 尽管它的子元素可以包含字符串数据. <body/>封装器必须包含零或多个完整的XML直接子元素(本文称为 "载荷" , 例如, 定义于
RFC 6120 的XMPP节或使用定义于
RFC 4627 16 的JSON数据交换格式来代表对象的包含XML字符串数据的元素).每个<body/>封装器可以包含各种广泛的命名空间限定的载荷.
每个客户端请求的<body/>元素必须拥有一个通过'rid'属性封装的顺序的请求ID; 具体信息, 参考本文的
请求IDs 章节.
发起BOSH会话
会话创建请求
从客户端到连接管理器的第一个请求是请求一个新的会话.
第一个请求的<body/>元素应该拥有以下属性 (它们应该不被包含在其他请求中,除非在
添加流到一个会话 时候指定):
- 'to' -- 这个属性指定第一个流的目标域.
- 'xml:lang' -- 这个属性 (定义于 XML 1.0 17 的2.12) 指定会话期间发送或接收的任何人类可读的字符串数据的缺省语言.
- 'ver' -- 这个属性指定客户端支持的BOSH协议的最高版本. 编号方案是 "<主版本号>.<小版本号>" (这里小版本号可以递增到不止一位数子, 所以它必须被当成一个单独的整数来处理). 注意: 'ver' 属性应该不被误解成任何正被传输的协议的版本.
- 'wait' -- 这个属性指定连接管理器在会话中应答任何请求之前被允许的等待时间(以秒计数). 这使客户端能限制它察觉任何网络失败之前的延迟, 并且阻止它的HTTP/TCP连接因为不活跃而过期.
- 'hold' -- 这个属性指定连接管理器在会话中被允许同时保持的请求的最大数量. 如果客户端不能使用HTTP流水线那么它应该设为 "1".
注意: 仅支持轮询会话的客户端可以通过设置'wait' 或 'hold' 为 "0" 来阻止连接管理器等待. 无论如何, 轮询是不推荐的,因为带宽消耗的增加和响应能力的降低都会达到一到两个数量级!
连接管理器可以被配置成允许在不同的域里和多个服务器进行会话. 当以一个"代理"连接管理器请求一个会话的时候, 客户端应该包含一个'route'属性来指定它想通讯的服务器的协议, 主机名, 和端口, 格式为 "proto:host:port" (例如, "xmpp:example.com:9999").
18 一个配置成只和一个 (或只和预定义的域列表以及服务于那些域的相关主机和端口列表的) 服务器配合工作的连接管理器可以忽略 'route' 属性. (注意 'to' 属性指定服务的域, 而不是服务于那个域的机器的主机名.)
第一个请求的 <body/> 元素也可以拥有 'from' 属性, 它指定第一个流的发起者并允许连接管理器转发发起者实体的身份到应用服务器 (例如, 连接到一个XMPP服务器上的实体的 JabberID ; 见
XEP-0206 ).
客户端可以包含一个 'ack' 属性 (设为 "1") 来指示它将在会话中始终使用确认机制并且任何请求中缺少'ack'属性都是有特定意义的 (见
确认 ).
一些客户端被约束只能接受特定Content-Types (例如, "text/html")的HTTP应答. 第一个请求的<body/>元素可以拥有一个'content'属性. 它指定在会话期间必须在所有连接管理器的应答中出现的HTTP Content-Type头的值. 如果该客户端请求没有'content'属性, 那么应答的HTTP Content-Type头必须是"text/xml; charset=utf-8".
示例1. 请求一个BOSH会话
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 104
<body content='text/xml; charset=utf-8'
from='user@example.com'
hold='1'
rid='1573741820'
to='example.com'
route='xmpp:example.com:9999'
ver='1.6'
wait='60'
ack='1'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
注意: 在第一个之后的所有请求必须包含一个有效的'sid'属性 (由连接管理器在会话创建应答中提供). 发起方请求是唯一的,在那个<body/>元素中必须没有'sid'属性.
会话创建应答
在接收到到一个新的会话请求之后, 连接管理器必须生成一个不透明的不可预测的会话标识符 (或称为 SID). 这个SID在该连接管理器应用中的上下文中必须是唯一的. 该连接管理器对客户端的会话创建请求的应答中的 <body/> 元素必须拥有以下属性 (它们应该不被包含在任何其他应答中):
- 'sid' -- 该属性指定SID
- 'wait' -- 这是连接管理器在会话期间应答任何请求之前等待的最长时间 (以秒计数) . 这个时间必须小于或等于该会话请求的指定的值.
<body/>元素也应该包含以下属性 (它们应该不被包含在任何其他应答中):
- 'ver' -- 这个属性指定连接管理器支持的BOSH协议的最高版本, 或客户端在它的请求中指定的版本, 取其中较低的版本.
- 'polling' -- 这个属性指定最小可允许的轮询借个(以秒计数). 这使得客户端不需要以超出预期的频率发送空的请求元素 (见 轮询会话 和 过度活跃 ).
- 'inactivity' -- 这个属性指定最长可允许的闲置期时间(以秒计数). 这使客户端能确保没有请求待发的时间段永远不会太长 (见 轮询会话 和 闲置 ).
- 'requests' -- 这个属性使得连接管理器能限制客户端产生的并发请求的数量 (见 过度活跃 和 轮询会话 ). 推荐的值是 "2" 或者比会话请求中指定的'hold' 属性值大一.
- 'hold' -- 这个属性通知客户端连接管理器在会话期间的任何时间将会保持等待的请求的最大数量. 这个值必须不大于客户端在会话请求中指定的值.
- 'to' -- 这个属性指定客户端尝试连接的后台服务器的通讯身份.
连接管理器可以在会话创建应答元素中包含一个'accept'属性, 来指定一个以空格分隔的可被解压的内容编码的列表. 在接收到一个带有'accept'属性的会话创建应答之后, 客户端可以在随后的请求中包含一个HTTP Content-Encoding头 (指示 'accept' 属性指定的编码之一) 并且据此压缩该请求的bodies.
连接管理器可以包含一个'ack'属性 (值设为会话创建请求中的'rid'属性值) 来指示它将在该会话中始终使用确认机制,并且任何应答中缺少'ack'属性都是有特定意义的 (见
确认 ).
如果连接管理器支持会话暂停 (见
闲置 ) 那么它应该通过在会话创建应答元素中包含一个'maxpause'属性来向客户端声明. 这个属性的值指示客户端可以请求的一个临时会话暂停的最大长度(以秒计数) .
对请求和应答都一样, <body/>元素和它的内容应该以UTF-8编码. 如果请求/应答的HTTP Content-Type头指定了一个不同于UTF-8的字符编码, 那么连接管理器可以在UTF-8和该字符编码之间转换. 无论如何, 即使在这种情况下, 连接管理器转换编码也是可选的. 连接管理器可以通知客户端哪些编码它能够转换,通过在会话创建应答元素中设置可选的'charsets'属性为以空格分隔的编码列表.
19
一旦连接管理器建立了一个连接到服务器并发现它的身份, 它可以在应答中包含一个'from'属性来转发这个身份给客户端, 要么在它的会话创建应答中, 要么 (如果那个时候它还没有从服务器接收到它的身份) 任何随后给客户端的应答中. 如果它建立了一个安全连接到服务器 (定义于
发起BOSH会话 ), 那么在同一个应答中连接管理器也应该包含'secure'属性并把值设为 "true" 或 "1".
示例2. 会话创建应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 128
<body wait='60'
inactivity='30'
polling='5'
requests='2'
hold='1'
ack='1573741820'
accept='deflate,gzip'
maxpause='120'
sid='SomeSID'
charsets='ISO_8859-1 ISO-2022-JP'
ver='1.6'
from='example.com'
xmlns='http://jabber.org/protocol/httpbind'/>
示例3. 随后的包含'from'和'secure'属性的应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 128
<body from='example.com'
xmlns='http://jabber.org/protocol/httpbind'/>
发送和接收XML载荷
客户端成功完成所有必需的前提条件之后, 它就可以通过HTTP绑定来发送和接收XML载荷了.
示例4. 传输载荷
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 188
<body rid='1249243562'
sid='SomeSID'
xmlns='http://jabber.org/protocol/httpbind'>
<message to='contact@example.com'
xmlns='jabber:client'>
<body>Good morning!</body>
</message>
<message to='friend@example.com'
xmlns='jabber:client'>
<body>Hey, what's up?</body>
</message>
</body>
在收到一个请求之后,一旦有可能连接管理器应该转发<body/>元素的内容到服务器. 在任何情况下它必须按照它们的'rid'属性指定的顺序来从不同的请求转发内容.
连接管理器也必须在<body/>元素中返回一个HTTP 200 OK应答给客户端. 注意: 这并不表示载荷已经被成功发送到应用服务器.
建议连接管理器在载荷已经从服务器到达客户端之前不要返回HTTP结果. 无论如何, 连接管理器不应该让等待时间超过客户在它的会话创建请求中指定的'wait'属性的值 , 而且在同一时间它保持的HTTP请求数量不应该多于会话创建请求中的'hold'属性的值. 任何情况下它必须以它们的'rid'属性指定的顺序来应答请求.
如果在等待周期里没有载荷等待或准备好发送, 那么连接管理器应该在HTTP结果中包含一个空的<body/>元素:
示例5. 空应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 64
<body xmlns='http://jabber.org/protocol/httpbind'/>
如果连接管理器已经从应用服务器收到一个或多个载荷要发送给客户端, 那么在它从服务器收到它们之后一旦有可能它应该立即在它的应答的bodu中返回这些载荷. 以下这个例子包含被不同命名空间限定的载荷:
示例6. 排队节的应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 185
<body xmlns='http://jabber.org/protocol/httpbind'
xmlns:json='http://json.org/'>
<message from='contact@example.com'
to='user@example.com'
xmlns='jabber:client'>
<body>Good morning to you!</body>
</message>
<message from='friend@example.com'
to='user@example.com'
xmlns='jabber:client'>
<body>Not much, how about with you?</body>
</message>
<json:json>
[
{
"precision": "zip",
"Latitude": 37.7668,
"Longitude": -122.3959,
"Address": "",
"City": "SAN FRANCISCO",
"State": "CA",
"Zip": "94107",
"Country": "US"
},
{
"precision": "zip",
"Latitude": 37.371991,
"Longitude": -122.026020,
"Address": "",
"City": "SUNNYVALE",
"State": "CA",
"Zip": "94085",
"Country": "US"
}
]
</json:json>
</body>
客户端可以通过发送空的<body/>元素来轮询连接管理器的输入节.
示例7. 请求XML载荷
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 88
<body rid='1249243563'
sid='SomeSID'
xmlns='http://jabber.org/protocol/httpbind'/>
连接管理器必须以和它从客户端接收载荷同样的方法等待并应.
确认
请求确认
当应答一个保持的请求时, 如果连接管理器发现它已经接收了另一个更高'rid'属性的请求(通常此时它正在保持第一个请求), 那么它可以向客户端确认已收到. 当连接管理器也接收了所有更低的'rid'属性的请求时,它可以设置任何应答的 'ack' 属性高于它已经接收到的最高的'rid'属性.
示例8. 应答请求的确认
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 64
<body ack='1249243564'
xmlns='http://jabber.org/protocol/httpbind'/>
如果连接管理器在一个会话过程中将要包含'ack'属性, 那么它必须在它的会话创建应答中包含一个'ack'属性, 并且在会话过程中始终设置应答的'ack'属性. 唯一的例外是, 在它的会话创建应答之后, 如果这个值和任何要应答的请求的'rid'值相同,连接管理器不应该包含一个'ack'属性在应答之中.
如果连接管理器被允许同一时间保持多个请求, 那么从连接管理器收到低于预期的'ack'值(或意外的缺少'ack'属性)可以给客户端一个可能发生网络失败的早期预警(例如, 如果客户端认为连接管理在它应答的时候应该已经接收到了另一个请求).
应答确认
客户端可以同样通过把任何请求的'ack'属性值设为它已经接收到的的应答的最高'rid'值(此时它也已经接收了所有拥有更低'rid'值的应答)来通知连接管理器它已经收到了应答. 如果客户端在一个会话中要在请求中包含'ack'属性, 那么它必须在它的会话创建请求中包含一个'ack'属性(值为'1'), 并且在会话中始终设置请求的'ack'属性. 唯一的例外是, 在它的会话创建请求之后, 如果客户端已经接收到所有在它之前的请求的应答,那么客户端不应该包含一个'ack'属性到任何请求.
示例9. 请求应答确认
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 88
<body rid='1249243566'
sid='SomeSID'
ack='1249243564'
xmlns='http://jabber.org/protocol/httpbind'/>
在接收到一个'ack'值低于最后一个已应答请求的'rid'值的请求时, 连接管理器可以通过立即发送它的下一个应答而不是等到它有需要发送给客户端的载荷的时候才发送应答(例如, 假如从它应答的时候到现在已经过了一段时间),以此告知客户端这种情况. 在这种情况下它应该包含一个'report'属性并把值设为高于它从客户端接收到的'ack'属性, 而'time'属性则设为它发送'report'相关应答的时间,以毫秒计数.
示例10. 应答报告
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 64
<body report='1249243565'
time='852'
xmlns='http://jabber.org/protocol/httpbind'/>
在收到一个拥有'report'和'time'属性的应答之后, 如果客户端仍未收到'report'属性指定的请求id相关的应答, 那么它可以选择重发这个丢失的应答相关的请求 (见
断开的连接 ).
闲置
从连接管理器接收到一个应答之后, 如果没有客户端请求继续被连接管理器保持 (并且如果会话不是一个轮询会话), 客户端应该一有可能就制造一个新的请求. 在任何情况下, 如果没有请求被hold住, 客户端必须在最大闲置时间过期之前制造一个新的请求. 这个时间段(以秒计数) 是在会话创建应答中的'inactivity'属性定义的.
如果连接管理器已经应答了一个会话中它所接收到的所有请求并且从最后一次应答到当前的时间超过最大闲置时间段, 那么它应该假定客户端已经失去连接并且不通知客户端就终止这个会话. 如果接下来客户端再制造另一个请求, 那么连接管理器应该像这个会话不存在一样来应答.
如果连接管理器在会话创建应答里没有指定一个最大闲置时间, 那么它应该允许客户端选择它的闲置时间.
如果会话不是轮询会话那么连接管理器应该指定一个相对短的闲置时间来确保尽可能快的发现断链. 建议的时间应该比连接管理器和客户端在困难的网络条件下一次顺利的网络往返的秒数多一点 (因为可以期待客户端立刻制造一个新请求 -- 见上文).
如果客户端遇到意外的临时状况,在此期间它将不能在最大闲置时间之内发送请求到连接管理器(例如, 当运行时环境从一个web页面变成另一个页面), 并且如果连接管理器在它的会话创建应答中包含了'maxpause'属性, 那么客户端可以通过在一个请求中包含'pause'属性来请求临时增加最大闲置时间的. 注意: 如果连接管理器没有在会话开始的时候定义一个'maxpause'属性那么客户端不能在会话期间发送'pause'属性.
示例11. 请求会话暂停
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 98
<body rid='1249243564'
sid='SomeSID'
pause='60'
xmlns='http://jabber.org/protocol/httpbind'/>
接收到一个会话暂停请求之后, 如果请求的时间段不超过最大允许时间, 那么连接管理器应该立刻应答所有未决的请求(包括暂停请求)并临时增加最大限制时间到请求的时间. 注意: 对暂停请求的应答不能包含任何载荷.
注意: 如果客户端简单地希望连接管理器返回所有它保持住的请求,那么它可以把'pause'属性值设为连接管理器的会话请求应答中的'inactivity'属性值. (如果客户端认为它有无限期断线的危险,那么它甚至可以通过指定一个小于'inactivity'值的'pause'值来请求临时减少闲置时间, 这样使得连接管理器能快速发现接下来的断链.)
连接管理器应该在从客户端接到下一个请求之前(假定连接管理器尚未终止该会话)把闲置时间设回正常.
过度活跃
客户端不应该制造超过连接管理器的会话创建应答中的'requests'属性定义数量的并发请求. 无论如何,如果客户端暂停或终止一个会话,它可以制造一个额外的请求.
如果在任何时间段客户端发送系列的新请求(也就是说请求的rid属性是增长的, 而不是重复的请求)超过了'requests'属性指定的数量, 并且如果连接管理器尚未应答这些请求中的任何一个, 并且如果最后一次请求既不包含一个'pause'属性也不包含一个值为"terminate"的'type'属性, 那么连接管理器应该认为客户端正在制造过多的并发请求, 并且发一个'policy-violation'终止绑定错误给客户端来终止该HTTP会话. 注意: 这个行为同时适用于普通和轮询会话.
示例12. 过多并发请求应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='policy-violation'
xmlns='http://jabber.org/protocol/httpbind'/>
注意: 如果连接管理器在会话创建应答中没有指定一个'requests'属性, 那么它必须允许客户端按它选择的数量来发送并发请求.
如果在任何时间段客户端发送系列新请求等于'requests'属性指定的数量, 并且如果连接管理器尚未应答这些请求中的任何一个, 并且如果最后一次请求既不包含一个'pause'属性也不包含一个值为"terminate"的'type'属性, 并且最后两个请求到达的时间差小于创建会话应答中的'polling'属性定义的值, 那么连接管理器应该认为客户端正在制造超过它允许的频率的请求并且发一个'policy-violation'终止绑定错误给客户端来终止该HTTP会话. 注意: 这个行为对于轮询会话略有不同.
示例13. 过于频繁的请求应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='policy-violation'
xmlns='http://jabber.org/protocol/httpbind'/>
注意: 如果连接管理器在会话创建应答中未定义'polling'属性, 那么它必须允许客户端按它选择的频率来发送请求.
轮询会话
对一个受约束的客户端来说不一定总是能使用HTTP流水线或在同一时间和连接管理器打开多个HTTP连接. 在这种情况下客户端应该在它的会话创建请求中把'wait' 和/或 'hold' 属性值设为"0"来通知连接管理器, 并且然后在会话中始终有规律地"轮询"连接管理器来获得它可能从服务器收到的载荷. 注意: 即使客户端不请求一个轮询会话, 连接管理器可以通过设置它的
会话创建应答 的'requests'属性(它指定客户端能制造的并发请求的数量)为"1"来要求一个客户端使用轮询, 无论如何这是不推荐的.
如果一个会话将使用轮询, 连接管理器应在它的会话创建应答中指定一个高于普通值的'inactivity'属性(见
闲置 ) . 这个增加值应该大于它指定的'polling'属性值.
如果客户端在一个低于会话创建应答中'polling'属性(允许的最短轮询间隔)指定的秒数的时间段内发送两个连续的空的新请求(也就是说请求的rid属性是增加的, 而不是重复的请求), 并且如果连接管理器应答的第一个请求不包含载荷, 那么在收到第二个请求之后连接管理器应该终止HTTP会话并返回一个'policy-violation'终止绑定错误给客户端.
示例14. 过于频繁的轮询应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='policy-violation'
xmlns='http://jabber.org/protocol/httpbind'/>
注意: 如果连接管理器在会话创建应答中未指定'polling'属性, 那么它必须允许客户端按它选择的频率来轮询.
终止HTTP会话
任何时候, 客户端可以发送一个'type'属性为"terminate"的<body/>元素来正常地终止会话. 终止请求可以包含一个或多个载荷,连接管理器必须转发给服务器以确保正常的注销登录.
示例15. 来自客户端的会话终止
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 153
<body rid='1249243565'
sid='SomeSID'
type='terminate'
xmlns='http://jabber.org/protocol/httpbind'>
<presence type='unavailable'
xmlns='jabber:client'/>
</body>
连接管理器应该用一个包含空的<body/>元素的HTTP 200 OK来应答这个请求 .
示例16. 连接管理器确认终止
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 72
<body type='terminate'
xmlns='http://jabber.org/protocol/httpbind'/>
在接收到该应答后, 客户端必须认为该HTTP会话已经被终止.
请求IDs
生成
客户端必须生成一个大的, 随机的, 正整数用于初始的'rid' (见
安全事项 ) ,并且随后的每个请求的rid做加一的处理. 客户端必须小心选择一个初始的'rid',在整个会话中它的值不能够加到超过 9007199254740991
21 . 在实践中, 一个会话可能被迫变得非常长 (或涉及到非常多的包交换) 以至于超过定义的限制.
顺序的消息转发
当一个客户端制造了并发请求, 连接管理器可能没能按顺序接收到它们. 连接管理器必须按照'rid'属性指定的顺序来转发载荷到服务器并应答客户端的这些请求. 客户端必须按照请求的顺序来处理从连接管理器接收到的应答.
连接管理器应该期望'rid'属性值在一个大于前一个请求的数值窗内. 这个数值窗等于连接管理器允许的最大并发请求数量. 如果它接收到的请求的'rid'大于这个数值窗, 那么连接管理器必须以一个错误来终止会话:
示例17. 意外的rid错误
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='item-not-found'
xmlns='http://jabber.org/protocol/httpbind'/>
断开的连接
不可靠的网络通讯或客户端约束可能造成断开的连接. 连接管理器应该记住'rid'以及相应的那些客户端的非暂停请求(见 [[XEP-0124#闲置|闲置]] )和非HTTP或绑定错误的请求的HTTP应答. 保持在缓冲之中的对于非暂停请求的应答的数量应该和连接管理器允许的并发请求数量相同, 或者,如果使用了确认机制, 则和还未确认的应答数量相同.
如果网络连接中断或在客户端接收到连接管理器对于某个请求的应答之前关闭了, 那么客户端刻一重新发送一个原请求的精确副本. 任何时候连接管理器发现收到的请求的'rid已经接收过, 它应该返回一个包含缓冲区的原有XML应答的 HTTP 200 (OK) 应答给客户端 (也就是说, 一个封装了了拥有适当属性以及可选的包含一个或多个XML载荷的<body/>). 如果原有的应答不可用 (例如, 它已经不在缓冲区了), 那么连接管理器必须返回一个 'item-not-found' 终止绑定错误:
示例18. 应答不在缓冲区错误
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='item-not-found'
xmlns='http://jabber.org/protocol/httpbind'/>
注意: 无论这个'rid'是过大还是过小,错误都是一样的. 这使得攻击者更难搜索到可接受的值.
保护不安全的会话
适用性
如果客户端和连接管理器之间的会话不安全,可以使用这里描述的可选的密钥序列机制. 只有所有客户端请求都是通过 SSL(或 TLS) HTTP连接并且连接管理器生成了一个不可预知的会话ID,该会话才应该被认为是安全的. 如果会话是安全的, 则不需要使用这个密钥序列机制.
即使会话不安全, 本文前述章节定义的意外会话和请求IDs已经通过把连接绑定到一对持久TCP/IP连接的方法来提供了一个类似级别的保护,并且因此提供了防止'盲'绑定的足够保护. 无论如何, 在某些环境下, 以下定义的密钥序列机制帮助抵御了更多确定的和有知识的攻击者.
重要的是要认识到以下定义的密钥序列机制只帮助我们保护抵御一个能在一个序列的不安全会话中看到所有请求或应答但不能修改那些请求的内容(在这种情况下, 这个机制阻止攻击者插入HTTP请求到会话, 例如, 终止请求或应答)的攻击者. 无论如何, 当攻击者能修改不安全的请求或应答的内容时,密钥序列机制不提供任何保护.
简述
每个会话的HTTP请求可以通过一系列不同的socket连接来散播. 这将使得一个未经授权的收到该会话ID和会话请求ID的用户能够使用他们自己的socket连接来插入<body/>请求元素到会话中并接收相应的应答.
以下的密钥序列机制通过允许连接管理器探查第三方插入的<body/>请求元素来抵御这类攻击.
生成密钥序列
在请求一个新的会话之前, 客户端必须选择一个不可预知的计数器 ("n") 和一个不可预知的值 ("seed"). 然后客户端通过把这个"seed"做一次加密哈希运算并从160位转换成一个十六进制字符串 K(1). 它运算 "n" 次然后得到发起密钥 K(n). 这个哈希算法必须是 SHA-1 定义于
RFC 3174 22 .
示例19. 创建密钥序列
K(1) = hex(SHA-1(seed))
K(2) = hex(SHA-1(K(1)))
...
K(n) = hex(SHA-1(K(n-1)))
使用密钥
客户端必须把该会话中第一个请求的 'newkey' 属性值设为 K(n).
示例20. 携带发起密钥的会话请求
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 104
<body content='text/xml; charset=utf-8'
hold='1'
rid='1573741820'
to='example.com'
wait='60'
xml:lang='en'
newkey='ca393b51b682f61f98e7877d61146407f3d0a770'
xmlns='http://jabber.org/protocol/httpbind'/>
客户端必须把所有接下来的请求的 'key' 属性值设为按生成顺序的下一个密钥 (每个请求发送的值从 K(n-1) 向 K(1) 衰减).
示例21. 携带密钥的请求
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 88
<body rid='1573741821'
sid='SomeSID'
key='bfb06a6f113cd6fd3838ab9d300fdb4fe3da2f7d'
xmlns='http://jabber.org/protocol/httpbind'/>
连接管理器可以通过运算密钥的 SHA-1 哈希并把它和前一个请求的 'newkey' 属性(或者如果没有'newkey' 属性则为 'key' 属性)进行比较来证实密钥. 如果这个值不匹配 (或者如果接收到一个不带 'key' 属性的请求,以及接收到的是前一个请求所设的 'newkey' 或 'key' 属性), 那么连接管理器必须不处理该元素, 必须终止该会话, 并且必须返回一个 'item-not-found' 终止绑定错误.
示例22. 非法的密钥序列错误
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='item-not-found'
xmlns='http://jabber.org/protocol/httpbind'/>
切换到另一个密钥序列
生成密钥序列的时候,客户端应该选择一个高的 "n" 值. 无论如何, 如果会话太长以至于客户端在序列 K(1) 中达到最后的密钥, 那么客户端必须切换到一个新的密钥序列.
客户端必须:
- 选择新的 "seed" 和 "n" 值.
- 使用上文定义的机制生成一个新的密钥序列.
- 把该请求的 'key' 属性设为旧的序列的下一个值 (也就是说 K(1), 最后的值).
- 把该请求的 'newkey' 属性设为新的序列的值 K(n).
示例23. 新密钥序列
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 188
<body rid='1573741822'
sid='SomeSID'
key='6f825e81f4532b2c5fa2d12457d8a1f22e8f838e'
newkey='113f58a37245ec9637266cf2fb6e48bfeaf7964e'
xmlns='http://jabber.org/protocol/httpbind'>
<message to='contact@example.com'
xmlns='jabber:client'>
<body>I said "Hi!"</body>
</message>
</body>
多重流
简介
本章描述的可选特性允许在一个HTTP会话中包含多重XML流. 这个特性实质上用于那些不允许HTTP流水线的运行时环境中, 此时一个客户端能和每个连接管理器打开的并发HTTP请求的数量是受限的, 如果他们在同一时间使用不止一个帐号连接,那么运行于这类环境下的客户端需要多重流会话. 这个特性对于任何通过HTTP建立了并联流的客户端来说也降低了网络流量.
查询
如果一个连接管理器支持多重流特性, 它必须在它的会话创建应答中包含一个'stream'属性. 如果一个客户端不接受这个'stream'属性,那么它必须假定连接管理器不支持该特性.
23
这个 'stream' 属性标识该会话打开的第一个流. 每个 'stream' 属性的值必须是一个无意义的不可预知的名字,并且在连接管理器应用的上下文中是唯一的.
示例24. 携带流名字(stream name)的会话创建应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 128
<body wait='60'
inactivity='30'
polling='5'
requests='2'
hold='1'
accept='deflate,gzip'
stream='firstStreamName'
maxpause='120'
sid='SomeSID'
charsets='ISO_8859-1 ISO-2022-JP'
ver='1.6'
from='example.com'
xmlns='http://jabber.org/protocol/httpbind'/>
添加流到会话
如果连接管理器在它的会话创建应答中包含了一个 'stream' 属性,那么客户端可以在任何时间发送一个空的带有'to'属性的<body/>元素来请求它打开另一个流. 请求必须包含合法的'sid'和'rid'
24 属性, 并且也应该包含一个 'xml:lang' 属性. 请求可以包含 'route', 'from' 和 'secure' 属性 (见
|会话创建请求 ), 但是它不应该包含 'ver', 'content', 'hold' 或 'wait' 属性 (因为并没有创建一个新的会话).
示例25. 请求另一个流
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 104
<body sid='SomeSID'
rid='1573741820'
to='example.com'
route='xmpp:example.com:9999'
xml:lang='en'
xmlns='http://jabber.org/protocol/httpbind'/>
如果连接管理器没有在会话的开始表示它支持多重流, 那么它必须忽略额外的属性并且和对待普通的用于载荷的空请求一样对待该请求(见
发送和接收XM载荷 ).
25 否则它必须打开一个新的流到指定的服务器 (见
会话创建应答 ), 生成一个新的流名字, 并以此名字应答客户端. 应答也可以包含 'from' 和 'secure' 属性, 但是它不应该包含 'sid', 'requests', 'polling', 'hold', 'inactivity', 'maxpause', 'accept', 'charsets', 'ver' 或 'wait' 属性.
示例26. 添加流应答
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 128
<body stream='secondStreamName'
from='example.com'
xmlns='http://jabber.org/protocol/httpbind'/>
注意: 如果应答既不包含 'from' 也不包含 'secure' 属性,那么它们可以留待接下来的应答中被发送 (见
会话创建应答 ). 在那种情况下 'stream' 属性也是必须指定的.
传送载荷
如果在一个会话中不止打开一个流, 那么被连接管理器发送的所有非空的<body/>元素必须包含'stream'属性,它定义所有的载荷属于哪个流. 客户端应该包含一个'stream'属性用于同样的目的. 如果客户端希望连接管理器广播这些载荷给所有打开的流,它可以忽略'stream'属性. 注意: 一个<body/>元素在不同的流中必须不能包含不同的载荷.
如果一个流名字不符合该会话打开的流之一, 那么接收到的连接管理器应该返回一个 'item-not-found' 终止绑定错误, 或者接收到的客户端应该终止这个会话. 无论如何, 如果接收到的实体只是关闭这个流 (并且发送者可能在它发送这个载荷的时候没有留意), 那么它可以简单安静地忽略该<body/>元素包含的任何载荷.
注意: 不包含'from'或'secure'属性的空的<body/>元素不应该包含'stream'属性(因为任何流都没有东西正在传送). 如果这样的一个<body/>元素包含了一个'stream'属性,那么接收到的实体应该忽略该属性.
示例27. 客户端以一个流名字发送载荷
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 188
<body rid='1249243562'
sid='SomeSID'
stream='secondStreamName'
xmlns='http://jabber.org/protocol/httpbind'>
<message to='contact@example.com'
xmlns='jabber:client'>
<body>I said hello.</body>
</message>
</body>
注意: 该应答的'stream'属性值可以和响应请求的不同.
26
示例28. 连接管理器应答一个不同的流名字
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 185
<body stream='firstStreamName'
xmlns='http://jabber.org/protocol/httpbind'>
<message from='contact@example.com'
to='user@example.com'
xmlns='jabber:client'>
<body>Hi yourself!</body>
</message>
</body>
如果连接管理器没有指定流名字,那么客户端必须假定载荷是和第一个流相关的(即使第一个流已经关闭了).
如果客户端没有指定一个流名字,那么连接管理器必须广播载荷给所有打开的流. [27]
示例29. 客户端请求一个载荷被广播
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 188
<body rid='1249243562'
sid='SomeSID'
xmlns='http://jabber.org/protocol/httpbind'>
<presence xmlns='jabber:client'>
<show>away</show>
</presence>
</body>
关闭流
如果在一个会话中打开了不止一个流, 客户端可以在任何时间使用上文
终止HTTP会话 描述的程序关闭一个流, 小心地在'stream'属性指定该流的名字. 如果客户端关闭了最后一个流,连接管理器必须终止该会话. 如果客户端没有指定一个流名字那么连接管理器必须关闭所有打开的流 (发送该终止请求的任何载荷到所有流), 并终止该会话.
示例30. 客户端关闭一个流
POST /webclient HTTP/1.1
Host: httpcm.example.com
Accept-Encoding: gzip, deflate
Content-Type: text/xml; charset=utf-8
Content-Length: 153
<body rid='1249243564'
sid='SomeSID'
stream='secondStreamName'
type='terminate'
xmlns='http://jabber.org/protocol/httpbind'>
<presence type='unavailable'
xmlns='jabber:client'/>
</body>
错误条件
如果在一个会话中不止打开了一个流, 连接管理器可以在致命绑定错误中包含一个'stream'属性(见
终止绑定条件 ). 如果指定了'stream'属性,那么该流必须被双方实体关闭但是会话不应该被终止.
示例31. 致命流错误
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='remote-connection-failed'
stream='secondStreamName'
xmlns='http://jabber.org/protocol/httpbind'/>
注意: 如果连接管理器在致命流错误中不包含'stream'属性,那么会话的所有的打开的流都必须被双方实体关闭并且会话必须被终止.
错误和状态代码
在HTTP应答中有四种类型的错误和状态报告:
表1: 错误条件类型
错误条件 |
描述 |
HTTP条件 (已过时) |
连接管理器应答从传统客户端来的非法请求一个HTTP错误. 这用于绑定语法错误, 可能的攻击, 等等. 注意受约束的客户端不能区分不同的HTTP错误. |
终止绑定条件 |
这些错误条件可以被受约束的客户端读取. 他们用于连接管理器问题, 抽象流错误, 连接管理器和服务器之间的通讯问题, 以及非法客户端请求 (绑定语法错误, 可能的攻击, 等等.) |
可恢复的绑定条件 |
这些报告连接管理器和客户端之间的通讯问题. 它们不终止会话. 客户端通过重新发送前面所有的未收到应答的<body/>封装器来从这些错误恢复. |
被传输的协议的条件 |
和<body/>封装器中的XML载荷有关的错误, 通常定义于被传输的协议的文档中. 它们不终止会话. |
完整的描述在下面.
HTTP条件
注意: 除了200所有HTTP代码已经被终止绑定条件取代,以使客户端能确定错误的来源是连接管理器应用还是HTTP媒介.
传统的客户端(或连接管理器)在它的会话创建请求(或应答)中是不包含'ver'属性的. 传统客户端(或连接管理器)将根据下表来解释(或应答) HTTP错误码. 非传统连接管理器应该不发送HTTP错误码,除非它们连接的是一个传统客户端. 在接收到一个HTTP错误(400, 403, 404)之后, 一个传统客户端或任何连接到传统连接管理器的客户端必须认为该会话为空的和无用的(null and void). 一个连接到非传统连接管理器的非传统客户端可以认为该会话仍活跃.
表2: HTTP错误和状态码
代码 |
名称 |
被...取代 |
目的 |
200 |
OK |
- |
应答给合法的客户端请求. |
400 |
Bad Request |
bad-request |
通知客户端一个HTTP头的格式或绑定的元素是不可接受的(例如, 语法错误). |
403 |
Forbidden |
policy-violation |
通知客户端它违反了会话规则(轮询太频繁, 请求太频繁, 太多并发请求). |
404 |
Not Found |
item-not-found |
通知客户端 (1) 'sid' 不合法, (2) 'stream' 不合法, (3) 'rid' 大于期望的窗口上限, (4) 连接管理器不能重发应答, (5) 'key' 顺序不合法. |
注意: 没有其他定义在早期版本BOSH里的HTTP错误和状态码 (例如, Internal Server Error).
终止绑定条件
在任何发送到客户端的应答里, 连接管理器可以通过把<body/>元素的'type'属性值设为"terminate"来返回一个致命错误. 这些绑定错误标识该HTTP会话被终止了(除非定义了一个'stream'属性 -- 见
多重流错误条件 ).
注意: 尽管这些条件中的一部分类似定义于
RFC 6120 的XMPP流错误条件, 它们不会被XMPP流错误混淆. 在这里BOSH是用于传输XMPP, 在连接管理器和XMPP服务器之间经历的任何致命XMPP流错误条件只会被用下面描述的"remote-stream-error"条件来报告.
示例32. 远程连接失败错误
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: 68
<body type='terminate'
condition='remote-connection-failed'
xmlns='http://jabber.org/protocol/httpbind'/>
'condition'属性的值定义如下:
表3: 终止绑定错误条件
条件 |
目的 |
bad-request* |
T一个HTTP头或从客户端收到的绑定元素的格式不可接受(例如, 语法错误). |
host-gone |
连接管理器的'to'属性指定的目标域或'route'属性指定的目标主机或端口不再提供服务. |
host-unknown |
连接管理器的'to'属性指定的目标域或'route'属性指定的目标主机或端口是未知的. |
improper-addressing |
发起元素缺少'to'或'route'属性(或属性没有值)但是连接管理器需要它. |
internal-server-error |
连接管理器经历了一个内部错误导致它无法为请求提供服务. |
item-not-found* |
(1) 'sid'不合法, (2) 'stream'不合法, (3) 'rid' 大于期望窗口的上限, (4) 连接管理器不能重发应答, (5) 'key' 序列非法. |
other-request |
和这个请求请求想同的另一个请求导致该会话终止. |
policy-violation* |
客户端违反了会话规则(轮询太频繁, 请求太频繁, 发送了过多的并发请求). |
remote-connection-failed |
连接管理器无法连接, 或无法安全地连接, 或已经丢失了它的连接, 到服务器. |
remote-stream-error |
封装了一个正在传输的协议的错误. |
see-other-uri |
连接管理器不执行这个URI(例如, 连接管理器只接受客户端以类似 https: URI 而不是类似 the http: URI 的请求以SSL或TLS连接). 客户端可以尝试 POSTing 到<uri/>子元素内含的那个 URI . |
system-shutdown |
连接管理器正在关闭. 所有激活的HTTP会话正在被终止. 不能创建新的会话. |
undefined-condition |
这个错误不是在这里定义的那些错误之一; 连接管理器应该在这个<body/>封装器的内容里包含 应用 |