SVN: r22001 - trunk/quixote

Neil Schemenauer <[email protected]> Mon, 14 Jul 2003 14:15:03 -0400
Newsgroups gmane.comp.web.quixote.cvs
Message-ID <[email protected]>
Author: nascheme
Date: 2003-07-14 14:15:02 -0400 (Mon, 14 Jul 2003)
New Revision: 22001

Modified:
   trunk/quixote/util.py
Log:
Add If-Modified-Since support for StaticFile objects.  Also, don't set the
Expires header by default.  Instead, set the Last-Modified header based on
the modification time of the file.  The Expires header can be enabled
by providing a value for the 'cache_time' argument.


Modified: trunk/quixote/util.py
==============================================================================
--- trunk/quixote/util.py	2003-07-14 15:37:24 UTC (rev 22000)
+++ trunk/quixote/util.py	2003-07-14 18:15:02 UTC (rev 22001)
@@ -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: