Posting XML With ColdFusion, CFHttp, And CFHttpParam
Posted June 1, 2007 at 4:27 PM by Ben Nadel
I use ColdFusion and CFHttp all the time to grab data from other pages, but it's very rare that I ever post something. In the past, I have fooled around with posting files and cookie parameters, but not much exploration has gone on. But now, I am going to be building an API for a client project that is going to accept XML data strings. I am also going to have a hand in building the applications that make requests to this API.
So, I thought this was a good time to experiment with posting XML with ColdFusion and CFHttp. The first thing I did was look at ColdFusion's CFHttpParam tag because I know that it has a lot of available types. As it turns out, CFHttpParam has a type, XML. When you select this type, ColdFusion identifies the request as having a content-type of text/xml and uses the given XML as the body of the HTTP request. How useful is that?!?
If XML was not available as a CFHttpParam type, it would be ok. Without it, we would have to use one CFHttpParam of type BODY and another to define the mime type as text/xml, but this would accomplish the same thing.
If you are used to dealing with requests all the time (from a browser), but these really only ever deal with the URL and FORM scopes it might not be obvious as to what a request "body" even is or where it can be found. ColdFusion provides the function GetHttpRequestData() which makes the request headers and content available in a nice structure. If you put this code into a new ColdFusion template and run it:
- label="Get Http Request Data"
... you will get something that looks like:
| || || |
| || |
| || || |
As you can see, all the request data is now easily accessible. We are going to be posting our XML data as the request body which will be available in the Content attribute of the above structure (currently showing an empty string).
Ok, to explore this, I set up a demo with two pages: one page creates a CFHttp request and the other page receives it and returns a confirmation value. Simple stuff, mostly proof of concept. Here is the page that posts the XML data using CFHttp:
- <!--- Get the URL. --->
- <cfset strURL = (
- CGI.server_name &
- GetDirectoryFromPath( CGI.script_name ) &
- ) />
- <!--- Create the XML data to post. --->
- <cfsavecontent variable="strXML">
- <name>Girls Gone Wild Vol. 13</name>
- Post the XML data to catch page. We are going
- to post this value as an XML object will actually
- just post it as an XML body.
- When posting the xml data, remember to trim
- the value so that it is valid XML.
- <!--- Output the return message. --->
- Message: #objGet.FileContent#
The XML posted by the above page is being caught and processed by this page:
- All data that is posted to this page will now
- be part of the HTTP Request data structure. Get
- a reference to this structure.
- <cfset objRequest = GetHttpRequestData() />
- Our XML data will be the content of the request.
- Let's grab it out of the request structure. When
- we do this, trim the value to help create a
- valid XML string.
- <cfset strXML = objRequest.Content.Trim() />
- At this point, we may or may not have a valid XML
- string. Before we try to do any parsing, let's
- validate it.
- <cfif IsXml( strXML )>
- Now that we know the request was of valid
- format, we can parse the XML document.
- <cfset xmlData = XmlParse( strXML ) />
- For this demo, we are going to validate the
- request ONLY for Credit / Debit.
- <cfset xmlType = XmlSearch(
- ) />
- Check to see if we found a Type node as a child
- to the transaction root node.
- <cfif ArrayLen( xmlType )>
- We found the type - this request has been
- validated. Now, we just need to return a
- confirmation message.
- <cfset strResponse = ("#xmlType[ 1 ].XmlText# approved") />
- <!--- We did not find a valid node. --->
- <cfset strResponse = "ERROR2: Missing type node" />
- The passed in data was not a valid XML string.
- Store our error into our response message.
- <cfset strResponse = "ERROR1: Invalid request (Bad XML)" />
- ASSERT: At this point, no matter what happend (if the
- request was valid or invalid) we will have a message
- in our response string.
- <!--- Return response string. --->
- variable="#ToBinary( ToBase64( strResponse ))#"
For the purposes of this demo, we are doing very little validation on the XML data as this is not a true API. If we run the above pages, we get the following return value:
Message: Debit approved
The process worked quite nicely. Now, posting XML data is related to a SOAP request. SOAP, from what I can understand, is standard for formatting XML requests. If you go that route, ColdFusion provides many SOAP-related convenience functions that are worth looking into.
What Other People Are Searching For
Hi, I have found the information in your tutorial great for an integration I am doing with the Chase paymentech orbital gateway, the only problem I have is that I am not able to send the correct MIME headers (which are mandatory) along with the XML, I have tried sending them using a CFHTTPparam of type Header, also of type Mimetype, but I am getting different errors, any information that can point me in the right direction would be greatly appreciated.
This is the code I am using:
<?xml version="1.0" encoding="UTF-8"?>
<cfhttpparam type="XML" value="#XMLpacket#">
And this is are Mime headers I need to send:
Interface-Version: Test 1.4
Mime information is only for the body of the request. The rest of those should just be standard HEADER values. Have you tried send them with CFHttpParam using that type?
Let me know if that helps at all?
very useful breakdown. One suggestion. Please always include the VERSION of ColdFusion you are using. I'm guessing this is MX 7 or above. Unfortunately I'm stuck implementing the Chase gateway with a CF version 5 server. I may have to tell the client to switch hosts...
Yeah, I am on ColdFusion MX 7.
Hi Ben thank you for your reply, I changed the code but I am receiving the same response which is a "20400 invalid request" error message (I guess I am not sending correctly the header information), this is what I tried:
<cfhttpparam type="HEADER" name="POST /AUTHORIZE HTTP/1.0" value=""/>
<cfhttpparam type="HEADER" name="MIME-Version" value="1.0"/>
<cfhttpparam type="HEADER" name="Content-type" value="application/PTI41"/>
<cfhttpparam type="HEADER" name="Content-length" value="#len(xmlentry)#"/>
<cfhttpparam type="HEADER" name="Content-transfer-encoding" value="text"/>
<cfhttpparam type="HEADER" name="Request-number" value="1"/>
<cfhttpparam type="HEADER" name="Document-type" value="Request"/>
<cfhttpparam type="HEADER" name="Merchant-id" value="173361"/>
<cfhttpparam type="Body" value="#XMLentry#"/>
Appreciating your attention
I have never seen this error before. After some brief Googling, it looks like it has to do with character encodings. Take a look at this page; it looks like it would help you out:
Looks like you might have to add CharSet to your encoding tag.
I am also trying to setup a transaction attempt with Chase Paymantech. They sure don't make things easy! I would try switching your cfhttp call to the following:
This is working for me so far, in that I'm not getting the error that you reported.
I'm also stuck with Coldfusion 5 and it appears you can't manipulate header information with CFHTTPPARAM. That is according to this post on the PayPal developer community site. http://www.pdncommunity.com/pdn/board/message?board.id=payflow&thread.id=938
If anyone knows a work around please share.
Hi folks, after many unsuccessful attempts trying to integrate the Orbital gateway with Coldfusion MX using XML, we gave a try to a COM integration using Paymentech's SDK which was really easy, so no more cfhttpparam headaches from this side.
seems to work just fine with paypal reporting api. thanks for the tip
Here is a ColdFusion solution that works with Paymenttech Orbital:
<cfheader name="POST /AUTHORIZE HTTP/1.1">
<!--- Create the XML data to post. --->
<?xml version="1.0" encoding="UTF-8"?>
<Comments>Sample Auth-Capture Transaction</Comments>
<cfhttp url="https://orbitalvar1.paymentech.net/authorize" method="post" throwonerror="yes" port="443" result="objGet">
<cfhttpparam type="BODY" value="#strXML#">
<cfhttpparam type="header" name="MIME-Version" value="1.0">
<cfhttpparam type="header" name="Content-transfer-encoding" value="text">
<cfhttpparam type="header" name="Request-number" value="1">
<cfhttpparam type="header" name="Document-type" value="Request">
<cfhttpparam type="header" name="Accept" value="application/xml">
<cfhttpparam type="header" name="Content-Type" value="application/PTI43">
<cfhttpparam type="header" name="Content-length" value="#len(strXML)#"/>
<!--- Output the return message. --->
Message: <cfdump var="#objGet#">
i m using paymentech sdk for integration of credit card functionality ,i don't know how to sent the production url (https://orbitalvar1.paymentech.net/authorize ) using the paymentech transaction object and we are using asp.net with vb need a sample code for Authorize and AuthorizeCapture .
Great post. Was a good help when trying to solve a problem with a RESTful style web service.
Have you ever done this with passing an attachment to a web service?
Absolutely. Check out this post for a file example:
I was able to create a form and submit a file to an action page:
<cfinvoke component="com_moveInvoiceFile" method="import" returnvariable="Contact_id">
<cfinvokeargument name="fileField" value="FORM.fileField">
<cfcomponent output="false" style="rpc">
<cffunction name="import" access="remote" output="false" returntype="any">
<cfargument name="fileField" required="true">
<cffile action="upload" filefield="#arguments.fileField#" destination="#application.rootdir#uploadedFiles\" nameconflict="makeunique">
However when I run the test page (below) I get nothing. I was trying cffile readbinary and putting the results into a cfhttpparam type=body", but no luck so far....
<cfhttp url="http://webdev.bos1.vrtx.com/invoiceGateway/com_moveInvoiceFile.cfc" method="post">
<cfhttpparam type="file" name="fileField" file="#expandpath('./checklist.js')#" />
When passing in the form field name, don't pass in the "FORM." prefix:
<cfinvokeargument name="fileField" value="fileField">
That way, when you use the CFHttpParam tag, you will end up passing in "fileField", not "FORM.fileField".
Thanks, but I was trying to remove the action page from the scenario and submit directly to the web service.
I am not sure going directly to the web service would offer you any benefit. You'd have to point the ACTION of the FORM to someone else's site at which you would lose control of the session.
Looks like your blog below may help me. Thanks.
You still need a processing page to convert the binary image into Base64 format data. I don't think you are gonna be able to get around a server-side processing page (plus, I don't think you'll gain anything by bypassing an action page).
I'm being told we need a CF web service that will some day be replaced with a service bus. I've been trying to tell them that whoever is using the service bus will probably have to change their code when we switch from CF so why not start with an action page and switch to web services when the bus is here, but they never listen :) If there are web services out there that accept binary file attachments can't we do it with CF?
I'm not sure if web services can take binary data. I guess, if they were set up to handle form-like submissions, they could do it. I guess you could also post the binary content as the request body... but its gotta be complicated to get that to work from the browser.
Yes, using the request body was what I thought. I'm pushing back on the web services so we'll see what happens next week. Thanks again.
No problem my man. Let us know what you come up with!
I'm trying to post XML to a site for verification, but it doesn't appear to make it in XML format. Not sure if I need to add a header entry or not, any help appreciated. Please note this site is using CF MX6.1.
<CFXML VARIABLE="MyXml"><CLient Version="8.2">
<Header Account_Id="1234" />
<Header Operator_Id="7201" />
<cfhttp url="someurl.cfm" method="POST" useragent="#CGI.http_user_agent#">
<cfhttpparam type="XML" value="#trim(MyXml)#" />
and someurl has
<cfset objRequest = GetHttpRequestData() />
<cfset strXML = objRequest.Content />
<cfset xmlData = XmlParse( strXML )>
but this just displays
wwwwwwwwwwwwwwwwwwwwww coldfusion.xml.XmlNodeList@7e7407 xxxxxxxxxxxxxxxxxxxxxx coldfusion.xml.XmlNodeList@7e7407 yyyyyyyyyyyyyyyyyyyyyy
rather than XML structure. What am I doing wrong?
It doesn't look like you're doing anything wrong. Perhaps its an error on the other end (the web service you are posting to).
Nice XMl introduction.
As a CF guru, could you please enlighten me with some web-service security implementations on CF?
I'm trying to consume a web-service with x.509 certificate security.
It requires: my x.509 certificate, signature, timestamp signature, body signature, timestamp (all this in SOAP header).
I have made a cfSaveContent for SOAP header (with all security signatures included).
Before calling the web-service I can attach the SOAP header (the one i generated in cfsavecontent) to the web-service call with addSOAPRequestHeader function.
addSOAPRequestHeader(SomeWebService, "someURL", "SomeName", MySOAPHeader, false);
I have no idea how to change SOAP body properties in CF.
If I just try and call the web-service:
CF generates a SOAP body itself with no properties (meaning there's no body Id - realtion between SOAP body and body signature in the SOAP header).
Web-Service responds "SOAP Body must be signed".
I want my SOAP body to be presented to a web-service like this (with and Id property):
<SOAP-ENV:Body xmlns:SOAP-ENV="http://" xmlns:wsu="http://" wsu:Id="body">
Is there a way to set SOAP body properties (like Id) in coldfusion?
Im new to CF so please feel free to point out my wrongs.
Like Dmitrii, I am also wondering if you know of any way we can manually set SOAP body contents in CF8.
I'm not sure how this will help until someone can more specifically answer the most recent questions, but whenever I work with web services - almost everyday it seems recently - I've found the best tool to use is SoapUI (free) - soapui.org. Build and test your requests/responses in the most raw form in this tool and then rebuild them in ColdFusion - using <cfinvoke> to call a CFC that makes the "real" call using <cfhttp> (copy-n-paste the XML from soapui).
Thanks for the tip. Seems like CFHTTP is the only way to do it. Turns out I had to use soapUI and craft my own SOAP requests anyways due to CF's poor struct conversion for complex web services.
It seems that if the WSDL defines an operation that takes an abstract base type as an argument, ColdFusion doesn't know to try to convert the struct to a concrete (child) type, and instead just tries to create the base Java (Axis) object which of course doesn't work. If this is incorrect I would love to know more.
I don't see any improvement in CF9 either which is unfortunate. Forcing me to choose to revert to Java or work with SOAP requests manually is not cool.
Anyways, Ben the example above to use CFHTTP for my problem is great thanks for the help.
I feel your frustration. I've just become accustomed to working with most web services this way. And, to be honest, I feel more comfortable with it when I compare what I go through to my coworkers trying the same in .NET. They rely SO heavily on the software to do all the work - I guess I look at this like I look at Code vs Design Mode in Dreamweaver. I like working in the raw xml (soap) code as it re-assures me that my applications are solid.
Not that that might serve as any consilation, but you're not alone (for sure).
I do what @Derek says - I build the XML manually using something like CFSaveContent of CFXML and then manually post is using CFHTTPParam / XML as the body of the request.
Honestly, I very rarely use CFInvoke or CreateObject() to hit a SOAP-based web service. I almost always create my own XML as this is much easier to debug and there are zero surprises as far as data types go. Plus, I have often times found problems with SOAP documentation by creating XML based off the WSDL file (which cannot lie ;)).
Ben, I think I love you.
This was the answer to something I've been looking into all day. You're always right on the money my duuuude!
Awesome my man! Always nice to hear that. I hope you got whatever you were working on done.
Trying to use this logic to get to Google's spell checker as listed in http://forums.devx.com/showthread.php?t=166324
using the following, it looks like I am connecting, but nothing is coming back:
<spellrequest textalreadyclipped="0" ignoredups="0" ignoredigits="1" ignoreallcaps="1">
<text>Ths is a tst</text>
<!--- Output the return message. --->
<cfset xmlData = XmlParse(#google.FileContent#) /><br>
What am I doing wrong?
@bkw: at a glance your XML is invalid. The <spellrequest> tag needs to have an end tag or self close i.e. you should have a </spellrequest> after the </text> or it should self close i.e. <spellrequest ... />. Depends on the documentation.
Can you provide more detail on the error message.
I am not sure what your error is? What is going wrong?
@Matthew is correct, though - your XML is not valid.
hi buddy, i tested it in coldfusion 8, in local host... there was an error
"Unable to determine MIME type of file"
and that is what i have to add a line to this
and is the port of my cf in localhost.
That might be a compression issue (if your localhost uses GZip); or, it could be a problem with SSL, but I don't think 8500 is a typical SSL port. Try adding the following CFHTTPParam to the request:
That might help.
Its a great tutorial. But I got stuck in this. I am on CF8. While executing the code, I received below output: -
Error1: Invalid request (Bad XML)
As the XML is Valid. I have also changed the XML and test the XML in different code, XML was working fine, but once again in this code snippet I got the same output. I am expecting the first <cfif> block should execute.
Are you saying the isXml() returns True, but the xmlParse() throws an error?
isXML() is returning false in this code, surprisingly when I tried the same xml in another file isXML() returns true.
I have also tried with other xml, but the flow of code is same.
Might be CFMX8 Developer version does not support the posting the XML. Not sure. :(
Very strange! I can't think of why XML would fail in one case and not in another. Sorry :(
Ben, thank you very much this is a wonderfull post.
I have a little problem with Greek characters.
On the senders end the request is going correctly with the correct characters.
On the receiving end the Greek characters get scrabbled.
I must admit that internationalization has always been a bit of an Achilles heel for me. Do you have the UTF charset set in the CFHTTP tag?
<cfhttp charset="utf-8" .... />
That might be necessary for the encoding.
Thanks for the response.
I have the charset UTF-8, also on the receiving end i have the following processing directive
This is really weird since in simple scenarios, wehere we simply display Greek characters, it works properly with the processing directive above.
Any other ideas?
Hmmm, I am not sure. If you are getting some of this out of a database, you might need to check the character encoding on the table as well. If not, I am out of ideas - like I said, internationalization is definitely some tricky stuff.
Thanks for the help.
I will look into it further.
If i find a solution i will be post it here for everyone that may encounter the same issue.
Ok cool, good luck and let us know what you come up with.
I'm just learning how to write CF web services; I've consumed them before, but never got into the low level minutia. Anyway, I added a dump of the getHttpRequestData() like you described above to my web service but I'm not seeing any data -- the "content" is always empty. Here is my web service function:
<cffunction access="remote" name="test" output="no" returntype="string">
<cfargument name="name" type="string" required="no" default="">
<cfset var result = 0 />
label="Get Http Request Data"
<cfdump var="#cgi#" label="cgi" output="c:\temp\soapapi.txt" />
<cfdump var="#form#" label="form" output="c:\temp\soapapi.txt" />
<h3>My First Web Service</h3>
<p>This is it! Impressive, ey?</p>
Here is my tester page that calls the above via cfinvoke:
<cfinvokeargument name="name" value="Mr. Bond"/>
</cfsilent><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<h1>And the result is:</h1>
And finally, here is a sample of the dumped output:
Get Http Request Data - struct
content: [empty string]
Accept: application/soap+xml, application/dime, multipart/related, text/*
Content-Type: text/xml; charset=utf-8
cgi - struct
AUTH_PASSWORD: [empty string]
AUTH_TYPE: [empty string]
AUTH_USER: [empty string]
CERT_COOKIE: [empty string]
CERT_FLAGS: [empty string]
CERT_ISSUER: [empty string]
CERT_KEYSIZE: [empty string]
CERT_SECRETKEYSIZE: [empty string]
CERT_SERIALNUMBER: [empty string]
CERT_SERVER_ISSUER: [empty string]
CERT_SERVER_SUBJECT: [empty string]
CERT_SUBJECT: [empty string]
CF_TEMPLATE_PATH: [masked, just because] \i4go4openTravel\pub\webServices\api.cfc
CONTENT_TYPE: text/xml; charset=utf-8
CONTEXT_PATH: [empty string]
HTTPS_KEYSIZE: [empty string]
HTTPS_SECRETKEYSIZE: [empty string]
HTTPS_SERVER_ISSUER: [empty string]
HTTPS_SERVER_SUBJECT: [empty string]
HTTP_ACCEPT: application/soap+xml, application/dime, multipart/related, text/*
HTTP_ACCEPT_ENCODING: [empty string]
HTTP_ACCEPT_LANGUAGE: [empty string]
HTTP_CONNECTION: [empty string]
HTTP_COOKIE: [empty string]
HTTP_REFERER: [empty string]
PATH_TRANSLATED: [masked, just because]\i4go4openTravel\pub\webServices\api.cfc
QUERY_STRING: [empty string]
REMOTE_USER: [empty string]
WEB_SERVER_API: [empty string]
The interesting part is that Content-Length has a value of 438 yet content is empty. What am I missing? Thank you in advance...
Oops, I forgot to mention that my goal is to see and have access to the soap wrapper that appears to be stripped off by the time my function is called.