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 »
11,246 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 »
11,246 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 »
11,246 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 »
3 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 »
11,246 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 »
17 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 »
17 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 »
17 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 »
11,246 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.


Sep 28, 2012 at 2:07 AM // reply »
1 Comments

Here is the proxy implementation in PHP.

This does not error check but works with the exact same jquery call.

<?php
$action = $_SERVER['HTTP_SOAPACTION'];
$target = $_SERVER['HTTP_SOAPTARGET'];
$soap_body = file_get_contents('php://input');

$headers = array(
"Content-type: text/xml;charset=\"utf-8\"",
"Accept: text/xml",
"Cache-Control: no-cache",
"Pragma: no-cache",
"SOAPAction: ".$action,
"Content-length: ".strlen($soap_body),
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_URL, $target);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $soap_body);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$response = curl_exec($ch);
curl_close($ch);
echo($response);
?>


Nov 6, 2012 at 4:16 AM // reply »
1 Comments

Hello Ben,

Thanks for this great tutorial, been trying to follow but it I get soap_proxy.cfm as my response in the firebug.

Where am I going wrong: Below is my code;

*************************************************

function getToken(username, password, appId)
{

var soapMessage =
'soap12:Envelope
soap12:Body
AdminLogin xmlns="http://www.omnibridge.com/SDKWebServices/Core"
UserName'+username+'UserName
Password'+password+'Password
ApplicationID'+appId+'pplicationID
AdminLogin
soap12:Body
soap12:Envelope';

$.ajax({
beforeSend:
function( xhr ){
// Pass the target URL onto the proxy.
xhr.setRequestHeader(
"SOAPTarget",
"http://api.fm-web.co.za/webservices/CoreWebSvc/CoreWS.asmx"
);

// Pass the action onto the proxy.
xhr.setRequestHeader(
"SOAPAction",
"http://www.omnibridge.com/SDKWebServices/Core/AdminLogin"
);
},
url: 'http://localhost/supr/soap_proxy.cfm',
type: "POST",
dataType: "xml",
data: soapMessage,
processData: false,
contentType: "text/xml; charset=\"utf-8\"",
success: function(returnedXMLResponse){
$('CoreLoginResult', returnedXMLResponse).each(function()
{
var feedback = $(this).find("Indicator").text();
alert(feedback);
});
} // End Success
});
return false;
}

************************************************

I welcome any help!

Thanks


Dec 19, 2012 at 10:22 AM // reply »
1 Comments

hi i was try your code sample but aget this error "500 Internal Server Error" , can you help me?


Dec 19, 2012 at 10:58 AM // reply »
3 Comments

By the way, if you need help testing your SOAP service, a good tool to use is SoapUI. It's written in Java and is a free download from http://www.soapui.org


Mar 22, 2013 at 4:38 AM // reply »
1 Comments

Hi, i'm trying to configure this env in my desktop but alway get the same parseerror tag... whatever SOAPTarget I try to connect... I believe its some colddusion issue i have not configured...
the errors are:

  • <parsererror xmlns="http://www.w3.org/1999/xhtml" style="display: block; white-space: pre; border: 2px solid #c77; padding: 0 1em 0 1em; margin: 1em; background-color: #fdd; color: black"><h3>This page contains the following errors:</h3><div style="font-family:monospace;font-size:12px">error on line 1 at column 29: Comment not terminated
  • &lt;!--- Get the request data.
  • error on line 2 at column 12: Comment not terminated
  • </div><h3>Below is a rendering of the page up to the first error.</h3></parsererror>

Any idea how could i solve this? may the cf proxy need something to be configured i have missed?

TY



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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
May 23, 2013 at 4:26 PM
ColdFusion QueryAppend( qOne, qTwo )
@Heather, Glad people are still getting value out of this! ... read »
May 23, 2013 at 3:49 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, I meant the code at the bottom (not the video). I did try to experiment with an intermediary variable, like: value = users.id[ i ]; arrayContains( userIDs, value ); ... but t ... read »
May 23, 2013 at 11:06 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Are you talking about As Number: YES As String: YES As Java: YES? If so, that's with 3 different ways of referencing the constant 1, not users.id[1]. Query object references(*) are what seem ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools