AJAXifying your Web service

Archived Content
This article is provided for historical perspective only, and may not reflect current conditions. Please refer to relevant product page for more up-to-date product information and resources.
  • By Chamil Thanthrimudalige
  • 29 Jun, 2006

Introduction

With the ability to generate clients on the fly for any given Web service that is hosted inside WSO2 Tungsten, we can make use of the wonderful world of Java of write once and run everywhere. However what if I tell you that there is one step that can better the "write once and run anywhere" experience of Java? What if we could write once and run in any browser?

The following will guide you to build an AJAX interface to the Web service that you have deployed inside WSO2 Tungsten. The analogies and examples that I use are code that we have used while building the Management Console and the Dynamic client modules of Tungsten. Please note that the code that is used may only work on the Tungsten server as it is. However you should be able to get it work in any environment with very little work.

Points to remember

Before we get started we need to remember some concepts about the user experience. Since the user is accessing the Web service through the browser it is expected to behave like a set of web pages and at the same time it is expected to respond as if it were a desktop application.

So take note of the following,

  1. Give user feed back when some thing is happening in the back ground.
    We are used to seeing an animation every time the web browser is fetching some web-page/image/etc. from the Internet. So we have come to expect some kind of feed back when the browser is communicating with the server. It may be a small set of text full-stops that blink or an animation graphic. Whatever it is we need to tell the user that we are communicating with the server in the background.
  2. Enable the back button.
    Since the user is expecting the interface to behave like a set of web pages that are linked together we need to find a way to let the user go back to the previous page by clicking a back button. The back button is one issue that anyone using an interface without it, complains about.
  3. Cross browser usage
    We need to remember that FireFox, IE, etc behave a bit differently when it comes to XML object manipulation.
  4. Use of CSS and IFrames
    If we want to implement a proper back button we need to avoid the usage of IFrames to fire events when object are loaded etc. Similarly we need to make sure all formatting is done using CSS to make sure that it is applied uniformly when dynamic objects are attached to the divs.

Implementation

Object creation

XMLHttpRequest object

function createXMLHttpRequest() {
try { return new XMLHttpRequest(); } catch(e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
alert("XMLHttpRequest not supported");
return null;
}

This code fragment will create a new XMLHttpRequest object and return it.

Calling the server

We will use a global variable, assign the new XMLHttpRequest object that we create and use it to send the request to the server.

var xhReq;
function call_server(operationName, body_xml, xslFileName, callURL, objDiv, callBack) {
xhReq = createXMLHttpRequest();
set_timeout();
if (callBack != null){
// Set the custom callback if one is passed in.
xhReq.onreadystatechange = callBack;
} else {
xhReq.onreadystatechange = function() {
if (!check_error()) {
return;
}
if (xslFileName != "" && objDiv != null){
// Remove the body from the object in a cross browser manner.
var data = getBody(xhReq.responseXML);
// Will do an XSLT transformation using the xslt file and
// the xml response.
processXML(data, xslFileName, objDiv, true);
// Will show the newly created div while hiding all the other divs.
showOnlyOneMain(objDiv);
}
};
}
//Starting the wait animation just before we call the server.
// This will give the user some feed back
// indicating that we are loading data from the server.
startWaitAnimation();

try {
// Open the connection to the server.
xhReq.open("POST", callURL, true);
} catch(e){
alert("Error while opening connection. ERROR message = " + e.message);
return false;
}

// Set the content type of the the data that is being sent to the server.
xhReq.setRequestHeader('Content-Type', "text/xml");
// Set a custom header telling the server telling what operation we are calling.
// This is not mandatory in WSO2 Tungsten server.
xhReq.setRequestHeader("SOAPAction", operationName);

var xml_to_send = '<?xml version="1.0" encoding="UTF-8"?>' +
' <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">' +
' <soapenv:Header xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">';
xml_to_send +=' <wsa:To>' + callURL + '</wsa:To>' +
' <wsa:Action>' + operationName + '</wsa:Action>' +
' <wsa:ReplyTo>' +
' <wsa:Address>' +
'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous' +
' </wsa:Address>' +
' </wsa:ReplyTo>' +
' <wsa:MessageID>uuid:' + createNewMessageId() + '</wsa:MessageID>' +
' </soapenv:Header>' + body_xml +
' </soapenv:Envelope>';
xhReq.send(xml_to_send);
}

If you need to see the XML that is being sent back from the server, the flowing code fragment will let you do that.

alert((new XMLSerializer()).serializeToString(xhReq.responseXML));

Checking for errors

In this method we will initially check whether the object has properly loaded. After which it will check if there are any errors. If there are any errors, it will try to decipher the error and will try to give the user a meaningful error message. Please note that this code is very WSO2 Tungsten dependent.

function check_error(){
if (xhReq.readyState != 4) { return false ; }
var tungstenError = "";
//Stop the animation for showing that data is being loaded from the server.
stopWaitAnimation();
var tunErrorMessage = "";
try {
if (xhReq.status != 200 && xhReq.status != 202) {
if (xhReq.responseXML != null){
//Retrieve the error details that are sent from the server.
if (xhReq.responseXML.getElementsByTagName("Reason")[0] != null) {
tunErrorMessage = "\nTungsten error is " + xhReq.responseXML.
getElementsByTagName("Reason")[0].childNodes[0].nodeValue;
} else {
if (xhReq.responseXML.getElementsByTagName("faultstring")[0] != null) {
tunErrorMessage = "\nTungsten error is " + xhReq.
responseXML.getElementsByTagName("faultstring")[0].
childNodes[0].nodeValue;
}
}
var errNode = getExceptionNode(xhReq.responseXML);
if (errNode != null){
try {
tungstenError = (new XMLSerializer()).serializeToString(errNode);
} catch (e) {
try{
tungstenError = errNode.xml;
} catch(e) {
tunErrorMessage = "Tungsten server encountered "+
"an error, and the browser you are using does" +
" not suport the error reporting code. \n";
alert(tunErrorMessage);
logoutVisual();
return false;
}
}
} else {
tunErrorMessage =
"\n Tungsten error message was :: Tungsten server"+
"encountered an unknown error.";
}
} else {
logoutVisual();
alert("Your session has expired please login");
return false;
}
alert("There was an error on the server. \n HTTP Status code ::" +
xhReq.status +"\n HTTP error message was :: "+
xhReq.statusText + tunErrorMessage);
return false;
}
} catch(e){
if (e.message.indexOf("NS_ERROR_NOT_AVAILABLE")>0){
alert("Could not connect to the server. Please try again in a moment.");
return false;
} else {
tunErrorMessage = "An unknown error occured. In line number " +
e.lineNumber + " the error message was " + e.message);
alert(tunErrorMessage);
return false;
}
}
return true;
}

Method call

function showVoteScreen(pollId){
var xsltFileName = "xslt/vote.xsl";
var objDiv = document.getElementById("divVoteForPoll");
//Create the XML request.
var body_xml = '<soapenv:Body>\n' +
'<req:getResultMessage xmlns:req="http://www.wso2.com/types">\n' +
'<req:pollID>' + pollId + '</req:pollID>\n' +
'</req:getResultMessage>\n' +
'</soapenv:Body>\n';
// Calling the server with the xslt file to use to transform the returned data
call_server("getResult", body_xml, xsltFileName, chadServiceURL, objDiv);
}

Custom callback

When using a custom callback, the callback function need to check if the object loaded properly and if there are any errors before moving onto do other work. This is done by calling the check_error() function till it returns a successful result.

function startPoll(pollId){
var body_xml = '<soapenv:Body>\n' +
'<req:startPollMessage xmlns:req="http://www.wso2.com/types">\n' +
'<req:pollID>' + pollId + '</req:pollID>\n' +
'</req:startPollMessage>\n' +
'</soapenv:Body>\n';
//Calling the server with a custom callback.
call_server("startPoll", body_xml, "", chadServiceURL, "", startPollCallback);
}

function startPollCallback(){
//Need to check for errors and whether the object loaded
// properly before moving on to other work.
if (!check_error()) {
return;
}
//Do other work here.
}

Limitations

  1. Difficulty in supporting WS-RM, WS-Sec and other QOS components.
    With the development of Axis2C extension for FireFox and now for IE we are one more step closer to getting full Web services support to the web-browser.
  2. Browser restrictions
    In most browsers, the front end can only communicate with the URL that it was loaded from. This is a security feature that can be disabled if users wished to do so. However this is not advised and we as developers should try to build a system granular enough to only send requests to the server we are accessing the AJAX application from. If we really need to send data in another direction it can be coded on the server in the business logic of the Web service.

Conclusion

As you can see it is very simple to get your own AJAX front end up and running for the Web service you have hosted inside WSO2 Tungsten. This method of making calls to the server gives you a very simple way to code an AJAX application to communicate with the server. It is also very easy to change and debug. The code fragments above integrates deeply into the world of Web services which requires less effort from you.

If you require to have a look at a full sample please have a look at the Chad sample code that is shipped with WSO2 Tungsten 1.0.

So that is our small dive into the world of AJAX. Good luck AJAXyfying your Web services!

Author

Chamil Thanthrimudalige, Software engineer, WSO2 Inc. , [email protected]

About Author

  • Chamil Thanthrimudalige
  • Software Engineer
  • WSO2 Inc.