Index: util.py =================================================================== --- util.py (revision 21933) +++ util.py (working copy) @@ -21,6 +21,7 @@ import os, mimetypes, urllib from quixote import errors, html from cStringIO import StringIO +from rfc822 import formatdate def xmlrpc (request, func): """xmlrpc(request:Request, func:callable) : string @@ -66,7 +67,7 @@ """ def __init__(self, path, follow_symlinks=0, - mime_type=None, encoding=None): + mime_type=None, encoding=None, cache_time=None): """StaticFile(path:string, follow_symlinks:bool) Initialize instance with the absolute path to the file. If @@ -74,6 +75,12 @@ 'mime_type' specifies the MIME type, and 'encoding' the encoding; if omitted, the MIME type will be guessed, defaulting to text/plain. + + Optional cache_time parameter indicates the number of + seconds a response is considered to be valid, and will + be used to set the Expires header in the response when + quixote gets to that part. If the value is None then + the Expires header will not be set. """ # Check that the supplied path is absolute and (if a symbolic link) may @@ -90,8 +97,15 @@ strict=0) self.mime_type = mime_type or guess_mime or 'text/plain' self.encoding = encoding or guess_enc or None + self.cache_time = cache_time def __call__(self, request): + last_modified = formatdate(os.stat(self.path).st_mtime) + if last_modified == request.get_header('If-Modified-Since'): + # handle exact match of If-Modified-Since header + request.response.set_status(304) + return '' + # Set the Content-Type for the response and return the file's contents. request.response.set_content_type(self.mime_type) if self.encoding: @@ -99,6 +113,18 @@ fsfile = open(self.path, 'rb') contents = fsfile.read() fsfile.close() + + request.response.set_header('Last-Modified', last_modified) + + if self.cache_time is None: + request.response.cache = None # don't set the Expires header + else: + # explicitly allow client to cache page by setting the Expires + # header, this is even more efficient than the using + # Last-Modified/If-Modified-Since since the browser does not need + # to contact the server + request.response.cache = self.cache_time + return contents @@ -110,7 +136,8 @@ _q_exports = [] - def __init__(self, path, use_cache=0, list_directory=0, follow_symlinks=0): + def __init__(self, path, use_cache=0, list_directory=0, follow_symlinks=0, + cache_time=None): """StaticDirectory(path:string, use_cache:bool, list_directory:bool, follow_symlinks:bool) @@ -118,6 +145,9 @@ If 'use_cache' is true, StaticFile instances will be cached in memory. If 'list_directory' is true, users can request a directory listing. If 'follow_symlinks' is true, symbolic links will be followed. + + Optional parameter cache_time allows setting of Expires header in + response object (see note for StaticFile for more detail). """ # Check that the supplied path is absolute @@ -129,6 +159,7 @@ self.cache = {} self.list_directory = list_directory self.follow_symlinks = follow_symlinks + self.cache_time = cache_time def _q_index(self, request): """ @@ -180,9 +211,10 @@ if os.path.isdir(item_filepath): item = StaticDirectory(item_filepath, self.use_cache, - self.list_directory, self.follow_symlinks) + self.list_directory, self.follow_symlinks, self.cache_time) elif os.path.isfile(item_filepath): - item = StaticFile(item_filepath, self.follow_symlinks) + item = StaticFile(item_filepath, self.follow_symlinks, + cache_time=self.cache_time) else: raise errors.TraversalError if self.use_cache: