构建Web服务客户机

日期: 2008-07-08 作者:James Snell 来源:TechTarget中国

  本文介绍如何使用异步JavaScript和XML(Asynchronous JavaScript and XML, Ajax) 设计模式来实现基于Web浏览器的SOAP Web服务客户机。


  本文是系列文章的第1部分,演示了如何使用针对Web应用程序的Ajax设计模式来实现跨平台的基于JavaScript的SOAP Web服务客户机。


  Ajax已普遍用于许多知名的Web应用程序服务,例如GMail、Google Maps、Flickr和Odeo.com。通过使用异步XML消息传递,Ajax为Web开发人员提供了一种扩展其Web应用程序价值和功能的途径。这里介绍的Web Services JavaScript Library扩展了该基础机制,其通过引入对调用基于SOAP的Web服务的支持来增强Ajax设计模式。


  从浏览器中调用Web服务


  从Web浏览器中调用SOAP Web服务可能会比较麻烦,这是因为大多数流行的Web浏览器在生成和处理XML方面都略有不同。所有浏览器都一致实现且用于XML处理的标准API或功能少之又少。


  浏览器实现人员一致支持的机制之一是XMLHttpRequest API,它是Ajax设计模式的核心。developerWorks网站最近发布的另一篇由Philip McCarthy撰写的的文章详细介绍了该API。XMLHttpRequest是一个用于执行异步HTTP请求的JavaScript对象。Philip McCarthy在其文章中描述了一个顺序图(请参见图1),此图对于理解 XMLHttpRequest 对象如何支持Ajax设计非常有帮助(请参阅参考资料,以获得指向全文的链接)。



  图1. Philip McCarthy的Ajax顺序图
 
  从此图中,您可以清楚地看到XMLHttpRequest对象是如何工作的。一些运行在Web浏览器内的 JavaScript 创建了一个XMLHttpRequest实例和一个用于异步回调的函数。然后,该脚本使用XMLHttpRequest对象对服务器执行HTTP操作。在接收到响应后,调用回调函数。在该回调函数内,可能处理返回的数据。如果返回的数据碰巧是XML,则XMLHttpRequest对象将自动使用浏览器中内置的XML处理机制来解析该数据。


  遗憾的是,使用Ajax方法的主要难题在于XMLHttpRequest对象自动解析XML的详细过程。例如,假设我正在请求的数据是一个SOAP信封,其包含来自许多不同XML命名空间的元素,并且我希望提取yetAnotherElement中属性 attr的值。(请参见清单1)


  清单1. 一个包含多个命名空间的SOAP信封
               
  <s:Envelope
   
   
    >
    <s:Header/>
    <s:Body>
      <m:someElement >
        <n:someOtherElement
         
          >
          <m:yetAnotherElement
            n_attr=”abc”
            />
        </n:someOtherElement>
      </m:someElement>
    </s:Body>
  </s:Envelope>
 
  在Mozilla浏览器和Firefox浏览器中,提取attr属性值非常简单,如清单2所示。


  清单2. 在Mozilla和Firefox中检索attr属性值的方法不能运用在Internet Explorer中
               
  var m = el.getElementsByTagNameNS(
    ’urn:example’,
    ’yetAnotherElement’)[0].
      getAttributeNS(
        ’urn:foo’,
        ’attr’);
  alert(m); // displays ’abc’
 
  遗憾的是,以上代码无法在Internet Explorer Version 6中运行,因为该浏览器不仅没有实现getElementsByTagNameNS功能,而且事实上还使用了一种很糟糕的方法——将XML命名空间的前缀作为其元素和属性名称的一部分来对待。


  Internet Explorer缺少对XML命名空间的支持,这使得它很难处理命名空间密集的XML格式,例如采用独立于浏览器的方式的SOAP。即使要执行一些像提取结果中的属性值这样简单的操作,您也必须编写能够在多个浏览器中实现一致预期行为的特殊代码。幸运的是,这种特殊代码可以封装并重用。


  为了从Web浏览器中调用Web服务,并可靠地处理SOAP消息,您需要首先了解一些安全问题(请参见侧栏“关于安全性”)。此外,您还需要编写一个JavaScript脚本库(图2),以便将底层浏览器XML实现中的不一致情况抽象出来,从而使您能够直接处理Web服务数据。



  图2. 在使用Web Services JavaScript Library的Web浏览器中通过Javascript调用Web服务
 
  图2中的Web Services JavaScript Library (ws.js)是一组JavaScript对象和实用功能,它们为基于SOAP 1.1的Web服务提供了基本的支持。Ws.js定义了下列对象:


  ·WS.Call:一个包装了XMLHttpRequest的Web服务客户机
  ·WS.QName:XML限定名实现
  ·WS.Binder:自定义XML序列化器/反序列化器的基础
  ·WS.Handler:请求/响应处理程序的基础
  ·SOAP.Element:包装了XML DOM的基本SOAP元素
  ·SOAP.Envelope:SOAP Envelope对象扩展了SOAP.Element
  ·SOAP.Header:SOAP Header对象扩展了SOAP.Element
  ·SOAP.Body:SOAP Body对象扩展了SOAP.Element
  ·XML:用于处理XML的跨平台实用方法


  ws.js的核心是WS.Call对象,该对象提供了调用Web服务的方法。WS.Call主要负责与XMLHttpRequest对象进行交互,并处理SOAP响应。


  WS.Call对象公开了以下三个方法:


  ·add_handler。向处理链添加请求/响应处理程序。处理程序对象在调用Web服务的前后被调用,以支持可扩展的预调用处理和后调用处理。
  ·invoke。将指定的SOAP.Envelope对象发送给Web服务,然后在接收到响应后调用回调函数。当调用使用文本XML编码的文档样式的Web服务时,请使用此方法。
  ·invoke_rpc。创建一个封装RPC样式请求的SOAP.Envelope,并将其发送到Web服务。当接收到响应时,调用回调函数。


  在通常情况下,WS.Call对象只不过是位于XMLHttpRequest对象顶层的瘦包装器 (thin wrapper),该包装器能够执行许多简化处理的操作。这些操作包括设置SOAP 1.1规范要求的SOAPAction HTTP Header。


  使用ws.js


  Web services JavaScript Library提供的API非常简单。


  SOAP.*对象(SOAP.Element、SOAP.Envelope、SOAP.Header和SOAP.Body)提供了构建和读取SOAP信封的方法,如清单3所示,因而处理XML文档对象模型的底层细节就顺利地抽象出来。


  清单3. 构建一个SOAP信封
               
  var envelope = new SOAP.Envelope();
  var body = envelope.create_body();
  var el = body.create_child(new WS.QName(’method’,’urn:foo’));
  el.create_child(new WS.QName(’param’,’urn:foo’)).set_value(’bar’);
 
  清单4显示了由清单3中的代码生成的SOAP信封。


  清单4. 构建一个SOAP信封
               
  <Envelope >
    <Body>
      <method >
        <param>bar</param>
      </method>
    </Body>
  </Envelope>
 
  如果您正在创建的SOAP信封代表一个RPC样式的请求,则SOAP.Body元素提供了一个简便方法set_rpc(如清单5所示),该方法能够构造一个完整的RPC请求——包含一个指定的操作名称、一个指定的输入参数数组和一个SOAP编码样式的URI。
 
  清单5. 构建一个RPC请求信封
               
  var envelope = new SOAP.Envelope();
  var body = envelope.create_body();
  body.set_rpc(
    new WS.QName(’param’,’urn:foo’),
    new Array(
      {name:’param’,value:’bar’}
    ), SOAP.NOENCODING
  );
 
  每个参数都作为一个JavaScript对象结构进行传递,且可能带有以下属性:


  ·name。一个指定参数名称的字符串或WS.QName对象。必需。
  ·value。参数的值。如果该值不是一个简单数据类型(例如,字符串、整数或其他),则应该指定一个能将该值序列化为适当的XML结构的WS.Binder。必需。
  ·xsitype:标识参数的XML模式实例类型的WS.QName(例如,xsi:type=”int” 对应xsitype:new WS.QName(’int’,’http://www.w3.org/2000/10/XMLSchema’))。可选。
  ·encodingstyle:标识参数所使用的SOAP编码样式的URI。可选。
  ·binder:能够将参数序列化为XML的WS.Binder实现。可选。


  例如,如果要指定的参数名为“abc”、XML命名空间为“urn:foo”、xsi:type为“int”且值为“3”,则我会使用以下代码:new Array({name:new WS.QName(’abc’,’urn:foo’), value:3, xsitype:new WS.QName(’int’,’http://www.w3.org/2000/10/XMLSchema’)})。


  一旦我为服务请求构建了SOAP.Envelope,我就会将该SOAP.Envelope传递到WS.Call对象的invoke方法,以便调用该信封内编码的方法:(new WS.Call(service_uri)).invoke(envelope, callback)


  另一种可选方案是手动构建SOAP.Envelope。我会将参数WS.QName、参数数组和编码样式传递到WS.Call对象的invoke_rpc方法,如清单6所示。


  清单6. 使用WS.Call对象调用Web服务
               
  var call = new WS.Call(serviceURI);
  var nsuri = ’urn:foo’;
  var qn_op = new WS.QName(’method’,nsuri);
  var qn_op_resp = new WS.QName(’methodResponse’,nsuri); 
    call.invoke_rpc(
      qn_op,
      new Array(
        {name:’param’,value:’bar’}
      ),SOAP.NOENCODING,
      function(call,envelope) {
        // envelope is the response SOAP.Envelope
        // the XML Text of the response is in arguments[2]
      }
    );
 
  在调用invoke方法或invoke_rpc方法时,WS.Call对象会创建一个基本的XMLHttpRequest对象,用包含SOAP信封的XML元素进行传递,并接收和解析响应,然后调用提供的回调函数。


  为了能够扩展SOAP消息的预处理和后处理,WS.Call对象允许您注册一组WS.Handler对象,如清单7所示。对于调用周期内的每个请求、每个响应和每个错误,都将调用这些对象。可以通过扩展WS.Handler JavaScript对象来实现新的处理程序。


  清单7. 创建和注册响应/响应处理程序
               
  var MyHandler = Class.create();
  MyHandler.prototype = (new WS.Handler()).extend({
    on_request : function(envelope) {
       // pre-request processing
    },
    on_response : function(call,envelope) {
       // post-response, pre-callback processing
    },
    on_error : function(call,envelope) {
    }
  });
  var call = new WS.Call(…);
  call.add_handler(new MyHandler());
 
  处理程序对插入或提取正在传递的SOAP信封中的信息最有用。例如,您可以设想一个处理程序自动向SOAP Envelope的Header插入适当的Web服务寻址(Web Services Addressing)元素,如清单8中的示例所示。


  清单8. 一个将Web服务寻址操作Header添加到请求中的处理程序示例
               
  var WSAddressingHandler = Class.create();
  WSAddressingHandler.prototype = (new WS.Handler()).extend({
    on_request : function(call,envelope) {    
      envelope.create_header().create_child(
          new WS.QName(’Action’,’http://ws-addressing’,’wsa’)
        ).set_value(’http://www.example.com’);
    }
  });
 
  WS.Binder对象(清单9)执行SOAP.Element对象的自定义序列化和反序列化。WS.Binder的实现必须提供以下两个方法:


  ·to_soap_element。将JavaScript对象序列化为SOAP.Element。传入的第一个参数是要序列化的值。第二个参数是SOAP.Element,必须将要序列化的值序列化为SOAP.Element。该方法不返回任何值。
  ·to_value_object。将SOAP.Element反序列化为JavaScript对象。该方法必须返回反序列化的值对象。


  清单9. WS.Binding实现示例
               
  var MyBinding = Class.create();
  MyBinding.prototype = (new WS.Binding()).extend({
    to_soap_element : function(value,element) {    
      …
    },
    to_value_object : function(element) {
      …
    }
  });
 
  一个简单示例


  我已经提供了一个示例项目来阐释Web Services JavaScript Library的基本功能。该演示所使用的Web服务(如清单10所示)已经在WebSphere Application Server中进行了实现,并提供了简单的Hello World功能。


  清单10. 一个简单的基于Java的“Hello World”Web服务
               
  package example;
  public class HelloWorld {
    public String sayHello(String name) {
      return “Hello ” + name;
    }
  }
 
  在实现了该服务并将其部署到WebSphere Application Server后,该服务(清单11)的WSDL描述定义了您需要传递的SOAP消息(用于调用Hello World服务)。


  清单11. HelloWorld.wsdl的代码片段
               
  <wsdl:portType name=”HelloWorld”>
    <wsdl:operation name=”sayHello”>
      <wsdl:input
        message=”impl:sayHelloRequest”
        name=”sayHelloRequest”/>
      <wsdl:output
        message=”impl:sayHelloResponse”
        name=”sayHelloResponse”/>
    </wsdl:operation>
  </wsdl:portType>
 
  通过使用Web Services JavaScript Library,您可以实现一个调用Hello World服务的方法,如清单12所示。


  清单12. 使用WS.Call调用HelloWorld服务
               
  <html>
  <head>
  …
  <script
    type=”text/javascript”
    src=”scripts/prototype.js”></script>
  <script
    type=”text/javascript”
    src=”scripts/ws.js”></script>
  <script type=”text/javascript”>
  function sayHello(name, container) {
    var call = new WS.Call(’/AjaxWS/services/HelloWorld’);
    var nsuri = ’http://example’;
    var qn_op = new WS.QName(’sayHello’,nsuri);
    var qn_op_resp = new WS.QName(’sayHelloResponse’,nsuri); 
    call.invoke_rpc(
      qn_op,
      new Array(
        {name:’name’,value:name}
      ),null,
      function(call,envelope) {
        var ret =
          envelope.get_body().get_all_children()[0].
            get_all_children()[0].get_value();
        container.innerHTML = ret;
        $(’soap’).innerHTML = arguments[2].escapeHTML();
      }
    );
  }
  </script>
  </head>
  …
 
  然后,您可以在我们的Web应用程序中的任意位置通过调用sayHello函数来调用Hello World服务。请参见清单13。


  清单13. 调用sayHello函数
               
  <body>
  <input name=”name” id=”name” />
  <input value=”Invoke the Web Service”
         type=”button”
         onclick=”sayHello($(’name’).value,$(’result’))” />
  <div id=”container”>Result:
  <div id=”result”>
  </div>
  <div id=”soap”>
  </div>
  </div>
  </body>
 
  调用成功后的结果如图3所示。在Mozilla、Firefox和Internet Explorer中运行该示例应该会得到相同的结果。



  图3. Firefox中的Hello World示例
 
  后续部分


  使用Web Services JavaScript Library,可以采用简单的独立于浏览器的方式将基本的SOAP Web服务合并到Web应用程序中。在本系列的下一个部分中,您不仅可以探讨如何使用该库来调用更多基于Web服务资源框架(WS-Resource Framework)系列规范的高级Web服务,而且还可以了解扩展该Web服务功能并将其集成到Web应用程序中的方法。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐

  • 八个超实用的jQuery技巧攻略

    jQuery是JavaScript最好的库之一,主要用于制作动画、事件处理,支持Ajax及HTML脚本客户端。文中分享了8个超实用的jQuery代码技巧攻略,希望你会喜欢。

  • HTML5强大功能背后的安全陷阱

    尽管HTML5使网站的功能更为强大,但开发人员需充分利用其新的技术特征来提高网站的安全性,使用不当会带安全问题,你知道吗?

  • 前端页面开发之Node.js初学者指南

    Node.js是刚刚兴起的一个概念,你对它的了解有多少?Node.js的意义是什么,它是怎么发展起来的?Node.js的作用是怎样的呢?

  • JavaScript解析:让搜索引擎看到更真实的网页

    我们都知道期的搜索引擎没有相应的处理能力,会导致很多问题。引入JavaScript解析的目的,可以使搜索引擎可以更为清晰的了解用户实际打开该网页时看到的效果。