本文的英文原文来自
RFC 3921| 网络工作组 | Saint-Andre, Ed. |
|---|
| 申请讨论: 3921 | Jabber软件基金会 |
| 类别: 标准跟踪 | 2004年10月 |
可扩展的消息和出席信息协议 (XMPP): 即时消息和出席信息关于本文的说明 本文为互联网社区定义了一个互联网标准跟踪协议,并且申请讨论协议和提出了改进的建议。请参照“互联网官方协议标准”的最新版本(STD 1)获得这个协议的标准化进程和状态。本文可以不受限制的分发。
版权声明 本文版权属于互联网社区 (C) The Internet Society (2004).
摘要 本文定义了可扩展消息和出席信息协议(XMPP)的核心功能的扩展和应用,XMPP提供了RFC 2779 定义的基本的即时消息和出席信息功能。
目录
- 绪论
- XML节语法
- 会话的建立
- 交换消息
- 交换出席信息
- 管理订阅
- 名册管理
- 名册条目和出席信息订阅的集成
- 订阅状态
- 屏蔽通信
- 服务器处理XML节的规则
- 即时消息和出席信息兼容性需求
- 国际化事项
- 安全性事项
- IANA事项
- 参考
- vCards
- XML规划
- Jabber IM Presence协议和XMPP之间的不同
贡献者 致谢 作者地址 完整的版权声明
1. 绪论
1.1. 概览 XMPP是一个流化XML[XML]元素的协议,用于准实时的交换消息和出席信息。XMPP的核心功能定义在Extensible Messaging and Presence Protocol (XMPP): Core
XMPP-CORE. 这些功能 -- 主要是 XML流, 使用 TLS和SASL,以及流的根元素之下的<message/>, <presence/>, 和 <iq/> 子元素 -- 为各种类型的准实时应用提供了一个构造基础, 它可以被放在核心的顶层,使用特定XML名字空间[XML-NAMES]发送特定的应用数据. 本文描述XMPP核心功能的扩展和应用,XMPP核心功能提供了RFC 2779 [IMP-REQS]定义的基本的即时消息和出席信息功能。
1.2. 需求 为了达到本文的目的, 基本的即时消息和出席信息应用的需求定义在[IMP-REQS],它是一个高阶的规定,一个用户必须完成以下用例:
- 和其他用户交换消息
- 和其他用户交换出席信息
- 管理和其他用户之间的订阅和被订阅
- 管理联系人列表中的条目(在 XMPP 中这被称为 "roster")
- 屏蔽和特定的其他用户之间的通信(出或入)
这些功能领域的详细定义在[IMP-REQS]中, 感兴趣的用户可以直接阅读原文关于需求方面的内容。
[IMP-REQS]也规定出席信息服务必须从即时消息服务中分离; 例如, 它必须可能用这个协议来提供一个出席信息服务,一个即时消息服务,或同时提供两者. 尽管本文假定实现和部署希望提供统一的即时消息和出席信息服务, 但没有要求一个服务必须同时提供出席信息服务和即时消息服务, 并且协议也提供了把出席信息服务和即时消息服务分离成为独立服务的可能性.
注意: 虽然基于XMPP的即时消息和出席信息符合[IMP-REQS]的要求,但它不是特意为那个协议设计的,因为基础协议是在RFC 2779成文之前通过Jabber开放源代码社区的一个开放的开发过程发展出来的. 也请注意尽管在Jabber社区发展的协议中定义了许多其他方面的功能,但是这些协议不包含在本文之中,因为它们不是[IMP-REQS]所要求的.
1.3. 属于 本文继承了
XMPP-CORE定义的术语.
大写关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", 和 "OPTIONAL" 在本文中的含义定义在 BCP 14, RFC 2119 [TERMS].
2. XML 节的语法
符合'jabber:client'和'jabber:server'名字空间的XML节的基本语义和通用属性已经在
XMPP-CORE中定义了. 无论如何, 这些名字空间也定义了yixie其他的子元素, 比如通用属性'type'的值, 对于即时消息和出席信息应用就是特殊的. 因而, 在选择用于这类应用的特定用例之前, 我们在这里需要先描述一下XML节的语法, 用来补充
XMPP-CORE中的讨论.
2.1. 消息语法 符合'jabber:client' or 'jabber:server'名字空间的消息节用于"推" 信息到另一个实体. 在即时消息应用中通常的用法是包含,一个单独的消息,在一个聊天会话中的消息,一个多用户聊天室的上下文中的消息,标题或其他警告和错误的消息,
2.1.1. 消息的类型 一个消息节的'type' 属性是建议的(RECOMMENDED); 如果包含了它,它指明这个消息的会话上下文,从而提供一个关于表达的线索(例如, 在一个GUI中). 如果包含了它, 'type' 属性必须(MUST)是以下的值之一 :
- chat -- 消息是在一对一聊天会话的语境被发送. 一个兼容的客户端应该(SHOULD)在一个允许两个实体进行一对一聊天的界面中显示消息,包括适当的会话历史.
- error -- 发生了一个和上次发送者发送的消息有关的错误(关于节错误语法的详细信息, 参考 XMPP-CORE). 一个兼容客户端应该(SHOULD)在一个适当的界面展示它以通知发送者这个错误的种类.
- groupchat -- 消息是在一个多用户聊天环境的语境下发送的(类似[IRC]). 一个兼容客户端应该(SHOULD)在允许多对多聊天的界面显示这个消息,包括, 包括这个聊天室的名册和适当的会话历史. 基于XMPP的群聊协议的完整定义超出了本文的范围.
- headline -- 一个消息可能是由一个递送或广播内容的自动化服务生成的(新闻, 体育, 市场信息, RSS feeds, 等等.). 这个消息是不需要回复的, 一个兼容客户端应该(SHOULD) 在一个适当的和单独消息,聊天会话,或群聊会话不同的界面显示这个消息(例如, 不给接收者提供回复能力).
- normal -- 这个消息是一个在一对一会话或群聊会话之外的单独消息, 并且它希望接收者能够回复.一个兼容客户端应该(SHOULD)在一个允许接收者回复的界面显示这个消息, 但不需要会话历史.
一个 IM 应用应该(SHOULD)支持所有前述的消息类型;如果一个应用接收了一个没有'type'属性的消息或这个应用不理解'type'属性的值, 它必须(MUST)认为这个消息是一个 "normal" 类型(如,"normal" 是缺省的). "error"类型必须(MUST)仅仅在应答一个和从别的实体接收到的消息有关的错误时生成.
尽管'type'属性是可选的(OPTIONAL), 处于礼貌原因对于消息的任何回复总是和原来的消息同一类型;此外, 一些特殊的应用(例如, 一个多用户聊天服务) 可以(MAY)根据它们的判断强制特定消息类型的使用(例如, type='groupchat').
2.1.2. 子元素 正如 扩展名字空间extended namespaces(第二章第四节)所述, 一个消息节可以(MAY)包含任何适当名字空间的子元素.
和缺省名字空间声明一致, 缺省消息节的名字空间是'jabber:client' 或 'jabber:server', 定义了某几个允许的消息节的子元素. 如果消息节的类型是 "error", 它必须(MUST)包含一个<error/>子元素; 详细情况, 见
XMPP-CORE. 否则, 消息节可以(MAY)包含以下子元素的任何一种并且无需显式地声明名字空间:
- <subject/>
- <body/>
- <thread/>
2.1.2.1. 主题 <subject/> 元素包含了人类可读的 XML 字符数据指明这个消息的主题. <subject/>元素不能(MUST
NOT)拥有任何属性, 除了'xml:lang'属性. <subject/> 元素可以(MAY)包含多个实例用于为同一主题提供备用版本, 但是仅在每个实例的拥有的'xml:lang'属性的值互不相同的时候才可以. <subject/> 元素不能(MUST NOT)包含混合的内容(定义在 [XML]第三章第二节第二小节).
2.1.2.2. 主体 <body/> 元素包含人类可读的XML字符数据表达消息的文本内容; 这个子元素通常会有但是是可选的(OPTIONAL). <body/>元素不能(MUST NOT)拥有任何属性, 除非是'xml:lang'属性. <body/> 元素可以(MAY)包含多个实例用于为同一主体提供备用版本, 但是仅在每个实例的拥有的'xml:lang'属性的值互不相同的时候才可以. <body/>元素不能(MUST NOT)包含混合的内容(定义在 [XML]第三章第二节第二小节).
2.1.2.3. 线索 <thread/> 元素包含非人类可读的XML字符数据表达一个标识符用于跟踪两个实体之间的一个会话线索(有时相当于一个"即时消息会话"). <thread/>元素的值是由发送者生成的并且应该(SHOULD)在任何回复中拷贝回来. 如果使用了它, 它必须(MUST)在这个流的会话线索中是唯一的并且必须(MUST)和那个会话相一致(一个从同一个全JID但不同线索ID接收到消息的客户端必须(MUST)假定这个有问题的消息存在于已有的会话线索之外. <thread/>元素的使用是可选的(OPTIONAL)并且不是用于标识独立的消息,而是标识会话. 一个消息节不能(MUST NOT)包含超过一个的<thread/>元素. <thread/>元素不能(MUST NOT)拥有任何属性. <thread/>属性的值必须(MUST)被实体处理成不透明的; 不能从它得到任何语义学上的含义,并且只能对它做精确的比较. <thread/>元素不能(MUST NOT)包含混合内容(定义在 [XML]第三章第二节第二小节).
2.2. 出席信息语法 符合'jabber:client' 或 'jabber:server'名字空间的出席信息节用于表达一个实体当前的网络可用性(离线或在线, 包括之后的各种亚状态和可选的用户名义的描述性文本), 并且通知其他实体它的可用性. 出席信息节也用于协商和管理对于其他实体的出席信息的订阅.
2.2.1. 出席信息的类型 出席信息节的'type'属性是可选的(OPTIONAL). 一个不拥有任何'type'属性的出席信息节用来通知服务器发送者已经在线并且可以进行通信了, 'type' 属性表示缺乏可用性, 请求管理对其他实体的出席信息的订阅, 请求其他实体的当前出席信息, 或发生了和上次发出的出席信息节有关的错误. 如果包含了它, 'type'属性必须(MUST)拥有以下值之一:
- unavailable -- 通知实体将不可通信.
- subscribe -- 发送者希望订阅接收者的出席信息.
- subscribed -- 发送者允许接收者接收他们的出席信息.
- unsubscribe -- 发送者取消订阅另一个实体的出席信息.
- unsubscribed -- 订阅者的请求被拒绝或以前的订阅被取消.
- probe -- 对一个实体当前的出席信息的请求; 只应(SHOULD)由服务器代替一个用户生成.
- error -- 处理或递送之前发送的出席信息节的时候发生了错误.
关于出席信息语义学的详细信息和基于XMPP的即时消息和出席信息应用程序的订阅模式,参考 交换出席信息Exchanging Presence Information(第五章) 和 管理订阅Managing Subscriptions(第六章).
2.2.2. 子元素 如 扩展名字空间extended namespaces(第二章第四节)所述, 一个出席信息节可以(MAY)包含任何适当名字空间的子元素.
和缺省名字空间声明一致, 缺省出席信息节的名字空间是'jabber:client' 或 'jabber:server', 定义了某几个允许的出席信息节的子元素. 如果出席信息节的类型是 "error", 它必须(MUST)包含一个<error/>子元素; 详细情况, 见
XMPP-CORE. 如果出席信息节不拥有'type'属性,它可以(MAY)包含以下任何子元素(注意<status/>子元素可以(MAY)在一个类型为"unavailable"或"subscribe"(出于历史原因)的出席信息中被发送):
- <show/>
- <status/>
- <priority/>
2.2.2.1. 展示 可选的(OPTIONAL)<show/>元素包含非人类可读的XML字符数据表达一个特定的实体或资源的特定的可用性状态. 一个出席信息节不能(MUST NOT)包含多于一个<show/>元素. <show/>元素不能(MUST NOT)拥有任何属性. 如果提供了, 这个XML字符数据值必须(MUST)是以下之一(额外的可用性类型可以通过出席信息的适当名字空间来定义):
- away -- 实体或资源临时离开.
- chat -- 实体或资源在聊天中是激活的.
- dnd -- 实体或资源是忙(dnd = "不要打扰").
- xa -- 实体或资源是长时间的离开(xa = "长时间离开").
如果没有提供<show/>元素, 实体被假定是在线和可用的.
2.2.2.2. 状态 可选的(OPTIONAL)<status/>元素包含XML字符数据表达一个可用性状态的自然语言描述. 它通常用于联合show元素以提供可用性状态的详细描述(例如, "会议中"). <status/>元素不能(MUST NOT)拥有任何属性,除了'xml:lang'属性. <status/>元素可以(MAY)包含多个实例但是每个实例的'xml:lang'属性值必须各不相同.
2.2.2.3. 优先权 可选的(OPTIONAL)<priority/>元素包含非人类可读的XML字符数据指明资源的优先级别. 这个值必须(MUST)是一个介于-128和+127之间的数字. 一个出席信息小节不能(MUST NOT)包含超过一个的<priority/>元素. <priority/>元素不能(MUST NOT)拥有任何属性. 如果没有优先权被提供,一个服务器应该(SHOULD)认为优先级是零. 关于即时消息和出席信息系统中节路由的优先级的语义, 参考 处理XML节的服务器规则Server Rules for Handling XML Stanzas(第十一章).
2.3. IQ语法 IQ节提供一个结构化的请求-应答机制. 这个机制的基本语义学(例如, 'id'属性是必需的(REQUIRED))定义在
XMPP-CORE, 然而完成特定用例所需要的特定语义的所有案例定义在扩展名字空间extended namespace(第二章第四节)之中(注意'jabber:client'和'jabber:server'名字空间没有定义除通用的<error/>子元素之外的任何IQ节子元素). 本文定义了两个这样的名字空间,一个用于 名册管理Roster Management(第七章)而另一个用于 屏蔽通信Blocking Communication(第十章); 无论如何, 一个IQ节可以(MAY)包含符合任何扩展名字空间的结构化信息.
2.4. 扩展名字空间 因为在"jabber:client"或"jabber:server"名字空间中定义的三个XML节类型(也包括它们的属性和子元素)提供了一个基本功能级用于消息和出席信息, XMPP使用XML名字空间来扩展节用于提供额外的功能, 所以一个消息或出席信息节可以(MAY)包含一个或更多可选的子元素表达扩展消息含义的内容(例如, 一个XHTML格式版本的消息主体), 并且一个IQ节可以(MAY)包含一个这样的子元素. 这个子元素可以(MAY)有任何名字并且可以(MUST)拥有一个'xmlns'名字空间声明(不同于"jabber:client", "jabber:server", 或"http://etherx.jabber.org/streams")定义所有包含在子元素中的数据.
对于任何特定的扩展名字空间的支持在任何实现中的一部分是可选的(OPTIONAL)(除了在这里定义的扩展名字空间以外). 如果一个实体不理解这样一个名字空间, 实体被期望的行为依赖于这个实体是(1) 接收者 或 (2) 一个正在路由到接收者的实体
接收者: 如果一个接收者接收了一个包含不理解的子元素的节, 它应该(SHOULD)忽略那个特定的XML数据,例如, 它应该(SHOULD)不处理它或不向用户或相关的应用程序(如果有的话)显示它. 具体来说:
- 如果一个实体接收了一个消息或出席信息节包含一个不理解的名字空间, 在节的未知名字空间的这部分应该(SHOULD)被忽略.
- 如果一个实体接收了一个消息节中仅有的一个子元素是不理解的, 它必须(MUST)忽略整个节.
- 如果一个实体接收了一个类型"get"或"set"的IQ节包含一个不理解的子元素, 这个实体应该(SHOULD)返回一个类型为"error"的<service-unavailable/>错误条件的IQ节.
路由: 如果一个路由实体(通常是一个服务器)处理一个包含它不理解的子元素的节, 它应该(SHOULD)原封不动地把它转给接收者而忽略相关的XML数据.
3. 会话的建立
绝大部分基于XMPP的消息和出席信息应用是由一个客户端-服务器体系结构实现的,为了参加期望的即时消息和出席信息活动,需要客户端在服务器上建立一个会话. 无论如何, 在客户端能够建立一个即时消息和出席信息会话之前有很多前提必须(MUST)满足. 它们是:
- 流验证 -- 客户端在尝试建立一个会话或发送任何XML节之前必须(MUST)完成XMPP-CORE中定义的流验证.
- 资源绑定 -- 完成流验证之后, 一个客户端必须(MUST)绑定一个资源到流上,使得客户端的地址符合
<user@domain/resource>格式, 然后实体以
XMPP-CORE规定的术语来说就是一个 已连接的资源"connected resource".
如果一个服务器支持会话, 在完成一个
XMPP-CORE定义的流验证之后它必须(MUST)在它向客户端声明的流特性中包含一个符合'urn:ietf:params:xml:ns:xmpp-session'名字空间的<session/>元素:
服务器向客户端声明会话确定特性:
<stream:stream
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
id='c2s_345'
from='example.com'
version='1.0'> <stream:features> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/> <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> </stream:features>
收到需要会话确立的通知之后(并且是在完成资源绑定之后), 客户端如果想使用即时消息和出席信息功能必须(MUST)建立一个会话; 它向服务器发送一个符合'urn:ietf:params:xml:ns:xmpp-session'名字空间的类型为"set"并包含空的<session/>子元素的IQ节以完成这一步骤:
步骤 1: 客户端向服务器请求会话:
<iq to='example.com'
type='set'
id='sess_1'>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
</iq>
步骤 2: 服务器通知客户端会话已经建立:
<iq from='example.com'
type='result'
id='sess_1'/>
建立会话之后, 一个 已连接的资源(
XMPP-CORE术语)就被称为一个 激活的资源"active resource".
许多错误条件是可能的. 例如, 服务器可能遭遇一个内部条件阻碍了它建立会话, 用户名或授权身份可能缺乏建立会话的许可, 或同一个名字相关的这个资源ID已经有一个激活的资源.
如果服务器遭到一个内部条件阻碍了它建立会话, 它必须(MUST)返回一个错误.
步骤 2 (替代): 服务器应答一个错误(内部服务器错误):
<iq from='example.com' type='error' id='sess_1'> <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> <error type='wait'> <internal-server-error
xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
如果用户名或资源不被允许建立一个会话, 服务器必须(MUST)返回一个错误(例如, 被禁止).
步骤 2 (替代): 服务器应答错误(用户名或资源不被允许建立一个会话):
<iq from='example.com' type='error' id='sess_1'> <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> <error type='auth'> <forbidden
xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
如果已经同一名字已经存在一个激活的资源,服务器必须(MUST) (1) 终止这个激活的资源并允许新请求的会话, 或者 (2) 不允许新申请的会话并继续激活的资源. 服务器做哪一步取决于具体的实现, 尽管建议的(RECOMMENDED)实现 情景 #1. 在 情景 #1, 服务器应该(SHOULD)发送一个<conflict/>流错误给激活的资源, 终止用于这个激活的资源的XML流和相关的TCP连接, 并返回一个类型为"result" 的IQ节(表示成功)给新申请的会话. 在 情景 #2, 服务器应该(SHOULD)发送一个<conflict/>节错误给新申请的会话但是继续那个连接的XML流使得新申请的会话在发送另一个会话建立申请之前有机会协商出一个不冲突的资源ID.
步骤 2 (替代): 服务器通知现有的激活的资源 资源冲突(情景 #1):
<stream:error> <conflict xmlns='urn:ietf:params:xml:ns:xmpp-streams'/> </stream:error> </stream:stream>
步骤 2 (替代): 服务器通知新申请的的会话资源冲突(情景 #2):
<iq from='example.com' type='error' id='sess_1'> <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/> <error type='cancel'> <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>
建立一个会话之后, 客户端应该(SHOULD)按以下描述来发送初始化出席信息并请求它的名册, 尽管这些动作是可选的(OPTIONAL).
注意: 在允许建立即时消息和出席信息会话之前, 一个服务器可能(MAY)需要先提供帐号. 可能的提供帐号的方法包括由服务器管理员新建帐号以及使用'jabber:iq:register'名字空间进行带内帐号注册; 后一个方法超出了本文的范围, 但是记录在[JEP-0077](译者注:这个协议已改名为 XEP-0077), 由 Jabber Software Foundation [JSF]发行(译者注:这个组织也已改名为XSF).
4. 交换消息
交换消息是XMPP的一个基本用途并且随之而来的是一个用户生成一个发给另一个实体的消息节. 正如 用于处理XML节的服务器规则Server Rules for Handling XML Stanzas(第十一章)中所定义的 , 发送者的服务器负责递送消息给预定的接收者(如果接收者在同一个服务器上)或路由消息给接收者的服务器(如果接收者在不同的服务器上).
关于消息节的语法和它们已定义的属性和子元素信息, 参考 消息语法Message Syntax(第二章第一节).
4.1. 指明一个预定的接收者 一个即时消息客户端应该(SHOULD)通过提供一个JID或<message/>节中不同于发送者的'to'属性来指定一个消息的预定接收者. 如果这个消息是在回复之前接收到的消息,而接收到的消息是从JID格式为<user@domain/resource>(例如,在一个聊天会话的上下文中)实体发来的, 这个回复消息的'to'地址的值应该(SHOULD)是<user@domain/resource>而不是<user@domain>,除非发送者知道(通过出席信息)预定的接收者的资源将不再可用. 如果消息是在任何现存的聊天会话或接收到的消息之外被发送的,'to'地址的值应该(SHOULD)格式为<user@domain>而不是<user@domain/resource>.
4.2. 指定一个消息类型 大家知道, 对于一个消息节来说拥有'type'属性(它的值代表了消息的会话上下文(参见 Type(第二章第一节第一小节)))是建议的(RECOMMENDED).
以下例子展示一个'type'属性的合法值:
例子: 一个已定义类型的消息:
<message
to='romeo@example.net'
from='juliet@example.com/balcony'
type='chat'
xml:lang='en'>
<body>Wherefore art thou, Romeo?</body>
</message>
4.3. 指定一个消息主体 一个消息节可以(MAY)(并且经常会)包含一个<body/>子元素,它的XML字符数据表达消息的主要含义(见 Body(第二章第一节第二小节第二小小节)).
例子: 一个带主体的消息:
<message
to='romeo@example.net'
from='juliet@example.com/balcony'
type='chat'
xml:lang='en'> <body>Wherefore art thou, Romeo?</body> <body xml:lang='cz'>PročeŽ jsi ty, Romeo?</body> </message>
4.4. 指定一个消息主题 一个消息节可以(MAY)包含一个或多个<subject/>子元素指明消息的主题(见 Subject(第二章第一节第二小节第一小小节)).
例子: 一个带主题的消息:
<message
to='romeo@example.net'
from='juliet@example.com/balcony'
type='chat'
xml:lang='en'> <subject>I implore you!</subject> <subject
xml:lang='cz'>Úpěnlivě prosim!</subject> <body>Wherefore art thou, Romeo?</body> <body xml:lang='cz'>PročeŽ jsi ty, Romeo?</body> </message>
4.5. 指定一个会话线索 一个消息可以(MAY)包含一个<thread/>子元素指定消息处于哪个会话线索, 用于跟踪会话(见 Thread(第二章第一节第二小节第三小小节)).
例子: 一个带线索的会话:
<message
to='romeo@example.net/orchard'
from='juliet@example.com/balcony'
type='chat'
xml:lang='en'> <body>Art thou not Romeo, and a Montague?</body> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> </message> <message
to='juliet@example.com/balcony'
from='romeo@example.net/orchard'
type='chat'
xml:lang='en'> <body>Neither, fair saint, if either thee dislike.</body> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> </message> <message
to='romeo@example.net/orchard'
from='juliet@example.com/balcony'
type='chat'
xml:lang='en'> <body>How cam'st thou hither, tell me, and wherefore?</body> <thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread> </message>
5. 交换出席信息
交换出席信息通过使用出席信息节直接和XMPP相关. 无论如何, 我们看到在这里和消息处理形成一个对比:尽管一个客户端可以(MAY)通过包含一个'to'地址直接给另一个实体发送出席信息, 通常出席信息通知(例如,不包含'type'的或类型为"unavailable"的并且没有'to'地址的出席信息节) 被客户端发送给它的服务器然后由服务器广播给任何订阅了发送实体的出席信息的实体(在 RFC 2778 [IMP-MODEL]术语中, 这些实体称为订阅者). 这个广播模式不适用于和订阅相关的节或类型为"error"的出席信息, 而仅适用于以上定义的出席信息通知. (注意: 虽然出席信息可以(MAY)由一个自动化服务代替用户提供, 通常它还是由用户的客户端提供.)
关于出席信息节的语法以及它们的已定义的属性和子元素的信息, 参考
XMPP-CORE.
5.1. 客户端和服务器出席信息职责
5.1.1. 初始化出席信息 建立起一个会话之后, 一个客户端应该(SHOULD)发送初始化出席信息给服务器来通知它的通信可用性.如这里定义的, 初始化出席信息节 (1) 必须(MUST) 不拥有'to'地址(这表示它是由服务器代替客户端发送的广播) 并且 (2) 必须(MUST) 不拥有'type'属性(者表示拥护的可用性). 在发送初始化出席信息之后, 一个激活的资源被称为 可用的资源"available resource".
从一个客户端接收到初始化出席信息之后, 如果这个用户没有一个或更多的已存在的可用资源(如果这个用户已经有一个或更多可用的资源, 服务器明显不需要发送出席信息探测, 因为它已经拥有需要的信息),用户的服务器必须(MUST)做以下的步骤
:
- 从用户的全JID(例如,<user@example.com/resource>)发送出席信息探针(例如, 'type'属性值为'probe'的出席信息节)给已被这个用户订阅了的所有联系人以确定它们是否可用; 这些联系人就是那些显示在用户的名册中的JID并且'subscription'属性值为"to"或"both"(注意: 用户的服务器不能(MUST NOT)发送出席信息探针给用户已经屏蔽入站出席信息通知的联系人, 具体的描述在 屏蔽入站出席信息通知Blocking Inbound Presence Notifications(第十章第十节).)
- 从用户的全JID (e.g.,<user@example.com/resource>)广播初始化出席信息给所有订阅了该用户的出席信息的联系人; 这些联系人就是那些显示在用户的名册中的JID并且'subscription'属性值为"from"或"both"(注意: 用户的服务器不能(MUST NOT)发送出席信息探针给用户已经屏蔽出站出席信息通知的联系人, 具体的描述在 屏蔽出站出席信息通知Blocking Outbound Presence Notifications(第十章第十一节).)
另外, 用户的服务器必须(MUST)从用户的新的可用的资源向用户任何现存的可用的资源(如果有的话)广播初始化出席信息.
从用户接收到初始化出席信息之后, 联系人的服务器必须(MUST)递送这个用户的出席信息节给所有联系人的可用资源相应的全JID(<contact@example.org/resource>), 但是仅适用于用户在联系人名册中并且订阅状态为"to"或"both"并且联系人的纯JID或全JID没有被屏蔽入站出席信息通知(定义在 屏蔽入站出席信息通知Blocking Inbound Presence Notifications(第十章第十节)).
如果用户的服务器接收到一个类型为"error"的出席信息节,而这个节是用来回复服务器代替用户向联系人发送的初始化出席信息, 它不应该(SHOULD NOT)发送更多的出席信息更新给那个联系人(直到并且除非它从这个联系人接收到一个出席信息节).
5.1.2. 出席信息广播 发送初始化出席信息之后, 用户可以(MAY)在任何时候更新它的出席信息,方法是在会话期间发送一个没有'to'地址也没有'type'属性的出席信息节或'type'属性值为"unavailable"的出席信息节.(注意:一个用户的客户端不应该(SHOULD NOT)发送一个出席信息更新来自行广播用户出席信息和可用性的改变信息.)
如果出席信息节缺乏'type'属性(例如, 表达可用性), 用户的服务器必须(MUST)广播那个出席信息节的全XML给所有联系人(满足以下三点) (1) 它们在用户的联系人名册中并且订阅类型是"from"或"both", (2) 用户对于这些联系人没有屏蔽出站出席信息通知, 并且 (3) 服务器在用户的会话期间没有从它们那里接收到出席信息错误(同样适用于这个用户的其他可用的资源).
如果出席信息节的'type'属性值是"unavailable", 用户的服务器必须(MUST)广播那个出席信息节的全XML给所有符合以上描述的实体, 也适用于用户曾经在会话过程中直接发送了可用出席信息的任何实体(如果用户还没来得及直接发送不可用出席信息给那个实体).
5.1.3. 出席信息调查 从用户接收到一个出席信息调查之后, 联系人的服务器应该(SHOULD)应答如下:
1. 如果用户联系人的名册中的状态不是 "From", "From + Pending Out", 或 "Both" (定义在 订阅状态Subscription States(第九章)), 联系人的服务器必须(MUST)返回一个类型为"error"的出席信息节应答这个出席信息调查 (无论如何, 如果一个服务器从这个服务器的主机名的子域或其他信任的服务接收到一个出席信息调查, 它可以(MAY)提供这个用户的出席信息给那个实体). 具体来说:
- 如果用户在联系人的名册中的订阅状态是 "None", "None + Pending Out", 或 "To" (或根本不在联系人的名册中), 联系人的服务器必须(MUST)返回一个<forbidden/>节错误应答这个出席信息调查.
- 如果用户在联系人的名册中的订阅状态是 "None + Pending In", "None + Pending Out/In", 或 "To + Pending In", 联系人的服务器必须(MUST)返回一个<not-authorized/>节错误应答这个出席信息调查.
2. 其次, 如果联系人对这个用户的纯JID或全JID屏蔽了出席信息通知(使用缺省列表或激活列表,定义在 屏蔽出站出席信息通知Blocking Outbound Presence Notifications (第十章第十一节)), 服务器不能(MUST NOT)应答这个出席信息调查.
3. 然后, 如果联系人没有可用的资源, 服务器必须(MUST) 要么 (1) 应答这个出席信息调查, 向这个用户发送服务器从联系人接收到的最后的类型为"unavailable"的出席信息节的全XML, 或 (2) 不应答.
4. 最后, 如果联系人至少有一个可用的资源, 服务器必须(MUST)应答这个出席信息调查, 向这个用户发送服务器从联系人的每一个可用的资源收到的最后的没有'to'属性的出席信息节的全XML (再一次的,对于每一个会话都要强制服从隐私列表).
5.1.4. 直接出席信息 一个用户可以(MAY)直接发送出席信息给另一个实体 (例如, 一个出席信息节,包含'to'属性并且值为另一个实体的JID并且没有'type'属性或'type'属性值为"unavailable"). 可能出现三种情形:
- 如果用户在已经发送过初始化出席信息广播之后,发送不可用信息广播之前,直接发送出席信息给它的名册中一个订阅状态为"from" 或 "both"的联系人, 这个用户的服务器必须(MUST)路由或递送这个出席信息节的全XML(服从隐私列表)但是不应该(SHOULD NOT) 根据出席信息广播修改联系人的状态(例如, 它应该(SHOULD)在任何接下来的由用户初始化的出席信息广播包含这个联系人的JID).
- 如果用户在已经发送过初始化出席信息广播之后,发送不可用信息广播之前,直接发送出席信息给一个不在用户名册中的实体并且其订阅状态为"from" 或 "both", 这个用户的服务器必须(MUST)路由或递送这个出席信息节的全XML(服从隐私列表)但是不能(MUST NOT) 根据可用性的出席信息广播来修改这个联系人的状态(例如, 它不能(MUST NOT)在任何接下来的由用户初始化的可用性的出席信息广播中包含这个联系人的JID); 无论如何, 如果无法从用户的可用的资源直接发送出席信息, 用户的服务器必须( MUST)广播不可用出席信息给那个实体(如果这个用户还没有直接发送不可用出席信息给那个实体).
- 如果用户不是在已经发送过初始化出席信息广播之后,或在发送不可用信息广播之前,直接发送出席信息(例如, 资源激活了但是还不可用), 用户的服务器必须(MUST)认为用户向其直接发送出席信息的这个实体视为上述第二种情形中的那个实体,采用相同的处理方式.
5.1.5. 不可用出席信息 在和一个服务器结束它的会话之前, 客户端应该(SHOULD)雅致地成为不可用的,发送一个最后的没有'to'属性并且'type'属性值为"unavailable"的出席信息节(可选的, 最后的出席信息节可以(MAY)包含一个或多个<status/>元素以指明为什么用户不再可用). 无论如何, 用户的服务器不能(MUST NOT)依赖于从一个可用的资源接收最后的出席信息, 因为资源可能意外的变成不可用或可能被服务器判定超时. 如果用户的资源之一因为任何原因成为不可用的(包括雅致的或粗鲁的), 用户的服务器必须(MUST)广播不可用出席信息给所有如下的联系人 (1) 在用户的名册中并且订阅类型为 "from" 或 "both", (2) 用户没有对它们屏蔽出站出席信息的联系人, 以及 (3) 用户会话期间,服务器没有从它们那里收到出席信息错误的联系人; 用户的服务器也必须(MUST)发送不可用出席信息节给这个用户的任何其他可用的资源, 以及任何用户的资源曾经在会话期间直接向其发送过出席信息的实体(如果用户还没有直接发送不可用出席信息给那个实体). 在直接发送或广播不可用出席信息之后发送的任何没有'type'属性也没有'to'属性的出席信息节必须(MUST)由服务器广播给所有订阅者.
5.1.6. 出席信息订阅 一个订阅请求就是一个'type'属性值为"subscribe"的出席信息节. 如果订阅请求被发送给一个即时消息联系人, 在'to'属性中提供的JID的格式应该(SHOULD)是<contact@example.org>而不是<contact@example.org/resource>, 因为用户期望的结果通常是从联系人的所有资源接收到出席信息, 而不仅是'to'属性中的特定资源.
一个用户的服务器不能(MUST NOT)代替用户自动批准订阅请求. 所有订阅申请必须(MUST)直接发给用户的客户端, 具体来说就是这一用户的一个或多个可用的资源. 如果当订阅申请被用户的服务器接收到的时候没有这个用户的可用资源, 用户的服务器必须(MUST)保持这个订阅申请的记录并且在用户下次建立一个可用的资源时递送这个订阅申请, 直到这个用户批准或拒绝这个请求. 如果如果当订阅申请被用户的服务器接收到的时候这个用户有多于一个的可用资源, 用户的服务器必须(MUST)广播这个订阅申请给所有可用的资源(根据 处理XML节的服务器规则Server Rules for Handling XML Stanzas(第十一章)). (注意: 如果一个激活的资源还没有提供初始化出席信息, 服务器不能(MUST NOT)认为它是可用的并且因而不能(MUST NOT)发送订阅申请给它.) 无论如何, 如果用户从一个它已授权可以看到用户的出席信息的联系人那里收到一个类型为"subscribe"的出席信息节(例如, 当一个联系人重新同步订阅状态的时候),用户的服务器应该(SHOULD)代替用户自动应答. 另外, 用户的服务器可以(MAY)基于一个特定实现的法则(例如, 无论何时当用户的一个新的资源可用的时候, 或在一段特定长度的时间过去之后)选择重新发送一个未批准的未决订阅申请给这个联系人; 这有助于恢复可能和原始订阅申请有关的瞬间的,无声的错误.
5.2. 指明可用性状态 一个客户端可以(MAY)使用<show/>元素提供关于可用性状态的更多信息(参见 Show (第二章第二节第二小节第一小小节)).
例子: 可用性状态:
<presence> <show>dnd</show> </presence>
5.3. 指明详细的可用性状态信息 通过联合<show/>元素, 客户端使用<status/>元素可以(MAY)提供详细的可用性状态信息(参见Status (第二章第二节第二小节第二小小节)).
例子: 详细的状态信息:
<presence xml:lang='en'> <show>dnd</show> <status>Wooing Juliet</status> <status xml:lang='cz'>Ja dvořím Juliet</status> </presence>
5.4. 指明出席信息优先级 客户端可以(MAY)使用<priority/>元素为它的资源提供优先级(参见 Priority (第二章第二节第二小节第三小小节)).
例子: 出席信息优先级:
<presence xml:lang='en'> <show>dnd</show> <status>Wooing Juliet</status> <status xml:lang='cz'>Ja dvořím Juliet</status> <priority>1</priority> </presence>
5.5. 出席信息例子 本章的例子用于阐明上述和出席信息相关的协议. 用户是 romeo@example.net, 他有一个可用的资源, 资源ID为 "orchard", 并且他的名册中有以下这些人:
- juliet@example.com (subscription="both" 并且她有两个可用的资源, 一个资源名为"chamber" 而另一个资源名为 "balcony")
- benvolio@example.org (subscription="to")
- mercutio@example.org (subscription="from")
例子 1: 用户发送初始化出席信息:
例子 2: 用户的服务器代替用户发送出席信息调查给 subscription="to" 和 subscription="both" 的联系人的可用资源:
<presence
type='probe'
from='romeo@example.net/orchard'
to='juliet@example.com'/> <presence
type='probe'
from='romeo@example.net/orchard'
to='benvolio@example.org'/> 例子 3: 用户的服务器代替用户发送初始化出席信息给 subscription="from" 和 subscription="both"的联系人的可用资源:
<presence
from='romeo@example.net/orchard'
to='juliet@example.com'/> <presence
from='romeo@example.net/orchard'
to='mercutio@example.org'/>
例子 4: 联系人的服务器代替所有可用的资源应答出席信息调查:
<presence
from='juliet@example.com/balcony'
to='romeo@example.net/orchard'
xml:lang='en'> <show>away</show> <status>be right back</status> <priority>0</priority> </presence> <presence
from='juliet@example.com/chamber'
to='romeo@example.net/orchard'> <priority>1</priority> </presence> <presence
from='benvolio@example.org/pda'
to='romeo@example.net/orchard'
xml:lang='en'> <show>dnd</show> <status>gallivanting</status> </presence>
例子 5: 联系人的服务器递送用户的初始化出席信息给所有可用的资源或返回错误给用户:
<presence
from='romeo@example.net/orchard'
to='juliet@example.com/chamber'/> <presence
from='romeo@example.net/orchard'
to='juliet@example.com/balcony'/> <presence
type='error'
from='mercutio@example.org'
to='romeo@example.net/orchard'> <error type='cancel'> <gone xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </presence>
例子 6: 用户直接发送出席信息给另一个不在他的名册中的用户:
<presence
from='romeo@example.net/orchard'
to='nurse@example.com'
xml:lang='en'> <show>dnd</show> <status>courting Juliet</status> <priority>0</priority> </presence>
例子 7: 用户发送更新的可用出席信息用于广播:
<presence xml:lang='en'> <show>away</show> <status>I shall return!</status> <priority>1</priority> </presence>
例子 8: 用户的服务器仅向一个联系人广播更新的出席信息 (不是那些返回错误的联系人,也不是那些用户直接向其发送出席信息的联系人):
<presence
from='romeo@example.net/orchard'
to='juliet@example.com'
xml:lang='en'> <show>away</show> <status>I shall return!</status> <priority>1</priority> </presence>
例子 9: 联系人的服务器递送更新的出席信息给联系人所有可用的资源:
[to "balcony" resource...] <presence
from='romeo@example.net/orchard'
to='juliet@example.com'
xml:lang='en'> <show>away</show> <status>I shall return!</status> <priority>1</priority> </presence> [to "chamber" resource...]
<presence
from='romeo@example.net/orchard'
to='juliet@example.com'
xml:lang='en'> <show>away</show> <status>I shall return!</status> <priority>1</priority> </presence>
例子 10: 联系人的资源之一广播最后出席信息:
<presence from='juliet@example.com/balcony' type='unavailable'/>
例子 11: 联系人的服务器发送不可用出席信息给用户:
<presence
type='unavailable'
from='juliet@example.com/balcony'
to='romeo@example.net/orchard'/>
例子 12: 用户发送最后出席信息:
<presence from='romeo@example.net/orchard'
type='unavailable'
xml:lang='en'> <status>gone home</status> </presence> 例子 13: 用户的服务器广播不可用出席信息给联系人,包括用户直接向其发送出席信息的那个人:
<presence
type='unavailable'
from='romeo@example.net/orchard'
to='juliet@example.com'
xml:lang='en'> <status>gone home</status> </presence> <presence
from='romeo@example.net/orchard'
to='nurse@example.com'
xml:lang='en'> <status>gone home</status> </presence>
6. 管理订阅
为了保护即时消息用户和任何其他实体的隐私, 出席信息和可用性信息仅向用户已批准的其他实体披露. 当一个用户同意其他用户可以看到它的出席信息, 这个实体被称为对于用户的出席信息有一个订阅. 订阅超越了会话; 实际上, 它一直存在直到订阅者取消订阅或被订阅者取消曾经授权的订阅为止. 在XMPP中订阅是通过发送包含特定属性的出席信息节来管理的.
注意: 在订阅和名册之间有重要的交互; 这些定义在 名册条目和出席信息订阅的集成Integration of Roster Items and Presence Subscriptions (第八章), 而且读者必须参考那一章才能完整地理解出席信息订阅.
6.1. 请求一个订阅 对另一个实体的出席信息的订阅请求是由发送一个类型为"subscribe"的出席信息节来开始的.
例子: 发送一个订阅请求:
<presence to='juliet@example.com' type='subscribe'/>
关于客户端和服务器在订阅请求中的职责, 参考 出席信息订阅Presence Subscriptions(第五章第一节第六小节).
6.2. 处理一个订阅请求 当一个客户端从另一个实体接收到一个订阅请求, 它必须(MUST)批准这个请求(发送一个类型为"subscribed"的出席信息节)或拒绝这个请求(发送一个类型为"unsubscribed"的出席信息节).
例子: 批准一个订阅请求:
<presence to='romeo@example.net' type='subscribed'/>
例子: 拒绝一个出席信息订阅的请求:
<presence to='romeo@example.net' type='unsubscribed'/>
6.3. 从另一个实体取消一个订阅 如果一个用户想取消一个曾经允许的订阅请求, 它发送一个类型为"unsubscribed"的出席信息节.
例子: 取消一个曾经允许的订阅请求:
<presence to='romeo@example.net' type='unsubscribed'/>
6.4. 取消对于另一个实体的出席信息的订阅 如果用户想取消对于另一个实体的出席信息的订阅, 它发送一个类型为"unsubscribe"的出席信息节.
例子: 取消对一个实体的出席信息的订阅:
<presence to='juliet@example.com' type='unsubscribe'/>
7. 名册管理
在XMPP中, 一个人的联系人列表被称为名册(roster), 它包括任意数量的特定名册条目, 每个名册条目被一个唯一的JID(通常格式是<contact@domain>)所标识. 一个用户的名册由用户的服务器代替用户储存从而这个用户可以从任何资源访问名册信息.
注意: 在名册和订阅之间有重要的交互; 这些定义在 名册条目和出席信息订阅的集成Integration of Roster Items and Presence Subscriptions (第八章), 而且读者必须参考那一章才能完整地理解名册管理.
7.1. 语法和语义 名册使用IQ节来管理, 具体来说就是符合'jabber:iq:roster'名字空间的<query/>子元素的含义.这个<query/>元素可以(MAY)包含一个或更多<item/>子元素, 每个描述一个唯一的名册条目或曰 联系人"contact".
每个名册条目的"key"或者说唯一标识符就是一个JID,封装在<item/>元素的'jid'属性(它是必需的(REQUIRED))之中. 如果这个条目是和另一个(人类)即时消息用户相关的,'jid'属性的值的格式应该(SHOULD)是<user@domain>.
和一个名册条目相关的出席信息订阅的状态从<item/>元素的'subscription'属性可以得到.这个属性允许的值包括:
- "none" -- 这个用户没有对这个联系人出席信息的订阅, 这个联系人也没有订阅用户的出席信息
- "to" -- 这个用户订阅了这个联系人的出席信息, 但是这个联系人没有订阅用户的出席信息
- "from" -- 这个联系人订阅了用户的出席信息, 但是这个用户没有订阅这个联系人的出席信息
- "both" -- 用户和联系人互相订阅了对方的出席信息
每个<item/>条目可以(MAY)包含一个'name'属性, 它设置和这个JID相关的"nickname", 取决于用户(而不是联系人). 'name'属性的值是不透明的.
每个<item/>条目可以(MAY)包含一个或多个<group/>子元素,用于把名册条目收集到多个类别之中. <group/>子元素的XML字符数据是不透明的.
7.2. 商业规则 在一个名册"set"中一个服务器必须(MUST)忽略任何'to'地址, 并且必须(MUST)认为任何名册"set"是应用于发送者的. 为了更多的安全性, 一个客户端应该(SHOULD)检查"roster push"(包含一个名册条目的类型为"set"的输入IQ)的"from"地址以保证它来自一个信任的源; 具体的, 这个节必须(MUST)没有 'from'属性(例如, 从服务器隐含的) 或'from'属性的值匹配用户的纯JID(格式为<user@domain>)或全JID(格式为<user@domain/resource>); 否则, 客户端应该(SHOULD)忽略这个"roster push".
7.3. 登录时接收一个人的名册 在连接到服务器并成为一个激活的资源之后, 一个客户端应该(SHOULD)在发送初始化出席信息之前请求名册(无论如何, 因为可能不是所有的资源都想接收名册, 例如, 一个带宽受限的连接, 客户端对于名册的请求是可选的(OPTIONAL)). 如果一个可用的资源在一个会话期间没有请求名册, 服务器不能(MUST NOT)向它发送出席信息订阅以及相关的名册更新.
例子: 客户端向服务器请求当前的名册:
<iq from='juliet@example.com/balcony' type='get' id='roster_1'> <query xmlns='jabber:iq:roster'/> </iq>
例子: 客户端从服务器收到名册:
<iq to='juliet@example.com/balcony' type='result' id='roster_1'> <query xmlns='jabber:iq:roster'> <item jid='romeo@example.net'
name='Romeo'
subscription='both'> <group>Friends</group> </item> <item jid='mercutio@example.org'
name='Mercutio'
subscription='from'> <group>Friends</group> </item> <item jid='benvolio@example.org'
name='Benvolio'
subscription='both'> <group>Friends</group> </item> </query> </iq>
7.4. 增加一个名册条目 任何时候, 一个用户可以(MAY)增加一个条目到他或她的名册.
例子: 客户端添加一个新的条目:
<iq from='juliet@example.com/balcony' type='set' id='roster_2'> <query xmlns='jabber:iq:roster'> <item jid='nurse@example.com'
name='Nurse'> <group>Servants</group> </item> </query> </iq>
服务器必须(MUST)在持久信息存储机构中更新名册信息,并且也要向这个用户的所有已请求名册的可用资源推送这一改变. 这个"名册推送"包括一个类型为"set"的IQ节,从服务器发送给客户端,使用户的所有可用资源保持和基于服务器的名册信息的同步.
例子: 服务器 (1) 推送更新的名册信息给所有已请求名册的可用资源 并且 (2) 以一个IO结果应答发送的资源:
<iq to='juliet@example.com/balcony'
type='set'
id='a78b4q6ha463'> <query xmlns='jabber:iq:roster'> <item jid='nurse@example.com'
name='Nurse'
subscription='none'> <group>Servants</group> </item> </query> </iq> <iq to='juliet@example.com/chamber'
type='set'
id='a78b4q6ha464'> <query xmlns='jabber:iq:roster'> <item jid='nurse@example.com'
name='Nurse'
subscription='none'> <group>Servants</group> </item> </query> </iq> <iq to='juliet@example.com/balcony' type='result' id='roster_2'/>
正如IQ节类型(定义在
XMPP-CORE)的语义所要求的,每个接收到了名册推送的资源必须(MUST)应答一个类型为"result"(或 "error")的IQ节.
例子: 资源应答一个IQ结果给服务器:
<iq from='juliet@example.com/balcony'
to='example.com'
type='result'
id='a78b4q6ha463'/> <iq from='juliet@example.com/chamber'
to='example.com'
type='result'
id='a78b4q6ha464'/>
7.5. 更新名册条目 更新一个已有的名册条目(例如, 改变组) 的方法和增加一个新的名册条目是一样的, 换言之, 在IQ set 节中发送名册条目给服务器.
例子: 用户更新名册条目(增加组):
<iq from='juliet@example.com/chamber' type='set' id='roster_3'> <query xmlns='jabber:iq:roster'> <item jid='romeo@example.net'
name='Romeo'
subscription='both'> <group>Friends</group> <group>Lovers</group> </item> </query> </iq>
正如增加一个名册条目, 当更新一个名册条目时服务器必须(MUST)在持久信息存储机构中更新名册信息, 并且也要初始化一个名册推送给这个用户的所有已请求名册的可用资源.
7.6. 删除一个名册条目 任何时候, 用户可以(MAY)从他或她的名册中删除一个条目,只要发送一个 IQ set 给服务器并确保其'subscription'属性值为"remove" (如果从一个客户端接收到'subscription'属性的任何其他值,一个兼容的服务器必须(MUST)忽略它).
例子: 客户端移除一个条目:
<iq from='juliet@example.com/balcony' type='set' id='roster_4'> <query xmlns='jabber:iq:roster'> <item jid='nurse@example.com' subscription='remove'/> </query> </iq>
和增加一个名册条目一样, 删除一个名册条目的时候服务器必须(MUST)在持久信息存储机构中更新名册信息,初始化一个名册推送给这个用户的所有已请求名册的可用资源(伴随着把'subscription'属性值设为"remove"),并且发送一个 IQ result 给初始化资源.
关于这个命令的含义的更多信息, 见 移除一个名册条目并取消所有订阅Removing a Roster Item and Cancelling All Subscriptions (第八章第六节).
8. 名册条目和出席信息订阅的集成
8.1. 概览 关于用户从或向别的联系人订阅出席信息,一个即时消息用户通常希望在名册条目和出席信息订阅之间有某些层次的集成. 本章描述了在XMPP即时消息应用中必须(MUST)支持的那些层次的集成.
有四种主要的订阅状态:
- None -- 这个用户没有对这个联系人出席信息的订阅, 这个联系人也没有订阅用户的出席信息
- To -- 这个用户订阅了这个联系人的出席信息, 但是这个联系人没有订阅用户的出席信息
- From -- 这个联系人订阅了用户的出席信息, 但是这个用户没有订阅这个联系人的出席信息
- Both -- 用户和联系人互相订阅了对方的出席信息(例如, 联合'from' 和 'to')
这些状态的每一个都被反射到用户和联系人双方的名册中, 从而导致持久的订阅状态.
在以下的子章节中将叙述这些订阅状态如何为了完成特定的已定义的用例而进行交互. 关于服务器和客队户端处理所有订阅状态的细节 (包括处于以上所列的状态之外的未决状态)在 订阅状态Subscription States(第九章).
服务器不能(MUST NOT)发送出席信息订阅请求或名册推送给不可用的资源, 也不能给没有已请求的名册的可用资源.
在名册推送中'from'和'to'地址是可选的(OPTIONAL); 如果包含了, 它们的值应该(SHOULD)是那个会话的资源的全JID. 一个客户端必须(MUST)以一个类型为"result"的IQ节来承认每个名册推送(为了暂时的原因, 这些节不显示在以下的例子中但是按
XMPP-CORE定义的IQ语义学的规定它们是必需的).
8.2. 用户向联系人订阅 以下描述一个用户向一个联系人订阅的过程, 包括名册条目和订阅状态之间的互动.
1. 为了能够在用户的客户端界面处理联系人以及在服务器跟踪订阅, 用户的客户端应该(SHOULD)为新的名册条目执行一个"roster set". 这个请求包括发送一个类型为'set'的IQ节并拥有符合'jabber:iq:roster'名字空间的<query/>子元素, 它(<query/>元素)再包含一个<item/>子元素来定义新的名册条目; 这个<item/>元素必须(MUST)拥有一个'jid'属性, 可以(MAY)拥有一个'name'属性, 不能(MUST NOT)拥有一个'subscription'属性, 并且可以(MAY)包含一个或多个<group/>子元素:
<iq type='set' id='set1'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
name='MyContact'>
<group>MyBuddies</group> </item> </query> </iq>
2. 作为结果, 这个用户的服务器 (1) 必须(MUST)为这个新的名册条目初始化一个名册推送给这个用户的所有已经请求名册的可用资源, 其'subscription'属性的值为 "none"; 并且 (2) 必须(MUST)以一个 IQ result 应答发送的资源表明名册设置成功了:
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
subscription='none'
name='MyContact'> <group>MyBuddies</group> </item> </query> </iq> <iq type='result' id='set1'/>
3. 如果用户想向这个联系人请求出席信息的订阅, 用户的客户端必须(MUST)发送一个类型为'subscribe'的出席信息节给联系人:
<presence to='contact@example.org' type='subscribe'/>
4. 作为结果, 用户的服务器必须(MUST)初始化第二个名册推送给这个用户的所有已经请求名册的可用资源,把这个联系人设置成'none'订阅状态的未决子状态; 这个未决子状态是由名册条目中包含的ask='subscribe'属性所指示的:
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
subscription='none'
ask='subscribe'
name='MyContact'> <group>MyBuddies</group> </item> </query> </iq>
注意: 如果用户在发送订阅请求之前没有新建一个名册条目, 服务器必须(MUST)现在代替用户新建一个,然后发送一个名册推送给这个用户的所有已经请求名册的可用资源, 不含以上所示的'name'属性和<group/>子元素.
5. 用户的服务器也必须(MUST)把这个类型为"subscribe"的出席信息节的'from'地址设置为用户的纯JID(例如, <user@example.com>)(如果用户提供了设置为用户的全JID的'from'地址, 服务器应该(SHOULD)移除资源ID). 如果联系人和用户在不同的主机上, 用户的服务器必须(MUST)路由这个出席信息节到联系人的服务器来递送到这个联系人(这种情形的假定贯穿本文; 无论如何, 如果联系人在同一台主机, 那么服务器可以简单地直接递送出席信息节):
<presence
from='user@example.com'
to='contact@example.org'
type='subscribe'/>
注意:如果用户的服务器从联系人的服务器收到了一个类型为"error"的出席信息节, 它必须(MUST)这个错误节给用户, 用户的客户端可以(MAY)确定那个错误是否对于上次用户发出的"subscribe"类型的出席信息节(例如, 通过跟踪'id'属性)的应答,然后选择重新发送"subscribe"请求还是发送一个"unsubscribe"类型的出席信息节给联系人以恢复到它的上一个状态.
6. 接收到指向联系人的"subscribe"类型的出席信息节之后, 这个联系人的服务器必须(MUST)决定是否至少有一个已请求名册的联系人的可用资源. 如果是, 它必须(MUST)递送这个订阅请求给这个联系人(如果不是, 联系人的服务器必须(MUST)离线存储这个订阅请求用于递送 when this condition is next met; 通常这是通过增加一个关于这个联系人的名册条目到用户名册中来实现的, 伴随着一个 "None + Pending In"的状态(定义在 订阅状态Subscription States (第九章)), 无论如何一个服务器不应该(SHOULD NOT)在那种状态下推送或递送名册条目给联系人). 不论何时订阅请求被递送到了, 联系人必须决定是否批准它(根据联系人的配置选项, 联系人的客户端可以(MAY)批准或拒绝订阅请求而无需向联系人显示). 这里我们假定这个 "happy path", 即联系人批准了订阅请求(替代的拒绝订阅请求的流程定义在第八章第二节第一小节). 在这种情形下, 这个联系人的客户端 (1) 应该(SHOULD) 执行一个roster set 为这个用户指明期望的昵称和组(如果有的话); 并且 (2) 必须(MUST)发送一个"subscribed"类型的出席信息节给这个用户以批准这个订阅请求.
<iq type='set' id='set2'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq> <presence to='user@example.com' type='subscribed'/>
7. 作为结果, 联系人的服务器 (1) 必须(MUST) 初始化一个名册推送给所有联系人已请求名册的可用资源, 包含一个关于那个用户的名册条目,并且其订阅状态为'from'(甚至联系人不执行roster set,服务器也必须(MUST)发送它); (2) 必须(MUST)返回一个 IQ result 给发送的资源表示名册设置(roster set)成功了; (3) 必须(MUST)路由这个"subscribed"类型的出席信息节给用户, 首先把'from'地址设为联系人的纯JID(<contact@example.org>); 然后 (4) 必须(MUST)从所有联系人的可用资源向用户发送可用的出席信息:
<iq type='set' to='contact@example.org/resource'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
subscription='from'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq> <iq type='result' to='contact@example.org/resource' id='set2'/> <presence
from='contact@example.org'
to='user@example.com'
type='subscribed'/> <presence
from='contact@example.org/resource'
to='user@example.com'/>
注意: 如果联系人的服务器从用户的服务器收到一个"error"类型的出席信息节, 它必须(MUST)递送这个错误节给联系人, 联系人的客户端可以(MAY)确定那个错误是否对于上次联系人发出的"subscribe"类型的出席信息节(例如, 通过跟踪'id'属性)的应答,然后选择重新发送"subscribe"请求还是发送一个"unsubscribe"类型的出席信息节给用户以恢复到它的上一个状态.
8. 接收到一个指向用户的"subscribed"类型的出席信息节之后, 用户的服务器必须(MUST)首先检查在用户名册中的这个联系人的状态是: (a) subscription='none' and ask='subscribe' 还是 (b) subscription='from' and ask='subscribe'. 如果联系人不是以上述的状态在用户的名册中,用户的服务器必须(MUST)安静的忽略这个"subscribed"类型的出席信息节(例如, 服务器不能(MUST NOT)路由它到用户, 修改用户的名册, 或生成一个名册推送到用户的可用资源). 如果联系人以上述任何一种状态存在于用户的名册中, 用户的服务器 (1) 必须(MUST)从联系人向用户递送这个"subscribed"类型的出席信息节; (2)必须(MUST)初始化一个名册推送给所有已请求名册的这个用户的可用资源,包含一个关于这个联系人的更新的名册条目,同时其'subscription'属性值设置为"to"; 并且 (3) 必须(MUST)从每一个联系人的可用资源向每一个用户的可用资源递送服务器接收到的可用的出席信息节:
<presence
to='user@example.com'
from='contact@example.org'
type='subscribed'/> <iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
subscription='to'
name='MyContact'> <group>MyBuddies</group> </item> </query> </iq> <presence
from='contact@example.org/resource'
to='user@example.com/resource'/>
9. 接收到"subscribed"类型的出席信息节之后, 用户应该(SHOULD)承认接收到了订阅状态通知,
要么发送一个"subscribe"类型的出席信息节给联系人证实它, 要么发送一个"unsubscribe"类型的出席信息节给联系人否认它;这个步骤不一定影响订阅状态(见 订阅状态Subscription States(第九章)的细节), 但是会让用户用户的服务器知道它必须(MUST)不再发送订阅状态改变通知给用户(见第九章第四节).
从用户这方面看, 现在存在一个向联系人的出席信息的订阅; 从联系人的方面看, 现在存在一个从用户的来的订阅.
8.2.1. 替代流程: 联系人拒绝订阅请求 以上活动流程展示了关于用户向联系人的订阅请求的 "happy path" . 如果联系人拒绝用户的订阅请求,那么主要的替代流程如下所述.
1. 如果联系人想拒绝这个请求, 联系人的客户端必须(MUST)发送一个"unsubscribed"类型的出席信息节给用户(取代第八章第二节中步骤6发送的 "subscribed"类型的出席信息节):
<presence to='user@example.com' type='unsubscribed'/>
2. 作为结果, 联系人的服务器必须(MUST)路由这个"unsubscribed"类型的出席信息节给用户,首先把'from'地址设为联系人的纯JID (<contact@example.org>):
<presence
from='contact@example.org'
to='user@example.com'
type='unsubscribed'/>
注意: 如果联系人的服务器之前把用户添加到了联系人的名册中用来跟踪, 这时它必须(MUST)移除这个相关的条目.
3. 接收到指向用户的"unsubscribed"类型出席信息节之后, 用户的服务器 (1) 必须(MUST)地送那个出席信息节给用户 并且 (2) 必须(MUST) 初始化一个名册推送给这个用户的所有已请求名册的可用资源, 包含一个关于这个联系人的一个更新条目,其'subscription'属性设为"none"并且没有'ask'属性:
<presence
from='contact@example.org'
to='user@example.com'
type='unsubscribed'/> <iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
subscription='none'
name='MyContact'> <group>MyBuddies</group> </item> </query> </iq>
4. 接收到类型为"unsubscribed"出席信息节之后, 用户应该(SHOULD)承认收到订阅状态通知, 要么发送一个"unsubscribe"类型的出席信息节给联系人证实它, 要么发送一个"subscribe"类型的出席信息节给联系人否认它; 这一步骤不影响订阅状态(见 订阅状态Subscription States(第九章)的细节), 但是让用户的服务器知道它必须(MUST)不再发送订阅状态改变的通知给用户(见第九章第四节).
作为这一行为的结果, 联系人现在在用户的名册中, 状态为"none",而用户根本不在联系人的名册中.
8.3. 建立一个相互的订阅 用户和联系人可以在前述"happy path"的基础上建立一个相互的订阅(例如, 一个"both"的订阅类型). 流程如下.
1. 如果联系人想建立一个相互的订阅, 联系人必须(MUST)发送一个订阅请求给用户(视联系人的配置选项而定, 联系人的客户端可以(MAY)自动发送它):
<presence to='user@example.com' type='subscribe'/>
2. 作为结果, 联系人的服务器 (1) 必须(MUST)初始化一个名册推送给联系人的所有已请求名册的可用资源, 伴随着用户仍在'from'订阅状态但同时有一个未决的'to'订阅状态(通过在名册条目中包含一个ask='subscribe'的属性来指示); 并且 (2) 必须(MUST)路由这个"subscribe"类型的出席信息节给用户(先把'from'地址设为联系人的纯JID(<contact@example.org>)):
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
subscription='from'
ask='subscribe'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq> <presence
from='contact@example.org'
to='user@example.com'
type='subscribe'/>
注意: 如果联系人的服务器从用户的服务器收到一个"error"类型的出席信息节, 它必须(MUST)递送这个错误节给联系人, 它的客户端可以(MAY)确定这个错误是用来应答上次发送的"subscribe"类型的出席信息节(换言之, 通过跟踪'id'属性) 并且选择重发这个"subscribe"请求还是发送一个"unsubscribe"类型的出席信息节给用户以把名册恢复到它的前一个状态.
3. 接收到指向用户的"subscribe"类型出席信息节之后, 用户的服务器必须确定是否至少有一个已请求名册可用资源. 如果是, 用户的服务器必须(MUST)递送这个订阅请求给用户(如果不是, 它必须(MUST)离线存储这个订阅请求等这种情形再次发生时递送). 无论何时订阅请求被递送了, 用户必须决定是否批准它(视用户的配置选项而定, 用户的客户端可以(MAY)批准或拒绝这个订阅请求而不需要向用户显示). 这里我们假定这是"happy path",用户批准了订阅请求(替代的拒绝订阅请求的流程定义在第八章第三节第一小节). 在这种情形下, 用户的客户端必须(MUST)发送一个"subscribed"类型的出席信息节给联系人表示批准了订阅请求.
<presence to='contact@example.org' type='subscribed'/>
4. 作为结果, 用户的服务器 (1) 必须(MUST)初始化一个名册推送给用户的所有已请求名册的可用资源, 包含一个关于联系人的名册条目,其'subscription'属性设为"both"; (2) 必须(MUST)路由这个"subscribed"类型的出席信息节给联系人(先把'from'地址设为用户的纯JID<user@example.com>)); 并且 (3) 必须(MUST)向联系人发送它从用户的每个可用资源收到的最近一次出席信息节的全XML(不带'to'属性)(强制每个会话遵守隐私列表):
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
subscription='both'
name='MyContact'> <group>MyBuddies</group> </item> </query> </iq> <presence
from='user@example.com'
to='contact@example.org'
type='subscribed'/> <presence
from='user@example.com/resource'
to='contact@example.org'/>
注意: 如果用户的服务器从联系人的服务器接收到一个"error"类型的出席信息节, 它必须(MUST)递送这个错误节给用户, 它客户端可以(MAY)确定这个错误是用来应答上次发出去的"subscribed"类型的出席信息节(换言之, 通过跟踪'id'属性) 并且选择重发这个订阅请求还是发送一个"unsubscribed"类型的出席信息节给联系人以把名册恢复到上次的状态.
5. 接收到指向联系人的"subscribed"类型的出席信息节之后, 联系人的服务器必须(MUST)首先检查用户在联系人的名册中的状态是否以下状态之一: (a) subscription='none' and ask='subscribe' 或 (b) subscription='from' and ask='subscribe'. 如果用户不是以上述两种状态之一存在于联系人的名册中, 联系人的服务器必须(MUST)安静地忽略这个"subscribed"类型的出席信息节(例如, 它不能(MUST NOT)路由它给联系人, 修改联系人的名册, 或生成一个名册推送给联系人的可用资源). 如果用户以上述两种状态之一存在于联系人的名册中, 联系人的服务器 (1) 必须(MUST)从用户向联系人递送这个"subscribed"类型的出席信息节; (2) 必须(MUST)初始化一个名册推送给这个联系人的所有已请求名册的可用资源, 包含一个关于这个用户的更新的名册条目,其'subscription'属性值设为"both"; 并且 (3) 必须(MUST)向这个联系人的每个可用资源递送它从这个用户的每个资源收到的可用出席信息节:
<presence
from='user@example.com'
to='contact@example.org'
type='subscribed'/> <iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
subscription='both'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq> <presence
from='user@example.com/resource'
to='contact@example.org/resource'/>
6. 收到"subscribed"类型的出席信息节之后, 联系人应该(SHOULD)承认收到订阅请求通知,要么发送一个"subscribe"的出席信息节给用户证实它,要么发送一个"unsubscribe"类型的出席信息节给用户否认它; 这一步骤不影响订阅状态(细节见 订阅状态Subscription States(第九章)), 但是让联系人的服务器知道它必须(MUST)不再发送订阅状态变更通知给联系人(见第九章第四节).
用户和联系人现在有了对双方的出席信息的一个相互订阅 -- 换言之, 这个订阅类型为 "both".
8.3.1. 替代流程: 用户拒绝订阅请求 以上活动流程展示了关于联系人对用户的订阅请求的 "happy path". 如果用户拒绝了联系人的订阅请求,其主要流程如下.
1. 如果用户想拒绝请求, 用户的客户端必须(MUST)发送一个"unsubscribed"类型的出席信息节给联系人(替代第八章第三节中的第三步中所发送的"subscribed"类型出席信息节):
<presence to='contact@example.org' type='unsubscribed'/>
2. 作为结果, 用户的服务器必须(MUST)路由这个"unsubscribed"类型的出席信息节给联系人(首先把'from'地址设为用户的纯JID(<user@example.com>)):
<presence
from='user@example.com'
to='contact@example.org'
type='unsubscribed'/>
3. 接收到指向联系人的"unsubscribed"类型的出席信息节之后, 联系人的服务器 (1) 必须(MUST)递送这个出席信息节给联系人; 并且 (2) 必须(MUST)初始化一个名册推送给这个联系人的所有已请求名册的可用资源, 包含关于这个用户的更新的名册条目,其'subscription'属性的值设为"from"并且没有'ask'属性:
<presence
from='user@example.com'
to='contact@example.org'
type='unsubscribed'/> <iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
subscription='from'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq>
4. 接收到"unsubscribed"类型的出席信息节之后, 联系人应该(SHOULD)承认收到那个订阅状态通知,要么向用户发送一个"unsubscribe"类型的出席信息节以证实它,要么向用户发送一个"subscribe"类型的出席信息以否认它; 这个步骤不会影响订阅状态(详见 订阅状态Subscription States(第九章)),但是让联系人的服务器知道它必须(MUST)不再发送订阅状态变更通知给联系人(见第九章第四节).
作为这一活动的结果, 订阅状态没有任何改变; 换言之, 联系人在用户的名册中的订阅状态为"to"并且用户在联系人的名册中的订阅状态为"from".
8.4. 取消订阅 在订阅了一个联系人的出席信息之后的任何时候, 一个用户可以(MAY)取消订阅. 在所有实例中用户发送来执行这一动作的XML是相同的, 接下来的订阅状态根据发出取消订阅命令时获得的订阅状态的情况而不同. 两种可能的情节描述如下.
8.4.1. 情形 #1: 当订阅不是相互的时候取消订阅 在第一种情形, 用户有一个向联系人的出席信息的订阅但是联系人没有对用户的出席信息的订阅(换言之, 订阅不是相互的).
1. 如果用户想取消对联系人的出席信息的订阅, 用户必须(MUST)发送一个"unsubscribe"类型的出席信息节给联系人:
<presence to='contact@example.org' type='unsubscribe'/>
2. 作为一个结果, 用户的服务器 (1) 必须(MUST) 发送一个名册推送给这个用户的所有已请求名册的可用资源,包含一个关于这个联系人的更新名册条目,其'subscription'属性设为"none"; 并且 (2) 必须(MUST)路由这个"unsubscribe"类型的出席信息节给联系人(首先把'from'地址设为用户的纯JID(<user@example.com>)):
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
subscription='none'
name='MyContact'> <group>MyBuddies</group> </item> </query> </iq> <presence
from='user@example.com'
to='contact@example.org'
type='unsubscribe'/> 3. 接收到指向联系人的"unsubscribe"类型出席信息节之后, 联系人的服务器 (1) 必须(MUST)初始化一个名册推送给这个联系人的所有已请求名册的可用资源, 包含一个关于这个用户的名册条目,其'subscription'属性值设为"none" (如果联系人不可用或未曾请求名册, 联系人的服务器必须(MUST)修改名册条目并在下次联系人请求名册时发送那个已修改的条目); 并且 (2) 必须(MUST)递送这个"unsubscribe"状态改变通知给联系人:
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
subscription='none'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq> <presence
from='user@example.com'
to='contact@example.org'
type='unsubscribe'/>
4. 接收到"unsubscribe"类型的出席信息节之后, 联系人应该(SHOULD)承认收到那个订阅状态通知,要么发送一个"unsubscribed"类型的出席信息节给用户以证实它,要么发送一个"subscribed"类型的出席信息节给用户否认它; 这个步骤不影响订阅状态(详见 订阅状态Subscription States (第九章)), 但是让联系人的服务器知道它必须(MUST)不再发送订阅状态变更通知给联系人(见第九章第四节).
5. 联系人的服务器接着 (1) 必须(MUST)发送一个"unsubscribed"类型的出席信息节给用户;并且 (2) 应该(SHOULD)向用户发送从这个联系人的所有可用资源收到的不可用出席信息:
<presence
from='contact@example.org'
to='user@example.com'
type='unsubscribed'/> <presence
from='contact@example.org/resource'
to='user@example.com'
type='unavailable'/>
6. 当用户的服务器收到类型为"unsubscribed" 和 "unavailable"的出席信息节, 它必须(MUST)递送它们给用户:
<presence
from='contact@example.org'
to='user@example.com'
type='unsubscribed'/> <presence
from='contact@example.org/resource'
to='user@example.com'
type='unavailable'/>
7. 接收到"unsubscribed"类型的出席信息节之后, 用户应该(SHOULD)承认收到那个订阅状态变更通知,要么向联系人发送一个"unsubscribe"类型的出席信息节以证实它,要么向联系人发送一个"subscribe"的出席信息节以否认它;这步骤不影响订阅状态(详见 订阅状态Subscription States(第九章)), 但是让用户的服务器知道它必须(MUST)不在发送订阅状态变更通知给用户(见第九章第四节).
8.4.2. 情形 #2: 当订阅是相互的时候取消订阅 在第二种情形下, 用户有一个向联系人的出席信息的订阅并且联系人也有一个向用户的出席信息的订阅(换言之, 订阅是相互的).
1. 如果用户想从联系人的出席信息取消订阅, 用户必须(MUST)发送一个"unsubscribe"类型的出席信息节给联系人:
<presence to='contact@example.org' type='unsubscribe'/>
2. 作为一个结果, 用户的服务器 (1) 必须(MUST)发送一个名册推送给这个用户的所有已请求名册的可用资源,包含一个关于这个联系人的更新名册条目,其'subscription'属性值设为"from"; 并且 (2) 必须(MUST)路由这个"unsubscribe"类型的出席信息节给这个联系人( 首先把'from'地址设为这个用户的纯 JID(<user@example.com>):
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='contact@example.org'
subscription='from'
name='MyContact'> <group>MyBuddies</group> </item> </query> </iq> <presence
from='user@example.com'
to='contact@example.org'
type='unsubscribe'/>
3. 接收到指向联系人的"unsubscribe"类型的出席信息节之后, 联系人的服务器 (1) 必须(MUST)初始化一个名册推送给这个联系人的所有已请求名册的可用资源, 包含一个关于这个用户的名册条目,其'subscription'属性值设为"to" (如果联系人不可用或未曾请求名册, 联系人的服务去必须(MUST)修改这个名册条目并且等下次联系人请求名册的时候再发送这个修改过的名册条目); 并且 (2) 必须(MUST)递送这个"unsubscribe"状态变更通知给联系人:
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
subscription='to'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq> <presence
from='user@example.com'
to='contact@example.org'
type='unsubscribe'/>
4. 接收到这个"unsubscribe"类型的出席信息节之后, 联系人应该(SHOULD)承认收到了那个订阅状态通知,要么向用户发送一个"unsubscribed"类型的出席信息节以证实它,要么向用户发送一个"subscribed"类型的出席信息节以否认它; 这个步骤不影响订阅状态(详见 订阅状态Subscription States(第九章)), 但是让联系人的服务器知道它必须(MUST)不再发送订阅状态变更通知给联系人(见第九章第四节).
5. 联系人的服务器然后 (1) 必须(MUST)发送一个"unsubscribed"类型的出席信息节给用户; 并且 (2) 应该(SHOULD)向用户发送它从联系人的所有可用资源收到的不可用出席信息:
<presence
from='contact@example.org'
to='user@example.com'
type='unsubscribed'/> <presence
from='contact@example.org/resource'
to='user@example.com'
type='unavailable'/>
6. 当用户的服务器收到"unsubscribed"和"unavailable"类型的出席信息节, 它必须(MUST)递送它们给用户:
<presence
from='contact@example.org'
to='user@example.com'
type='unsubscribed'/> <presence
from='contact@example.org/resource'
to='user@example.com'
type='unavailable'/>
7. 接收到"unsubscribed"类型的出席信息节之后, 用户应该(SHOULD)承认收到了那个订阅状态的通知,要么向联系人发送一个"unsubscribe"类型的出席信息节以证实它,要么向联系人发送一个"subscribe"类型的出席信息节以否认它;这个步骤不影响订阅状态(详见 订阅状态Subscription States(第九章)), 但是让用户的服务器知道它必须(MUST)不在发送订阅状态变更通知给用户(见第九章第四节).
注意: 显然这不会导致名册条目从用户的名册移除, 并且联系人仍然有一个对用户的出席信息的订阅.为了完全取消双向的订阅并完全从用户的名册中移除名册条目, 用户应该(SHOULD)使用subscription='remove'(定义在 移除一个名册条目并取消所有订阅项Removing a Roster Item and Cancelling All Subscriptions (第八章第六节))更新名册条目.
8.5. 取消一个订阅项 在批准来自一个用户的任何订阅请求之后的任何时候, 一个联系人可以(MAY)取消那个订阅项. 联系人在所有实例中执行这个动作中发送的XML是相同的, 接下来的订阅状态根据取消命令发出当时所获得的订阅状态而有所不同. 所有可能的情节描述如下.
8.5.1. 情形 #1: 当订阅不是相互的时候取消订阅项 在第一种情形下, 用户有一个对联系人的出席信息的订阅但是联系人没有对于用户的出席信息的订阅(换言之, 订阅还不是相互的).
1. 如果联系人想取消用户的订阅项, 联系人必须(MUST)发送一个"unsubscribed"类型的出席信息节给用户:
<presence to='user@example.com' type='unsubscribed'/>
2. 作为一个结果, 联系人的服务器 (1) 必须(MUST)发送一个名册推送给这个联系人的所有已请求名册的可用资源, 包含一个关于这个用户的更新的名册条目,其'subscription'属性值设为"none"; (2) 必须(MUST)路由这个"unsubscribed"类型的出席信息节给用户(首先把'from'地址设为联系人的纯JID(<contact@example.org>)); 并且 (3) 应该(SHOULD)向用户发送它从联系人的所有可用资源收到的不可用出席信息:
<iq type='set'> <query xmlns='jabber:iq:roster'> <item
jid='user@example.com'
subscription='none'
name='SomeUser'> <group>SomeGroup</group> </item> </query> </iq> <presence
from='contact@example.org'
to='user@example.com'
type='unsubscribed'/> <presence
from='contact@example.org/resource'
to='user@example.com'
type='unavailable'/>
3. 接收到指向用户的"unsubscribed"类型的出席信息节之后, 用户的服务器 (1) 必须(MUST)初始化一个名册推送给这个用户的所有已请求名册的可用资源, 包含一个关于这个联系人的名册条目更新,其'subscription'属性值设为"none"(如果用户不可用或未曾请求名册, 用户的服务器必须(MUST)修改这个名册条目并且等下次用户请求名册的时候发送修改过的名册条目); (2) 必须(MUST)递送这个"unsubscribed"状态改变通知给这