65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript SOAP 客户端

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (41投票s)

2006年1月24日

4分钟阅读

viewsIcon

1099299

downloadIcon

17459

使用 AJAX 调用 Web 服务。

引言

关于 AJAX 的讨论随处可见;AJAX 是“Asynchronous JavaScript and XML”的缩写,这是一项基于 XMLHttpRequest 的技术,目前所有主流浏览器都支持它。基本思想很简单——实际上并不是什么突破——但它允许在服务器请求后更新页面,而无需重新加载所有数据。您可以在 GMailGoogle Suggest 上找到一些示例。有关 AJAX 的更多信息,您可以查看 Wikipedia

在本文中,我们提出了一种基于 AJAX 的解决方案,与互联网上常见的解决方案相比,它具有一个很大的优点:调用的是 Web 服务。

这允许

  1. 在服务器端,我们只需公开一个包含所需方法的 Web 服务(而不是生成包含基于自定义语法或通用 XML 的数据的动态页面)。
  2. 在客户端,我们使用 WSDL(Web Service Description Language)自动生成一个 JavaScript 代理类,以便使用 Web 服务的返回类型——这与 Visual Studio 添加 Web 引用到解决方案时所做的事情类似。

下图显示了异步调用的 SOAP Client 工作流程

客户端使用 JavaScript 函数调用“SOAPClient.invoke”方法,并指定以下内容:

  • Web 服务 URL(请注意,出于安全原因,许多浏览器不允许跨域调用)。
  • Web 方法名称。
  • Web 方法参数值。
  • 调用模式(async = truesync = false)。
  • 响应接收后调用的回调方法(同步调用时可选)。

SOAPClient.invoke”方法执行以下操作(数字对应于上一图):

  1. 它获取 WSDL 并缓存描述以供将来的请求使用。
  2. 它准备并向服务器发送一个 SOAP(v. 1.1)请求(调用方法和参数值)。
  3. 它使用 WSDL 处理服务器的回复,以便构建要返回的相应 JavaScript 对象。
  4. 如果调用模式是 async,则调用回调方法,否则它返回相应的对象。

使用代码

在公开了我们通过 JavaScript 使用 Web 服务的想法之后,我们只需要分析代码。

让我们从用于定义要传递给 Web 方法的参数的类开始:“SOAPClientParameters

function SOAPClientParameters()
{
    var _pl = new Array();
    this.add = function(name, value) 
    {
        _pl[name] = value; 
        return this; 
    }
    this.toXml = function()
    {
        var xml = "";
        for(var p in _pl)
        {
            if(typeof(_pl[p]) != "function")
                xml += "<" + p + ">" + 
                       _pl[p].toString().replace(/&/g, 
                         "&").replace(/</g, 
                         "<").replace(/>/g, 
                         ">

代码仅包含一个内部字典(关联数组),其中包含参数名称(键)和相关值;“add”方法允许添加新参数,而“toXml”方法提供 SOAP 请求的 XML 序列化(请参阅“SOAPClient._sendSoapRequest”)。

让我们定义“SOAPClient”类,该类只能包含静态方法以允许异步调用,以及该类中唯一的“公共”方法:“SOAPClient.invoke”。

注意:由于 JavaScript 没有预留访问修饰符——例如“public”、“private”、“protected”等——我们将使用“_”前缀来表示私有方法。

function SOAPClient() {}
SOAPClient.invoke = function(url, method, 
                      parameters, async, callback)
{
    if(async)
        SOAPClient._loadWsdl(url, method, 
                    parameters, async, callback);
    else
        return SOAPClient._loadWsdl(url, method, 
                   parameters, async, callback);
}

SOAPClient.invoke”方法的接口在上面进行了描述;我们的实现检查调用是异步的(调用结果将传递给回调方法)还是同步的(调用结果将直接返回)。对 Web 服务的调用通过调用“SOAPClient._loadWsdl”方法开始。

SOAPClient._loadWsdl = function(url, method, parameters, async, callback)
{
    // load from cache?

    var wsdl = SOAPClient_cacheWsdl[url];
    if(wsdl + "" != "" && wsdl + "" != "undefined")
        return SOAPClient._sendSoapRequest(url, method, 
                    parameters, async, callback, wsdl);
    // get wsdl

    var xmlHttp = SOAPClient._getXmlHttp();
    xmlHttp.open("GET", url + "?wsdl", async);
    if(async) 
    {
        xmlHttp.onreadystatechange = function() 
        {
            if(xmlHttp.readyState == 4)
                SOAPClient._onLoadWsdl(url, method, 
                     parameters, async, callback, xmlHttp);
        }
    }
    xmlHttp.send(null);
    if (!async)
        return SOAPClient._onLoadWsdl(url, method, parameters, 
                                    async, callback, xmlHttp);
}

该方法在缓存中搜索相同的 WSDL,以避免重复调用。

SOAPClient_cacheWsdl = new Array();

如果 WSDL 未在缓存中找到(这是当前上下文中的第一次调用),则使用 XMLHttpRequest 根据所需的模式(同步或不同步)从服务器请求它。一旦从服务器获得响应,“SOAPClient._onLoadWsdl”方法将被调用。

SOAPClient._onLoadWsdl = function(url, method, 
             parameters, async, callback, req)
{
    var wsdl = req.responseXML;
    SOAPClient_cacheWsdl[url] = wsdl;
    return SOAPClient._sendSoapRequest(url, method, 
                       parameters, async, callback, wsdl);
}

WSDL 的副本存储在缓存中,然后执行“SOAPClient._sendSoapRequest”方法。

SOAPClient._sendSoapRequest = function(url, method, 
                 parameters, async, callback, wsdl)
{
    var ns = (wsdl.documentElement.attributes["targetNamespace"] + 
              "" == "undefined") ? 
              wsdl.documentElement.attributes.getNamedItem(
              "targetNamespace").nodeValue : 
              wsdl.documentElement.attributes["targetNamespace"].value;
    var sr = 
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
        "<soap:Envelope " +
        "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
        "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
        "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
        "<soap:Body>" +
        "<" + method + " xmlns=\"" + ns + "\">" +
        parameters.toXml() +
        "</" + method + "></soap:Body></soap:Envelope>";
    var xmlHttp = SOAPClient._getXmlHttp();
    xmlHttp.open("POST", url, async);
    var soapaction = 
      ((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + method;
    xmlHttp.setRequestHeader("SOAPAction", soapaction);
    xmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    if(async) 
    {
        xmlHttp.onreadystatechange = function() 
        {
            if(xmlHttp.readyState == 4)
                SOAPClient._onSendSoapRequest(method, 
                     async, callback, wsdl, xmlHttp);
        }
    }
    xmlHttp.send(sr);
    if (!async)
        return SOAPClient._onSendSoapRequest(method, 
                    async, callback, wsdl, xmlHttp);
}

服务命名空间从 WSDL 中提取(为 Internet Explorer 和 Mozilla / FireFox 使用不同的 XPath 查询),然后创建一个 SOAP v. 1.1 请求并提交。在收到服务器响应后将调用“SOAPClient._onSendSoapRequest”方法。

SOAPClient._onSendSoapRequest = function(method, 
                     async, callback, wsdl, req)
{
    var o = null;
    var nd = SOAPClient._getElementsByTagName(
             req.responseXML, method + "Result");
    if(nd.length == 0)
    {
        if(req.responseXML.getElementsByTagName(
                     "faultcode").length > 0)
            throw new Error(500, 
              req.responseXML.getElementsByTagName(
              "faultstring")[0].childNodes[0].nodeValue);
    }
    else
        o = SOAPClient._soapresult2object(nd[0], wsdl);
    if(callback)
        callback(o, req.responseXML);
    if(!async)
        return o;        
}

服务器响应被处理以查找故障:如果找到故障,则会引发错误。相反,如果获得正确的结果,一个递归函数将使用服务描述生成返回类型。

SOAPClient._soapresult2object = function(node, wsdl)
{
    return SOAPClient._node2object(node, wsdl);
}

SOAPClient._node2object = function(node, wsdl)
{
    // null node

    if(node == null)
        return null;
    // text node

    if(node.nodeType == 3 || node.nodeType == 4)
        return SOAPClient._extractValue(node, wsdl);
    // leaf node

    if (node.childNodes.length == 1 && 
       (node.childNodes[0].nodeType == 3 || 
        node.childNodes[0].nodeType == 4))
          return SOAPClient._node2object(node.childNodes[0], wsdl);
    var isarray = SOAPClient._getTypeFromWsdl(node.nodeName, 
                  wsdl).toLowerCase().indexOf("arrayof") != -1;
    // object node

    if(!isarray)
    {
        var obj = null;
        if(node.hasChildNodes())
            obj = new Object();
        for(var i = 0; i < node.childNodes.length; i++)
        {
            var p = SOAPClient._node2object(node.childNodes[i], wsdl);
            obj[node.childNodes[i].nodeName] = p;
        }
        return obj;
    }
    // list node

    else
    {
        // create node ref

        var l = new Array();
        for(var i = 0; i < node.childNodes.length; i++)
            l[l.length] = 
              SOAPClient._node2object(node.childNodes[i], wsdl);
        return l;
    }
    return null;
}

SOAPClient._extractValue = function(node, wsdl)
{
    var value = node.nodeValue;
    switch(SOAPClient._getTypeFromWsdl(
           node.parentNode.nodeName, wsdl).toLowerCase())
    {
        default:
        case "s:string":            
            return (value != null) ? value + "" : "";
        case "s:boolean":
            return value+"" == "true";
        case "s:int":
        case "s:long":
            return (value != null) ? parseInt(value + "", 10) : 0;
        case "s:double":
            return (value != null) ? parseFloat(value + "") : 0;
        case "s:datetime":
            if(value == null)
                return null;
            else
            {
                value = value + "";
                value = value.substring(0, value.lastIndexOf("."));
                value = value.replace(/T/gi," ");
                value = value.replace(/-/gi,"/");
                var d = new Date();
                d.setTime(Date.parse(value));                                        
                return d;                
            }
    }
}
SOAPClient._getTypeFromWsdl = function(elementname, wsdl)
{
    var ell = wsdl.getElementsByTagName("s:element");    // IE

    if(ell.length == 0)
        ell = wsdl.getElementsByTagName("element");    // MOZ

    for(var i = 0; i < ell.length; i++)
    {
        if(ell[i].attributes["name"] + "" == "undefined")    // IE

        {
            if(ell[i].attributes.getNamedItem("name") != null && 
               ell[i].attributes.getNamedItem("name").nodeValue == 
               elementname && ell[i].attributes.getNamedItem("type") != null) 
                return ell[i].attributes.getNamedItem("type").nodeValue;
        }    
        else // MOZ

        {
            if(ell[i].attributes["name"] != null && 
               ell[i].attributes["name"].value == 
               elementname && ell[i].attributes["type"] != null)
                return ell[i].attributes["type"].value;
        }
    }
    return "";
}

SOAPClient._getElementsByTagName”方法根据可用的 XML 解析器优化 XPath 查询。

SOAPClient._getElementsByTagName = function(document, tagName)
{
    try
    {
        return document.selectNodes(".//*[local-name()=\""+ 
                                           tagName +"\"]");
    }
    catch (ex) {}
    return document.getElementsByTagName(tagName);
}

一个工厂函数根据浏览器类型返回 XMLHttpRequest

SOAPClient._getXmlHttp = function() 
{
    try
    {
        if(window.XMLHttpRequest) 
        {
            var req = new XMLHttpRequest();
            if(req.readyState == null) 
            {
                req.readyState = 1;
                req.addEventListener("load", 
                    function() 
                    {
                        req.readyState = 4;
                        if(typeof req.onreadystatechange == "function")
                            req.onreadystatechange();
                    },
                    false);
            }
            return req;
        }
        if(window.ActiveXObject) 
            return new ActiveXObject(SOAPClient._getXmlHttpProgID());
    }
    catch (ex) {}
    throw new Error("Your browser does not support XmlHttp objects");
}

SOAPClient._getXmlHttpProgID = function()
{
    if(SOAPClient._getXmlHttpProgID.progid)
        return SOAPClient._getXmlHttpProgID.progid;
    var progids = ["Msxml2.XMLHTTP.5.0", 
                   "Msxml2.XMLHTTP.4.0", 
                   "MSXML2.XMLHTTP.3.0", 
                   "MSXML2.XMLHTTP", 
                   "Microsoft.XMLHTTP"];
    var o;
    for(var i = 0; i < progids.length; i++)
    {
        try
        {
            o = new ActiveXObject(progids[i]);
            return SOAPClient._getXmlHttpProgID.progid = progids[i];
        }
        catch (ex) {};
    }
    throw new Error("Could not find an installed XML parser");
}

关注点

通过使用一个小型(小于 10KB)JavaScript 库,并在服务器端简单地公开一个带有远程方法的 Web 服务,您可以使用 AJAX 创建动态 Web 应用程序,而无需重新加载整个页面。

有关使用示例,请参阅 在线演示

© . All rights reserved.