Accessing Web Services in Mozilla Using WSDL Proxying

Obsolete
This feature is obsolete. Although it may still work in some browsers, its use is discouraged since it could be removed at any time. Try to avoid using it.

Firefox 3 note

Native WSDL and SOAP support has been removed from Mozilla 1.9/Firefox 3.

The SOAP in Gecko-based Browsers (Simple Object Access Protocol) article covered how to access web services using the low-level SOAP API in Mozilla-based browsers. SOAP is an XML language that forms the basis upon which web services are built. Using SOAP has been somewhat tedious, requiring manual construction and delivery of the SOAP envelope the web service expects. SOAP-based response also has need to be parsed manually for the information required.

Relief is in sight: As of Netscape 7.1/Mozilla 1.4, Gecko supports WSDL 1.1 (Web Services Description Language) proxying. A WSDL file describes the interfaces that a web service provides, similar to a header file in C or IDL. Using the WSDL file, Gecko can offer developers a way to "script" web services as if it were a native object, hiding the SOAP and XML aspect. For example, after creating a proxy instance of a web service using WSDL, one can call methods on the proxy object like one would on any JavaScript object (proxy.getTranslation("en_fr", "Hello World") for example).

This article covers WSDL support in Mozilla 1.7-based browsers. It also addresses the cross-domain issue and new security model that Netscape is proposing that would allow web services to determine if the client can access the service from any domain or only specific domains.

This article draws upon the Babelfish web service provided by XMethods, which was the first site to implement the new Gecko web services security model, allowing Gecko browsers to directly access web services from other domains.

Creating a WSDL Proxy

A web service proxy can be created from JavaScript by creating an instance of WebServiceProxyFactory. The actual WSDL file is loaded by calling the createProxyAsync method on the WebServiceProxyFactory object.

createProxyAsync takes in five parameters:

  1. The first one is the location of the WSDL file. For the BabelFish web service, it is http://www.xmethods.net/sd/2001/BabelFishService.wsdl.
  2. The second parameter is the port name. The port name can be found in the WSDL file itself, under the service element as shown in figure 1.
  3. The third argument is an optional qualifier, which one doesn't have to worry about.
  4. The fourth parameter is a boolean indicating if the proxy should be loaded asynchronous or not. Netscape 7.1/Mozilla 1.4 doesn't support synchronous proxy creation. Since the method name itself contains "Async", this parameter is somewhat redundant and should always be set to true.
  5. The final parameter is the callback function (the creation listener) that gets called once the proxy is generated, which is discussed in detail in the next section.

JavaScript:
var factory = new WebServiceProxyFactory();
factory.createProxyAsync("http://www.xmethods.net/sd/2001/BabelFishService.wsdl", "BabelFishPort", "", true, aCreationListener);

WSDL:
<?xml version="1.0"?>
<definitions name="BabelFishService" ...>
  ...
  <service name="BabelFishService">
    <documentation>Translates text of up to 5k in length, between a variety of languages.</documentation>

    <port name="BabelFishPort" binding="tns:BabelFishBinding">
      <soap:address location="http://services.xmethods.net:80/perl/soaplite.cgi"/>
    <port>
  </service>
</definitions>
Figure 1. Instantiating and initializing a web service proxy.

To recap, an interface look at the createProxyAsync method:

void createProxyAsync (String wsdlURL, String portname, String qualifier, boolean isAsync, WebServiceProxyCreationListener listener)

edited by sebastian gurin where the argument's semantics are:

   wsdlURL: The URL of the WSDL service description. This description will be loaded and used as the basis for the service proxy. 
   portname: The name of the port of the service that this service proxy represents. Currently the port must represent a SOAP binding. 
   qualifier: The user-specified qualifier is incorporated into the names of XPCOM interfaces created for the service proxy. For C++ callers, this qualifier should be the same one used in creating the IDL used at compile time. Script callers need not specify a qualifier. 
   isAsync: If PR_TRUE, the method signatures of the service proxy represent an asynchronous calling convention. A callback instance must be registered with the proxy. A method call to a web service is only completed when the corresponding callback method is invoked. If PR_FALSE, the method signatures of the service proxy represent a synchronous callling convention. A method call to a web service is completed when the method call to the proxy returns. 
   listener: The callback instance which will be invoked when the proxy is completely initialized.

You can also use the simpler WebServiceProxyFactory::createProxy(wsdlURL, portname, qualifier,isAsync) method for create a web service proxy. The API description of WebServiceProxyFactory can be found here here

The Callback

As noted above, the last parameter createProxyAsync takes is a creation listener. The creation listener is an object which implements several methods. The creation listener is called when either the proxy generation has been successful or if an error has occurred. It is also used when a method is called on the proxy object.

The creation listener is a variable that holds several methods. Since the proxy is generated asynchronously, it holds a onLoad function that gets called when the proxy has been initialized, meaning it is now possible to call methods on the proxy. onError is called if an error occurs while generating the proxy or during a method call.

Calling a method on the proxy is also executed asynchronously. Therefore, the creation listener holds callbacks for each method that will be called. The methods for these follow a specific naming scheme: {methodName}Callback. The BabelFish web service only contains one method, BabelFish (in the WSDL, methods are denoted by the operation element), so the callback function is called BabelFishCallback. As can be seen from the WSDL file (relevant parts shown in figure 2), the BabelFish method takes in an BabelFishRequest, which is composed of two parameters, and returns the translated value as a string.

JavaScript:
var listener = {

  // gets called once the proxy has been instantiated
  onLoad: function (aProxy)
  {
    gProxy = aProxy;
    gProxy.setListener(listener);
    requestTranslation(aValue);
  },

  // gets called if an error occurs
  onError: function (aError)
  {
    alert("An error has occured while processing the WSDL file: " + aError);
  },

  // callback function is hardcoded to {methodname}Callback in 1.4beta
  BabelFishCallback  : function (aResult)
  {
    alert(aResult)
  }
};

function requestTranslation(aValue){
  if (gProxy) {
    gProxy.BabelFish("en_fr", aValue);
  } else {
    alert("Error: Proxy set up not complete!");
  }
}
 

WSDL:
<message name="BabelFishRequest">
  <part name="translationmode" type="xsd:string"/>
  <part name="sourcedata" type="xsd:string"/>
</message>

<message name="BabelFishResponse">
  <part name="return" type="xsd:string"/>
</message>

<portType name="BabelFishPortType">
  <operation name="BabelFish">
    <input message="tns:BabelFishRequest"/>
    <output message="tns:BabelFishResponse"/>
  </operation>
</portType> Figure 2. Handling the callback.

Example

This example takes the parts shown in previous figures and creates a fully working example of how to call the BabelFish web service to translate an user inputted string.

The user is given a form to fill out, with two dropdowns and an input field. The first dropdown (id="lang_from") contains the language to translate from, and the second (id="lang_to") has the language to translate to. The input is used to enter the string that will be translated. There is also a button labeled "translate", which calls the function initTranslation. The function gets the chosen languages from the form and checks if they are the same. If they are different, the function Translate is called. The Babel Fish web service takes in two methods: a string of the format fromLanguage_toLanguage and the string to translate.

function initTranslation(){
  var fromLang = document.getElementById('lang_from').value;
  var toLang = document.getElementById('lang_to').value;
  if (fromLang != toLang)
    Translate(fromLang+'_'+toLang, document.getElementById('inputValue').value);
  else
    alert("Translating a language to itself is kinda useless :)");
}

Figure 3. Initializing the translation

The Translate function is the one that actually takes care of the web service call. It first checks if a proxy object has already been created by checking if the global variable gProxy is not null. If it is null, then a creation listener is generated and stored into a variable called listener. It then calls the function createProxy with the creation listener. If however the proxy was already created, the requestTranslation function is called.

var gProxy = null;

function Translate(aLangToFrom, aString){
  if (!gProxy) {
    var listener = {

      // gets called once the proxy has been instantiated
      onLoad: function (aProxy)
      {
        gProxy = aProxy;
        gProxy.setListener(listener);
        requestTranslation(aLangToFrom, aString);
      },

      // gets called if an error occurs
      onError: function (aError)
      {
        alert("An error has occured: " + aError);
      },

      // callback function is hardcoded to {methodname}Callback
      BabelFishCallback  : function (aResult)
      {
        document.getElementById("results").innerHTML = aResult;
      }
    };

    createProxy(listener);
  } else {
    requestTranslation(aLangToFrom, aString);
  }
}

function createProxy(aCreationListener){
  try {
    var factory = new WebServiceProxyFactory();
    factory.createProxyAsync("http://www.xmethods.net/sd/2001/BabelFishService.wsdl", "BabelFishPort", "", true, aCreationListener);
  } catch (ex) {
    alert("Failed creating the proxy: "+ ex);
  }
}

function requestTranslation(aLangToFrom, aString){
  if (gProxy) {
    gProxy.BabelFish(aLangToFrom, aString);
  } else {
    alert("Error: Proxy hasn't been set up correctly!");
  }
}
Figure 4. Proxy generation

createProxy is run the first time a translation is requested. It instantiates a WebServiceProxyFactory and creates a new proxy using createProxyAsync, which uses the creation listener. Once the proxy has been created, the onLoad method defined in the creation listener is called. It stores the generated proxy in the global gProxy variable, sets the listener to be the creation listener and calls requestTranslation, as the proxy is now ready to be used.

The requestTranslation function calls the BabelFish method on the proxy to initiate the web service call. If the call is sucessfull, the BabelFishCallback method in the creation listener is called, which writes out the translated value into a div. If the call failed for some reason (such as a SOAP fault was returned), onError is called.

The full example can be seen[example.html here] (requires Netscape 7.1/Mozilla 1.4 or above).

The security model

One problem facing web services support in the browser is the cross-domain security model. JavaScript is limited to only being able to load data from the same domain the JavaScript lives on. For example, Netscape.com can only load XML using XMLHttpRequest from the netscape.com domain, and not from foo.com. If a site is to be able to connect to a remove web service, a new security model is required.

Netscape has proposed a security model to the W3C in which the web service provider determines if the web service is accessible by anyone, from certain domains only, or not at all from the Internet. An in-depth look at the security model can be found at extensions/webservices/docs/New_Security_Model.html. In brief, the web service provider has to put an XML file in the top level directory where the web service is located. In the case of XMethods, it is located at http://services.xmethods.net/web-scripts-access.xml and allows any domain to contact the web service. This is why the example in this article can contact a cross-domain server.

Document Tags and Contributors

 Last updated by: trevorh,