Not so long ago, I created my first web service. I figured it was time to see how the other half live (those that invoke web services, not create them). To test it out, I decided to invoke one of Amazon.com's web services. To start out, I had to register to use their eCommerce web service. Then, I had to dig through many many links to actually find some documentation on how to use this stuff. The whole Amazon Web Service (AWS) section is a bit confusing to someone who has never used it before. It is hard to differentiate between examples and pre-built solutions and documentation.
Finally, I figured it out, and worked out this small example to search for a book with the title "Muscle: Confessions of an Unlikely BodyBuilder." This happens to be an amazing book and for anyone who loves to workout, this is a must-read!
But anyway, onto the code:
<!--- Use the REST web service that uses traditional URL invokation. In this methodology, the arguments for the web service are sent as standard, encoded URL arguments (essentially query string arguments). ---> <cfhttp url="http://webservices.amazon.com/onca/xml" method="GET" result="REQUEST.WSResponse"> <!--- Desired Amazon web service. ---> <cfhttpparam type="URL" name="Service" value="AWSECommerceService" /> <!--- Authorization ID for use of Amazon Web Services. ---> <cfhttpparam type="URL" name="AWSAccessKeyId" value="xxxxxxxxxxxxxxxxxxxx" /> <!--- Action to take on this web service. In this case, we are going to be searching the product catalog. ---> <cfhttpparam type="URL" name="Operation" value="ItemSearch" /> <!--- Searching must be done within a macro-category. In this case, we are searching for books. ---> <cfhttpparam type="URL" name="SearchIndex" value="Books" /> <!--- Search for books of this title. ---> <cfhttpparam type="URL" name="Title" value="Muscle confessions bodybuilder" /> </cfhttp> <!--- Check to see if the request hit a valid page and that the generated reponse is a valid XML document. ---> <cfif ( Find( 200, REQUEST.WSResponse.StatusCode ) AND IsXml( REQUEST.WSResponse.FileContent ) )> <!--- We have gotten a valid response back from the web service. Before we start to work with it, let's strip out all XML name spaces. This will make my XPath search queries much easier (HEY! I don't know that much about XPath so get off my back). We are stripping out any instance of something like: xmlns="...." This should create simple XML nodes. ---> <cfset strXmlData = REQUEST.WSResponse.FileContent.ReplaceAll( "\s*xmlns=""[^""]*""", "" ) /> <!--- Now that we have cleaned the XML data, let' parse it into a ColdFusion XML document structure. ---> <cfset xmlResults = XmlParse( strXmlData ) /> <!--- Get the item nodes. ---> <cfset arrItems = XmlSearch( xmlResults, "//Item" ) /> <!--- Get the error nodes. This will contain any problems that occurred with the web service invokation. ---> <cfset arrErrors = XmlSearch( xmlResults, "//Error" ) /> <!--- Check to see if we have any errors. ---> <cfif ArrayLen( arrErrors )> <!--- Something has gone wrong with the web service. Output these errors. ---> <cfloop index="intError" from="1" to="#ArrayLen( arrErrors )#" step="1"> <h4> #arrErrors[ intError ].Code.XmlText# </h4> <p> #arrErrors[ intError ].Message.XmlText# </p> </cfloop> <cfelse> <!--- The web service request went off without a problem. Now, all we have to do is list out the results. ---> <table cellspacing="2" cellpadding="3" border="1"> <!--- Loop over items. ---> <cfloop index="intItem" from="1" to="#ArrayLen( arrItems )#" step="1"> <!--- Get a reference to this item. ---> <cfset xmlItem = arrItems[ intItem ] /> <tr> <td colspan="2"> <strong> #xmlItem.ItemAttributes.Title.XmlText# </strong> </td> </tr> <tr> <td> Author </td> <td> #xmlItem.ItemAttributes.Author.XmlText# </td> </tr> <tr> <td> ASIN </td> <td> <a href="#xmlItem.DetailPageURL.XmlText#" target="_blank" >#xmlItem.ASIN.XmlText#</a> </td> </tr> </cfloop> </table> </cfif> <cfelse> <!--- Something went wrong with the web service request. Either the server returned a non-200 response code or the generated content was not in XML format. Output the data for debugging. ---> <p> <em>There was a problem invoking the web service.</em> </p> <code> #HtmlEditFormat( REQUEST.WSResponse.FileContent )# </code> </cfif>
This was my first introduction to REST-style web service invocation. I had seen the phrase REST thrown around here and there, but never really understood what it meant. After doing some quick Googling, it looks like REST is basically a no-methodology-methodology???? Basically, it uses standard HTTP to invoke web services. The XML response is just returned as part of the generated file content. Sorry if this is grossly incorrect... like I said, this was my first time trying to even look it up.
So, as you can see, I invoke the web service using ColdFusion's CFHttp tag. Each argument to the web service is passed as a URL CFHttpParameter. You could just as easily put these values as part of the query string in the URL attribute of the CFHttp tag, but this way creates a much nicer structure that will be easier to manipulate programmatically.
The rest of it is just being able to traverse XML documents and perform some simple XPath xml searches. One thing that I do that I would like to touch upon is the removing of all name space attributes from the XML nodes:
<cfset strXmlData = REQUEST.WSResponse.FileContent.ReplaceAll( "\s*xmlns=""[^""]*""", "" ) />
I do this because I am not very good at using XPath and ColdFusion's XmlSearch() method. The name space arguments seem to mess up the XPath search "//Item". However, if I go through and use a regular expression replace to strip out all xml node attributes that define a name space, the XPath works quite nicely. So, admittedly, this is a bit of a hack (I am altering the returned data), but it's the only way I know how to get it working. Plus, I couldn't care less about name spaces. I am not even sure yet what they are used for.
That's all there is to it. Quite easy. In fact, I found it was harder to find the web service definition than it was to eventually invoke it. And a final note: Please do not look at any of this as a BEST practice. This was my first time ever invoking a web service, so it's all trial and error.
Want to use code from this post? Check out the license.