XEP-0030

来自Jabber/XMPP中文翻译计划
2009年3月30日 (一) 08:43Admin (讨论 | 贡献)的版本
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳转到: 导航, 搜索


本文的英文原文来自XEP-0030

XEP-0030:服务发现

本文档定义了XMPP协议扩展用于发现(1)Jabber实体的信息和(2)与这些实体相关的细节。

注意 此处定义的协议是XMPP标准基金会的最终标准,可作为稳定技术用于开发和部署。

文档信息

系列:XEP

序号:0030

发布者:XMPP标准基金会

状态:终结版

类型:标准跟踪

版本:2.3

最后更新:2007-02-15

批准机构:XMPP理事会

依赖标准:XMPP Core

替代标准:XEP-0011, XEP-0094

被替代标准:无

缩略名:disco

命名空间disco#info的XML方案(Schema)[1]

命名空间disco#items的XML方案(Schema)[2]

作者信息

法律通告

讨论地点

相关的XMPP

术语的一致性

目录

绪论

在Jabber网络中发现实体相关信息的能力是极有价值的。这些信息包括实体支持的特性或支持的协议、实体的类型或身份、同原实体以某种方式相联系的另外一些实体等(这些实体通常当作“父”实体的“子”实体)。虽然XMPP核心并没有定义怎样做的机制,但在Jabber社区内,过去已经有几个协议用在了服务发现、特定的Jabber浏览\[2\]和代理信息\[3\]上。然而,这些协议总使人感觉不那么合适,其原因如下:

  1. Jabber浏览和代理信息都不容易扩展。例如,在XEP-0011中列出JID类型的种类及子类都明确地定义为唯一的正式种类,向列表加入新的JID类型都要修改XEP-0011。而Jabber浏览规范不允许用非正式种类和以‘x-’打头的类型,这会产生移植问题。这种适应性的缺乏违反了Jabber社区的协议设计原则核心之一\[4\]。
  2. 代理信息中,没有告知所支持的特性的方法。虽然Jabber浏览包含了这样的机制,但表达一个特性的有效性的唯一方法是告知一个所支持的名字空间。然而,有些特性也许并非唯一地对应一个协议名字空间,它只是特性的一个实现,但并不唯一。
  3. 一个Jabber浏览结果返回一个组合,由(1)Jabber实体支持的名字空间,(2)与Jabber实体相关联的细节,(3)相关细节支持的名字空间组成。这种方法混淆了信息级别,要求父节点知晓子节点的一切,也就引入了极大的混乱。
  4. 在Jabber浏览和代理信息中,要求细节(items)必须可定址为JID;然而,这在有些应用中是作不到的。

本文档旨在克服Jabber浏览和代理信息这两个协议的弱点。其结果就是一个用于服务发现的标准追踪协议(通常缩写为“disco”,就和一些熟悉的协议,如简单对象访问协议(SOAP)\[5\]一样)。

需求

在作者的头脑中构思的服务发现协议要满足下面的需求:

  • 协议 必须 支持它所取代的协议(Jabber浏览和代理信息)之全部功能
  • 有三种关于一个实体的信息需要发现:
  1. 它的基本身份(类型和/或目录)
  2. 它提供的特性和支持的协议
  3. 与实体相关联的任何附加细节,无论它们是否能定址为JID

三种信息都 必须 支持,但前两种信息都与实体本身有关,而第三种信息与实体关联的细节有关;因此只需要两种不同的查询类型。

  • 子细节信息的发现 必须 通过向它自己,而不是向父实体,发送单独的发现请求实现。(其后果之一是,要发现整个三种信息,需要多个请求/响应对,以“遍历整个信息树”)。
  • 身份和特性表 必须 是柔性的。
  • 协议本身 必须 是可扩展的。

发现Jabber实体的信息

基本协议

一个发出请求的实体会想发现网络中另一个实体的信息。想得到的信息一般分为两种:

  1. 对象实体的身份(identity)。在disco中,一个实体的身份被分成种类(category)(服务器、客户端、网关、目录等等)及其种类中的特殊类型(IM服务器、电话对处理的客户端、MSN网关对AIM网关、用户目录对聊天室目录等等)。这个信息帮助请求实体测定服务组或服务“桶”,以使实体最恰当地放置其中(例如,或许用个合适的图标把实体显示成GUI)。一个实体 __可以__ 有多个身份。当提供多个身份元素的时候,每个身份元素的名称属性 __应该__ 有相同的值。
  2. 目标实体提供的特性和支持的协议。这个信息帮助请求实体测定对目标实体可以做什么样的动作(注册、搜索、联合等等),实体支持什么样的协议,以及是否有感兴趣的特性类型(例如,为了特性协商的目的)。

为了发现这样的信息,请求实体 必须 向目标实体的JID发送类型为“get”的IQ段,包含一个用命名空间'http://jabber.org/protocol/disco#info'标识的空的<query/>元素('to'的地址是 必需的必须 包含有效的JID;<query>元素的'node'属性是 可选的 ,本文中~~信息节点及细节节点~~一节对此有说明)。

例1. 查询信息

<iq type='get'
    from='romeo@montague.net/orchard'
    to='plays.shakespeare.lit'
    id='info1'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

然后目标实体 __必须__ 返回一个IQ结果,或者返回一个错误(见文中[出错条件|XMPP文档列表/XMPP扩展/XEP-0030#出错条件])。结果 __必须__ 包含用命名空间'http://jabber.org/propocol/disco#info'标识的<query/>元素,元素中依次包含一个或多个<identity/>元素及一个或多个<feature/>元素。(注意:每个实体 __必须__ 至少有一个身份,每个实体 __必须__ 至少支持‘http://jabber.org/protocol/disco#info’特性;然而,并不要求一个实体一定返回一个结果,它也 __可以__ 返回一个错误,最可能的是<feature-not-implemented/>或<service-unavilable/>,虽然其他出错条件也是可以的)。每个<identity/>元素 __必须__ 拥有'category'和'type'属性,用来说明实体的种类。每个<feature/>元素 __必须__ 拥有一个'var'属性,它的值是协议的命名空间或是实体提供的其他特性。如果象本文中[XMPP注册事项|XMPP文档列表/XMPP扩展/XEP-0030#XMPP注册事项]一节说明的那样,种类/类型的值和特性值都在公共登记处注册了的话就更好了。

例2. 信息请求的结果集

<iq type='result'
    from='plays.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='info1'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <identity
        category='conference'
        type='text'
        name='Play-Specific Chatrooms'/>
    <identity
        category='directory'
        type='chatroom'
        name='Play-Specific Chatrooms'/>
    <feature var='http://jabber.org/protocol/disco#info'/>
    <feature var='http://jabber.org/protocol/disco#items'/>
    <feature var='http://jabber.org/protocol/muc'/>
    <feature var='jabber:iq:register'/>
    <feature var='jabber:iq:search'/>
    <feature var='jabber:iq:time'/>
    <feature var='jabber:iq:version'/>
  </query>
</iq>

如果指定目标实体的JID不存在,服务器或其他认证实体 __应该__ 返回一个<item-not-found/>错误,除非如果这么做会破坏在XMPP核心协议和{link:XMPP IM|http://tools.ietf.org/html/rfc3921}\[6\]中说明的隐私和安全事项,或是破坏本地隐私和安全策略(见文中[安全事项|XMPP文档列表/XMPP扩展/XEP-0030#安全事项])

例3. 目标实体不存在

<iq type='error'
    from='plays.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='info1'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
  <error code='404' type='cancel'>
    <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  </error>
</iq>

如果隐私和安全事项或策略阻止服务器或其他授权实体返回<item-not-found/>错误,取而代之它 __应该__ 返回一个<service-unavailable/>错误。

例4. 服务不可用

<iq type='error'
    from='plays.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='info1'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
  <error code='503' type='cancel'>
    <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  </error>
</iq>

当一个实体向一个由服务器做宿主的裸JID(<account@domain.tld>)发送disco#info请求的时候,这个服务器 __必须__ 代表寄宿的帐号回应一个IQ错误或IQ结果。有关访问这些功能的重要规则,见本文的~~安全事项~~一节。特别地,为响应向一个没有节点的裸JID发出的disco#info请求,如果访问没被拒绝,服务器 __应该__ 为这个裸JID返回一个IQ结果,里面的主身份 __应该__ 有具有合适类型的“account”种类,类型在~~服务发现的身份~~注册中说明(最可能的是类型"registered")。注意:这使得那些已认证或信任的实体能发现帐号是否存在及帐号的类型(例如,在IM系统中,在把帐号加入联系列表之前检测它是否存在)。

例5. 从裸JID那里请求信息

<iq type='get'
    from='shakespeare.lit'
    to='juliet@capulet.com'
    id='info2'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

这里我们假定shakespeare.lit被capulet.com信任,而帐号<juliet@capulet.com>是个注册帐号:

例 6. 服务器替裸JID答付

<iq type='result'
    from='juliet@capulet.com'
    to='shakespeare.lit'
    id='info2'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <identity category='account' type='registered'/>
  </query>
</iq>

向关联实体发出的查询结果会不同,或能得到更详细的信息。一个例子是向一个特别的会议室而不是父实体的会议服务发出查询:

例7. 查询指定的会议室

<iq type='get'
    from='juliet@capulet.com/balcony'
    to='balconyscene@plays.shakespeare.lit'
    id='info3'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
 
<iq type='result'
    from='balconyscene@plays.shakespeare.lit'
    to='juliet@capulet.com/balcony'
    id='info3'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <identity
        category='conference'
        type='text'
        name='Romeo and Juliet, Act II, Scene II'/>
    <feature var='http://jabber.org/protocol/disco#info'/>
    <feature var='http://jabber.org/protocol/muc'/>
    <feature var='http://jabber.org/protocol/feature-neg'/>
    <feature var='muc-password'/>
    <feature var='muc-hidden'/>
    <feature var='muc-temporary'/>
    <feature var='muc-open'/>
    <feature var='muc-unmoderated'/>
    <feature var='muc-nonanonymous'/>
  </query>
</iq>

另一个例子是向一个带明确资源连接的IM用户发出查询:

例 8. 查询连接资源的更详细的信息

<iq type='get'
    from='juliet@capulet.com/balcony'
    to='romeo@montague.net/orchard'
    id='info4'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>
 
<iq type='result'
    from='romeo@montague.net/orchard'
    to='juliet@capulet.com/balcony'
    id='info4'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <identity
        category='client'
        type='pc'
        name='Gabber'/>
    <feature var='jabber:iq:time'/>
    <feature var='jabber:iq:version'/>
  </query>
</iq>

=信息节点

disco#查询 可以 直接发送到和一个JID关联的指定的节点标识符,虽然节点的主要作用是作为细节(Items)节点而不是信息(info)节点。

例 9. 查询指定的JID和节点的组合

<iq type='get'
    from='romeo@montague.net/orchard'
    to='mim.shakespeare.lit'
    id='info3'>
  <query xmlns='http://jabber.org/protocol/disco#info' 
         node='http://jabber.org/protocol/commands'/>
</iq>

如果请求包含'node'属性,那么响应也 __应该__ 映射指定的'node'属性,以保证请求和响应的一致。

例 10. JID+node result

<iq type='result'
    from='mim.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='info3'>
  <query xmlns='http://jabber.org/protocol/disco#info' 
         node='http://jabber.org/protocol/commands'>
    <identity
        category='automation'
        type='command-list'/>
  </query>
</iq>

发现与Jabber实体相关的细节

基本协议

请求实体为了发现一个Jabber实体相关联的细节,它 __必须__ 向目标实体发送类型为"get"的IQ段,其中包含一个空的<query/>元素,受限命名空间为‘http://jabber.org/protocol/disco#items’:

例11. 请求全部细节

<iq type='get'
    from='romeo@montague.net/orchard'
    to='shakespeare.lit'
    id='items1'>
  <query xmlns='http://jabber.org/protocol/disco#items'/>
</iq>

目标实体 必须 或者返回它的公开可用的细节列表,或者返回一个错误。细节列表 必须 是类型为"result"的IQ段,每个细节用<query/>的子元素<item/>来说明,受限命名空间是'http://jabber.org/protocol/disco#items'(子元素<item/> 必须 用'jid'属性指定细节的JID, 可以 用'name'属性说明细节的自然语言名):

例12. 全部细节的结果集

<iq type='result'
    from='shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='items1'>
  <query xmlns='http://jabber.org/protocol/disco#items'>
    <item jid='people.shakespeare.lit'
          name='Directory of Characters'/>
    <item jid='plays.shakespeare.lit'
          name='Play-Specific Chatrooms'/>
    <item jid='mim.shakespeare.lit'
          name='Gateway to Marlowe IM'/>
    <item jid='words.shakespeare.lit'
          name='Shakespearean Lexicon'/>
    <item jid='globe.shakespeare.lit'
          name='Calendar of Performances'/>
    <item jid='headines.shakespeare.lit'
          name='Latest Shakespearean News'/>
    <item jid='catalog.shakespeare.lit'
          name='Buy Shakespeare Stuff!'/>
    <item jid='en2fr.shakespeare.lit'
          name='French Translation Service'/>
  </query>
</iq>

<item/>元素 必须不可 包含XML字符数据,并 应该 是空元素;当然它 可以 在其他命名空间中包含XML数据,如果一个Jabber实现不理解这些数据,则 必须 忽略它。

如果没有细节与实体相关联(或者这些细节并非公开可用的),目标实体必须向请求实体返回一个空的查询元素:

例13. 空的结果集

<iq type='result'
    from='shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='items1'>
  <query xmlns='http://jabber.org/protocol/disco#items'/>
</iq>

就请求disco#info来说,当实体向寄生在一个服务器的裸JID(<account@domain.tld>)发送disco#items请求时,宿主服务器自身 __必须__ 代表寄生帐号回应。有关访问这个功能的重要准则,见本文档的安全事项一节。特别地,为响应发到一个不带节点的裸JID的disco#info请求,如果访问没被拒决,服务器 __应该__ 反回相关的细节,包括连接的和可用的资源:

例14. 从一个裸JID请求细节

<iq type='get'
    from='shakespeare.lit'
    to='juliet@capulet.com'
    id='items2'>
  <query xmlns='http://jabber.org/protocol/disco#items'/>
</iq>

在这儿我们假设shakespeare.lit被capulet.com信任,帐号<juliet@capulet.com>有两个可用的资源:

例15. 服务器代表裸JID响应

<iq type='result'
    from='juliet@capulet.com'
    to='shakespeare.lit'
    id='items2'>
  <query xmlns='http://jabber.org/protocol/disco#items'>
    <item jid='juliet@capulet.com/balcony'/>
    <item jid='juliet@capulet.com/chamber'/>
  </query>
</iq>

细节节点

与实体相关联的信息有可能不能写成JID地址的形式;这样的例子包括,储存在收件箱的离线消息(见柔性离线消息获取 [7])、有Jabber能力的博客中的条目、与一个客户和组件关联的XML-RPC服务、位于一个NNTP网关的新闻张贴、及由发布-订阅 [8]主办的标题等等。为处理这些细节,<item/>元素 可以 有个 可选的 'node'属性来补充这个 必须的 'jid'属性。

节点的属性值或许有或许没有语义上的含义;从服务发现的观点来看,一个节点只不过是个与实体相联系的某种东西。为了发现节点更多的东西,请求实体 必须 在指定节点的时候查询实体的JID。如果'node'属性的值有语义上的含义,则这个含义是由使用的协议或应用提供的,而不是由服务发现协议决定的。不应该 包含节点属性,除非需要提供,或者要发现信息的实体不能直接写成JID地址的形式(就是说,如果相关的条目能写成一个JID就不要包含一个节点)。'node'属性的指 必须不能 为空。

在下面的例子中,用户从一个在线目录服务中请求所有可用的细节:

例16. 请求节点

<iq type='get'
    from='romeo@montague.net/orchard'
    to='catalog.shakespeare.lit'
    id='items2'>
  <query xmlns='http://jabber.org/protocol/disco#items'/>
</iq>

如果有与目标实体相关的细节单它们不能写成JID地址,那么服务 应该 返回一个节点列表(每个<item/>元素 必须 有一个'jid'属性, 应该 有个'node'属性, 可以 有个'name'属性):

例17. 服务反回节点

<iq type='result'
    from='catalog.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='items2'>
  <query xmlns='http://jabber.org/protocol/disco#items'>
    <item jid='catalog.shakespeare.lit'
          node='books'
          name='Books by and about Shakespeare'/>
    <item jid='catalog.shakespeare.lit'
          node='clothing'
          name='Wear your literary taste with pride'/>
    <item jid='catalog.shakespeare.lit'
          node='music'
          name='Music from the time of Shakespeare'/>
  </query>
</iq>

在返回上面的查询时,也许有更深层的节点与"第一级"节点相关联(比如,那些有相关细节的种类节点)。请求实体可以通过向这个JID发送请求,并在请求中说明感兴趣的节点,来进一步查询节点。

例18. 请求更深层的节点

<iq type='get'
    from='romeo@montague.net/orchard'
    to='catalog.shakespeare.lit'
    id='items3'>
  <query xmlns='http://jabber.org/protocol/disco#items' 
         node='music'/>
</iq>

然后服务返回与“父”节点相关的更深层的节点。在下面的例子中,服务本身将返回的节点按字母顺序排序,不过这种结构是实现的问题而不是协议的要求。

例19. 服务返回更深层的节点

<iq type='result'
    from='catalog.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='items3'>
  <query xmlns='http://jabber.org/protocol/disco#items' 
         node='music'>
    <item jid='catalog.shakespeare.lit'
          node='music/A'/>
    <item jid='catalog.shakespeare.lit'
          node='music/B'/>
    <item jid='catalog.shakespeare.lit'
          node='music/C'/>
    <item jid='catalog.shakespeare.lit'
          node='music/D'/>
    .
    .
    .
  </query>
</iq>

如果想要,请求实体可以继续查询更深层的节点:

例20. 请求更深层节点

<iq type='get'
    from='romeo@montague.net/orchard'
    to='catalog.shakespeare.lit'
    id='items4'>
  <query xmlns='http://jabber.org/protocol/disco#items' 
         node='music/D'/>
</iq>

例21. 服务返回更多的节点

<iq type='result'
    from='catalog.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='items4'>
  <query xmlns='http://jabber.org/protocol/disco#items' 
         node='music/D'>
    <item jid='catalog.shakespeare.lit'
          node='music/D/dowland-firstbooke'
          name='John Dowland - First Booke of Songes or Ayres'/>
    <item jid='catalog.shakespeare.lit'
          node='music/D/dowland-solace'
          name='John Dowland - A Pilgrimes Solace'/>
  </query>
</iq>

节点层次结构

前面的例子显示了节点的层次结构,里面的有些节点是分支(也即包含更深层的节点),有些节点是叶子(即不包含更深层的节点)。 应该 用"分层的"的种类来区分这些节点,“分支”和“叶子”类型详尽地列出了这个种类中的类型。

如果使用了分层种类,层次结构中的每个节点 必须 区分为要么是分支要么是叶子;然而,因为一个节点 可以 有多个身份,所以任一节点也 可以 有一个除了“层次/分支”或“层次/叶子”结构外的身份。

因此,上面显示的例子中,一个向"music/D"的disco#info请求会产生<identity category='hierarchy' type='branch'/>,而向"<music/D/dowland-firstbooke>"节点发出的请求会产生<identity category='hierarchy' type='leaf'/>(每个节点会产生恰当的附加身份)。

实体和其细节的关系

本节要说明的是一个实体和它的相关细节更详细的关系。

一般情况下,由一个实体在disco#items结果中返回的细节 __必须__ 是与实体有隶属关系的项\--或者是实体直接控制的细节的本身(比如,实体拥有的发布-订阅节点),或者实体至少能以Jabber网络中规范的方式提供或确保这样的细节(例如,群聊房间直接由一个多用户聊天服务来主持,还有网关提供的对IRC通道的访问等)。

这样的关系并不限制所属实体的地址与相关实体地址间的关系。特别地,下面任何一种情况都是非常合适的:

  1. 查询一个实体(JID1)的细节时,接收到可定址为JID的细节列表;每个相关项都有自己的JID,这些JID都与JID1不相同。
  2. 查询一个实体(JID1)的细节时,接收到不可定址为JID的细节列表;每个相关项有各自的JID+Node,其中JID与JID1相同但每个NodeId都是唯一的。
  3. 查询一个实体(JID1+NodeID1)的细节时,接收到可定址为JID的细节列表;每个相关项有自己的JID,但没有JID与JID1相同。
  4. 查询一个实体(JID1+NodeId1)的细节时,接收到不能定址为JID的细节列表;每个相关项有自己的JID+node,但是没有JID+node与JID1+NodeID1相同,并且每个NodeID都在相关JID的上下文中都是唯一的。

另外,返回的结果也 __可以__ 是混合型的,这样查询一个JID或JID+node会产生(1)定址为JID的项和(2)定址为JID+node这两种的组合。

考虑一下实体的这种情况,它拥有多个发布-订阅节点\--例如,一个拥有多个音乐演奏家节点的人。下面的例子演示了disco#items查询及其结果会是什么样子(用了在{link:用户心情|http://www.xmpp.org/extensions/xep-0118.html}中定义的协议):

例22. 用户请求有关实体的心情

<iq from='juliet@capulet.com/chamber'
    id='items4'
    to='romeo@montague.net'
    type='get'>
  <query xmlns='http://jabber.org/protocol/disco#items' 
         node='http://jabber.org/protocol/tune'/>
</iq>

被查询的实体现在返回一个它控制的发布-订阅节点的列表,每个节点都由不同的发布订阅服务主持:

The queried entity now returns a list of publish-subscribe nodes over which it has control, each of which is hosted on a different pubsub service:

例23. 实体返回多项细节

<iq from='romeo@montague.net'
    id='items4'
    to='juliet@capulet.com/chamber'
    type='get'>
  <query xmlns='http://jabber.org/protocol/disco#items' 
         node='http://jabber.org/protocol/tune'>
    <item jid='pubsub.shakespeare.lit'
          name='Romeo&apos;s CD player'
          node='s623nms9s3bfh8js'/>
    <item jid='pubsub.montague.net'
          node='music/R/Romeo/iPod'/>
    <item jid='tunes.characters.lit'
          node='g8k4kds9sd89djf3'/>
  </query>
</iq>

发表可用的细节

在XMPP IM中定义的服务器处理规则要求,如果IQ存取指令的'to'属性是<user@host>这样的形式的话,服务器应代表用户回应请求。这种功能目前已得到应用,这样用户就能“发布”信息(不如,在{link:vcard-temp|http://www.xmpp.org/extensions/xep-0054.html}中说明的vCard),这使得其他实体得到这些信息成为可能,即使这个用户不在线。这里定义的服务发现规范就建立在这个想法之上,使用户能够向服务器发布他的一些服务发现信息,服务器将把这些信息做持久化保存,当其他实体以用户的“裸JID”(user@host),或单独或与一个特殊节点组合来请求信息的时候,服务器将其返回。

在即时通服务器中实现服务发现 __应该__ 允许用户按这种方式发布细节,虽然服务发现规范并不要求这么做。为了发现他或她的服务器是否支持这种发布功能,用户 应该 向服务器发送一个disco#info请求:

例24. 用户向服务器发送disco#info请求

<iq from='kinglear@shakespeare.lit'
    id='pubinfo'
    to='shakespeare.lit'
    type='get'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

如果服务器支持服务发现的发布,并且服务器想要向用户披露这个事实,它 __必须__ 在响应中包含特性'http://jabber.org/protocol/disco#publish'。

例25. 服务器用身份及特性信息响应请求

<iq from='shakespeare.lit'
    id='pubinfo'
    to='kinglear@shakespeare.lit'
    type='result'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <identity category='server' type='im'/>
    ...
    <feature var='http://jabber.org/protocol/disco#publish'/>
    ...
  </query>
</iq>

为了发布细节,实体将一个IQ-set会话发给服务器,服务器代表实体负责响应请求。父查询的每个<item/>子元素 必须 拥有下面的属性:

  • action -- 说明向细节施加的动作
  • jid -- 说明细节“属主 或位置的Jabber ID

<item/>元素也 可以 有下面的属性:

  • name -- 说明细节的自然语言名
  • node -- 说明与细节“属主”或位置的JID相关联的特殊节点

'action'属性允许的值是“update”和“remove”。如果动作是“update”,服务器 必须 新建一个新的实体(如果节点和jid的组合不存在的话),或者改写现有的实体。如果动作是“remove”, 必须 将细节从持久化保存中去除。

下面的例子演示了用户向一个知名的(但却是假设的)服务发现节点发布他的孩子的列表。

例26. 发布细节

<iq from='kinglear@shakespeare.lit'
    id='publish1'
    type='set'>
  <query xmlns='http://jabber.org/protocol/disco#items'
         node='jabber:iq:kids'>
    <item action='update'
          jid='cordelia@shakespeare.lit'
          name='Cordelia'/>
    <item action='update'
          jid='goneril@shakespeare.lit'
          name='Goneril'/>
    <item action='update'
          jid='regan@shakespeare.lit'
          name='Regan'/>
  </query>
</iq>

例27. 服务以成功响应

<iq id='publish1'
    to='kinglear@shakespeare.lit'
    type='result'/>

之后如果对“jid='kinglear@shakespeare.lib”和"node='jabber:iq:kids"查询就会产生上面显示的列表(没有'action'属性)。

如果服务器或服务不支持持久化保存,它 __必须__ 对IQ-set请求响应<feature-not-implemented/>错误。

例28. 持久化保存不可用

<iq id='publish1'
    to='kinglear@shakespeare.lit'
    type='error'>
  <query xmlns='http://jabber.org/protocol/disco#items'
         node='jabber:iq:kids'>
    <item action='update'
          jid='cordelia@shakespeare.lit'
          name='Cordelia'/>
    <item action='update'
          jid='goneril@shakespeare.lit'
          name='Goneril'/>
    <item action='update'
          jid='regan@shakespeare.lit'
          name='Regan'/>
  </query>
  <error code='501' type='cancel'>
    <feature-not-implemented xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  </error>
</iq>

实现时的注意事项

信息请求数

当请求应用是一个客户端的时候,在得到用户取得花名册后,它也许想得到所有联系人的服务发现信息,并接收用户花名册中联系人的在线信息。不幸的是,用户的花名册可能很长,结果登录后会向外发出大量的disco#info查询和接收进来大量的disco#info响应。基于升级和带宽利用的原因,并不希望有这种“发现洪流”,因此,客户端应用 __应该__ 用{link:实体能力|http://www.xmpp.org/extensions/xep-0115.html}\[11\]来确定它们接收到的在线信息的那些实体的能力,而 __不应该__ 向这些实体发送disco#info请求。

细节请求数

为了获取实体及相关细节的完整信息,发出请求的应用程序需要“遍历”细节树。很自然,这会产生大量的请求和响应。如果细节列表很长的话(比如,超过20条),发出请求的应用程序 __不应该__ 对所有的细节项连续发送请求。日常需要维护大量细节的那些实体(如IRC网关货NNTP服务) __应该__ 将节点组织成层次结构,并且/或者通过象{link:Jabber搜索|http://www.xmpp.org/extensions/xep-0055.html}\[12\]之类的协议,提供更健壮的搜索能力; __不应该__ 通过服务发现返回极其巨大的结果集。

响应连续性

本文档推荐但并不要求响应实体必须对来自不同的请求实体的相同的请求返回一样的结果(比如,一个实体基于对请求实体的信任度或是已知能力的不同,可能返回不同的细节或特性列表)。然而,响应实体 __应该__ 对发送到相同的JID+节点组合的所有disco#info请求返回相同的<identity/>元素。

出错条件

如果特定的实体(JID或JID+节点)不支持disco命名空间,拒绝对特定的请求实体或任何请求实体返回disco结果,它 应该 返回适当的错误信息(象<service-unavailable/>,<forbidden/>,或<not-allowed/>等等)。示例如下:

例29. JID+节点错误

<iq type='error'
    from='mim.shakespeare.lit'
    to='romeo@montague.net/orchard'
    id='info3'>
  <query xmlns='http://jabber.org/protocol/disco#info' 
         node='http://jabber.org/protocol/commands'/>
  <error code='405' type='cancel'>
    <not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  </error>
</iq>

根据应用的不同,也可以有其他的错误条件。

下表总结了通用的错误条件,它们在服务发现上下文中可能有特殊的含义(关于错误条件的格式和语义的信息,见{link:错误条件映射|http://www.xmpp.org/extensions/xep-0086.html}\[13\])。

表1: 错误条件

条件 原因
<feature-not-implemented/> 发送方试图发布细节,但服务器不支持发布可用细节这个特性。
<item-not-found/> 由JID或JID+NodeId指定的目标实体并不存在,并且这一事实只可依据隐私及安全事项和策略来暴露。
<service-unavailable/> 目标实体不支持这一协议,或者指定的目标实体并不存在,但这一事实因为隐私及安全事项不能暴露。

其他在XMPP核心中说明的错误条件也 __可以__ 返回(<forbidden/>,<not-allowed/>,<not-authorized/>等等),包括应用专有的错误条件。

正象上面说明的那样,如果一个实体没有相关的细节,它 __必须__ 返回一个空的<query/>元素(而不是一个错误)来响应disco#items请求。

安全事项

当实体暴露(通过disco#info响应)它支持的特定的协议或特性的时候,某些攻击会更容易发生;然而,一般来说,服务发现不会引入新的攻击点,因为恶意实体会通过发送对这些协议的特定的请求而不是服务发现请求,来发现响应实体支持这些协议和特性。

当响应实体应答从不同的请求实体接收到的服务发现请求的时候,它没有义务返回相同的服务发现响应。 它 可以 在响应前执行授权检测,以决定怎样(或是否)响应。

服务器 必须 小心地控制对任何能发生目录收获攻击或泻露已连接或可用资源的功能的访问,这些功能由服务器对发送到寄宿其中的裸JID(地址型如account@domain.tld)的disco#info和disco#items请求的响应组成,因为服务器要代表这个帐户响应这样的请求。处理发送到裸JID的服务发现请求时应用如下规则:

1. 为了响应disco#info请求,如果下列条件为真,服务器 '必须 返回<service-unavailable/>错误:

  • 目标实体不存在(无论请求指定的是否是一个节点)
  • 请求实体未经授权从目标实体接收在线信息(也即,在线订阅的类型是"both"或"from"),或者请求实体不被信任(例如,在一个信任网络中的另一个服务器)。

2. 为了响应disco#items请求,服务器 必须 返回一个空结果集如果:

  • 目标实体不存在(无论请求指定的是否是一个节点)
  • 请求没有指定节点,唯一的细节是可用的资源(象在RFC3921中定义的那样),并且请求实体未经授权从目标实体接收在线信息(也即,在线订阅的类型是"both"或"from"),或者请求实体不被信任(例如,在一个信任网络中的另一个服务器)\[14\]

IANA事项

本文档与互联网分配数字授权 [15]无关。

XMPP注册事项

协议命名空间

XMPP注册处 [16]在它的协议命名空间注册项中包含了'http://jabber.org/protocol/disco#info'和'http://jabber.org/protocol/disco#items'

注册

身份种类及类型注册

XMPP注册处维护着命名空间'http://jabber.org/protocol/disco#info'里<identity/>元素的属性的'category'和'type'的注册值;见<{link:http://www.xmpp.org/registrar/disco-categories.html}>

过程

为了向注册项提交新值,注册人必须按如下的形式定义XML段,然后将其包含在相关的XMPP扩展协议中或是发送到<registrar@xmpp.org>:

<category>
  <name>the name of the category (all lower-case)</name>
  <desc>a natural-language description of the category</desc>
  <type>
    <name>the name of the specific type (all lower-case)</name>
    <desc>a natural-language description of the type</desc>
    <doc>the document (e.g., XEP) in which this type is specified</doc>
  </type>
</category>

注册人每次可以注册一个以上的种类,每个种类包含在独立的<category/>元素中。每个注册人也可以每次注册一个以上的类型,每个类型包含在独立的<type/>子元素中。已有种类中的新类型的注册必须包含完整的XML片段,但不应包含种类的描述(仅有种类的名称)。

初始提交

本文档定义了一个种类的“层次结构”,它仅包含两个类型:“branch”和“leaf”;相关的注册提交项如下:

<category>
  <name>hierarchy</name>
  <desc>
    An entity that exists in the context of a 
    service discovery node hierarchy.
  </desc>
  <type>
    <name>branch</name>
    <desc>
      A "container node" for other entities in a 
      service discovery node hierarchy.
    </desc>
    <doc>XEP-0030</doc>
  </type>
  <type>
    <name>leaf</name>
    <desc>
      A "terminal node" in a service discovery 
      node hierarchy.
    </desc>
    <doc>XEP-0030</doc>
  </type>
</category>

特性注册

XMPP注册处维护着以名字空间'http://jabber.org/protocol/disco#info'命名的一个属性注册项,用于元素<feature/>的属性‘var’的值;见<[3]>。

过程

为了向注册项提交新值,注册人必须按如下格式定义一个XML片段,将它包含在相关的XMPP扩展协议中,或者发到<registrar@xmpp.org>:

<feature var='name of feature or namespace'>
  <desc>a natural-language description of the feature</desc>
  <doc>the document (e.g., XEP) in which this feature is specified</doc>
</feature>

注册人可一次注册一个以上的特性,每个特性包含在独立的<feature>元素中。

初始提交

本文档定义了一个“publish”特性,它不与上面列出的任何协议命名空间相关;这个特性注册项的提交形式如下:

<feature var='http://jabber.org/protocol/disco#publish'>
  <desc>the service discovery "publish" feature</desc>
  <doc>XEP-0030</doc>
</feature>

知名节点

一个正在使用的协议可以指定一个或以上的服务发现节点,这些节点在协议上下文中有特殊的、恰当定义的含义。为了全局性地跨所有Jabber协议保留这些节点名字,XMPP注册处在<{link:http://www.xmpp.org/registrar/nodes.html}>维护着一个知名服务发现节点的注册。

过程

为了向注册处提交新值,注册人必须按如下格式定义一个XML片段,然后将它包含在相关的XMPP扩展协议中,或者发到<registrar@xmpp.org>:

<node>
  <name>the name of the node</name>
  <desc>a natural-language description of the node</desc>
  <doc>the document (e.g., XEP) in which this node is specified</doc>
</node>

注册人可以一次注册一个以上的节点,每个节点包含在单独的<node/>元素中。

URI查询类型

作为由{link:XMPP URI 查询组件|http://www.xmpp.org/extensions/xep-0147.html}\[17\]授权的机构,XMPP注册处维护着一个用在XMPP URIs中的查询和键-值对的注册(见<{link:http://www.xmpp.org/registrar/querytypes.html}>)。

在这里定义了用于服务发现交互的查询类型“disco”,它有三个键:(1)"node"(查询可选节点),(2)"request"(带“info”值来取回服务发现信息,带“items”值取回服务发现细节),(3)“type”(带值“get”用于IQ取值,“set”用于IQ设置)。

例30. 服务发现信息请求:IRI/URI

xmpp:romeo@montague.net?disco;type=get;request=info

例31. 服务发现信息请求:结果段

<iq to='romeo@montague.net' type='get'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

例32. 服务发现细节请求: IRI/URI

xmpp:romeo@montague.net?disco;type=get;request=items

例33. 服务发现细节请求:结果段

<iq to='romeo@montague.net' type='get'>
  <query xmlns='http://jabber.org/protocol/disco#items'/>
</iq>

下列提交注册"disco"查询类型。

<querytype>
  <name>disco</name>
  <proto>http://jabber.org/protocol/disco</proto>
  <desc>enables interaction for the purpose of service discovery</desc>
  <doc>XEP-0030</doc>
  <keys>
    <key>
      <name>node</name>
      <desc>the (optional) service discovery node</desc>
    </key>
    <key>
      <name>request</name>
      <desc>the service discovery request type</desc>
      <values>
        <value>
	  <name>info</name>
          <desc>a service discovery information (disco#info) request</desc>
        </value>
        <value>
	  <name>items</name>
          <desc>a service discovery items (disco#items) request</desc>
        </value>
      </values>
    </key>
    <key>
      <name>type</name>
      <desc>the IQ type</desc>
      <values>
        <value>
	  <name>get</name>
          <desc>an IQ get</desc>
        </value>
        <value>
	  <name>set</name>
          <desc>an IQ set (disco publish)</desc>
        </value>
      </values>
    </key>
  </keys>
</querytype>

XML方案

disco#info

<?xml version='1.0' encoding='UTF-8' ?>
 
<xs:schema
    xmlns:xs='http://www.w3.org/2001/XMLSchema'
    targetNamespace='http://jabber.org/protocol/disco#info'
    xmlns='http://jabber.org/protocol/disco#info'
    elementFormDefault='qualified'>
 
  <xs:annotation>
    <xs:documentation>
      The protocol documented by this schema is defined in
      XEP-0030: http://www.xmpp.org/extensions/xep-0030.html
    </xs:documentation>
  </xs:annotation>
 
  <xs:element name='query'>
    <xs:complexType>
      <xs:sequence minOccurs='0'>
        <xs:element ref='identity' maxOccurs='unbounded'/>
        <xs:element ref='feature' maxOccurs='unbounded'/>
      </xs:sequence>
      <xs:attribute name='node' type='xs:string' use='optional'/>
    </xs:complexType>
  </xs:element>
 
  <xs:element name='identity'>
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base='empty'>
          <xs:attribute name='category' type='xs:string' use='required'/>
          <xs:attribute name='name' type='xs:string' use='optional'/>
          <xs:attribute name='type' type='xs:string' use='required'/>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
 
  <xs:element name='feature'>
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base='empty'>
          <xs:attribute name='var' type='xs:string' use='required'/>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
 
  <xs:simpleType name='empty'>
    <xs:restriction base='xs:string'>
      <xs:enumeration value=''/>
    </xs:restriction>
  </xs:simpleType>
 
</xs:schema>

disco#items

<?xml version='1.0' encoding='UTF-8' ?>
 
<xs:schema
    xmlns:xs='http://www.w3.org/2001/XMLSchema'
    targetNamespace='http://jabber.org/protocol/disco#items'
    xmlns='http://jabber.org/protocol/disco#items'
    elementFormDefault='qualified'>
 
  <xs:annotation>
    <xs:documentation>
      The protocol documented by this schema is defined in
      XEP-0030: http://www.xmpp.org/extensions/xep-0030.html
    </xs:documentation>
  </xs:annotation>
 
  <xs:element name='query'>
    <xs:complexType>
      <xs:sequence minOccurs='0'>
        <xs:element ref='item' minOccurs='0' maxOccurs='unbounded'/>
      </xs:sequence>
      <xs:attribute name='node' type='xs:string' use='optional'/>
    </xs:complexType>
  </xs:element>
 
  <xs:element name='item'>
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base='empty'>
          <xs:attribute name='action' use='optional'>
            <xs:simpleType>
              <xs:restriction base='xs:NCName'>
                <xs:enumeration value='remove'/>
                <xs:enumeration value='update'/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name='jid' type='xs:string' use='required'/>
          <xs:attribute name='name' type='xs:string' use='optional'/>
          <xs:attribute name='node' type='xs:string' use='optional'/>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
 
  <xs:simpleType name='empty'>
    <xs:restriction base='xs:string'>
      <xs:enumeration value=''/>
    </xs:restriction>
  </xs:simpleType>
 
</xs:schema>

作者介绍

注释

修订历史

个人工具