In the jQuery 1.4 Reference Guide by Karl Swedberg and Jonathan Chaffer, it is explained that the processData and contentType properties of a jQuery ajax() request can be adjusted to allow for XML document posts. Typically, AJAX data is serialized into a query string; but, if you set the processData property to false, the data property will be posted as-is. After I read this, I thought it would be fun to see if I could leverage this setting to post XML SOAP requests directly from the client.
The immediate problem with posting a SOAP request from the client is that in most cases, the target SOAP web service is located on a 3rd party domain. While this is not an issue for server-side code, this presents a security concern for the client. As such, if you try to post XML to a 3rd party URL directly from the client, you'll get a 403 Forbidden exception. To get around this, we will have to create a SOAP proxy page on our server that accepts SOAP data and passes it along to the target web service.
This SOAP proxy can get complicated, but we're going to keep it very straightforward for our experimentation. The ColdFusion SOAP proxy is going to expect three things:
An XML request body. This is the SOAP packet that we'll be re-posting to the target web service.
The SOAPTarget header. This is the URL of our 3rd party web service.
The SOAPAction header. This is the SOAP action which is required for most every SOAP web service I've ever used.
Once our ColdFusion proxy has this information, it will use the CFHTTP tag to invoke the proxied web service:
<!--- Get the request data. ---> <cfset requestData = getHTTPRequestData() /> <!--- Check to make sure this is a valid SOAP request - we need to make sure that the content is XML and that we have the SOAPTarget and SOAPAction. ---> <cfif ( isXml( requestData.content ) && structKeyExists( requestData.headers, "SOAPTarget" ) && structKeyExists( requestData.headers, "SOAPAction" ) )> <!--- Pass the SOAP request onto the target. ---> <cfhttp result="soapResponse" method="post" url="#requestData.headers.SOAPTarget#"> <!--- Set SOAP action header. ---> <cfhttpparam type="header" name="SOAPAction" value="#requestData.headers.SOAPAction#" /> <!--- Submit the XML post body. ---> <cfhttpparam type="xml" value="#requestData.content#" /> </cfhttp> <!--- Conver the SOAP response to binary. ---> <cfset binaryResponse = toBinary( toBase64( soapResponse.fileContent ) ) /> <!--- Stream back to client. ---> <cfcontent type="text/xml" variable="#binaryResponse#" /> <cfelse> <!--- Create a malformed request error response. ---> <cfheader statuscode="400" statustext="Bad Request" /> <!--- Create an error message. ---> <cfsavecontent variable="responseText"> Your SOAP request must: - Be XML Content - Have the SOAPTarget header - Have the SOAPAction header </cfsavecontent> <!--- Convert the response to binary. ---> <cfset binaryResponse = toBinary( toBase64( responseText ) ) /> <!--- Stream back to client. ---> <cfcontent type="text/plain" variable="#binaryResponse#" /> </cfif>
As you can see, this page grabs the SOAP request information using the getHTTPRequestData() function. This gives our ColdFusion page access to the posted XML content body as well as the HTTP headers. It then takes this information and re-posts it using CFHTTP and CFHTTPParam. Whatever SOAP response comes back from the target web service is simply streamed back to the client using the CFContent tag. Like I said - we're trying to keep this as simple as possible.
With our ColdFusion SOAP proxy in place, we can now look at the client-side jQuery code. The meat of the code is in the ajax() method call:
Like I said above, we are setting the processData AJAX property to false so that our data property is not serialized. We need to do this because our data property does not contain the standard name-value data points; rather, it contains our raw XML SOAP packet. In order to get our AJAX post ready for our ColdFusion SOAP proxy page, we need to add two headers - SOAPTarget and SOAPAction. We can do this in the beforeSend() AJAX event handler; this event handler gives us access to the given XMLHTTPRequest object, which has methods for setting custom header values.
To demonstrate the XML data being used, I'm going to submit the form with the zip code, "10016." When merged into the script-based SOAP template, this will give us the following XML post:
<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> <getinfobyzip xmlns="http://www.webserviceX.NET"> <uszip>10016</uszip> </getinfobyzip> </soap:body> </soap:envelope>
After this SOAP request has gone through our ColdFusion proxy, it comes back with the following SOAP response:
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <GetInfoByZIPResponse xmlns="http://www.webserviceX.NET"> <GetInfoByZIPResult> <NewDataSet xmlns=""> <Table> <CITY>New York</CITY> <STATE>NY</STATE> <ZIP>10016</ZIP> <AREA_CODE>212</AREA_CODE> <TIME_ZONE>E</TIME_ZONE> </Table> </NewDataSet> </GetInfoByZIPResult> </GetInfoByZIPResponse> </soap:Body> </soap:Envelope>
In the onSuccess() AJAX event handler, I am then converting this SOAP response into a jQuery collection. From within this collection, I can then use the find() method to locate the CITY and STATE XML nodes and move their text values into my jQuery / HTML place holders. The XML document is case-sensitive which is why I have to use uppercase CITY and STATE in my jQuery traversal methods.
Due to cross-domain security concerns as well as the highly structured nature of the post data, SOAP requests are not something that lend very well to AJAX requests. However, if you are ever in a situation where this has to be done, it's nice to know that the jQuery ajax() method makes this mostly possible. On a side note, this just goes to show you how good "Reading The Manual" can be; had I not read the jQuery 1.4 Reference Guide, chances are I would have never even thought about posting XML requests with AJAX.
Want to use code from this post? Check out the license.