最近使用到即时推送信息框架,该框架走的是Jabber协议,由于Jabber协议十分臃肿,因此,对框架的协议进行改造。从框架中看到如下的请求方式,着实让我无语了,状况如下:  

用例

实体注册到一个主机

为了确定向一个主机注册需要哪些字段, 一个实体应该(SHOULD)首先发送一个 IQ get 给主机. 这个实体不应该(SHOULD NOT)通过先发送一个IQ set来尝试猜测需要的字段 , 因为需要的数据的性质是由服务决定的.

例子 1. 实体向主机请求注册字段

<iq type='get' id='reg1'>

  <query xmlns='jabber:iq:register'/>

</iq>

如果实体未曾注册并且主机支持带内注册, 主机必须(MUST)通知实体需要的注册字段. 如果主机不支持带内注册, 它必须(MUST)返回一个 <service-unavailable/> 错误. 如果一个主机重定向注册请求到一些其他的媒介(例如web网站), 它只可以(MAY)返回一个 <instructions/> 元素, 详见本文的 重定向 章节.

例子 2. 主机返回注册字段给实体

<iq type='result' id='reg1'>

  <query xmlns='jabber:iq:register'>

    <instructions>      Choose a username and password for use with this service.

      Please also provide your email address.

    </instructions>

    <username/>

    <password/>

    <email/>

  </query>

</iq>

如果主机确定(基于'from'地址) 该实体已经注册, 它给该IQ get返回的应答IQ result中必须(MUST)包含一个空的<registered/>元素(表示该实体已注册), 应该(SHOULD)包含该实体当前已有的注册信息(尽管<password/>元素可以(MAY)是空的), 并且应该(SHOULD)包含一个<instructions/>元素(其XML字符数据可以(MAY)被修改以反映该实体已被注册的事实).

如果该主机是一个即时消息服务器, 在这个阶段它应该(SHOULD)假设请求的实体是未注册的,除非实体已经被验证(详见下文中的 注册到一个服务器 章节).

例子 3. 主机通知实体当前的注册

<iq type='result' id='reg1'>

  <query xmlns='jabber:iq:register'>

    <registered/>

    <username>juliet</username>

    <password>R0m30</password>

    <email>juliet@capulet.com</email>

  </query>

</iq>

如果实体还未注册, 该实体应该(SHOULD)提供必需的信息.

例子 4. 实体提供必需信息

<iq type='set' id='reg2'>

  <query xmlns='jabber:iq:register'>

    <username>bill</username>

    <password>Calliope</password>

    <email>bard@shakespeare.lit</email>

  </query>

</iq>

注意: 请求的实体必须(MUST)在IQ result中包含提供的所有元素信息(除了<instructions/>元素).

例子 5. 主机通知实体注册成功

<iq type='result' id='reg2'/>

反之, 注册可能失败. 可能的失败包括用户名冲突(期待的用户名已经被另一个实体使用)以及请求的实体未能提供完全的必需信息.

例子 6. 主机通知实体注册失败(用户名冲突)

<iq type='error' id='reg2'>

  <query xmlns='jabber:iq:register'>

    <username>bill</username>

    <password>m1cro$oft</password>

    <email>billg@bigcompany.com</email>

  </query>

  <error code='409' type='cancel'>

    <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>

  </error>

</iq>

如果请求的实体在注册时未提供所有的请求信息 7, 那么服务器或者服务必须(MUST)返回一个<not-acceptable/>错误给请求实体.

例子 7. 主机通知实体注册失败(一些必需的信息未提供)

<iq type='error' id='reg2'>

  <query xmlns='jabber:iq:register'>

    <username>bill</username>

    <password>Calliope</password>

  </query>

  <error code='406' type='modify'>

    <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>

  </error>

</iq>

如果主机需要超出以上的以及超过schema定义的数据元素的额外信息, 它应该(SHOULD)使用本文的 可扩展性 章节里描述的数据表单. (下面三个例子展示一个现有的账号注册到一个第三方服务, 而不是一个实体向一个服务器注册账号)


例子 8. 实体向主机请求注册字段

<iq type='get'

    from='juliet@capulet.com/balcony'

    to='contests.shakespeare.lit'

    id='reg3'>

  <query xmlns='jabber:iq:register'/>

</iq>

例子 9. 主机返回注册表单给实体

<iq type='result'

    from='contests.shakespeare.lit'

    to='juliet@capulet.com/balcony'

    id='reg3'>

  <query xmlns='jabber:iq:register'>

    <instructions>      Use the enclosed form to register. If your Jabber client does not

      support Data Forms, visit http://www.shakespeare.lit/contests.php

    </instructions>

    <x xmlns='jabber:x:data' type='form'>

      <title>Contest Registration</title>

      <instructions>        Please provide the following information

        to sign up for our special contests!

      </instructions>

      <field type='hidden' var='FORM_TYPE'>

        <value>jabber:iq:register</value>

      </field>

      <field type='text-single' label='Given Name' var='first'>

        <required/>

      </field>

      <field type='text-single' label='Family Name' var='last'>

        <required/>

      </field>

      <field type='text-single' label='Email Address' var='email'>

        <required/>

      </field>

      <field type='list-single' label='Gender' var='x-gender'>

        <option label='Male'><value>M</value></option>

        <option label='Female'><value>F</value></option>

      </field>

    </x>

  </query>

</iq>

然后用户应该(SHOULD)返回表单:

例子 10. 用户提交注册表单

<iq type='set'

    from='juliet@capulet.com/balcony'

    to='contests.shakespeare.lit'

    id='reg4'>

  <query xmlns='jabber:iq:register'>

    <x xmlns='jabber:x:data' type='submit'>

      <field type='hidden' var='FORM_TYPE'>

        <value>jabber:iq:register</value>

      </field>

      <field type='text-single' label='Given Name' var='first'>

        <value>Juliet</value>

      </field>

      <field type='text-single' label='Family Name' var='last'>

        <value>Capulet</value>

      </field>

      <field type='text-single' label='Email Address' var='email'>

        <value>juliet@capulet.com</value>

      </field>

      <field type='list-single' label='Gender' var='x-gender'>

        <value>F</value>

      </field>

    </x>

  </query>

</iq>

注册到一个服务器

当一个未注册的实体和一个服务器而不是一个服务交互时必须特别注意. 通常, 一个服务器允许带内注册以使实体能够在Jabber网络里面自举"bootstrap"参与; 这个 bootstrapping 发生了一个未注册以及未验证的实体向一个服务器打开一个TCP连接并立刻完成该服务器的注册用例的时候, 然后使用这个全新注册的标识进行验证. 如上所述, 当一个服务器收到一个 IQ-get 用于注册信息, 它应该(SHOULD)假设请求实体是未注册的除非该实体已经验证.

取决于本地服务的供应, 如果一个未注册的实体在验证之前尝试太多次数的注册,或者一个实体成功完成注册用例之后尝试注册第二个身份,那么一个服务器可以(MAY)返回一个<not-acceptable/>节错误; 如果未注册的实体在验证之前等待太久或者在成功完成注册用例之后尝试完成一个动作而这个动作不是在做验证, 一个服务器也可以(MAY)返回一个<not-authorized/>流错误.

实体取消现存注册

'jabber:iq:register'名字空间允许一个实体也能够从一个主机取消一个注册, 通过发送一个包含<remove/>元素的 IQ set. 该主机必须(MUST)根据 IQ get 的 'from' 地址来确定请求实体的身份.

例子 11. 实体请求注销现存注册

<iq type='set' from='bill@shakespeare.lit/globe' id='unreg1'>

  <query xmlns='jabber:iq:register'>

    <remove/>

  </query>

</iq>

例子 12. 主机通知实体成功注销

<iq type='result' to='bill@shakespeare.lit/globe' id='unreg1'/>

有两种情形:

1 如果实体从他的"home"服务器取消注册(也就是说,维护它的XMPP帐号的服务器), 那么该实体不应该(SHOULD NOT)包含一个 'from' 或 'to' 地址在移除请求中, 该服务器应该(SHOULD)返回一个<not-authorized/>流错误并且终止该实体所有激活的会话. 该服务器应该(SHOULD)执行的移除操作以其收到移除请求的当前会话或连接的相关的纯JID (<node@domain.tld>)为准. 如果服务器是一个即时消息和出席信息服务期并遵循 RFC 3921 8, 该服务器也应该(SHOULD)取消所有和该实体相关的现存的出席信息订阅(储存在实体的花名册中).

2 如果实体从一个服务而不是它的宿主服务器取消注册, 它的宿主服务器必须(MUST)贴一个'from'地址到移除请求中, 这个遵循 XMPP Core 的地址应为该实体的全 JID (<node@domain.tld/resource>). 该服务必须(MUST)基于'from'地址的纯JID (<node@domain.tld>)部分来执行移除操作.

如下所述, 各种错误可能发生.

表 1: 注销出错例子

条件

描述

<bad-request/>

<remove/> 元素不只是<query/> 元素的唯一子元素.

<forbidden/>

发送者没有足够的权限取消注册.

<not-allowed/>

没有发送者被允许在带内取消注册.

<registration-required/>

发送移除请求的实体未曾注册.

<unexpected-request/>

主机是一个即时消息服务器而 IQ get 不包含一个 'from' 地址, 因为该实体不是注册到该服务器.

例子 13. 主机通知客户端注销失败(错误的请求)

<iq type='error' from='shakespeare.lit' to='bill@shakespeare.lit/globe' id='unreg1'>

  <error code='400' type='modify'>

    <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>

  </error>

</iq>

例子 14. 主机通知客户端注销失败(禁止)

<iq type='error' from='shakespeare.lit' to='bill@shakespeare.lit/globe' id='unreg1'>

  <error code='401' type='cancel'>

    <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>

  </error>

</iq>

例子 15. 主机通知客户端注销失败(不允许)

<iq type='error' from='shakespeare.lit' to='bill@shakespeare.lit/globe' id='unreg1'>

  <error code='405' type='cancel'>

    <not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>

  </error>

</iq>

一个给定的布署可以(MAY)选择在取消注册之前要求额外的信息(例如旧密码或以前收集的个人信息). 为了做到这一点, 服务器或服务应该(SHOULD)在错误节里面返回一个数据表单:

例子16. 服务器在错误信息中返回注销表单

<iq type='error' from='shakespeare.lit' to='bill@shakespeare.lit/globe' id='unreg1'>

  <query xmlns='jabber:iq:register'>

    <x xmlns='jabber:x:data' type='form'>

      <title>Password Change</title>

      <instructions>Use this form to cancel your registration.</instructions>

      <field type='hidden' var='FORM_TYPE'>

        <value>jabber:iq:register:cancel</value>

      </field>

      <field type='text-single' label='Username' var='username'>

        <required/>

      </field>

      <field type='text-private' label='Password' var='password'>

        <required/>

      </field>

      <field type='text-single' label='Mother's Maiden Name' var='x-mmn'>

        <required/>

      </field>

    </x>

  </query>

  <error code='405' type='cancel'>

    <not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>

  </error>

</iq>

然后用户应该(SHOULD)返回表单:

例子 17. 用户返回注销表单

<iq type='set' from='bill@shakespeare.lit/globe' to='shakespeare.lit' id='unreg2'>

  <query xmlns='jabber:iq:register'>

    <x xmlns='jabber:x:data' type='submit'>

      <field type='hidden' var='FORM_TYPE'>

        <value>jabber:iq:register:cancel</value>

      </field>

      <field type='text-single' var='username'>

        <value>bill@shakespeare.lit</value>

      </field>

      <field type='text-private' var='password'>

        <value>theglobe</value>

      </field>

      <field type='text-single' var='x-mmn'>

        <value>Throckmorton</value>

      </field>

    </x>

  </query>

</iq>

 

看了微信里面说的将jabber协议映射成二进制协议的,我暂时没有这方面的解决方案,因此,打错使用缩短传输<xml>流的方式进行设计。在xml中,最常用的莫属dom4j这个组件了,dom4j采用节点的方式,置入内存运行,很多人都说这种方式不好,但是对于2-5节点来说,无非使用dom4j这种解析方式无非是最好的方式,而且很多开源框架仍然用这种dom4j的模式。现在回归正题,Openfire框架的做法是: 
            1、创建IQ,初始化IQ包: 
            // Create the basic element of the probeResult which contains the basic registration
            // information (e.g. username, passoword and email)
            probeResult = DocumentHelper.createElement(QName.get("query", "jabber:iq:register"));
            probeResult.addElement("username");
            probeResult.addElement("password");
            probeResult.addElement("email");
            probeResult.addElement("name");
            // Create the registration form to include in the probeResult. The form will include
            // the basic information plus name and visibility of name and email.
            // TODO Future versions could allow plugin modules to add new fields to the form 
            final DataForm registrationForm = new DataForm(DataForm.Type.form);
            registrationForm.setTitle("XMPP Client Registration");
            registrationForm.addInstruction("Please provide the following information");
            final FormField fieldForm = registrationForm.addField();
            fieldForm.setVariable("FORM_TYPE");
            fieldForm.setType(FormField.Type.hidden);
            fieldForm.addValue("jabber:iq:register");
            final FormField fieldUser = registrationForm.addField();
            fieldUser.setVariable("username");
            fieldUser.setType(FormField.Type.text_single);
            fieldUser.setLabel("Username");
            fieldUser.setRequired(true);
            final FormField fieldName = registrationForm.addField(); 
            fieldName.setVariable("name");
            fieldName.setType(FormField.Type.text_single);
            fieldName.setLabel("Full name");
            if (UserManager.getUserProvider().isNameRequired()) {
                fieldName.setRequired(true);
            }


            final FormField fieldMail = registrationForm.addField();
            fieldMail.setVariable("email");
            fieldMail.setType(FormField.Type.text_single);
            fieldMail.setLabel("Email");
            if (UserManager.getUserProvider().isEmailRequired()) {
                fieldMail.setRequired(true);
            }
            final FormField fieldPwd = registrationForm.addField();
            fieldPwd.setVariable("password");
            fieldPwd.setType(FormField.Type.text_private);
            fieldPwd.setLabel("Password");
            fieldPwd.setRequired(true);
            // Add the registration form to the probe result.
            probeResult.add(registrationForm.getElement());
2、当客户端发送的时候,Openfire对其进行判断:
  1)判断当前用户的session是否存在,如果session不存在,则发送一条error的包到用户。 
  2)判断iq的类型,是get还是set还是其他类型。 
  3)如果类型匹配,则返回xml语句至发送方。 代码如下:      
现在来解释下dom4j的执行方式。 现在来介绍下dom4j的XML语句生成方式:      
public void demos(){
       Element element=null;
     try{            
         Document document = DocumentHelper.createDocument();
         element = document.addElement(QName.get("1","2"));
         Element pushElement= element.addElement("push"); 
         pushElement.setText("3");
         Element    typeElement= element.addElement("type");
         typeElement.addElement("type");
         typeElement.setText("4");
         System.out.println(document.asXML());
       }catch(Exception e){   
         
     } 
  } 

生成方式如下:

      <?xml version="1.0" encoding="UTF-8"?>

           <1 xmlns="2">

             <push>3</push>

             <type/>4<type/> 

       </1>

  解析方式跟上面的差不多,先讲到这里了。