Is it possible to lazyload images using Coldfusions cfthread?

325 views Asked by At

I'm running coldfusion8/MySQL5.0.88 and I have a product search for which I need to pull in and resize images from various external sources before displaying them.

While I can mostly do this when products are created (vs being searched), I can not ensure all images will be avaiable in resized format from my server, so I need some sort of fallback inside the product search.

Right now, I'm checking whether the image is available on server and if not, I fire a routine to grab and resize the image like so:

// check if image is available
<cfif results.typ NEQ "img" AND results.typ NEQ "alt">
    <cfif results.bildpfad neq "" AND results.bilddateiname neq "">
    <cfset imgSuccess = form_img_handler.upload
        ( command   = "upload_search"
        , imgPath   = results.bildpfad
        , imgFile   = results.bilddateiname
        , sellerILN = results.iln
        , cookie    = variables.screenWidth
        )/>
    <cfif imgSuccess EQ "false">
        <cfset variables.imgFail = "true">
    </cfif>
 </cfelse>
     <cfset variables.imgFail = "true">
 </cfif>

// missing img
<cfif variables.imgFail EQ "true">
    <cfoutput><div class="resultsImgWrap noImgFound"></div></cfoutput> 
    <cfset variables.imgFail = "false">
<cfelse>
// show image
    <cfoutput><div class="resultsImgWrap"><img src="#variables.imageSrc#" /></div></cfoutput>
</cfif>

The upload function handles the cfhttp request and resizing and returns true/false.

I'm wondering if it is possible to use cfthread in this context, so when a user does a search, I output the markup including correct URL links, but the image pull/resize/saving to destination will be done in a cfthread, in order to speed up displaying results for the user.

Question:
Will images show up once the cfthread has finished processing or will this approach produce a 404 error when trying to load an image that has not been created (probably)? Are there any other means of showing something to the user and enabling the user to continue while the images are being uploaded and processed?

Thanks for inputs!

EDIT:
Ok. Based on the answers I came up with the following. While not working yet, I think it goes in the right direction.

// check media log, if image is logged (already created), if so, load it, if not pull it from external
<cfif results.typ NEQ "img" AND results.typ NEQ "alt">
    // check path and filename
    <cfif results.bildpfad neq "" AND results.bilddateiname neq "">
        // pull in
        // arguments: 
        // cm = form
        // pt = path to image
        // fl = filename
        // se = seller id  
        // ck = screen width (I'm using adaptive image sizes
        // ax = origin
        // gb = which image size to return
        <cfset variables.imgSrc = expandPath("../services/img_handler.cfc") & "?method=up&cm=results&pt=" & results.bildpfad & "&fl=" & results.bilddateiname & "&se=" & results.id & "&ck=" & variables.screenWidth & "&ax=rm&gb=s">
    <cfelse>
        cfset variables.imgFail = "true">
    </cfif>
</cfif>

<cfif variables.imgFail EQ "true">
    <cfoutput><div class="resultsImgWrap noImgFound"><img src="../images/not_found.png"></div></cfoutput>
    <cfset variables.imgFail = "false">
 <cfelse>
    <cfoutput><div class="resultsImgWrap"><a class="swipeMe" rel="external" href="#variables.zoomSrc#">
                <img src="#variables.imageSrc#" class="adaptImg ui-li-thumb" /></a>
              </div></cfoutput>
</cfif>

So this will check for the image in my media log which I'm querying along with the results (avoid unnecessary s3 check for existing image). If no image is in the log, I'm checking for path/filename not being empty and trigger my intelligent image loader, which does the following:

<cfcomponent output="false" hint="image handler">
    <cffunction name="Init" access="public" returntype="any" output="false" hint="Initialize">
        <cfreturn true />
    </cffunction>       

    <cffunction name="upload" access="remote" output="false" hint="creates images and stores them to S3">
        <cfargument name="cm" type="string" required="true" hint="" /> 
        <cfargument name="pt" type="string" required="true" hint="" /> 
        <cfargument name="fl" type="string" required="true" hint="" />
        <cfargument name="se" type="string" required="true" hint="" />
        <cfargument name="ck" type="string" required="true" hint="" />
        <cfargument name="gb" type="string" required="false" hint="" />
        <cfargument name="ax" type="string" required="false" hint="" />

        <cfscript>  
           var LOCAL = {};  
           // arguments
           LOCAL.command = cm;
           LOCAL.imgPath = pt;
           LOCAL.imgFile = fl;
           LOCAL.sellerILN = se;
           LOCAL.cookie = LSParseNumber(ck);
           LOCAL.getBack = gb;
           LOCAL.access = ax;
           // s3
           // commander
           if ( LOCAL.command NEQ "" ) {
                LOCAL.action = LOCAL.command;
           } else {
                LOCAL.action = "upload";
                }
           // s3 misc
           LOCAL.bucketPath = Session.bucketPath;
           LOCAL.bucketName = Session.bucketName;
           LOCAL.acl = "public-read";
           LOCAL.storage = "";
           LOCAL.tempDirectory = expandPath( "../members/img/secure/" );
           LOCAL.allow = "png,jpg,jpeg";
           LOCAL.failedLoads = "";
           LOCAL.altFailedLoads = "";
           LOCAL.createBucket = "false";
           LOCAL.errorCount = 0;
           LOCAL.altErrorCount = 0;
           LOCAL.cacheControl = 1;
           LOCAL.contentType = "image";
           LOCAL.httptimeout = "300";
           LOCAL.cacheDays = "30";
           LOCAL.storageClass = "REDUCED_REDUNDANCY";
           LOCAL.keyName = "";
           LOCAL.baseUrl = "http://www.baseurl.com";
           LOCAL.imageSrc = "";
           LOCAL.testFilePath = LOCAL.imgPath & LOCAL.imgFile;
           LOCAL.fileExt = ListLast(LOCAL.testFilePath, ".");
           LOCAL.runner = "s,m,l,xl";
           LOCAL.worked = "true";
      </cfscript>   
      // runner is the file size setter, in results I only create two sizes, during imports I create all four
      <cftry>
        <cfhttp timeout="45" 
            throwonerror="no" 
            url="#LOCAL.testFilePath#" 
            method="get" 
            useragent="Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.8.1.12) Gecko/20080201 Firefox/2.0.0.12" 
            getasbinary="yes" 
            result="objGet">
        <cfscript>
            // set file sizes to create
            if ( LOCAL.command EQ "upload_search" ){
                if ( LOCAL.cookie LT 320 ) {
                    LOCAL.runner = "l,s";
                    }
                else if ( LOCAL.cookie GTE 320 AND LOCAL.cookie LTE 767 ) {
                    LOCAL.runner = "l,m";
                    }
                else if ( LOCAL.cookie GT 768 ) {
                    LOCAL.runner = "xl,m";
                    }
                else if ( LOCAL.cookie GT 1280 ){
                    LOCAL.runner = "xl,l";
                    }
                } else if ( LOCAL.command EQ "upload_import") {
                    LOCAL.runner = "xl,l,m,s";
                    }
            // validate
            if ( len(objGet.Filecontent) EQ 0 OR objGet.Mimetype EQ "text/html"  ){
                LOCAL.worked = "false length or mime";
                } else if ( NOT listfindnocase(LOCAL.allow, LOCAL.fileExt ) ){
                LOCAL.worked = "false wrong extension";
                } else {
                // create temp
                LOCAL.objImage = ImageNew(objGet.FileContent);
                LOCAL.basePath = LOCAL.tempDirectory & "_base_" & LOCAL.imgFile;
                imageWrite( LOCAL.objImage, LOCAL.basePath, ".99");
                LOCAL.base = imageRead( LOCAL.basePath );
                LOCAL.imageSrc = LOCAL.tempDirectory;

                // formats
                // S = 100x127, 
                // M = 180x230, 
                // L = 290x370, 
                // XL = 870x1110 

                // portrait
                if ( ImageGetWidth( LOCAL.base ) LT ImageGetHeight( LOCAL.base ) ){
                    for (LOCAL.i = 1; LOCAL.i LTE ListLen(LOCAL.runner,","); LOCAL.i = LOCAL.i+1){
                        LOCAL.lt = ListGetAt(LOCAL.runner, LOCAL.i, ",");
                        LOCAL.base = imageRead( LOCAL.basePath );
                        ImageSetAntialiasing(LOCAL.base,"on");
                        // default image width/height
                        LOCAL.height = Application.strConfig.respH[LOCAL.lt];
                        LOCAL.width = "";
                        ImageScaleToFit(LOCAL.base, LOCAL.width, LOCAL.height, "highestQuality");

                        LOCAL.filekey = LOCAL.lt & "_" & LOCAL.imgFile;
                        LOCAL.keyName = LOCAL.sellerILN & "/" & LOCAL.filekey;
                        LOCAL.filename = LOCAL.tempDirectory & LOCAL.filekey;
                        imageWrite( LOCAL.base, LOCAL.filename, ".99" );

                        // s3
                        Application.strObjs.s3.putObject(LOCAL.bucketName, LOCAL.filekey, LOCAL.contentType, LOCAL.httptimeout, LOCAL.cacheControl, LOCAL.cacheDays, LOCAL.acl, LOCAL.storageClass, LOCAL.keyName, LOCAL.imageSrc, "false" );
                        fileDelete( LOCAL.tempDirectory & LOCAL.lt & "_" & LOCAL.imgFile );
                        }
                    } else {
                    // same for landscape
                    ...
                    }
                }
                // cleanup
                fileDelete( LOCAL.tempDirectory & "_base_" & LOCAL.imgFile );
            } 
        </cfscript>
    // update media log
        ...
        <cfcatch>
        // dump errror message
        </cfcatch>
        </cftry>

    // return image 
        <cfif LOCAL.access EQ "rm">
            <cftry>
            <cfscript>
            if ( LOCAL.getBack EQ "s" ){
                LOCAL.passPath = Session.bucketPath & Session.bucketName & "/" & LOCAL.sellerILN & "/" & ListGetAt(LOCAL.runner, Listlen(LOCAL.runner), ",") & "_" & LOCAL.imgFile;
            } else if( LOCAL.getBack EQ "l" ) {
                LOCAL.passPath = Session.bucketPath & Session.bucketName & "/" & LOCAL.sellerILN & "/" & ListGetAt(LOCAL.runner, 1, ",") & "_" & LOCAL.imgFile;
            }
            LOCAL.mime = "image/" & LOCAL.fileExt;
            </cfscript>

            <cfcontent type="#LOCAL.mime#" file="#LOCAL.passPath#" />
            <cfcatch>
            // dump errors
            </cfcatch>                              
        </cftry>
    <cfelse>
        <cfreturn LOCAL.worked>
    </cfif>
    </cffunction>
</cfcomponent>

So I'm setting which file sizes I want to create LOCAL.runner and look through this list creating resizing the base image to my preset image dimensions and storing the created image on s3.

Problem: cfcontent is trying to return the image before it is created. I'm getting an The file specified in contentTag does not exist error when I run above code. If I check on S3, the image is there though, so I'm assuming this is a timing issue.

Thanks for any pointers on how I can force the 'cfcontent' to wait until the image has been created!

1

There are 1 answers

4
Raymond Camden On BEST ANSWER

What about outputting a link to a local CFM that will load the img? What I mean is - for images that exist on your server, you output the <img> tag that points to it. For images you need to load, you output something like this:

<img src="imagegetter.cfm?p=#urlofimagetoload#">

imagegetter.cfm would be responsible for doing the http to get and resize the image locally. It can then serve up the bits via cfcontent.