Throwing And Catching A File Using CFHttp For Both Actions
I can't remember where I heard this, but not so long ago I heard or read about someone who wanted to send a file to another server using CFHttp. He didn't have any FTP stuff set up, so that was they best idea he had. This seemed like a fun little piece of functionality to test as I have never done that before.
I set up two pages: cfhttp_throw.cfm and cfhttp_catch.cfm. As you can probably guess, the cfhttp_throw.cfm takes a file from the local file system and "posts" it to the cfhttp_catch.cfm. The cfhttp_catch.cfm file then "uploads" it to its server and echoes back the file name as it was stored on the new machine.
This was suprisingly easy. I have not used CFHttp all that extensively and in about 10 minutes I worked out something that did just what I wanted to do. And, in the process, I used a CFHttpParam tag of type "File" which I had never done before. I have to go back and check out the rest of the CFHttpParam types to see what else I am missing (damn you ignorance!).
Here is the cfhttp_throw.cfm code. It doesn't have all the CFTry / CFCatch tags that probably should be used, but for this proof of concept, it was not necessary:
<!---
Submit the file the CFHTTP_Catch.cfm file. Notice that we
are sending the file via a CFHttpParam FILE tag. By using
this file param, CFHTTP automatically sends all form
fields as multi-part form data; therefore, we do NOT need
to specify the "multipart='true'" CFHttp attribute.
--->
<cfhttp
url="http://swoop/..../cfhttp_catch.cfm"
method="POST"
useragent="Mozilla/5.0 Gecko/20070309 Firefox/2.0.0.3"
result="objHTTP">
<!---
Send along a file via the FORM post. This acts the same
as a stanard form Input type="file" field and can be
handled as such on the "Catch" page.
--->
<cfhttpparam
type="FILE"
name="file"
file="#ExpandPath( './test.jpg' )#"
/>
</cfhttp>
<cfoutput>
<h4>
CFHttp Post Result:
</h4>
<p>
#objHTTP.FileContent#
</p>
</cfoutput>
And, here is the cfhttp_catch.cfm code:
<!--- Kill extra output. --->
<cfsilent>
<!---
Param the form fields. Since this data is coming via
a CFHTTP "Post" we can operate as if it was a standard
form submission.
--->
<cfparam
name="FORM.file"
type="string"
default=""
/>
<!--- Try to upload the file. --->
<cftry>
<!---
Upload the file. When defining the CFFile, we can
treat the posted file as if it was submitted via a
standard File Input (since it was posted as a FILE
using CFHttpParam).
--->
<cffile
action="UPLOAD"
filefield="file"
destination="#ExpandPath( './files/' )#"
nameconflict="MAKEUNIQUE"
/>
<!---
Echo back the name of the file as it has been
saved to the server.
--->
<cfcontent
type="text/plain"
variable="#ToBinary( ToBase64( CFFILE.ServerFile ) )#"
/>
<!---
Catch any errors that were thrown during the
file upload.
--->
<cfcatch>
<!--- Echo back the error message. --->
<cfcontent
type="text/plain"
variable="#ToBinary( ToBase64( CFCATCH.Message ) )#"
/>
</cfcatch>
</cftry>
</cfsilent>
That was pretty cool. I like that using the proper CFHttp / CFHttpParam tags, your target page can act as if the request was coming from a standard Form-action page with standard FORM-scoped values. I think it's totally awesome that CFFile can actually upload the file that is posted to it. Very cool.
Want to use code from this post? Check out the license.
Reader Comments
What is the deal with this?
#ToBinary( ToBase64( CFFILE.ServerFile ) )#
CFFILE.* should all be strings. So why not just output as is?
@Ray,
Cause I am a sick person .... the Variable attribute of CFContent only takes binary data and won't allow string data unless you convert it to binary first (hence the ToBinary stuff). Sure, I could just output it after the CFSilent, but I don't think that looks as nice.... Like I said, I am sick (in the head) and code formatting is that special to me :)
Plus, I just really like using CFContent. Feels so good.
Hah, that is insane.
What fun would "sane" be ;)
Hey Ben,
I've been playing around with drag-n-drop uploads using jQuery and XMLHttpRequest Level 2. Check out: http://onehub.com/blog/posts/designing-an-html5-drag-drop-file-uploader-using-sinatra-and-jquery-part-1
Basically it's a PUT method and not sure if this relates with exactly what you are doing here, but it seems to be somewhat the same.
I can get the PUT to work great, but I'm having problems with the "catcher" file, since obviously this is not using traditional form fields. Have you had any experience playing around with this yet?
@Ben
I think I answered my own question.
Looks like you can grab the XHR data using ColdFusions "GetHttpRequestData()" function. So to capture the file and save you would do something like this:
<cfset xhr_data = GetHttpRequestData() />
<cffile action="write" file="#ExpandPath('.')#/#CGI.HTTP_X_FILENAME#" output="#xhr_data.content#">
p.s. this doesn't work so well on older browsers.
@Doug,
I've played around very briefly with the XMLHttpRequest 2 API; from the little that I've read up on it, you should be able to actually create a multi-part form request that acts juts like a native request. I think this is the blog post that I learned from:
http://blog.igstan.ro/2009/01/pure-javascript-file-upload.html
Of course, this only works in like FireFox 3.6 which is like the most current few releases.
@Ben,
Cool, I'll check it out.
The one I linked to above works in everything except IE. I've gotten it to work with the "PUT" method, but I'm struggling a little bit with the normal "POST" fallback option.
I'm sure I'll figure it out. Thanks.
@Doug,
If you have your stuff working in everything but IE, disregard my link. I believe the example in the link I sent you *only* works in the latest Firefox releases. Sounds like you are on a better track than me.
This is a good post! This is about sending a file with a http post request from Server A to Server B. I have a question about doing something slightly different.
what if server A wants to download a file from server B via an http request. so in essennce an http download. Is it something to do with what you set in cffilecontent response type? How nice it would be if there was a working example somewhere.
Thanks!
I guess i figured it out. I would need this on the httpResponse
<cfheader name="Content-Disposition" value="inline; filename=testing.doc">
<cfcontent type="application/msword" file="#fileName#" deletefile="no">
and something like this on the requester file.
<cfscript>
ioOutput = CreateObject("java","java.io.FileOutputStream");
ioOutput.init("C:\...\testing.doc");
objHTTP.Filecontent.writeTo(ioOutput);
ioOutput.close();
</cfscript>
Nice post!