Posting XML SOAP Requests With jQuery

Posted February 16, 2010 at 10:10 AM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML

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:

soap_proxy.cfm

  • <!--- 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:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Submitting SOAP Requests With jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize the script.
  • jQuery(function( $ ){
  •  
  • // Get a handle on our SOAP template.
  • var soapTemplate = $( "#soap-template" );
  •  
  • // Get a handle on our form.
  • var form = $( "form:first" );
  •  
  • // Get a handle on our zip code input field.
  • var zip = $( "#zip" );
  •  
  • // Get a hand on our city and state place holders.
  • var city = $( "#city" );
  • var state = $( "#state" );
  •  
  •  
  • // Bind the form submission to re-route through our
  • // SOAP-based AJAX request.
  • form.submit(
  • function( event ){
  • // Prevent the default submit.
  • event.preventDefault();
  • event.stopPropagation();
  •  
  • // Create our SOAP body content based off of
  • // the template.
  • var soapBody = soapTemplate.html().replace(
  • new RegExp( "\\$\\{[^}]+\\}", "i" ),
  • zip.val()
  • );
  •  
  • // Trim the SOAP body so that we don't get any
  • // XML prolog errors.
  • soapBody = $.trim( soapBody );
  •  
  • // Post SOAP request.
  • $.ajax({
  • type: "post",
  • url: "./soap_proxy.cfm",
  • contentType: "text/xml",
  • data: soapBody,
  • dataType: "xml",
  • processData: false,
  • beforeSend: function( xhr ){
  • // Pass the target URL onto the proxy.
  • xhr.setRequestHeader(
  • "SOAPTarget",
  • "http://www.webservicex.net/uszip.asmx"
  • );
  •  
  • // Pass the action onto the proxy.
  • xhr.setRequestHeader(
  • "SOAPAction",
  • "http://www.webserviceX.NET/GetInfoByZIP"
  • );
  • },
  • success: function( response ){
  • // Get a jQuery-ized version of the response.
  • var xml = $( response );
  •  
  • // Populate the city. NOTE: These
  • // node names are case-sensitive and
  • // have to be uppercase.
  • city.text(
  • xml.find( "CITY" ).text()
  • );
  •  
  • // Populate the state. NOTE: These
  • // node names are case-sensitive and
  • // have to be uppercase.
  • state.text(
  • xml.find( "STATE" ).text()
  • );
  • },
  • error: function(){
  • console.log( "ERROR", arguments );
  • }
  • });
  •  
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Submitting SOAP Requests With jQuery
  • </h1>
  •  
  • <form>
  •  
  • <p>
  • Zip Code:
  • <input id="zip" type="text" size="20" />
  • <input type="submit" value="Get Info" />
  • </p>
  •  
  • <p>
  • City: <span id="city"> ... </span><br />
  • State: <span id="state"> ... </span>
  • </p>
  •  
  • </form>
  •  
  • <!--
  • This is the SOAP template that we will use when making
  • our SOAP POST to the server (ColdFusion SOAP Proxy).
  • -->
  • <script id="soap-template" type="application/soap-template">
  •  
  • <?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>
  •  
  • <GetInfoByZIP xmlns="http://www.webserviceX.NET">
  • <USZip>${zip}</USZip>
  • </GetInfoByZIP>
  •  
  • </soap:Body>
  •  
  • </soap:Envelope>
  •  
  • </script>
  •  
  • </body>
  • </html>

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.




Reader Comments

Feb 28, 2010 at 10:19 PM // reply »
1 Comments

do you have a sample of this using PHP?
thanks.


Mar 1, 2010 at 8:00 AM // reply »
10,638 Comments

@Jing,

I don't know much of anything about PHP.


Mar 18, 2010 at 10:28 PM // reply »
1 Comments

can you please point me to the jquery documentation on the following
# // Create our SOAP body content based off of
# // the template.
# var soapBody = soapTemplate.html().replace(
# new RegExp( "\\$\\{[^}]+\\}", "i" ),
# zip.val());

inparticular the replace method and RegExp object.

I have not been able to find either at http://api.jquery.com/

Thank You


Mar 19, 2010 at 8:26 AM // reply »
10,638 Comments

@Jason,

The RegExp object and the replace method are parts of the core Javascript language; they are not part of jQuery.


Jun 20, 2010 at 8:58 PM // reply »
1 Comments

I there,

Thanks for your post, it's great!
I'm doing something similar but the request needs to be done through ssl and the client needs to send a certificate too. Do you have any idea of how the JavaScript code can be changed to reflect this?

cheers


Jun 20, 2010 at 9:15 PM // reply »
10,638 Comments

@Miguel,

Have you tried just switching to an HTTPS address? As long as the page that contains the jQuery is also HTTPS, I can't see why an SSL connection would fail (in theory - not sure if this is true).


Jul 8, 2010 at 11:09 AM // reply »
2 Comments

I had read a previous blog post of yours about the troubles you were having with getHTTPRequestData and "Premature end of file" errors. Did you resolve or work around that? If yes, how? Tx!


Jul 9, 2010 at 10:27 AM // reply »
10,638 Comments

@Matt,

I *did* find a work-around to the problem... sort of. It's wicked complex and very robust, but it seems to work. I used to intercept "ColdFusion-as-a-Service" requests to grab incoming SOAP images manipulation requests and add my own watermark to the images before they were returned.

http://www.bennadel.com/blog/1774-Intercepting-ColdFusion-As-A-Service-SOAP-And-RESTful-Component-Requests.htm

It came down to actually grabbing the SOAP request, examining it, and then RE-posting it to the local server for the actual processing (then intercepting the return, mutating it, and returning it manually).

Like I said - way overly complicated; but, it's the only thing I could ever come up with.


Dec 14, 2010 at 7:25 PM // reply »
16 Comments

How does the code below change if there are more than one parameters to pass (i.e. not only zip):

  • var soapBody = soapTemplate.html().replace(
  • new RegExp( "\\$\\{[^}]+\\}", "i" ),
  • zip.val()


Dec 14, 2010 at 8:07 PM // reply »
16 Comments

I flagged the regex and just did something like this:

  • var soapBody = soapTemplate.html()
  • .replace('${SessionID}', vSessionID )
  • .replace('${CustomerCode}', vCustomerCode )
  • .replace('${FullUserName}', vUser )
  • .replace('${StartDate}', vStartDate )
  • .replace('${EndDate}', vEndDate );


Dec 15, 2010 at 12:09 AM // reply »
16 Comments

Hi Ben,
Could you show us how to work with just a subset of the returned XML. I managed to get your jquery/ajax example working, but it's a huge SOAP XML return with "soap:envelope" and "table:diffgr" etc crapola all over the place and I want to just get down to the data that matters.

Without the jquery/Ajax, I managed to narrow down the xml to the subset I needed using:

  • dataContainer = rspContainer.xmlChildren[2].xmlChildren[1].xmlChildren;

Can I do something similar in the jQuery?


Jan 7, 2011 at 9:32 PM // reply »
10,638 Comments

@Paul,

Definitely your replace() function is totally fine. There's really no need to use a regular expression in this case; I am just so used to using them. I am glad you got it working.

I am not sure what you are asking about the XML ? Are you talking about handling it on the server-side?


Jun 13, 2011 at 7:33 AM // reply »
1 Comments

it's work on ie(9) if i comment

/********************/
beforeSend: function( xhr ){
// Pass the target URL onto the proxy.
xhr.setRequestHeader(
"SOAPTarget",
"http://www.webservicex.net/Rate.asmx"
);

// Pass the action onto the proxy.
xhr.setRequestHeader(
"SOAPAction",
"http://www.webserviceX.NET/Get_rateby"
);
},
/**********************/

but don't work on firefox(3.6.17) and Google Chrome(12.0))


Jun 18, 2011 at 10:50 AM // reply »
1 Comments

Hi,
I'm using this to access a java/glassfish web service.

The code is quite simple, it's only to test it.

  • $.ajax({
  • type: "post",
  • url: "/js/soap_proxy.cfm",
  • contentType: "text/xml",
  • data:soapBody,
  • dataType: "xml",
  • processData: false,
  • beforeSend: function(xhr)
  • {
  • xhr.setRequestHeader("SOAPTarget","http://localhost:8080/handicarte");
  • xhr.setRequestHeader("SOAPAction","");
  • },
  • success: function(response){
  • var xml = $(response);
  • alert(xml);
  • },
  • error :function(jqXHR, textStatus, errorThrown){
  • alert(textStatus);
  • alert(errorThrown);
  • }
  • });

The content of my soapBody is working (in php) but here the response is the content of your ColdFusion proxy.

What am I doing wrong ?

Oh and there is no soapAction header because the field is empty in my wsdl.


Aug 10, 2011 at 11:12 PM // reply »
1 Comments

I'm studying it now and I'm glad finding this article, very helpful for me.


Nov 26, 2011 at 10:53 AM // reply »
1 Comments

Hi, i'm trying to call a php soap webservice with a jQuery.ajax call using. i found this article and use your approach to make the call.

now the call response me "XML error on line 1 col 61 byte 61 not well-formed (invalid token)" in the "SOAP-ENV:Parser"

i have this php webservice

http://choizez.com/modules/mod_SurveyStream/webservice.php/

with a method "getSurveyList" that accept two parameters "start" and "count" (both int) and return a multi-array

i use the same method from php and it returns me a correct response

the "data" field in jQuery ajax call is:

<?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>
<getSurveyList xlns="http://choizez.com/modules/mod_SurveyStream/webservice.php/">
<start>0</start>
<count>10</count>
</getSurveyList>
</soap:Body>
</soap:Envelope>

i'm trying to solve it but i've no luck and low experience with soap, any suggest?


Jan 23, 2012 at 5:46 AM // reply »
1 Comments

I am not geing any data. Getting an error.
Kindly Help :

I am having snippet_1.cfm in my local system folder named "A_Test" and HTML file "Result_Test" under the same folder.
And having jquery-1.4.1.js file under the "A_Test" folder.

I am giving the path :
<script type="text/javascript" src="jquery-1.4.1.js"></script>

url: "./snippet_1.cfm"

Please suggest.



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »