Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


Using the Mozilla SOAP API

by Scott Andrew LePera and Apple Developer Connection
08/30/2002

Introduction

Although still in its infancy, the age of Web services and SOAP has already created a demand for a wide array of client technologies. A search for "SOAP client" turns up myriad implementations for C++, Perl, .NET, PHP, and Java. If you dig a little deeper, you can even find clients for languages such as Ruby, Python and Tcl. Most of these clients operate at the server level, either as middleware or as a component of a larger system.

Web browsing clients have traditionally been left out of this loop, however, since composing SOAP messages and connecting with Web services was better suited to server-based applications tailored specifically for those tasks.

Until recently, that is. With the release of Mozilla 1.0, the world now has a browser that supports SOAP natively. No longer do the tasks of assembling, executing, and handling SOAP operations fall solely on the server side. A Web application running in Mozilla (or in a client using the same scripting engine, such as Netscape 7.0) can now make SOAP calls directly from the client without requiring a browser refresh or additional calls to the server. The data returned from a SOAP operation can be accessed via the same DOM Level 2 methods used to traverse any XML document.

Note: The example scripts used in this article were developed using Mozilla 1.0 on an iBook running Mac OS X. Only the most recent Mozilla builds include the SOAP API. To run these scripts, you'll need to copy the code into an HTML document and run it from your local machine (see Security Issues, below).

The Mozilla SOAP API

Mozilla's SOAP API is a JavaScript interface for a series of objects designed to create, send, and receive SOAP messages. These messages are encoded as XML, but you don't need to know much about the XML part of SOAP to use the Mozilla API. Construction of a SOAP message is as easy as creating any other JavaScript object.

A basic SOAP message contains some key information: the URI of the target service, the name of the service method you want to invoke, and a series of parameters to be passed as method arguments. Once these pieces are in place, the SOAP call is ready to go.

I chose the popular Google Web API as the target SOAP service for these sample scripts. Since Google places limits on the use of their API, you'll need to obtain a free Google API Key so these scripts can have full access to the search data.

Invoking SOAP services via the Mozilla API can be done either synchronously or asynchronously. A synchronous SOAP call will cause the script to wait for a response from the service before proceeding - a behavior known as blocking. An asynchronous (non-blocking) call will allow the script to proceed without waiting for a response, and uses a "listener" function (a callback) to handle the response from the service when it arrives. The example scripts in this article use asynchronous SOAP calls.

If you'd like to experiment with different services beyond Google, you can find a robust directory of SOAP services at XMethods.

Security Issues

Because the SOAP API enables the browser to make HTTP calls to services not controlled by you (the user), the security of your system becomes an issue. A secure browser client will not, for example, allow scripting between frames if they contain pages from two different domains. Similarly, it will not allow a script to make risky calls to a service at a foreign domain. After all, who knows what kind of potentially malicious code this so-called "service" is going to send back?

Creating Applications with Mozilla

Related Reading

Creating Applications with Mozilla
By David Boswell, Brian King, Ian Oeschger, Pete Collins, Eric Murphy

To ensure the safety of your system, a script that uses Mozilla's SOAP API must be either a signed script or run from the user's local machine. (For more on signed scripts in Mozilla see Signed Scripts & Privileges: An Example.) Even when taking these precautions, Mozilla will require that the user grant the proper security privileges to run the script.

Constructing the SOAP Message

You create a SOAP message just as you would an array or image object with scripting. A number of SOAP-related objects are available, but for now you'll only need to worry about these four:

The SOAPCall and SOAPParameter objects are the only ones you'll need to create with JavaScript. SOAPResponse and SOAPFault are automatically generated when the service sends back a response.

Identifying Service Methods

So what methods can you call on a given Web service? Usually a service will provide a WSDL (Web Service Description Language, yet another XML schema) file describing the available methods and the parameters required for calling them. A smart SOAP client will be able to parse this file and automatically generate the interfaces it needs to communicate with the service.

Mozilla is smart, but not quite that smart - its WSDL interface is still being developed. So for now we're stuck reading the WSDL file ourselves to learn what a service has to offer. A tutorial on WSDL could easily fill another article, so I'm going to skip the gory details and only point to the relevant parts as needed throughout this article.

The WSDL file for the Google Web API identifies three methods: doGoogleSearch, doGetCachedPage, and doSpellingSuggestion. Of these three, I'm going to focus on doGoogleSearch for the sample code, but I'll leave some room for the other two methods as well. I'll begin by defining a function to create and send a SOAP message specifically tailored for Google:

function callGoogle(method,params,callback){

}

The callGoogle function requires three arguments:

callGoogle will use the values of these three arguments to create a SOAPCall object representing the message I want to send.

Enabling Security Privileges

Before you create a SOAP message, you need to allow the user to grant "UniversalBrowserRead" permissions to the script so it can make the call to the remote service. To do this, you'll need to use the Privilege Manager, part of the original Netscape product and the Mozilla code base.

function callGoogle(method,params,callback){
  try {
    netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
  } catch (e) {
    alert(e);
    return false;
  }
}

The above code must go inside the JavaScript function or code block responsible for initiating the SOAP call, and must come before the call itself. When this code executes, users are presented with a prompt that asks them to grant or deny the privilege:

Mozilla privileges diaglog box

This should happen every time the callGoogle function is invoked.

Remember that this privilege can only be granted to a signed script or one running from the local machine. Otherwise, the code will produce an "enablePrivilege not granted" error and the SOAP call will not be made.

Creating the SOAPCall

Once the script has been given permission to proceed, the next order of business is to create a SOAPCall object:

function callGoogle(method,params,callback){
  try {
    netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
  } catch (e) {
      alert(e);
    return false;
  }
  var soapCall = new SOAPCall();
  soapCall.transportURI = "http://api.google.com/search/beta2";
}

The transportURI property of the SOAPCall object is a string indicating the endpoint URI of the service. You can find this near the end of the Google WSDL file, under the SERVICE element.

Next, the message must be encoded with the requested method and provided parameters. This is done by passing the following arguments to the encode method of the SOAPCall object:

soapCall.encode(0, method, "urn:GoogleSearch", 0, null, params.length, params);

The above is a bit tricky to describe because some of the arguments aren't necessary for basic SOAP operations, and therefore have zero and null values. The only arguments you need be concerned with are method, which is the service method to be invoked, and the target namespace of the service (in this case, the namespace "urn: GoogleSearch", which can be found in the WSDL file as the value of the targetNamespace attribute, under the DEFINITIONS element).

The final two arguments supply the encode method with the number of parameters to be passed to the service method and the array of parameters, respectively.

Invoking the Call

Now you're ready to invoke the SOAPCall and send the service request to Google. The asyncInvoke method of the SOAPCall object takes one argument: a pointer to a callback function to handle the SOAP response.

As noted above, the callGoogle function requires such a callback as its third argument, but I'm not ready to use that just yet. Instead, I'm going to do a little error-checking before handing the response off to the rest of the application. So I'll pass it to handleSOAPResponse:

function callGoogle(method,params,callback)
{
  try {
    netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
  } catch (e) {
      alert(e);
    return false;
  }
  var soapCall = new SOAPCall();
  soapCall.transportURI = "http://api.google.com/search/beta2";
  soapCall.encode(0, method, "urn:GoogleSearch", 0, null, params.length, params);
  soapCall.asyncInvoke(
    function (response, soapcall, error)
     {
        var r = handleSOAPResponse(response,soapcall,error);
        callback(r);
      }
  );
}

The last part of this example requires a little more explanation. When the callback passed to asyncInvoke executes, it's provided with three arguments which come as a result of the SOAP call being completed:

Since you want the response to go to handleSOAPResponse for error-checking, and then to the callback initially supplied to callGoogle, you should wrap the two handlers in an anonymous function to be passed to the asyncInvoke method. Remember that asyncInvoke does not wait for a response from the service before allowing the script to continue. Doing it this way ensures that the script doesn't forget the original callback when the callGoogle function ends.

OK, you've completed the callGoogle function for assembling and sending SOAP messages to Google. Now it's time to use handleSOAPResponse to deal with the response.

Handling the SOAP Response

The handleSOAPResponse function receives the results of the SOAP call and checks to make sure there wasn't a SOAP fault or service error. If no faults or errors are found, the script assumes the response is safe and passes the SOAPResponse to the user-defined callback originally given to callGoogle.

Checking for Errors

The first step is to see if the error argument contains a value other than zero. If so, we can assume a fatal error has occurred and exit the script:

function handleSOAPResponse (response,call,error)
{
    if (error != 0)
    { 
        alert("Service failure");
        return false;
    } 
}

If no error is found here, the script proceeds to check for a SOAP fault.

Handling a SOAPFault

A SOAP fault is usually generated when something goes horribly wrong with the call: a missing parameter, an invalid method, etc. When such a fault occurs, a SOAPFault object is generated which contains information about what went wrong during the SOAP operation.

To test for a SOAPFault, the script must check the fault property of the SOAPResponse object. If no fault has occurred, the value of this property will be null. Otherwise, it's a SOAPFault object.

A SOAPFault object has the following properties:

The handleSOAPResponse function below uses faultCode and faultString to warn the user that a fault has occurred. If you want, you can change the warning messages or add code to provide alternate behavior in the case of a fault.

function handleSOAPResponse (response,call,error)
{
    if (error != 0)
    { 
        alert("Service failure");
        return false;
    } else 
    {
        var fault = response.fault; 
        if (fault != null) { 
            var code = fault.faultCode; 
            var msg = fault.faultString; 
            alert("SOAP Fault:\n\n" +
                "Code: "  + code +
                "\n\nMessage: " + msg
            );
            return false;
        } else 
        {
            return response;
        }
    }
}

Note that in the case of either a fault or a service error, a value of "false" is returned to the callback specified in callGoogle. If the response passes these checks, the script returns the full SOAPResponse to the callback.

Using the callGoogle Function

I'm going to backtrack for a moment and look at how to use the callGoogle function to make a SOAP call.

First, you must prepare an array of parameters required by the service method. As mentioned above, the Google Web API provides three service methods. One of them, doSpellingSuggestion, requires two parameters: the word or phrase you want to check, and a Google API key.

The second argument required by callGoogle is an array containing the parameters needed to make the call. This is easily done with the SOAPParameter constructor, which requires two arguments: the value and name of the parameter to be added, in that order.

// create the parameters array
var params = new Array();

// replace 'yourKeyHere' with your Google key
params[0] = new SOAPParameter(yourKeyHere, "key");
params[1] = new SOAPParameter("brintey speers","phrase");

// now call the Google service
callGoogle("doSpellingSuggestion",params,parseResult);

Here's a more complex example for the doGoogleSearch method:

var p = new Array();
p[0] = new SOAPParameter(yourKeyHere,"key");
p[1] = new SOAPParameter("Mozilla","q"); // the search query
p[2] = new SOAPParameter(0,"start");
p[3] = new SOAPParameter(10,"maxResults");
p[4] = new SOAPParameter(false,"filter");
p[5] = new SOAPParameter("","restrict");
p[6] = new SOAPParameter(false,"safeSearch");
p[7] = new SOAPParameter("","lr");
p[8] = new SOAPParameter("Latin-1","ie");
p[9] = new SOAPParameter("Latin-1","oe");

callGoogle("doGoogleSearch",p,parseResult);

In the above code, parseResult is the callback function we want to handle the processing of a successful (no-fault, no-error) SOAP operation. Since the handleSOAPResponse function takes care of the error-checking, you don't have to worry about that in your callbacks. If an error or fault is found, the value returned to parseResult will simply be false.

Parsing the SOAPResponse

The last piece of our puzzle is actually writing the parseResult callback function to handle the SOAP response. The first order of business is to see if the result handed to parseResult after a SOAP operation is indeed a SOAPResponse object:

function parseResult(result)
{
    if (result==false)
    {
         return;
    }
}

The above script exits without warning if no SOAPResponse is available. A more effective implementation would provide an alert or some alternate behavior.

Retrieving Parameters

Now you can retrieve the information returned from the SOAP service method. The most direct approach is to use the getParameters method of the SOAPResponse object. As you may have guessed, getParameters returns an array containing any parameters sent back from the SOAP service.

function parseResult(result)
{
    if (result==false)
    {
         return;
    }
    var num = new Object();
    var params = result.getParameters(false,num);
}

The call to getParameters requires two arguments: a Boolean false (indicating that this is an RPC-style message) and a generic JavaScript object num, which will hold the count of parameters returned.

The number of parameters returned depends on the service, and should be described in the WSDL file. In the case of Google, all three service methods return a single parameter as a response. In Google's WSDL file, under "Messages," you can see that the parameter in all three cases is a single RETURN element.

Parsing the Return Values

Retrieving the data from the service is now a simple matter of parsing the parameters. Each parameter in the array has the following properties:

Let's put this knowledge to work. Since the doSpellingSuggestion returns only one parameter containing the suggested spelling, you can refer directly to the value property of the parameter to retrieve the suggestion:

function parseResult(result)
{
    if (result==false)
    {
         return;
    }
    var num = new Object();
    var params = result.getParameters(false,num);
    var suggestion = params[0].value;

    alert("Suggested spelling: " + suggestion);
}

Alternately, you can use the DOM to retrieve the information via the parameter's element property:

var suggestion = params[0].element.firstChild.nodeValue);

The ability to use the DOM in this way is particularly helpful when the parameters return complex XML rather than simple name/value pairs. An example of this is the doGoogleSearch service method, which provides not only rich information about the search results but also meta-information regarding search time, total result count, and filtering. For example, upon receiving a response from the Google search service, you may want to grab only the names and cached file sizes of the matching documents. With the DOM, this is fairly straightforward:

function showResults(results)
{
    if (!results)
    {
        return;
    }
    var params = results.getParameters(false,{});
    var matches = params[0].element.getElementsByTagName("item");
    var str = "";
    for (var i=1;i<matches.length;i++)
    {
      var URL = matches[i].getElementsByTagName("URL").item(0).firstChild;
      var title = matches[i].getElementsByTagName("title").item(0).firstChild;
      var cache = matches[i].getElementsByTagName("cachedSize").item(0).firstChild;

      str += "<a href=\"" + URL.nodeValue + "\">" + title.nodeValue + 
          "</a> (" + cache.nodeValue + ")<br/>";
    }
    document.body.innerHTML = str;
}

Play Along at Home

You can download the complete working example I've created, made with the Google API:

Mozilla Google Search

This covers the entire process, from defining parameters to executing the SOAP call to parsing and displaying the search results with DOM methods. Go ahead and experiment with the code and write your own interfaces to other SOAP services, and be creative when using the returned data in your Web applications.

Looking beyond the browser, the scripting engine is part of the complete Mozilla package, so it's possible to write XUL (XML User-interface Language, on which the Mozilla UI is built) applications that take advantage of the same API. The Mozilla SOAP API allows you to use familiar Web standards - JavaScript and the DOM - to easily integrate Web services into your client-side applications.

Scott Andrew LePera lives in San Francisco, where he ekes out a schizoid existence as both a web applications developer for KnowNow and a frustrated urban folk singer.


Return to the Web Development DevCenter.

Copyright © 2009 O'Reilly Media, Inc.