Earlier this week I worked with John Bliss on an interesting problem he had. Let me describe his issue, and the solution he came up, and what I learned from it.
John was trying to consume a web service. The web service demanded that a particular SOAP header be sent:
<soap:Header>
<ApiUserAuthHeader xmlns="namespace">
<UserName>xxxxx</UserName>
<Password>xxxxx</Password>
<UserAccessKey>xxxx</UserAccessKey>
</ApiUserAuthHeader>
</soap:Header>
He created the header like so:
<cfset ApiUserAuthHeader = StructNew()>
<cfset
ApiUserAuthHeader.UserAccessKey = "xxxxx">
<cfset ApiUserAuthHeader.Password =
"xxxxx">
<cfset ApiUserAuthHeader.UserName = "xxxxx">
<cfset
AddSOAPRequestHeader(MyWebservice, "namespace", "ApiUserAuthHeader",
ApiUserAuthHeader)>
So far so good, right? I've never actually used addSOAPRequestHeader, but just looking at his code it made sense to me. The result however was off:
<soapenv:Header>
<ns1:ApiUserAuthHeader xmlns:ns1="namespace">
<item
xmlns:ns2="http://xml.apache.org/xml-soap">
<key
xsi:type="xsd:string">PASSWORD</key>
<value
xsi:type="xsd:string">xxxxx</value>
</item>
<item>
<key
xsi:type="xsd:string">USERNAME</key>
<value
xsi:type="xsd:string">xxxxx</value>
</item>
<item>
<key
xsi:type="xsd:string">USERACCESSKEY</key>
<value
xsi:type="xsd:string">xxxxx</value>
</item>
</ns1:ApiUserAuthHeader>
</soapenv:Header>
If you compare this to what the API requires, you can see they don't match. The first suggestion I had was to change how he worked with the structures. When you do:
<cfset s = {}>
<cfset s.name = "Ray">
to create a structure, ColdFusion will uppercase the struct key (name), to give you: s.NAME = "Ray". I recommended he try bracket notation, essentially:
<cfset s = {}>
<cfset s["name"] = "Ray">
This didn't help either (although the resultant header did have the right case for the keys). I hashed this a bit more in my head and then I had an idea. He created the XML data pretty much the way I always did - with structure functions. Of course, ColdFusion also has native XML functions to create XML nodes/data. I never use them because the structure functions are simpler (to me!) and just plain work. In this case though switching to 'proper' XML manipulation functions worked perfectly for him:
<cfset doc = XmlNew()>
<cfset doc.ApiUserAuthHeader = XmlElemNew(ApiUserAuthHeader, "namespace", "ApiUserAuthHeader")>
<cfset doc.ApiUserAuthHeader.UserName = XmlElemNew(ApiUserAuthHeader, "UserName")>
<cfset doc.ApiUserAuthHeader.UserName.XmlText = "xxxxx">
<cfset doc.ApiUserAuthHeader.UserAccessKey = XmlElemNew(ApiUserAuthHeader, "UserAccessKey")>
<cfset doc.ApiUserAuthHeader.UserAccessKey.XmlText = "xxxxx">
<cfset doc.ApiUserAuthHeader.Password = XmlElemNew(ApiUserAuthHeader, "Password")>
<cfset doc.ApiUserAuthHeader.Password.XmlText = "xxxxx">
<cfset AddSOAPRequestHeader(MyWebservice, "namespace", "ApiUserAuthHeader", doc)>
So I guess I don't have much to add here, but I'd be curious if this type of problem has tripped up others?
Archived Comments
it'd be telling to run this through tcpmon and watch the messages going across the wire. I'd imagine that would at least tell you what's different, though probably not *why* it's different when doing it one way vs. the other.
i must admit... this is intriguing!
The documentation for addSOAPRequestHeader says that the value "can be a CFML XML value". Guess they should have written "MUST be a CFML XML value".
Regarding the soap header generated using a struct, it looks like it is trying to handle two namespaces: the one you defined in addSOAPRequestHeader and a default XML namespace "http://xml.apache.org/xml-soap".
You'd think that if you supplied a namespace in addSOAPRequestHeader, it wouldn't try to use a default XML namespace.
Could it be a CF bug?
@russ: Hmm, I don't know. But, shoot, wouldn't hurt to get the bug filed and let Adobe chew on it.
@russ @ray Bug filed.
I know this is a bit tangent, but when dealing with SOAP requests, I definitely find it much easier to manually build the XML request via CFSaveContent or CFXML. This is especially true when you start to deal with complex arguments that don't easily map (for one reason or another) to ColdFusion structures. Even though manual XML adds overhead, I found the overhead to be far easier than debugging the CF-to-SOAP implicit conversions.
Ben: I totally agree with you, building the XML manually is so much easier - but I have experienced that doing a http post with xml is actually faster than invoking the soap webservice directly. I even remember reading someone did a performance comparison and pure xml was like 4 times faster
Technically when you invoke a web service, you ARE doing a http post with XML. Are you saying that not using cfinvoke/webservice= gives you a speed increase? Are you saying you generate the WSDL for the request by hand and just cfhttp it?