Making SOAP Web Service Requests With ColdFusion And CFHTTP

Posted January 11, 2010 at 9:59 AM

Tags: ColdFusion

I get a large number of people asking me how to make SOAP web service requests using ColdFusion features like CFInvoke and CreateObject( "webservice" ). Typically, these methods works just as advertised, encapsulating SOAP requests (and responses) in a clean, easy to use ColdFusion wrapper. Of course, that's not the cases that these people are asking me about; typically, people want to know how to get CFInvoke or CreateObject() to work with a particularly complicated API.

There's no doubt that ColdFusion has some excellent (if only slightly outdated) SOAP functionality. But when things get too complicated, I find it's often easier to execute and maintain SOAP requests when you drop down into the raw XML and manual HTTP post. This kind of SOAP handling gives you complete control over all aspects of both the request and the response and leaves no mystery as to how data types should be translated.

To demonstrate this, let me walk through a SOAP request to Campaign Monitor's newsletter subscription API. Given any WSDL file, you should be able to figure out (with some determination) what kind of SOAP request a particular method call is expecting. Depending on the complexity of the given WSDL file, this could be a simple task or a total nightmare. Luckily, most professional APIs provide solid documentation complete with sample XML request and response values. The following screenshot is taken directly off of the Campaign Monitor API url and is typical (in nature) of the kind of documentation I have found with many API providers:

 
 
 
 
 
 
Typical SOAP API Documentation With Sample XML Requests And Responses. 
 
 
 

As you can see, the first XML document is a sample of the SOAP request you need to make; the second XML document is a sample of the SOAP response you will get. In addition to the XML, the API documentation also defines the SOAPAction that must be included with the request - take note of this as it will become a required Header value in our CFHTTP post.

Once we have this documentation in hand, all we have to do is formulate our own XML document and post it to the API using ColdFusion's CFHTTP and CFHTTPParam tags:

 Launch code in new window » Download code as text file »

  • <!---
  • We are going to subscribe to Campaing Monitor using the
  • AddAndResubscribe actions. This is a SOAP-based method that
  • requires the following XML body.
  • --->
  • <cfsavecontent variable="soapBody">
  • <cfoutput>
  •  
  • <?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>
  •  
  • <Subscriber.AddAndResubscribe
  • xmlns="http://api.createsend.com/api/">
  •  
  • <ApiKey>#campaignMonitorKey#</ApiKey>
  • <ListID>#campaignMonitorList#</ListID>
  • <Email>kim@sweet-n-sassy.com</Email>
  • <Name></Name>
  •  
  • </Subscriber.AddAndResubscribe>
  •  
  • </soap:Body>
  •  
  • </soap:Envelope>
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Now that we have our SOAP body defined, we need to post it as
  • a SOAP request to the Campaign Monitor website. Notice that
  • when I POST the SOAP request, I am NOT required to append the
  • "WSDL" flag to the target URL (this is only required when you
  • actually want to get the web service definition).
  • --->
  • <cfhttp
  • url="http://api.createsend.com/api/api.asmx"
  • method="post"
  • result="httpResponse">
  •  
  • <!---
  • Most SOAP action require some sort of SOAP Action header
  • to be used.
  • --->
  • <cfhttpparam
  • type="header"
  • name="SOAPAction"
  • value="http://api.createsend.com/api/Subscriber.AddAndResubscribe"
  • />
  •  
  • <!---
  • I typically use this header because CHTTP cannot handle
  • GZIP encoding. This "no-compression" directive tells the
  • server not to pass back GZIPed content.
  • --->
  • <cfhttpparam
  • type="header"
  • name="accept-encoding"
  • value="no-compression"
  • />
  •  
  • <!---
  • When posting the SOAP body, I use the CFHTTPParam type of
  • XML. This does two things: it posts the XML as a the BODY
  • and sets the mime-type to be XML.
  •  
  • NOTE: Be sure to Trim() your XML since XML data cannot be
  • parsed with leading whitespace.
  • --->
  • <cfhttpparam
  • type="xml"
  • value="#trim( soapBody )#"
  • />
  •  
  • </cfhttp>
  •  
  •  
  • <!---
  • When the HTTP response comes back, our SOAP response will be
  • in the FileContent atribute. SOAP always returns valid XML,
  • even if there was an error (assuming the error was NOT in the
  • communication, but rather in the data).
  • --->
  • <cfif find( "200", httpResponse.statusCode )>
  •  
  • <!--- Parse the XML SOAP response. --->
  • <cfset soapResponse = xmlParse( httpResponse.fileContent ) />
  •  
  • <!---
  • Query for the response nodes using XPath. Because the
  • SOAP XML document has name spaces, querying the document
  • becomes a little funky. Rather than accessing the node
  • name directly, we have to use its local-name().
  • --->
  • <cfset responseNodes = xmlSearch(
  • soapResponse,
  • "//*[ local-name() = 'Subscriber.AddAndResubscribeResult' ]"
  • ) />
  •  
  • <!---
  • Once we have the response node, we can use our typical
  • ColdFusion struct-style XML node access.
  • --->
  • <cfoutput>
  •  
  • Code: #responseNodes[ 1 ].Code.xmlText#
  • <br />
  • Success: #responseNodes[ 1 ].Message.xmlText#
  •  
  • </cfoutput>
  •  
  • </cfif>

Here, we are defining our SOAP request in the content buffer, soapBody. This XML variable is then posted to the Campaign Monitor API using CFHTTP. Notice that when I post to the API, I am not using the "WSDL" URL flag; this is only needed when we actually want to retrieve the web service definition (as is typically required by the ColdFusion SOAP wrappers). Since we are posting the raw XML, no additional web service definition is required.

The SOAPAction value that I mentioned before is now being included as a Header value using ColdFusion's CFHTTPParam tag. If you look at the previous screenshot of the API sample, you will notice that the SOAPAction value is surrounded by double quotes. While this is not required for the Campaign Monitor API, I am pretty sure that I remember running into a few situations where adding the quotes to the SOAPAction was critical:

 Launch code in new window » Download code as text file »

  • <cfhttpparam type="header" name="SOAPAction" value="""....""" />

Notice the extra, escaped double quotes in the Value attribute.

Once we have posted the SOAP XML, we need to handle the SOAP response. Assuming that the response was formed well (valid XML) and that the target server was available, your SOAP request should always come back with a 200 response code, regardless of its execution success; any errors within the API request will be defined in the result nodes of the response. While I am not 100% sure that all SOAP APIs act this way, I have never found one to deviate.

When the SOAP response comes back, it should look something like this:

 
 
 
 
 
 
A SOAP Response As Returned From CFHTTP. 
 
 
 

As a final caveat, because the SOAP XML response has name-spaced nodes, querying the document becomes a bit more complicated; rather than using standard node names in your XPATH, you have to query for "*" (any node) and check its local-name(). I don't care for this approach, but it seems to be the easiest way to deal with name spaces.

When we run the above code, we get the following output:

Code: 0
Success: Success

As you can see, we were able to post the SOAP XML and parse its response without any problems.

ColdFusion provides some really great SOAP functionality; API wrappers like CFInvoke and CreateObject( "webservice" ) allow for seamless integration of SOAP web service requests into your ColdFusion code. When APIs get more complicated, however, these wrappers start to break down. Luckily, in times like that, ColdFusion also makes it easy for us to drop down into the raw XML and make manual SOAP HTTP requests. And, if you encapsulate all of this into a ColdFusion component, you end up, once again, with a nice API wrapper.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Other Searches  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Jan 11, 2010 at 6:26 PM // reply »
10 Comments

I've found this often only way to invoke "complex" web services. They don't really have to be that complex - typically .NET ones just don't work well with Axis that is doing heavy lifting in ColdFusion.

In my opinion easiest way to figure out required XML is to use SoapUI (http://www.soapui.org/) to make a call to the web service and route request trough TCP Monitor (part of Axis and as such already installed with ColdFusion).

Here's shortcut that I usually use to start TCP Monitor (I've downloaded latest version of Axis)

C:\jdk1.5.0_08\bin\java.exe -cp "C:\Program Files\axis-1_4\lib\axis.jar" org.apache.axis.utils.tcpmon 12345

Hope this helps.

Tero


Jan 11, 2010 at 6:57 PM // reply »
7,572 Comments

@Tero,

I agree, regarding the low complexity of an API that necessitates XML. Generally, I only use XML. I almost never even check to see if the ColdFusion wrappers work; to me, the XML just seems more straightforward.

I'll have to check out the SoapUI stuff.


Jan 28, 2010 at 3:33 PM // reply »
5 Comments

Do you take a similar approach for publishing (as opposed to calling) web services in CF? I've pretty much come to the same conclusion that you have-- CF lets you customize a lot of things, but more often then not, not quite enough to get things actually working. We have a running joke around here that SOAP stands for SOrry Ass Protocol.


Jan 28, 2010 at 9:56 PM // reply »
7,572 Comments

@Arthur,

If you want to publish SOAP web services in ColdFusion, it is just about the easiest thing every. All you have to do is create a CFC with "remote" access methods (it's an attribute on the CFFunction tag). That will automatically make the cfc/method a SOAP web service; CF will create the stub files for you and provide the WSDL files at request with the "?wsdl" flag.


Jan 29, 2010 at 10:49 AM // reply »
5 Comments

@Ben Nadel,

Yes, that is exactly how I'm doing it. My point is that it's very difficult to customize things when you do it at that way, just like when doing an outgoing SOAP call. For this particular web service, I'm trying to conform to an incoming SOAP call (from a .NET platform) that was already pre-established, it's 95% working, but just hard to get one little part of it working... I have the urge to just go in tweak the SOAP xml going over the wire, but there is no easy way to do that. I saw your other related post where you are doing just that for an image watermark-- interesting stuff.


Jan 29, 2010 at 11:06 PM // reply »
7,572 Comments

@Arthur,

If you are working with an existing request format, you might want to just expose a CFM file as a remote web service (there's nothing technically different from a standard web page). Basically, you're gonna create a page that returns XML content rather than HTML content. The tricky thing there is that you have to manually form a SOAP XML response packet. Of course, since that is a standard, you can look it up for examples.

That's sucks though, that you have to conform to someone else's request given that *you* are the one exposing the web service :)


Feb 15, 2010 at 7:08 AM // reply »
2 Comments

I'm currently trying to access a web service using the method outlined above, the service uses HTTP compression so is causing problems.

I'm including the http param:

<cfhttpparam
type="header"
name="accept-encoding"
value="no-compression"
/>

but I'm still not having any luck. When I look at the headers that are being sent (by posting to a script on my own server and looking at the CGI vars) I can see CF is adding its own list to the header.

HTTP_ACCEPT_ENCODING=no-compression, deflate, gzip, x-gzip, compress, x-compress

I presume the web service I am accessing are looking for the presence of gzip, as opposed to the presence of no-compression.

Any ideas how I can get CF to stop adding a list of encodings it doesn't support?

Thanks
Dave


Feb 19, 2010 at 10:02 AM // reply »
6 Comments

I'm having a similar problem to Arthur - Our webservice is basically a listener there to receive results back from a 3rd Party supplier

I successfully created the calls & dealt with the responses to create the order using the code you supplied but the response is just an acknowledgement that the order has been received - The actual results come back asynch - Hence the listener

If I send the response XML from my test harness - the code works exactly as I'm expecting - It falls over when it hits a db call but thats fine as it's only test data - It does however read and parse the XML correctly however when it's sent directly from the supplier using their C# setup it falls over at the point where I'm trying to do a read (to dump out a copy of the response to the local file system before processing) on the XML document that's been sent

We don't receive any errors in our exception log but the calling service gets an error

ErrorMessage=Unable to send API Result to EPSL:
faultstring=coldfusion.xml.rpc.CFCInvocationException:
[coldfusion.runtime.CfJspPage$ArrayBoundException : The element at position 1 cannot be
found.];detail=*coldfusion.xml.rpc.CFCInvocationException:
[coldfusion.runtime.CfJspPage$ArrayBoundException : The element at position 1 cannot be found.]
at
coldfusion.xml.rpc.CFComponentSkeleton.__createCFCInvocationExceptio
n(CFComponentSkeleton.java:723)
at
coldfusion.xml.rpc.CFComponentSkeleton.__invoke(CFComponentSkeleto
n.java:670)
at
sol.SolServiceListener.SolReceiveResult(........

This is a very weird error and is the only thing standing in our way to getting the whole thing working - we've tested the rest of the system - just as usual a weird integration problem!


Feb 22, 2010 at 8:30 PM // reply »
7,572 Comments

@David,

Hmmm, I was not aware that ColdFusion was adding additional headers on top of the accept-encoding that you were sending. I am not sure I have any better advice. What kind of error are you getting? Typically, in my experience, the GZIP encoding issue returns a Failure to Connect or some sort of mime-type issue. Is that what you were getting?

@Jo,

Is the calling server a ColdFusion server? Or just the one hosting the web service? This error is clearly ColdFusion-based, so I was hoping that might help us narrow it down.


Feb 23, 2010 at 4:37 AM // reply »
2 Comments

That's right Ben, it was the failure to connect issue.

I resolved it in the end by purchasing the CFXHTTP5 tag - that allowed me to control exactly which headers were being sent.


Feb 23, 2010 at 4:50 AM // reply »
6 Comments

Hi Ben,

The calling server is a C# one sending the XML in a SOAP envelope - My webservice at the other end working as the listener is Coldfusion - We've done a bit more investigation and found that the underlying object that Coldfusion uses for the XML is a org.apache.xerces.dom.DeferredDocumentImpl -
This works fine when I use my test harness (which is my local dev Coldfusion server) but when the remote system sends the SOAP envelope - Coldfusion doesn't seem to be able to read it correctly and returns a seemingly null object

I've tried making use of the underlying java methods but they return null and I've tried getting the toString() of the object and parsing that but that is empty as well

The only real clue I have that might help is the WSDL supplied to us by the supplier

In my Coldfusion generated wsdl - the object is being defined as

<element name="SOL_Result" type="apachesoap:Document"/>

whereas in the suppliers original wsdl - it is defined as

<xs:element name="SOL_Result" type="tns:TSOL_Result" nillable="true" minOccurs="1" maxOccurs="1" />

I don't know how to deal with this object difference - It's obviously significant - I don't know (because I've only just asked the question)whether they send the result out as an XML string or as this pre-defined object

I'm going to run WSDL2Java and get a copy of the equivalent Java TSOL_Result object and see if that makes any difference


Feb 23, 2010 at 8:53 AM // reply »
6 Comments

I've actually now managed to get something working with the supplier WSDL - the service accepts calls to it - However the object created from the SOAP envelope sent is still empty - I'm not sure Coldfusion actually likes the suppliers WSDL as it has alot of complex type definitions in it

I'm completely at a standstill as how to progress with this - It is usually just so easy with Coldfusion but when it doesn't work boy doesn't it work big time!


Feb 23, 2010 at 9:23 PM // reply »
7,572 Comments

@David,

I wish I knew more about what was going wrong; in the past, the accept encoding trick has always worked for me. Good to know that at least something was able to fix it on your end.

@Jo,

That's super irritating. With SOAP, I typically create the requests via CFSaveContent and CFHTTP. However, I am not sure that I've ever actually manually created a SOAP response; as such, letting ColdFusion handle that would be the easiest approach. And, when that's not working, that's super frustrating. I wish I had a better suggestion (other than to add to the complaining).


Feb 24, 2010 at 11:54 AM // reply »
5 Comments

@Jo,
I eventually got mine working by writing a "proxy" in coldfusion that forwards to the real SOAP service (since CF doesn't let you expose a lot of the guts of it.) That way I can monkey with the XML any way I please, on the way in and out. It was a pain, but ultimately I got everything working by doing that...


Feb 24, 2010 at 12:02 PM // reply »
6 Comments

Thanks Arthur - Thats more or less the conclusion I'd come to - I'm currently writing in Java natively which I'm actually rather enjoying because my background is Java

My colleague found an interesting article about the possible problem - It's to do with needing to flatten the WSDL so that Coldfusion and other languages can deal with the stuff generated out of WCF

http://weblogs.thinktecture.com/cweyer/2007/05/improving-wcf-interoperability-flattening-your-wsdl.html

What happened to Webservices & XML being the definitive method of communication across disparate systems!


Mar 11, 2010 at 8:49 AM // reply »
4 Comments

cfhttp response is located in it's FileContent property ????
You can't imagine how many times I had to work with cfinvoke and it's complex responses and their weird classes deserialization because I did not know this !!!

Thank you Ben!


Mar 11, 2010 at 8:55 AM // reply »
7,572 Comments

@Dmitry,

No problem my man. You should be able to just parse the SOAP response into XML and be able to access all the nodes that way. The only thing you have to be careful of is the namespaces which require you to use some funky xmlSearch() XPath (local-name()).


Mar 11, 2010 at 9:00 AM // reply »
6 Comments

I finally got to the bottom of my problem - the fact was that the WSDL was full of complex types that Axis didn't like - I should have been able to receive it as a struct but that just didn't work so it had to stay as an XML input

So what we ended up having to do and its blindingly obvious once you know! Is the following

<cfset soapreq = GetSOAPRequest()>
<cfset soapResponse=XmlParse(soapreq)>

This can then be accessed using the XPath expression
<cfset responseNodes = xmlSearch( soapResponse,"//*[ local-name()='SOL_Result']" )/>

And off you go! Days of scratching your head already solved by a little used Coldfusion tag!


Mar 11, 2010 at 9:14 AM // reply »
6 Comments

Just need to add - we still did have to get the external service to clean up their WSDL and flatten it as per the article I posted before


Mar 19, 2010 at 9:07 AM // reply »
7,572 Comments

@Jo,

Glad you got it working. I've never had a lot of luck using the SOAP methods (getSOAPRequest(), getSOAPResponse(), isSOAPRequest(), etc.) in ColdFUsion. I just never seem to want to use them in the right place. It's like you can only use them IF you are already using SOAP wrappers. But, it seems like they would be most useful when you are not using SOAP already... maybe I just don't really understand what they are supposed to be used for.


Mar 19, 2010 at 9:47 AM // reply »
4 Comments

@Ben,

Using soap is pretty simple when working with Java objects:

<cfset objServerXMLHttp.open("POST", "https://www.somehost.com/ReceiveXML.asp", False) />
<cfset objServerXMLHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8") />
<cfset objServerXMLHttp.setRequestHeader("SOAPAction", "https://www.somehost.com/ReceiveXML.asp") />
<cfset objServerXMLHttp.send("#myXMLrequest#") />
<cfset objDOM = objServerXMLHttp.responseXML />
<cfset lv_xml = xmlparse(objDom.xml) />


Mar 19, 2010 at 10:05 AM // reply »
7,572 Comments

@Dmitry,

I'll have to take a look at that. Typically, I just use CFHTTP, but I check out more Java-based approaches.


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 19, 2010 at 7:26 PM
MySQL 3/4 - com.mysql.jdbc.Driver And allowMultiQueries=true
Thank you very much for this post. Adding allowMultiQueries="true" in context.xml didn't help until I added it to url as allowMultiQueries=true Good idea is to use prepared statements and it will he ... read »
Jim
Mar 19, 2010 at 4:49 PM
Nobody Puts Baby In The Corner!
Wow. This is like suddenly finding a support group for your secret shame. I'm not alone! I always liked this movie, even though it is extremely cheesy. I just wish Jennifer Grey hadn't gotten the ... read »
Mar 19, 2010 at 4:47 PM
Application.cfc OnRequest() Method Affects OnError() Arguments
@Jason and @Ben, I've been doing some CF9 refactoring on our systems and noticed an odd occurrence with onError as well. Found a way to work around my problem, but what I saw was... Background: Our ... read »
Jim
Mar 19, 2010 at 4:44 PM
Shoot 'Em Up Starring Clive Owen And Paul Giamatti
I actually enjoyed this movie quite a lot. It was different, certainly, but I think they were going for more of a Quentin Tarentino-"wow, that was weird"-vibe than an actual spoof. Once I realize ... read »
Mar 19, 2010 at 4:34 PM
An Intensive Exploration Of jQuery With Ben Nadel (Video Presentation)
Hey I guess the video is down. Is there anyway you can upload to youtube or vimeo or some other service? Greatly appreciated. ... read »
Mar 19, 2010 at 4:24 PM
ColdFusion CFPOP - My First Look
@Ben Thanks for the follow up! The root of the problem had to do with being able to trace bounced emails to specific records in a DB table. Let's say you run an email campaign and you get 1,000 bou ... read »
Mar 19, 2010 at 4:15 PM
SQL COUNT( NULLIF( .. ) ) Is Totally Awesome
Thank you Ben and Tony! Either of these work for the summary report I am working on and the info is much appreciated! I think I like Tony's a little better because I won't have to educate every ... read »
Mar 19, 2010 at 3:35 PM
ColdFusion Path Usage And Manipulation Overview
@Ben, Sorry. Clarification. expandpath worked for me in application.cfc, but not in other templates. ... read »