Using ColdFusion's CFLocation Tag For Inline Image SRC Attributes
Posted May 17, 2007 at 2:50 PM
Not so long ago, I discovered that you could use ColdFusion's CFLocation tag in conjunction with inline image tag SRC attributes. I actually found this out by accident. Before hand, I would have never assumed that this would work. The idea here is that the SRC attribute points to a proxy page which then CFLocates the request on to the actual image file. How does this work? No idea... as far as I am concerned, it's just some crazy voodoo browser request magic.
To help explain what I am talking about, take a look at this HTML page:
Launch code in new window » Download code as text file »
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html>
- <head>
- <title>ColdFusion CFLocation And Image Soures</title>
- <style type="text/css">
- body {
- font: 12px georgia ;
- }
- </style>
- </head>
- <body>
-
- <p>
- Hey dude, you gotta check out the photo of
- this chick that I found:
- </p>
-
- <p>
- <!--- NOTE: Image proxy. --->
- <img
- src="image_proxy.cfm?id=1"
- width="316"
- height="217"
- border="0"
- />
- </p>
-
- <p>
- She's a bit thick, but I think it goes to
- show that woman of all shapes and sizes can
- be smokin' hot!
- </p>
-
- </body>
- </html>
Notice that the SRC attribute of the IMG tag points to image_proxy.cfm ColdFusion template, not to an actual, physical image file. Now, if the image_proxy.cfm template used CFContent to return an image binary, that would make sense to me; but, as you will see, the image_proxy.cfm template simply forwards the request to the image file:
Launch code in new window » Download code as text file »
- <!--- Kill extra output. --->
- <cfsilent>
-
- <!--- Param the image ID that will be coming in. --->
- <cfparam
- name="URL.id"
- type="string"
- default=""
- />
-
-
- <!---
- Check the ID to see if this is a valid photo ID
- request. If it is, we are going to set the target
- URL so that we only need ONE CFLocation tag.
- --->
- <cfswitch expression="#URL.id#">
- <cfcase value="1">
- <cfset strURL = "thick_chick_still_a_hottie.jpg" />
- </cfcase>
- <cfdefaultcase>
- <cfset strURL = "image_not_found.gif" />
- </cfdefaultcase>
- </cfswitch>
-
-
- <!---
- ASSERT: At this point, even if we were given an
- invalid image ID, we are going to have a target URL
- thanks to the CFDefaultCase tag.
- --->
-
- <!--- Forward image request to actual image file. --->
- <cflocation
- url="#strURL#"
- addtoken="false"
- />
-
- </cfsilent>
Again, notice that ColdFusion is not really dealing with the images directly; it's just forwarding the browser's request on to the image. Now, the ColdFusion CFLocation tag actually sends a header value back to the browser which means that the IMG tag loads a page, which returns a header, which then forwards the request to the image.
Not sure how that works, but it does:
| | | | ||
| | ![]() | | ||
| | | |
Pretty cool, eh? This could be really cool if you don't have a fancy image database or anything, but you don't want the image location fixed. You could set up a proxy that then knows where the image directory is and can forward appropriately.
Download Code Snippet ZIP File
Post Comment | Ask Ben | Other Searches | Print Page
Newer Post
Ask Ben: Grabbing World Of Warcraft Data With ColdFusion And CFHTTP
Older Post
ColdFusion 8 Release Date Contest!!!
Reader Comments
Not much mystery here - HTTP is a very simple protocol - a GET is a GET, whether it's triggered by typing a URL, or a IMG SRC attribute - the browser fetches it in exactly the same way, and as long as the final result to the browser is something it expects, it will work fine.
This is a really handy tip though - even though it makes perfect sense that it works, it hadn't ever occurred to me to try this...
@Doug,
I understand that the HTTP request protocol is very simple. I guess what makes it hard for me (mental visualization) is that when you request a page, no problem, it the primary request... but then on the page it has to load various other things like SCRIPT tags and IMG tags... those are like sub-requests that can then interact with the returned headers.
I guess I can't think of it as a single page request - it's really multiple data requests that the browser then merges (ie. putting in image content into a different request data set). Just seems funky to think about. I get it, just seems funky.
Glad you find it a good tip though.
One particular situation I use this mechanism for quite frequently is generating and caching dynamic content. To follow the image theme, if you want to generate thumbnails for your photo gallery, you point your thumbnails at tn.cfm?id=1234, which then gets the full-size image, shrinks it, writes it to cache storage, and redirects to the cache file. Subsequent requests can check for existence of the cache file, and if it's already there, just redirect directly.
I actually use mod_rewrite to do that (which saves the extra HTTP roundtrip), but the concept is the same. Managing the cache is a snap too, just delete the cache file and it'll be regenerated on next request. If you name you cache files intelligently, you can create very granular caching mechanisms for dynamic content, but (if you're using mod_rewrite) serve it in a 100% static way, without involving the app server in any way.
@Barney,
Good suggestion. I like the idea of the thumbnail generation (but more so, the cache idea in general). Thanks. Caching is something that I don't do enough of (my poor server!).
@Ben
Oh, I understand - it's not necessarily intuitive, but when you look as http log files it's pretty clear that, under the hood, all the requests are really the same, and that it's the browser that molds it all together into the proper context.
The only reason I am underscoring the point is this is something that a lot of people seem to not quite "get", and I've just seen a couple of examples of it in the last few days.
Ben, there's another use: track visualizations in e-mails.
You can use a image inside the e-mail, via <img src=""> pointing to a ColdFusion page, passing URL parameters to identify which user openned and correctly visualized the e-mail.
BTW, your blog became obligatory nowadays!
Best wishes,
Fernando S. Trevisan
Fernando, et. all
I recently wrote an email tracker as part of a CRM system using this trick not too long ago. Unfortunately, Outlook doesn't automatically download the image so tracking only occurs when the image is downloaded. Do you know of anyway to force the image to download, so tracking work regardless..??
Many (most?) email clients don't automatically download images for exactly this reason. It's a rather devious mechanism from the perspective of non-techie users, and as such, should be frowned upon. Therefore, clients disable it by default.
If you need to include images in emails, much better to inline the images. Makes the email bigger, and removes the ability to [sneakily] track reads, but it's a trusted method for getting image content in front of users.
Ben, what's the advantage of using the cflocation over the cfcontent? The code is slightly neater, but apart from that I don't see any difference in how the image is served.
Alan,
As far as I know, there's no way to track which user received the e-mail, even when using "request read receipt", user has the right to answer yes or no. As I said in my comment, this is a way to track which user correctly visualized your e-mail; if you send it as a full image, the only way user can see the contents is downloading the image and then... :)
Barney,
I agree with you, but there's a lot of good reasons to use this type of verification. In a intranet, you can send reports to users and then scheduled warnings for those who doesn't correctly visualized it. And it's just a dummy example.
@Fernando,
Firstly, thanks for the kind words. I am glad you appreciate the stuff that I do. Secondly, I think what you are referring to actually has a name. I think it's called an "email bug", but that may have been something someone just told me in passing.
I heard somewhere that while email clients do not automatically download images, more often, they will download external style sheet links. I assume you could do the same thing with the URL and tracking.
@Duncan,
The advantage of using CFLocation over CFContent is one of speed. If you use CFContent, then ColdFusion has to set aside a thread that takes care of reading in the binary and streaming it to the client. While it can do this, it is SLOOOOOW. If you use CFLocation instead, then IIS (in my case) takes care of streaming the file to the browser and it is much faster.
The downside is that CFLocation must use web-accessible URLs where as CFContent can use private directory access. But there are work arounds for that.
@Fernando
I experimented with using a full image in an email, this wasn't to track anything, the clients design was just so crazy i didn't want to slice it. One problem i found though while testing was that it often went into my spam folder, especially in gmail, Outlook and Hotmail. Do you know how to get around that?
-Si
I've used to insert text relative to the e-mail, but with the background color and 1px size. But it was a long time ago, I don't know if this still work.
I've been pondering the same question too. Why does some email go into Junk folders while other does not.
I'd also be interested in anyone's thoughts on this. Is it a ratio of text to images? An unsubscribe line?, how the images are handled within an email? Simpicity of the email, or complexity? I've lightly experimented with this a little today. But if anyone has some knowldege on this, it'd be desirable to know from a best practice sort of thing.
Alan,
There are a pile of rules that are used. Best bet is to get access to an email account that has visibility into it's spam filtering. SpamAssassin is one that I've used, and it'll send you a nice report about each message that it tagged as spam, with a breakdown of what factors influenced it's decision. High HTML-to-text ratio is one factor that I recall being a significant detriment to a given message's spam score.
Does anyone have an actual example of how to implement an email bug.
I have tried this but it doesn't seem to work in terms of updated the DB to let me know that the user has viewed the email I have sent. Using outlook 2007.
Firstly:
I include the img in the email being sent:
<img src="#Application.http_host#track.cfm?track_id="123">
Secondly::
In the track.cfm I have the following code
<cfset transGif = "#GetDirectoryFromPath(ExpandPath('*.*'))#images\blank.gif">
<cfparam name="url.track_id" default="0">
<CFQUERY name="qUpdate" datasource="#Application.dsn#" username="#Application.usr#" password="#Application.psw#">
UPDATE mytable
SET has_viewed = 1
WHERE uuid = <CFQUERYPARAM cfsqltype="CF_SQL_VARCHAR" value="#url.track_id#">
</cfquery>
<cfcontent type="image/gif" file="#transGif#">
Problem:
When I open the email I just sent to myself in outlook it doesn't update the db column has_viewed.
Any help would be greatly appreciated..
Thanks
O
Oliver, if the code you posted is exactly the one you`re using, maybe the problem is with the line:
<img src="#Application.http_host#track.cfm?track_id="123">
You may want to use it this way:
<img src="#Application.http_host#track.cfm?track_id=123">
Please note that I removed the second " before the number, that must "close" the src info and what the CF page would get was:
[yoursite]track.cfm?track_id=
As you have a param setting the track_id to 0 the script would not throw an error and also would not update the field as it would be always 0.
Just a thought.
Fernando,
What a rookie mistake I have made.. Thanks soo much.
Removing the param setting did the trick.
I actually pasted the line incorrectly. I had the formatting correct...
Thanks again.
O
@Oliver,
Also keep in mind that this will only work IF the email client allows the image to be loaded (from what I have read). A lot of clients prevent this from happening by default (only with approval from user).
That said, I have used 3rd party email campaign software (Campaign Monitor) that uses this technique and it appears that a LOT of people do actually load images in their email, so I guess this really does work.
Ben,
Great article.
I am building a mass email application and have been working on this notification issue for awhile. Outlook and many other applications to not load images so it has posed quite an issue for me trying to determine who opened the email.
Do you think it would work if I embeded a bogus CSS tag in the email? I create a dynamic css file from settings pulled from a database using something like this embedded in my pages.
<link href="../styles/styles.cfm?id=23" rel="stylesheet" type="text/css" />
Do you think outlook would download and execute the css line even if it blocks images?
SO I got so dang excited about my own question I just had to blow off my important work today and test it out. I did not want Ben to have all of the fun with this one.
Ok, this is pretty freaking cool. I tested out calling a .cfm file in the stylesheet line from the email I sent out and it does execute the code even without downloading the pictures in outlook.
Just when you think this cannot get any cooler it inserts when I view it in the preview window or when I double click and open the full screen.
If you already viewed the email in Outlook you do not get another insert after that even if you get out of outlook and get back in.
For some reason good or bad Outlook does seem to cache that email so when I click on it in to view it in the preview window then click another email and go back to the original email it does not insert a new row.
Here is what I have tested so far sending test emails from my new email application.
The 'yes; means I got a row inserted into the database the 'no' means no matter what I did I got zilch....
1. Sent directly to outlook - yes
2. Forward from outlook to gmail - no
3. Send directly to gmail - yes
4. Forward from Gmail to Outlook - no - and it does not matter if you download the pitures or not before you send it.
In each case it only inserted a row the first time the email was accesed. Kind of wierd, maybe it was because IE cached it in Gmail, I have no idea. To make it even more wierd if I get out of IE and login to Gmail through Mozilla it does not insert a new row when I view that email again.
I suspect most email clients will behave the same way and at least execute it once.
Who woulda thought... At this early stage of testing it seems to accomplish what I needed which was to determine who the heck opened the email. I could care less at this point if they view it 1 or 100 times I just need to know who opened it that one time.
Thoughts anyone?
Kevin
Whoo Hooo this also inserts a row into the DB when I forward this test email to my Motorola Droid phone and open it. So this proves that you can cheat the system a bit.
So I think I am on to something here.....
Kevin
@Kevin,
Really awesome findings! I'll need to try it for myself (just to see it in action). Thanks a lot for posting this.
So after further trials it looks like outlook web access may strip the stylesheet tag out but the Outlook 2007 application does not.
Wierd and a little deflating. Maybe there is another way to fool it.
I really hate testing.
Kevin
@Kevin,
Test is bad... testing across different CLIENTS (web, OS, brands) is even worse; especially when the various clients need logins. I don't want to create a login to HotMail *just* to test this feature :(
Actually it worked on the following web clients
1. Outlook 2003 - 2007
2. Gmail - should also work on hotmail
3. Yahoo
4. Smarter Mail Web Client
5. Android Mail Client
The only browser based email client I have come across that has stripped it out so far is OWA.
I sent test emails to someone I know with msn ,aol and thunderbird, Blackberry and IPhone to see if it works.
That has to cover about 80% or more of the email clients out there right?
Kevin
Ok so I created a test yahoo and hotmail account and cleared my test email database.
Test emails to both did not generate an open response with the stylesheet tag but they did generate one with the image tag once images were allowed. I was bummed but there is hope. I am not sure why GMail allows it. Maybe I will start another round of tests to be sure
I am 100% sure it allows the execution of the stylesheet tag in the outlook application. It has worked 100% of the time during the last 25 tests with 3 different users.
I will explore other ways to generate responses from the yahoo and hotmail web clients. It is a safe bet the people my clients will be emailing will be using one or the other.
There has to be a more consistant way to get open responses. If an image works for web clients and the stylesheet works for windows clients then I may send both.
Kevin
The plot thickens.... I need a beer..... I may have to rely on the images more than the stylesheet after this deflating day.
It seems that when I embedded a non dynamic stylesheet line in the email and not in the middle of the template it worked more often than throwing the dynamic tag with the uuid in the top of th email.
Maybe I will start over and see if I can reproduce the results from my first tests with a hard coded tag.
@Kevin,
Take a break, it sounds like you've earned it :) Maybe images IS the way that services traditionally do it? We use Campaign Monitor to organize newsletter campaign for some of our clients and the definitely provide the "X people have opened this email" statistic. Perhaps I'll take a peek at the source of one of the emails that gets sent out and see what they do.
It has to be a URL of *some* sort! It just comes down to what URL is most likely to be used.
I'm using the same approach for images, caching them into a directory using the pseudocode
if fileexist
stream the image out
else
generate and stream
endif
But I discovered the webserver never provides a statuscode "304 NOT MODIFIED" even when working with cfheader along last-modified and etag.
This method seems to be bandwitdh intensive.
Any suggestion?
@Daniel,
So are you saying you're passing the 304 status code back with the image request explicitly? I am not too familiar with etag stuff, so sorry if I just asked a silly question.
To say the truth I'm not using cflocation cause Im trying to hide the physical location of my images.
As like in your example my page contains
<img src="image_proxy.cfm?id=1"/>
let me better explain my case:
-the very first time the image not exists, so I create, save in a cache directory, and display it.
-The user visits the page for the first time: the web browser should display and store it into its cache. ( webserver response : "100 OK" )
-The user visits again the same page: the browser should use the cached version of the file since it is not modified (avoiding a new download).
In this case the webserver should response as "304 Not Modified".
-ETag header is essentially the CRC of the page content.
When a new HTTP response contains the same ETag as an older HTTP response, the client can conclude that the content is the same without further downloading.
You can easily set ETag using
<cfheader name="ETag" value='"#hash(GetPageContext().getCFOutput().getString())#"' />
Since I'm developing a gallery-like site, I should find a way to optimize cpu/bandwidth utilization.
With the method described, the webserver always replies with "100 OK" status message, never using a (browser)cached version
@Daniel,
So, when a browser is making a HTTP request, does it read the headers and then potentially shut down the HTTP request if the ETag is a certain value? Or, is it actually making two requests: one for the headers and one for the content?
From the FireBug network activity, it would appear that it is only making a single request. I'll have to play around with this stuff some more.
Is this was causes the "Not Modified" status in FireBug's network activity?
@Ben,
Yes, the client reads the headers and if it has a cached version of the file (same ETAG) then shuts down the request and render the cached version.
( I think it is done in the same request/connection )
a sample of Apache log with a gif image
127.0.0.1 - - [08/Feb/2010:18:54:08 +0100] "GET /CFIDE/debug/images/topdoc.gif HTTP/1.1" 304 -
the same with a image returned from a cfm page
127.0.0.1 - - [19/Mar/2010:17:38:41 +0100] "GET /tests/getimage_cfimage.cfm HTTP/1.1" 200 384812
As you can see, the first one have no size, the second one downlaod 384k of image everytime.




