Ask Ben: Streaming Binary Data From The Database (BLOB) To The User Using ColdFusion

Posted June 30, 2008 at 8:29 AM

Tags: ColdFusion, Ask Ben

Hi Ben, I must have searched our old friend Mr. Google and your site for nearly an hour without luck - so I figured I better ASK BEN. I normally retrieve binary object from a database by first writing them to the server's disk and then href-ing to that file. My question is, how do I retrieve a file directly from the database without the intermediary step of writing to the server first? Thanks in advance!

To be honest, I have never used binary storage capabilities of a database; as such, I decided to take your question and flesh it out into a full demo so that I could learn as well as teach. I knew what the answer would be, but had never implemented it. Let's take a look at the demo:


 
 
 

 
 
 
 
 

This demo is divided up into two ColdFusion pages. The first page, the form input, does not really pertain to the question asked; it merely sets up the environment for the answer (and lets me experiment with using BLOB fields for the first time). The second ColdFusion page is the one used as the IMG src attribute; this is the page that reads the binary data out of the database and streams it to the user without creating an intermediary anchor tag / href or writing the file to disk (which was the primary question).

Let's take a quick look at the form input page:

 Launch code in new window » Download code as text file »

  • <!--- Param form data. --->
  • <cfparam name="FORM.id" type="numeric" default="0" />
  • <cfparam name="FORM.name" type="string" default="" />
  • <cfparam name="FORM.hotness" type="string" default="" />
  • <cfparam name="FORM.photo" type="string" default="" />
  • <cfparam name="FORM.submitted" type="boolean" default="false" />
  •  
  •  
  • <!--- Check to see if the form as been submitted. --->
  • <cfif FORM.submitted>
  •  
  • <!---
  • Normally, this is where we would validate data, but for
  • this demo, we are not going to worry about valid data.
  • --->
  •  
  •  
  • <!---
  • Check to see if are inserting or updating data. For this
  • part, we are not going to worry about the photo - that
  • will come afterwards.
  • --->
  • <cfif FORM.id>
  •  
  • <!--- Update the record. --->
  • <cfquery name="qUpdate" datasource="#REQUEST.DSN#">
  • UPDATE
  • hottie
  • SET
  • name =
  • <cfqueryparam
  • value="#FORM.name#"
  • cfsqltype="cf_sql_varchar"
  • />,
  •  
  • hotness =
  • <cfqueryparam
  • value="#FORM.hotness#"
  • cfsqltype="cf_sql_decimal"
  • scale="1"
  • />
  • WHERE
  • id =
  • <cfqueryparam
  • value="#FORM.id#"
  • cfsqltype="cf_sql_integer"
  • />
  • </cfquery>
  •  
  • <cfelse>
  •  
  • <!--- Insert the new record. --->
  • <cfquery name="qInsert" datasource="#REQUEST.DSN#">
  • INSERT INTO hottie
  • (
  • name,
  • hotness
  • ) VALUES (
  • <cfqueryparam
  • value="#FORM.name#"
  • cfsqltype="cf_sql_varchar"
  • />,
  •  
  • <cfqueryparam
  • value="#FORM.hotness#"
  • cfsqltype="cf_sql_decimal"
  • scale="1"
  • />
  • );
  •  
  • <!--- Return the new ID. --->
  • SELECT
  • ( LAST_INSERT_ID() ) AS id
  • ;
  • </cfquery>
  •  
  • <!--- Store the new id into our form variable. --->
  • <cfset FORM.id = qInsert.id />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: At this point, we have either created or
  • updated the given record and the ID of that record
  • is stored in FORM.id.
  • --->
  •  
  •  
  • <!---
  • Now that we have created a hottie record, let's check
  • to see if we need to update photo.
  • --->
  • <cfif Len( FORM.photo )>
  •  
  • <!--- Upload the file. --->
  • <cffile
  • action="upload"
  • filefield="photo"
  • destination="#ExpandPath( './' )#"
  • nameconflict="makeunique"
  • />
  •  
  • <!--- Read in the binary data. --->
  • <cffile
  • action="readbinary"
  • file="#ExpandPath( './' )##CFFILE.ServerFile#"
  • variable="binPhoto"
  • />
  •  
  • <!--- Delete photo from server. --->
  • <cffile
  • action="delete"
  • file="#ExpandPath( './' )##CFFILE.ServerFile#"
  • />
  •  
  •  
  • <!---
  • Insert the binary data as a BLOB into the current
  • hottie record.
  • --->
  • <cfquery name="qInsert" datasource="#REQUEST.DSN#">
  • UPDATE
  • hottie
  • SET
  • photo =
  • <cfqueryparam
  • value="#binPhoto#"
  • cfsqltype="cf_sql_blob"
  • />
  • WHERE
  • id =
  • <cfqueryparam
  • value="#FORM.id#"
  • cfsqltype="cf_sql_integer"
  • />
  • </cfquery>
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Query for the selected hottie. At first, this will return
  • no records, but once the form is submitted, it will return
  • the selected hottie.
  • --->
  • <cfquery name="qHottie" datasource="#REQUEST.DSN#">
  • SELECT
  • h.id,
  • h.name,
  • h.hotness,
  •  
  • <!--- Check to see if the hottie has a photo. --->
  • (
  • CASE
  • WHEN
  • h.photo IS NULL
  • THEN
  • 0
  • ELSE
  • 1
  • END
  • ) AS has_photo
  • FROM
  • hottie h
  • WHERE
  • h.id =
  • <cfqueryparam
  • value="#FORM.id#"
  • cfsqltype="cf_sql_integer"
  • />
  • </cfquery>
  •  
  •  
  • <cfoutput>
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>ColdFusion / MySQL Blob Demo</title>
  • </head>
  • <body>
  •  
  • <h1>
  • ColdFusion / MySQL Blob Demo
  • </h1>
  •  
  •  
  • <form
  • action="#CGI.script_name#"
  • method="post"
  • enctype="multipart/form-data">
  •  
  • <!--- Send submission flag. --->
  • <input type="hidden" name="submitted" value="true" />
  •  
  • <!---
  • Send back the ID of the record we are working
  • with. This will be zero the first time.
  • --->
  • <input type="hidden" name="id" value="#FORM.id#" />
  •  
  •  
  • <p>
  • Name:<br />
  • <input
  • type="text"
  • name="name"
  • value="#FORM.name#"
  • size="20"
  • maxlength="40"
  • />
  • </p>
  •  
  • <p>
  • Hotness:<br />
  • <input
  • type="text"
  • name="hotness"
  • value="#FORM.hotness#"
  • size="10"
  • maxlength="4"
  • />
  • </p>
  •  
  • <p>
  • Photo:<br />
  • <input
  • type="file"
  • name="photo"
  • size="50"
  • />
  • </p>
  •  
  • <!--- Check to see if a photo is available. --->
  • <cfif (
  • qHottie.RecordCount AND
  • qHottie.has_photo
  • )>
  •  
  • <p>
  • <img
  • src="./photo.cfm?id=#qHottie.id#"
  • title="#HtmlEditFormat( qHottie.name )#"
  • />
  • </p>
  •  
  • </cfif>
  •  
  • <p>
  • <input type="submit" value="Submit" />
  • </p>
  •  
  • </form>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

This is a pretty basic form input and file upload page. I am not using any real data validation since this is just a demo. When the form is processed, I am either inserting or updating the hottie record, in this case, for Julia Stiles. Once this has been done, I take care of the photo upload. This step is a bit ganky since I have to store the file to server first, then read it in using a BinaryRead, then delete the file from the server, then update the hottie record using a CF_SQL_BLOB data type. I am sure that there is a cleaner way to grab the binary data right out of the ColdFusion page request, but that is a whole other blog post.

You will notice that the IMG tag of our displayed photo points to our other ColdFusion page, photo.cfc. This is where we really start to answer the user's question - how do we stream the binary data to the user. The answer is, assuming you have ColdFusion 7 or later, the CFContent tag. The CFContent tag allows you to stream binary data directly to the client:

 Launch code in new window » Download code as text file »

  • <!--- Param URL data. --->
  • <cfparam name="URL.id" type="numeric" default="0" />
  •  
  •  
  • <!--- Query for hottie record and pull back image binary. --->
  • <cfquery name="qHottie" datasource="#REQUEST.DSN#">
  • SELECT
  • h.id,
  • h.name,
  • h.photo
  • FROM
  • hottie h
  • WHERE
  • h.id =
  • <cfqueryparam
  • value="#URL.id#"
  • cfsqltype="cf_sql_integer"
  • />
  • </cfquery>
  •  
  •  
  • <!--- Set the header values. --->
  • <cfheader
  • name="content-length"
  • value="#ArrayLen( qHottie.photo )#"
  • />
  •  
  • <!---
  • Set the file name of the inline graphic to be the name
  • of the hottie record.
  • --->
  • <cfheader
  • name="content-disposition"
  • value="inline; filename=#qHottie.name#"
  • />
  •  
  • <!--- Stream the binary image data to the user. --->
  • <cfcontent
  • type="image/*"
  • variable="#qHottie.photo#"
  • />

Once you see the code, it's pretty straightforward; we are simply grabbing the BLOB data out of the database and using that as the binary data we stream via ColdFusion's CFContent Variable attribute.

Now, in our example, we are streaming to the client using an IMG src attribute; however, we could have easily let the user click on the a link and taken them directly to the photo.cfm URL for a similar effect. Right now, you are probably thinking, holy macrol! CFContent is amazing! And, you are way right. However, depending on the size of your files and the traffic on your site, you might want to be careful about using it as it does have some impact on the available resources for the server. The good thing is that several people have found that this affect is not too bad.

I have to say that I really liked using the database to store binary data rather than rely on a third party file system. All things being equal, it just seems like a better solution to asset management; why bother keeping another moving part when you could store your assets directly in the database. I know I have looked into this a bit before, but I should give it another look and see what the pros and cons of database storage are.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Jun 30, 2008 at 9:15 AM // reply »
3 Comments

Ben,

Just wanted to say I enjoy the small screencasts you have been including with your posts as of late. Help with the learning process. This post has me intrigued to try out BLOB data storage again myself. I tried years ago with not much success and haven't looked much at it since then.


Jun 30, 2008 at 9:18 AM // reply »
6,516 Comments

@C.Gratz,

Thanks man. I think they help tie the whole idea together a bit. I like to show it up front so you get a birds eye view of everything that is going on; then, when we dive into the code to get a close-up, you have a much better reference for where you are.


Jun 30, 2008 at 10:15 AM // reply »
10 Comments

I second the motion - great screencasts. It looks like you're using the Jing software to create them. How are you liking it?

Getting back to the topic of streaming binary files... if your HTTP server uses mod_rewrite or isapi_rewrite, you can use URL rewriting to pretty up the URL in the img tag.

The streamed image could appear as "1234.jpg" in your markup, but be converted to "photo.cfm?id=1234" by Apache/IIS/etc. before it gets to ColdFusion.

I got this to work on my machine without too much trouble. I have absolutely no idea why you'd ever need to do this, but it's a nifty technique to keep in mind. :)


JD
Jun 30, 2008 at 12:58 PM // reply »
3 Comments

What about caching? We were serving photos from our DB using a similar method, but decided to switch back to storing the images in web accessible directories. In my head at the time (i'll admit i tend to make assumptions without proper study), I assumed our method did not cache the image. If your image changes regularly, your method is justified, but if the image is static, I don't see how the extra traffic on the db and bandwidth is necessary. Or am I crazy?


Jun 30, 2008 at 2:40 PM // reply »
9 Comments

JD: I would usually implement caching as a reverse proxy with Apache mod_cache or Squid. Caching is complex thing and using existing code for it is usually good idea.


Jul 1, 2008 at 2:13 AM // reply »
1 Comments

Thanks man.

Excellent site.

this site is nice and very helpful.


Jul 1, 2008 at 8:46 AM // reply »
6,516 Comments

@David,

Nothing wrong with a little mod-rewrite action, even if just to create nicer looking URLs :)

@JD,

You raise a good point. I think with this kind of operation, there are always gonna be pros and cons. And, the extra Database transfer is definitely a con. There's a lot to be said about having an image as a web-accessible file that can just be handled by the server rather than ColdFusion. I assume that database-stored assets are generally done out of necessity or when you simply don't want to deal with a file system.


Jul 1, 2008 at 10:35 AM // reply »
15 Comments

Seems like another debate on the use of File System or Database for binary data storage. Choice is often dependent on use-case. But for images, I'd rather use the File system to store them in non-web accessible areas and store related-information in a database.

Caching of course can be handled at either the application server (using cfheader tag or header function for ColdFusion or PHP respectively, to set Expires response headers) or web-server level (using .htaccess for Apache to set Expires response headers)


Jul 11, 2008 at 12:15 PM // reply »
1 Comments

Hi,

I am new at this but when testing this page on my server I get the following error. Can any one help. Thanks a lot!

Error Executing Database Query.
[Macromedia][SQLServer JDBC Driver][SQLServer]'LAST_INSERT_ID' is not a recognized built-in function name.


Jul 11, 2008 at 12:19 PM // reply »
6,516 Comments

@john,

It looks like you are on a SQL Server (rather than MySQL). You would need to replace the LAST_INSERT_ID() with:

@@Identity

... to get the latest ID creation.


Jim
Sep 11, 2008 at 6:23 PM // reply »
16 Comments

Hi Ben,

A few questions.

1) Is there a reason you didn't use the result attribute, and use the IDENTITYCOL variable to grab the id?

2) Do you know if it's possible to store java classes as binary objects in the db?

Thanks,

j


Sep 12, 2008 at 9:14 AM // reply »
6,516 Comments

@Big Jim,

I believe that the IDENTITYCOL was only added in ColdFusion 8. Plus, I am just used to returning the identity manually. It's not enough of an effort for me to change to using the CF8 results column. Maybe I will move that way eventually.

As far as storing binary java classes, I have no idea idea. There very well might be. You can definitely store the physical files in the DB (just as you could on a file server) - the question becomes, is there an easy way to load the binary class file into the Java memory space. Or, would you have to write it first to a file and then use some sort of URL class loader?


Mar 2, 2009 at 6:21 PM // reply »
1 Comments

Any idea if there is a quick fix for MX6.1? Apparently it doesn't like the "Variable" attribute in the CFCONTENT tag:

Attribute validation error for tag CFCONTENT.
The tag does not allow the attribute(s) VARIABLE. The valid attribute(s) are DELETEFILE,FILE,RESET,TYPE.

The error occurred in C:\wwwroot\BlobTest\photo.cfm: line 58

56 :
57 : <!--- Stream the binary image data to the user. --->
58 : <cfcontent
59 : type="image/*"
60 : variable="#qHottie.photo#"

I tried changing it to file and adding the filename from the above cfheader tag, but no go.


Mar 2, 2009 at 6:27 PM // reply »
6,516 Comments

@Eric,

In this post, you can see how to access the underlying Binary output and then write to it directly. This concept should work in CFMX 6.1, but might need to be tweaked:

http://www.bennadel.com/index.cfm?dax=blog:1227.view

The trick is:

GetPageContext().GetResponse().GetResponse()


May 28, 2009 at 12:46 PM // reply »
1 Comments

What's the data type that should be used in MSSQL to store the blob? binary(50)?


Jun 1, 2009 at 1:34 PM // reply »
6,516 Comments

@azapper,

I haven't tested this, but I'd go with VARBINARY so you can have variable-length binary data.


Jun 2, 2009 at 1:10 PM // reply »
2 Comments

Any idea how to modify this to work with large blobs? By "large", I mean blobs too big to be pulled from the table in a single read. (I'm not exactly sure if the size limitation is ODBC, JDBC or ColdFusion, but there is a limit)

I'm using similar code to download premade PDF's and some can be much larger than the average web graphic -- some greater than 1Mb.


Jun 2, 2009 at 1:23 PM // reply »
6,516 Comments

@Steve,

For that, you can do one of two things:

1. Make multiple BLOB reads and create a server-local file based on all of them. Then, when you build up the file, stream that to the client.

2. Make multiple BLOB reads and write them to the underlying binary stream being sent to the client:

http://www.bennadel.com/blog/1227-Using-ColdFusion-To-Stream-Files-To-The-Client-Without-Loading-The-Entire-File-Into-Memory.htm

Hope that helps.


Jun 4, 2009 at 2:56 PM // reply »
2 Comments

Thank you VERY much. That is exactly what I was looking for (actually more, I did not know about the preload/RAM limitation of CFContent).


Jun 4, 2009 at 3:00 PM // reply »
6,516 Comments

@Steve,

No problem my man. Unless you are loading huge files, though, I wouldn't worry too much about the RAM limitations of CFContent.


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 21, 2009 at 6:47 PM
Hal Helms - Real World Object Oriented Development, Sarasota - Day Five
@charlie griefer, Thank you.. ... read »
Nov 21, 2009 at 5:15 PM
Using ColdFusion Structures To Remove Duplicate List Values
@Jose Galdamez, Oh heh yeah I didn't paste the whole code. I should have defined the vars -- my bad. It's fixed thou. Thanks. ... read »
Nov 21, 2009 at 4:49 PM
Styling The ColdFusion 8 WriteToBrowser CFImage Output
Great work yet again Ben! Whilst I didn't use this whole code, I copied some of your regex code for a similar problem with the lack of an alt attribute and unescaped ampersands in CFIMAGE for Railo 3 ... read »
Nov 21, 2009 at 1:13 PM
My First ColdFusion Builder Extension - Encrypting And Decrypting CFM / CFC Files
@Ben, Because I am pedantic, I just want to make sure that everyone knows there is absolutely no encryption going on. There is only encoding and obfuscation. The cfencode tool only obfuscates your C ... read »
Nov 21, 2009 at 12:28 PM
Using ColdFusion Structures To Remove Duplicate List Values
@Jody I can't seem to get your code sample to work. If you are still having problems, try this code out and see if it gets you what you wanted. <!--- Comma delimited list with various duplicates ... read »
Nov 21, 2009 at 11:03 AM
Groovy Operator Overloading Does Not Work In The ColdFusion Context
Hi Ben, Thanks for this informative post. Now I am reading ur old posts too ... read »
Nov 21, 2009 at 10:56 AM
HostMySite.com Has The Best ColdFusion Hosting
@Mehul, Yes very nice people, however several downtimes per day which was not acceptable. Hence we had to move out. I am glad you are having good luck with them so far. ... read »