Is there a way to cache images from "download" function?

325 views
Skip to first unread message

Tito Garrido

unread,
Apr 5, 2013, 10:42:45 PM4/5/13
to web...@googlegroups.com
Hi!

I was running page speed on my website and all image files from the database (upload folder) are not cached...
Is there a way to enable cache for them?

Thanks!

Tito

--

Linux User #387870
.........____
.... _/_õ|__|
..º[ .-.___.-._| . . . .
.__( o)__( o).:_______

BlueShadow

unread,
Apr 6, 2013, 5:58:17 AM4/6/13
to web...@googlegroups.com
There supposedly is a way the fast download function(web2pyslices)
def fast_download():
   
# very basic security (only allow fast_download on your_table.upload_field):
   
if not request.args(0).startswith("db.your_table.your_field"):
       
return download()
   
# remove/add headers that prevent/favors client-side caching
   
#7days
   response
.headers['Cache-Control'] = "max-age=604800"
   
del response.headers['Pragma']
   
del response.headers['Expires']
   filename
= os.path.join(request.folder,'uploads',request.args(0))
   
# send last modified date/time so client browser can enable client-side caching
   response
.headers['Last-Modified'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime(os.path.getmtime(filename)))
   
   
return response.stream(open(filename,'rb'))

this is the version I'm experimenting with. the original had a del Cache Control instead of the max age. but google pagespeed still tells me that no expiration date is set.

Niphlod

unread,
Apr 6, 2013, 7:48:55 AM4/6/13
to web...@googlegroups.com
there's a new @cache.client decorator to set expire headers in an easy way ....
as soon as the book gets updated the docs will be here http://web2py.com/books/default/chapter/29/04#cache

Massimo Di Pierro

unread,
Apr 6, 2013, 9:23:22 AM4/6/13
to web...@googlegroups.com
Does that work for download? Download returns a stream not a string.

Niphlod

unread,
Apr 6, 2013, 10:19:28 AM4/6/13
to web...@googlegroups.com
cache.client (when no cache_model is passed) just sets headers (that's the idea at the bottom).
When you pass a cache_model, it sets headers AND cache the results (and that's the "idea at the top").

cache.client was created in the first place to avoid having to set headers repeatedly.

There's no "improvement" to look for caching the results of a download() ^_^

Tito Garrido

unread,
Apr 6, 2013, 12:36:19 PM4/6/13
to web...@googlegroups.com
Hi Folks!

Thanks for your answers but my implementation is a little bit different so none of the solutions above worked, I have tried to adapt the slice solution but it didn't work also, here is my "download" function, I am using the name of the files instead of the hash name (I am also using upload separated = True:

def images():
    ''' get the image using its name
    param1 = table
    param2 = type (image,thumb,minithumb,logo,banner)
    param3 = name '''
    import os,time
    tabela =  request.args(0)
    tipo = request.args(1)
    nome_imagem = request.args(2)
    if tabela == 'artista':
        row = db.artista(image_filename=nome_imagem)
        image=row[tipo]
    elif tabela == 'personagem':
        row = db.personagem(image_filename=nome_imagem)
        image=row[tipo]
    elif tabela == 'novela':
        if tipo == 'logo':
            row = db.novela(logo_filename=nome_imagem)
        elif tipo == 'banner':
            row = db.novela(banner_filename=nome_imagem)
        image=row[tipo]
    if not image:
        raise HTTP(404)
    else:
        request.args.append(image)
    response.headers['Cache-Control'] = "max-age=604800" # it is still showing Cache-Control=0
    #del response.headers['Pragma'] # error, Pragma key not available
    #del response.headers['Expires'] # error, Expires key not available
    splits = image.split('.')
    path = splits[0] + '.' + splits[1] + '/' + splits[2][:2] + '/'
    global_path = path + image
    filename = os.path.join(request.folder,'uploads',global_path)
    # send last modified date/time so client browser can enable client-side caching
    response.headers['Last-Modified'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime(os.path.getmtime(filename)))
    return response.download(request,db)

I am pretty syre that  I am missing something here... using @cache-client raised an "argument missing error", 0 given 1 expected...

How can I change my function to works with cache?

Thanks!

Tito



--
 
---
You received this message because you are subscribed to the Google Groups "web2py-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to web2py+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Tito Garrido

unread,
Apr 6, 2013, 12:41:12 PM4/6/13
to web...@googlegroups.com
Now it works! I just changed the return function to return response.stream(open(filename,'rb')) :D 

Thanks folks!

PS: How to use @client-cache? :)

Regards,

Tito

BlueShadow

unread,
Apr 14, 2013, 8:40:37 AM4/14/13
to web...@googlegroups.com
So I changed my function to this:

#7days
@cache.client(time_expire=604800, quick='SVL')
def fast_download():
   session
.forget(response)

   
# very basic security (only allow fast_download on your_table.upload_field):

   
if not request.args(0).startswith("db.Images"):

       
return download()
   
# remove/add headers that prevent/favors client-side caching

   
#del response.headers['Cache-Control']
   
#del response.headers['Pragma']
   
#del response.headers['Expires']

   filename
= os.path.join(request.folder,'uploads',request.args(0))

   
#response.headers['Last-Modified'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime(os.path.getmtime(filename)))
   
return response.stream(open(filename,'rb'))


developers.google.com/speed/pagespeed/
still says I should enable caching for those pictures.
any idea why its not working?

Niphlod

unread,
Apr 14, 2013, 9:06:26 AM4/14/13
to web...@googlegroups.com
sorry for the misguidance .... unfortunately response.stream doesn't return , it raises HTTP() so basically the cache decorator hasn't the possibility of completing. I'm looking into it right now.

Niphlod

unread,
Apr 14, 2013, 9:21:18 AM4/14/13
to web...@googlegroups.com
ok. So, basically the problem is that response.stream is a "special" kind of function.
It raises HTTP(200, content_of_the_file) instead of returning it, and raising an HTTP(200) is a smart way to do it.
Unfortunately, this means that
def download():
      return response.stream(....)

basically doesn't return from download, it raises an exception inside response.stream and the execution is cutted of right in the response.stream function.

A decorator "outside" download() doesn't work, because it doesn't have the chance to execute that function completely.
Now, on the bright side, the download() function should be the only one behaving in this way, so the cache.client implementation shouldn't change.

I'll see if we can use a "public" function just to adjust headers beforehand without requiring for the actual function.
For the time being, this works ok.

 
def download():
    cache
.client(time_expire=604800, quick='SVL')(lambda: 0)()
   
"""
    allows downloading of uploaded files
    http://..../[app]/default/download/[filename]
    """

   
return response.download(request, db)

basically because cache.client is coded to be a decorator, you have to pass it a function.
In this case, a dummy "lambda:0" is passed. To fire the actual "calculations" of the cache decorator, you have to call it (and that's why there's an empty () at the end). The headers are then manipulated in the current response, so response.download pick it up where headers are already set, and when it returns the image, the headers are shipped with the response.

If you have any doubts, please ask.

BlueShadow

unread,
Apr 14, 2013, 9:36:38 AM4/14/13
to web...@googlegroups.com
It works like a charm. I used the stream function and that works too.
#7days
def fast_download():
   session
.forget(response)
   cache
.client(time_expire=604800)(lambda: 0)()

   
# very basic security (only allow fast_download on your_table.upload_field):
   
if not request.args(0).startswith("db.Images"):
       
return download()

   filename
= os.path.join(request.folder,'uploads',request.args(0))

   
return response.stream(open(filename,'rb'))

thanks Niphlod you are awesome.

James Burke

unread,
Jan 25, 2014, 3:38:37 PM1/25/14
to
def fast_download():
   
import time, os
   
import contenttype as c
   
    cache
.client(time_expire=604800, quick='SVL')(lambda: 0)()

    file_id
= request.args(-1)
    myfile
= db.file(db.file.file==file_id)


    filename
, file = db.file.file.retrieve(myfile.file)
    response
.headers["Content-Type"] = c.contenttype(file_id)
    response
.headers["Content-Disposition"] = "attachment; filename=%s" % filename


    stream
= response.stream(file, chunk_size=64*1024, request=request)
   
raise HTTP(200, stream, **response.headers)


I tried using the above code, but results in the follow error message:

    cache.client(time_expire=604800, quick='SVL')(lambda: 0)()

AttributeError: 'Cache' object has no attribute 'client'


Am I missing something?

Cheers

-James

Niphlod

unread,
Jan 27, 2014, 8:49:52 AM1/27/14
to web...@googlegroups.com
probably you're not running a recent web2py release.


On Saturday, January 25, 2014 9:37:12 PM UTC+1, James Burke wrote:
def fast_download():
   
import time, os
   
import contenttype as
c
   
    cache
.client(time_expire=604800, quick='SVL')(lambda: 0)()

    file_id
= request.args(-1)

    myfile
= db.file(db.file.file==file_id)


    filename
, file = db.file.file.retrieve(myfile.file)
    response
.headers["Content-Type"] = c.contenttype(file_id)
    response
.headers["Content-Disposition"] = "attachment; filename=%s" % filename


    stream
= response.stream(file, chunk_size=64*1024, request=request)
   
raise HTTP(200, stream, **response.headers)
I tried using the above code, but results in the follow error message:

    cache.client(time_expire=604800, quick='SVL')(lambda: 0)()

AttributeError: 'Cache' object has no attribute 'client'


Am I missing something?

Cheers

-James


On Monday, April 15, 2013 1:21:18 AM UTC+12, Niphlod wrote:

James Burke

unread,
Jan 28, 2014, 1:40:22 AM1/28/14
to web...@googlegroups.com
Version 2.7.4-stable+timestamp.2013.10.14.15.16.29 recent enough?

It's after your original post.

Niphlod

unread,
Jan 28, 2014, 8:30:37 AM1/28/14
to web...@googlegroups.com
lots and lots of time passed under the bridge. Now there's cache.action ...........

James Burke

unread,
Jan 28, 2014, 1:46:18 PM1/28/14
to web...@googlegroups.com
Ok I tried:

@cache.action(time_expire=1200, cache_model=cache.memcache, quick='SVL')

def fast_download():
   
import time, os
   
import contenttype as
c


   
# very basic security:
   
if not request.args(0).startswith("file.file"):
       
return download()

   
    file_id
= request.args(-1)
    myfile
= db.file(db.file.file==file_id)


    filename
, file = db.file.file.retrieve(myfile.file)
    response
.headers["Content-Type"] = c.contenttype(file_id)
    response
.headers["Content-Disposition"] = "attachment; filename=%s" %
filename
   
#response.headers['Last-Modified'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime(myfile.last_modified))

    stream
= response.stream(file, chunk_size=64*1024, request=request)
   
raise HTTP(200, stream, **response.headers)

Now I'm getting an error:

PicklingError: Can't pickle <type 'generator'>: attribute lookup __builtin__.generator failed

Is there a way to response.render the download?

Niphlod

unread,
Jan 28, 2014, 2:58:34 PM1/28/14
to web...@googlegroups.com
wasn't the original point just to add the proper headers ?
Of course you can't cache a generator (that is what response.stream returns)
It makes no sense at all to store the actual contents of the file on memcache

James Burke

unread,
Jan 28, 2014, 6:53:00 PM1/28/14
to web...@googlegroups.com
I'm exploring options.

I want to be able to cache my images. Client side, server side it doesn't matter.

What is the best method to go about this?

Niphlod

unread,
Jan 29, 2014, 3:20:21 AM1/29/14
to web...@googlegroups.com
server-side it doesn't make any sense, so there's no native way to do it. 
It's not going to be faster than serving a static-file.

Andrew Rogers

unread,
Jun 25, 2020, 7:03:10 AM6/25/20
to web2py-users

Hi
I tried this from the book, but got  an error:
@cache.action(time_expire=874, cache_model=cache.ram, session=True, vars=True, public=True)

So i tried the ideas in this post and they had miscellaneous errors. I thought  things have probably changed since 2014. 

Then i started removing parameters from the book's suggestion and found that if I didn't include 'cache_model=cache.ram' it worked and set the expire time as i expected. So i am happy.

But I'm not sure if i should be happy because i know so little about caching that maybe leaving that parameter out is bad. Anyhow, thought this might be helpful to someone else who knows as little about this as I do :)
Reply all
Reply to author
Forward
0 new messages