Earlier this week, I was building a gateway for the Amazon Simple Storage Service (S3) API. As part of my testing, I tried to read an image in from Amazon S3 and then write it to the browser using the CFImage tag [action=writeToBrowser]. And, while this worked last week, this week I was getting the following ColdFusion error:
The cfimage tag accepts only those ColdFusion variables that contain Base64 strings, BLOBs, Byte arrays or other images as inputs.
I wanted to see if this was an issue specific to Amazon S3; or, if there was some problem with my code. And so, I tried to isolate the problem down to a simple CFHTTP request to my local server:
<!--- Build our "Test" image url on the local server. ---> <cfset imageUrl = ( "http://" & cgi.server_name & getDirectoryFromPath( cgi.script_name ) & "monkey.jpg" ) /> <!--- Request the file content of the image. ---> <cfhttp result="get" method="get" url="#imageUrl#" /> <!--- Write the image to the browser. ---> <cfimage action="writetobrowser" source="#get.fileContent#" />
Here, I'm just requesting a JPG image from the same folder (over HTTP) and then attempting to output it. And since the HTTP request is reading a "binary" object, the CFImage tag should work. However, I get the same ColdFusion error as above. So, it has nothing to do with the Amazon S3 API.
When I went to CFDump the HTTP response, I noticed that my fileContent variable, which should have been a ByteArray (ie. a binary value) was, in fact, an instance of the Java object:
When I saw this, I realized what I was missing in my earlier code snippet - the GetAsBinary attribute on the CFHTTP tag. When you omit this attribute, ColdFusion attempts to convert the HTTP response into an "appropriate" data type. And, as Adam Cameron pointed out a few months ago, ColdFusion doesn't always get this right.
To fix my problem, I added the GetAsBinary="yes" attribute to the CFHTTP tag such that my response would always be a byte array, even if the remote resource was a text value. Since ColdFusion can convert binary data into string data, this seems like an easy way to normalize the CFHTTP response content format.
To see this in action, the following code makes a request for a "remote" XML file and reads it in as a binary object. Then, I'm going to convert that binary to a string, parse it into an XML document, and extract one of the XML nodes:
<!--- Build our XML request url on the local server. ---> <cfset dataUrl = ( "http://" & cgi.server_name & getDirectoryFromPath( cgi.script_name ) & "data.xml" ) /> <!--- Request the "remote" XML file as a binary value. ---> <cfhttp result="get" method="get" url="#dataUrl#" getasbinary="yes" /> <!--- Parse the response into an XML document. Notice that I have to tell ColdFusion to convert the binary value to a string. ---> <cfset response = xmlParse( charsetEncode( get.fileContent, "utf-8" ) ) /> <!--- Extract the response message node. ---> <cfset message = xmlSearch( response, "string( //Message/text() )" ) /> <cfoutput> Message: #message#<br /> </cfoutput>
And, when I run the above code, I get the following XML node output:
Message: Woot, I am XML!
This may seem like overkill - always returning CFHTTP file content as a byte array. But, if you're dealing with a remote API that returns various types of content, this is an easy way to ensure that you're always parsing a response with a known format.
thank you ben for sharing your knowledgeable thoughts.
My pleasure :)