Accessing Web Services In Netscape 7.1/Mozilla 1.4 Using WSDL Proxying
Introduction
The SOAP (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 Netscape 7.1/Mozilla 1.4's WSDL support. 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.
- 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.
- The second parameter is the port name. The port name can be found in the WSDL file itself, under the
serviceelement as shown in figure 1. - The third argument is an optional qualifier, which one doesn't have to worry about.
- 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. - 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>
To recap, an interface look at the creatProxyAsync method:
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>
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 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.
JavaScript:
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 :)");
}
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.
JavaScript:
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!");
}
}
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 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 here. 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.
