Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with:

Posting XML SOAP Requests With jQuery

By Ben Nadel on

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.

Tweet This Deep thoughts by @BenNadel - Posting XML SOAP Requests With jQuery Thanks my man — you rock the party that rocks the body!



Reader 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

Reply to this Comment

@Jason,

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

Reply to this Comment

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

Reply to this Comment

@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).

Reply to this Comment

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!

Reply to this Comment

@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.

Reply to this Comment

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()

Reply to this Comment

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 );

Reply to this Comment

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?

Reply to this Comment

@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?

Reply to this Comment

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.

Reply to this Comment

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?

Reply to this Comment

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.

Reply to this Comment

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);
?>

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

Yeh , thanks for the post.Very helpful.
I am new and very appreciate to see this.
Thanks

Reply to this Comment

Ben Nadel

when i click ,parse error is calling . i have just copied the full data from this page and also save the jquery1.4.1 ...... why this is occuring

Reply to this Comment

Ben Nadel

when i click getinfo,parse error is calling . i have just copied the full data from this page and also save the jquery1.4.1 ...... why this is occuring

Reply to this Comment

Hi,I'm student and I'm trying to make same but I'm using json not xml... someone have a example or tutorial that help me..please...

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.