JavaScript SOAP Client

转载 2006年05月24日 00:29:00

Introduction

A lot of talking about AJAX is taking place here and there; AJAX is the acronym of "Asynchronous JavaScript and XML", a technology based on XMLHttpRequest, which is now supported by all main browsers. The basic idea is quite simple - and not actually a breakthrough - but it allows updating a page following a server request, without reloading the entire set of data. Some examples can be found on GMail or Google Suggest. For additional information about AJAX, you can see Wikipedia.

In this article, we propose a solution based on AJAX that has a great advantage with respect to those commonly found in Internet: calls are made to the Web Services.

This permits:

  1. On the server side, we only have to expose a Web Service with the required methods (instead of generating dynamic pages incorporating data that are based on a custom syntax or on a generic XML).
  2. On the client side, we use the WSDL (Web Service Description Language) to automatically generate a JavaScript proxy class so as to allow using the Web Service return types - that is similar to what Visual Studio does when a Web Reference is added to the solution.

The following diagram shows the SOAP Client workflow for asynchronous calls:

The Client invokes the "SOAPClient.invoke" method using a JavaScript function and specifies the following:

  • Web Service URL (please note that many browsers do not allow cross-domain calls for security reasons).
  • Web method name.
  • Web method parameter values.
  • Call mode (async = true, sync = false).
  • Callback method invoked upon response reception (optional for sync calls).

The "SOAPClient.invoke" method executes the following operations (numbers refer to the previous diagram):

  1. It gets the WSDL and caches the description for future requests.
  2. It prepares and sends a SOAP (v. 1.1) request to the server (invoking method and parameter values).
  3. It processes the server reply using the WSDL so as to build the corresponding JavaScript objects to be returned.
  4. If the call mode is async, the callback method is invoked, otherwise it returns the corresponding object.

Using the code

After having exposed our idea about consuming a Web Service via JavaScript, we only have to analyze the code.

Let's start with the class for the definition of the parameters to be passed to the Web method: "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, 
                         ">") + "</" + p + ">";
        }
        return xml;    
    }
}

The code simply consists of an internal dictionary (associative array) with the parameter name (key) and the related value; the "add" method allows appending new parameters, while the "toXml" method provides XML serialization for SOAP requests (see "SOAPClient._sendSoapRequest").

Let's define the "SOAPClient" class, which can only contain static methods in order to allow async calls, and the only "public" method within this class: "SOAPClient.invoke".

Note: since JavaScript does not foresee access modifiers - such as "public", "private", "protected", etc. - we'll use the "_" prefix to indicate private methods.

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);
}

The "SOAPClient.invoke" method interface is described above; our implementation checks whether the call is async (call result will be passed to the callback method) or sync (call result will be directly returned). The call to the Web Service begins by invoking the "SOAPClient._loadWsdl" method:

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);
}

The method searches the cache for the same WSDL in order to avoid repetitive calls:

SOAPClient_cacheWsdl = new Array();

If the WSDL is not found in the cache (it's the first call in the current context), it is requested from the server using an XMLHttpRequest, according to the required mode (sync or not). Once an answer is obtained from the server, the "SOAPClient._onLoadWsdl" method is invoked:

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);
}

A WSDL copy is stored into the cache and then the "SOAPClient._sendSoapRequest" method is executed:

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);
}

The service namespace is taken out of the WSDL (using different XPath queries for Internet Explorer and Mozilla / FireFox), then a SOAP v. 1.1 request is created and submitted. The "SOAPClient._onSendSoapRequest" method will be invoked upon receiving the server response:

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;        
}

The server response is processed looking for faults: if found, an error is raised. Instead, if a correct result is obtained, a recursive function will generate the return type by using the service description:

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 "";
}

The "SOAPClient._getElementsByTagName" method optimizes XPath queries according to the available XML parser:

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

A factory function returns the XMLHttpRequest according to the browser type:

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");
}

Points of Interest

By using a single little (less than 10 KB) JavaScript library and, on the server side, simply exposing a Web Service with remote methods, you can use AJAX to create dynamic Web applications with no need for reloading the entire page.

利用JavaScript SOAP Client直接调用webService --完整的前后台配置与调用示例

在前端javascript代码中直接调用webService服务,可以将后台调用webService的业务代码转移到前台,这样做的好处是: 1) 减少了后台编码量; 2) 在特定的情况下有助于减轻...
  • hu_shengyang
  • hu_shengyang
  • 2013-01-04 17:15:32
  • 19174

js调用webservice 并构造SOAP进行身份验证

http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/1999/xhtml">    无标题页    /...
  • ypacyhero
  • ypacyhero
  • 2010-05-04 14:58:00
  • 4699

js发送SOAP请求

var  xmlhttp;          function RequestWebService() {             //这是我们在第一步中创建的Web服务的地址         ...
  • Kellogg_and_Nina
  • Kellogg_and_Nina
  • 2017-10-28 16:05:21
  • 160

js调用webservice:通过通过SOAP Header身份验证

js调用webservice,怎样验证调用者的合法性呢。本方法用Soap头来解决。
  • zhangxuyu1118
  • zhangxuyu1118
  • 2010-08-27 09:33:00
  • 2897

JavaScriptSoapClient

  • 2007年12月06日 22:14
  • 10KB
  • 下载

php的SOAP-CLIENT详解

一、概述 SOAP有大体两种模式WSDL模式和non-WSDL模式。WSDL模式访问的url中带有?wsdl,SoapClient在实例化的时候需要传入第一个参数。 无论是何种模式,方法的参数结构...
  • Godfy1990
  • Godfy1990
  • 2017-01-13 09:54:02
  • 5210

soapclient调用webservice接口

1.开启soap支持,在php.ini中去除extension=php_soap.dll之前的‘;’ 2.掉用页面 header('Content-Type: text/h...
  • lt15032237182
  • lt15032237182
  • 2014-04-08 11:17:11
  • 12210

php中soap使用,SoapServer服务端编写,SoapClient客户端编写

一,首先要设置服务器环境。 修改php.ini  得添加extension=php_soap.dll (加载soap 内置包)  修改soap.wsdl_cache_enabled=1 改为soap....
  • nanshan_hzq
  • nanshan_hzq
  • 2016-10-14 11:35:07
  • 6375

创建SOAP Client的options参数解释

An array of options. If working in WSDL mode, this parameter is optional. If working in non-WSDL mod...
  • wenzhixing
  • wenzhixing
  • 2012-03-01 09:40:04
  • 4994

jquery.<em>soap</em>.<em>js</em>

jquery的一个插件来使用<em>js</em>调用基于<em>soap</em>协议的webservice发送报文... jquery的一个插件来使用<em>js</em>调用基于<em>soap</em>协议的webservice发送报文 综合评分:4 收藏评论(1)举报 所需...
  • 2018年04月14日 00:00
收藏助手
不良信息举报
您举报文章:JavaScript SOAP Client
举报原因:
原因补充:

(最多只允许输入30个字)