SOAP Web Services in jQuery vs JavaScript

So I’ve been working with a LOT more SOAP-based Web Services since I’ve started up in a government software development position.
While in the past, most of my Web Service development has focused on creating or consuming RESTful Web Services, now my current position requires that I integrate with legacy systems built many years ago (with internal code largely untouched); in some cases, I don’t even have access to the original code, which doesn’t give me the option to switch or simply update the Web Services layer to use a simpler REST-based mechanism. Since we’re utilizing an ESB, I also need to maintain some form of data availability for the more complex stuff we need to do on the project (event-driven actions, rules, transformations, routing, logging and the like), and no, the ESB is NOT a good bandage for old systems, I think that is a common fallacy.
At the same time, we also need to build a Rich Internet Application and sometimes I want to go and get the data for a given object (i.e. User List upon loading). Thus, I’ve devised some approaches for calling SOAP-based Web Services directly from the client, via JavaScript and/or jQuery. My first choice was obviously to use jQuery but later I was told by my employer that due to licensing and other various business/political concerns it would not be desired in the final product, so I had to really go back to the old school and call the SOAP Web Services by hand from JavaScript directly.
In fact, I had to first prove it was even possible as I had many doubters on the team as well as superiors in charge of decision-making who doubted that it was even technically possible to call SOAP Web Services from JavaScript!
The following is the short and somewhat elegant (considering the situation) jQuery approach:
$('#callService').click(function(){ $.ajax({ url: '../proxy.php?e=utf-8&f=soap&action='+$('#action').val()+'&url='+$('#url').val(), type: "POST", dataType: "xml", data: $('#requestXML').val(), processData: false, contentType: "text/xml; charset="utf-8"", complete: function(xhr, status) { $('#responseXML').val(xhr.responseText); }, success: function(data, status) { alert('SOAP response received!'); }, error: function(request, status, error) { alert('Error: ' + status); } }); });
Using this XML format in the Request field, you can make a test call to the default WebServiceX CurrencyConverter SOAP-based API:
<?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> <ConversionRate xmlns="http://www.webserviceX.NET/"> <FromCurrency>CAD</FromCurrency> <ToCurrency>USD</ToCurrency> </ConversionRate> </soap:Body> </soap:Envelope>
Try it out below…
-OR-
Next, we have the hackish and somewhat ugly pure AJAX JavaScript code for calling the same SOAP Web Service:
function callService(url, xmlMsg, _soapEnvelope, _soapAction, _soapNS, _soapSchema, _nsName, _ns, _callback, _serialize) { //check input parameters and use some defaults if they weren't passed correctly var serviceURL = url; // the URL gives us the location of the Web Service we'd like to call plus a Proxy (if any) //if (empty(serviceURL)) { log("ERROR: You can't contact a Web Service without specifying its URL!"); } var xmlBody = xmlMsg || 'GOOG'; //the XML Message Body to pass to the Web Service var soapEnvelope = _soapEnvelope; //says whether to wrap in a SOAP Envelope (true) or pass-through XML as-is (false) var soapAction = _soapAction || 'http://www.webserviceX.NET/GetQuote';//the Operation of the Web Service to call var soapNamespace = _soapNS || 'soapenv'; //the SOAP namespace for the Web Service "soap12" for SOAP 1.2 var soapSchemaURL = _soapSchema || 'http://schemas.xmlsoap.org/soap/envelope/'; //the SOAP namespace for the Web Service "http://www.w3.org/2003/05/soap-envelope" for SOAP 1.2 var namespaceName = _nsName || 'quote'; var namespace = _ns || "http://www.webserviceX.NET/"; // the XML Schema namespace to optionally use with our request/response var callback = _callback || 'alert("SOAP response received!")'; // handles the XML response by passing to the desired callback function (i.e. for parsing and output) var searializeAsString = (!empty(_serialize)) ? _serialize : false; //whether or not to serialize the SOAP response as a String var soapMsg = ''; /////////////////// // REQUEST // /////////////////// // Create an XMLHttpRequest to issue the SOAP request (try regular XHR, then IE-version specific) var req; try { req = new XMLHttpRequest(); } catch (excep) { try { req = new ActiveXObject('Msxml2.XMLHTTP'); } catch (except) { try { req = new ActiveXObject('Microsoft.XMLHTTP'); } catch (exception) { //log('ERROR: XMLHttpRequest not supported...' + exception); } } } if (soapEnvelope) { //DEBUG: alert(_soapEnvelope + ' ' + soapEnvelope); soapMsg = '' + '<'+soapNamespace+':Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:'+soapNamespace+'="'+soapSchemaURL+'" xmlns:'+namespaceName+'="'+namespace+'">' + //SOAP Header details could also be supported, not necessary in SOAP 1.2 (unless using WS-* extensions) '<'+soapNamespace+':Header/>' + ' <'+soapNamespace+':Body>' + ' ' + xmlBody + ' </'+soapNamespace+':Body>' + '</'+soapNamespace+':Envelope>'; } else { soapMsg = xmlBody; } //DEBUG: console.log('INFO: This XML should be sent: ' + soapMsg); req.open("POST", serviceURL, false); // We're going to be POSTing to this URL and want a synchronous response // Set some headers: req.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); // the body of this POST request is XML "application/soap+xml; charset=utf-8" for SOAP 1.2 req.setRequestHeader("Content-Length", soapMsg.length); // the body length (in characters) of this POST request's body message req.setRequestHeader("SOAPAction", soapAction); // This header is a required part of the SOAP 1.1 protocol (optional for SOAP 1.2) req.send(soapMsg); // Now send an XML-formatted SOAP request to the server /////////////////// // RESPONSE // /////////////////// if (req.status != 200) { console.log(req.statusText); // If we got an HTTP error, throw an exception } var xmlDoc = ''; if (searializeAsString) { xml = req.responseXML; // place the Response XML in an accessible variable var response = xml.getElementsByTagName('GetQuoteResponse'); var result = xml.getElementsByTagName('GetQuoteResult'); //deserialize result var xmlString = [].map.call(result, function(node){ return node.textContent || node.innerText || ''; }).join(''); //parse XML xmlDoc = loadXMLString(xmlString); } else { xmlDoc = req.responseText; // place the Response XML in an accessible variable } //run callback function try { setTimeout(callback+'()',500); // process the response using the specified response handler (callback function) } catch(e) { console.log(e); } return xmlDoc; }
NOTE: You can also use a barebones XML request format for calling a WebService, just hit the checkmark indicating such and you’ll get the surrounding SOAP envelope padding added automatically, so you don’t have to waste any time mucking around with SOAP’s somewhat verbose formatting requirements. You can also try calling a few other publicly available SOAP-based Web Service APIs and work through coming up with the required XML request formats to get the desired XML responses back, so in theory the form works as a nice little template for mocking up a simple SOAP WS test tool.
-OR-
As you can see, the JavaScript is much longer and prone to error on browsers that don’t support one of the three checked for XHR objects XMLHttpRequest with no params (IE7+, Mozilla 2+, Opera 9+, Safari 2+, Chrome), ActiveXObject with param “Microsoft.XMLHTTP” (IE5.5 & IE6), or the same ActiveXObject this time with param of either “Msxml2.XMLHTTP.3.0” or “Msxml2.XMLHTTP.3.0” (older versions of IE and some IE mobile versions).
BTW: For the record, I think the whole SOAP .vs. REST debate is a bit too hyped and overly argued (yes, we also argued extensively about that). As a developer, you should use the best solution for the job and not make technology decisions based on emotion or stubbornly ingrained preferences. For me, the analogy of the corporate phone system works quite well in explaining the difference between the two:
- SOAP is like having extensions, everybody calls the root number such as 1-888-abc-company (i.e. the WSDL at a URL like http://abc.company.com/EmployeeDirectory?WSDL), then I’m asked to dial #4311 for Jenny’s extension (or, pass #1001 to the PhoneExtension method via HTTP, as in a GET or POST request to http://abc.company.com/EmployeeDirectory with the XML body <extension>4311</extension> and I’d get back <employee>Jenny Howard</employee> and possibly other info related to her in separate tags). To avoid collisions in naming of elements and sub-elements, we have the option of putting all request/repsonse data inside of namespaces specific to our application/service, and can define this in the Xml Schema Definition (XSD) that defines the structure of our XML messages. SOAP continually passes these well-defined XML messages back and forth between client & server.
SOAP Web Services as a Corporate Phone Directory (FULLSIZE)
- REST is like everybody (or each item of data) getting their own unique phone number (or URL) since extra phone numbers (like URLs) aren’t that expensive and may be worth the convenience in productivity by being able to reach people (data) directly, so we don’t have to talk to an operator or deal with an annoying automated voice response system (eh-hrrm… WSDL). Lookups would be done much like in the Public Phone System, an operator could be called one time and then we’d jot the number down (i.e. bookmark it, or, save to contacts/endpoints DB) so we had it and didn’t have to call the operator ever again, or, we could consult a phonebook that lists every possible number on the call network and perform a one-time lookup by some search criteria (i.e. lastname, which most phonebooks are sorted by). Once we have a number, we’d just dial Jane at 1-888-123-4567 (or a unique URL, more appropriately a URI, like http://abc.company.com/employee/jane/). If I wanted Jeremy instead, I’d just dial 1-888-123-4568 (or hit the URI http://abc.company.com/employee/jeremy/ and all the data about him would be available in XML, JSON, or maybe just a nice human-readable HTML table if I wanted to access the info directly in a browser). Output formats can be specified as a parameter, or, more “RESTfully” (according to REST founder Roy Fielding) via the HEADER information exchanged between my client and the server). Lastly, if we ran out of certain URL’s because there were more than Jeremy; well, you can’t really run out of URLs like you could Phone numbers (since you could use Last Names and/or MiddleNames to uniquely qualify names, separate employees by department and adjust the URL accordingly, or add unique usernames to the URL scheme); but if you did somehow run out of every possible combination of Jeremy’s, you could still just make it a first come first served policy and append hashes (random numbers, or userID’s but I’d tend towards the former for security reason) to the end of identical names.
REST Web Services as the Public Phone System (FULLSIZE)
To see a RESTful version of this jQuery .vs. JavaScript implementation comparison, check out my earlier post on parsing RSS news feeds, RSS is essentially a contract akin to WSDL for REST services publishing content so that it can be consumed by RSS Readers (parsers) in clients of any kind, and RSS is deployed widely all over the web.
Conclusion
At the end of the day, just be sure to choose the right tool for the job. If you need more security than HTTPS alone provides, or, upfront agreement on a rarely changing format is critical, go with SOAP. If you just need to throw a quick web service together that will get the job done as efficiently as possible, pick REST. If you’ve got a massive Enterprise dataset that needs to be sliced and diced into several different operational layers due to data separation requirements (like our “Big Company Phone Directory” example) go with SOAP; but if you are building a Phone Directory for a tiny Startup that doesn’t have many bureaucratic layers and tends to prefer working with really lightweight versions of XML, or, more likely JSON then go with REST.
Related Articles
- The jQuery Divide:Understand where jQuery ends and JavaScript begins (slideshare.net)
- 9 jQuery Scripts to Enhance Your Website | Queness (queness.com)
- opensocial-jquery – concise JavaScript Library for rapid OpenSocial Apps development (code.google.com)
- How Simple is REST? (Learn REST)