Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 1492

Show
Ignore:
Timestamp:
12/09/06 16:12:57
Author:
fumanchu
Message:

2.x backport of [1117] (Fix for #531 (Make an ETag tool). Also refactored If-Modified-Since validation.) I wasn't able to use validate_since inside cachefilter due to its use of RequestHandled?.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/cherrypy-2.x/cherrypy/lib/cptools.py

    r1481 r1492  
    22 
    33import inspect 
     4import md5 
    45import mimetools 
    56import mimetypes 
     
    6364    def __getattr__(self, key): 
    6465        return self.items[key] 
     66 
     67 
     68#                     Conditional HTTP request support                     # 
     69 
     70def validate_etags(autotags=False): 
     71    """Validate the current ETag against If-Match, If-None-Match headers. 
     72     
     73    If autotags is True, an ETag response-header value will be provided 
     74    from an MD5 hash of the response body (unless some other code has 
     75    already provided an ETag header). If False (the default), the ETag 
     76    will not be automatic. 
     77     
     78    WARNING: the autotags feature is not designed for URL's which allow 
     79    methods other than GET. For example, if a POST to the same URL returns 
     80    no content, the automatic ETag will be incorrect, breaking a fundamental 
     81    use for entity tags in a possibly destructive fashion. Likewise, if you 
     82    raise 304 Not Modified, the response body will be empty, the ETag hash 
     83    will be incorrect, and your application will break. 
     84    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 
     85    """ 
     86    response = cherrypy.response 
     87     
     88    # Guard against being run twice. 
     89    if hasattr(response, "ETag"): 
     90        return 
     91     
     92    status, reason, msg = httptools.validStatus(response.status) 
     93     
     94    etag = response.headers.get('ETag') 
     95     
     96    # Automatic ETag generation. See warning in docstring. 
     97    if (not etag) and autotags: 
     98        if status == 200: 
     99            etag = response.collapse_body() 
     100            etag = '"%s"' % md5.new(etag).hexdigest() 
     101            response.headers['ETag'] = etag 
     102     
     103    response.ETag = etag 
     104     
     105    # "If the request would, without the If-Match header field, result in 
     106    # anything other than a 2xx or 412 status, then the If-Match header 
     107    # MUST be ignored." 
     108    if status >= 200 and status <= 299: 
     109        request = cherrypy.request 
     110         
     111        conditions = request.headers.elements('If-Match') or [] 
     112        conditions = [str(x) for x in conditions] 
     113        if conditions and not (conditions == ["*"] or etag in conditions): 
     114            raise cherrypy.HTTPError(412, "If-Match failed: ETag %r did " 
     115                                     "not match %r" % (etag, conditions)) 
     116         
     117        conditions = request.headers.elements('If-None-Match') or [] 
     118        conditions = [str(x) for x in conditions] 
     119        if conditions == ["*"] or etag in conditions: 
     120            if request.method in ("GET", "HEAD"): 
     121                raise cherrypy.HTTPRedirect([], 304) 
     122            else: 
     123                raise cherrypy.HTTPError(412, "If-None-Match failed: ETag %r " 
     124                                         "matched %r" % (etag, conditions)) 
     125 
     126def validate_since(): 
     127    """Validate the current Last-Modified against If-Modified-Since headers. 
     128     
     129    If no code has set the Last-Modified response header, then no validation 
     130    will be performed. 
     131    """ 
     132    response = cherrypy.response 
     133    lastmod = response.headers.get('Last-Modified') 
     134    if lastmod: 
     135        status, reason, msg = httptools.validStatus(response.status) 
     136         
     137        request = cherrypy.request 
     138         
     139        since = request.headers.get('If-Unmodified-Since') 
     140        if since and since != lastmod: 
     141            if (status >= 200 and status <= 299) or status == 412: 
     142                raise cherrypy.HTTPError(412) 
     143         
     144        since = request.headers.get('If-Modified-Since') 
     145        if since and since == lastmod: 
     146            if (status >= 200 and status <= 299) or status == 304: 
     147                if request.method in ("GET", "HEAD"): 
     148                    raise cherrypy.HTTPRedirect([], 304) 
     149                else: 
     150                    raise cherrypy.HTTPError(412) 
     151 
    65152 
    66153def modified_since(path, stat=None): 
     
    87174    response.headers['Last-Modified'] = strModifTime 
    88175    return True 
    89      
     176 
    90177def serveFile(path, contentType=None, disposition=None, name=None): 
    91178    """Set status, headers, and body in order to serve the given file. 
     
    130217    response.headers['Content-Type'] = contentType 
    131218     
    132     if not modified_since(path, stat): 
    133         response.body = [] 
    134         return [] 
     219    # Set the Last-Modified response header, so that 
     220    # modified-since validation code can work. 
     221    response.headers['Last-Modified'] = httptools.HTTPDate(time.gmtime(stat.st_mtime)) 
     222    validate_since() 
    135223     
    136224    if disposition is not None: 
  • branches/cherrypy-2.x/cherrypy/test/test.py

    r1488 r1492  
    296296        'test_custom_filters', 
    297297        'test_decodingencoding_filter', 
     298        'test_etags', 
    298299        'test_gzip_filter', 
    299300        'test_logdebuginfo_filter', 

Hosted by WebFaction

Log in as guest/cpguest to create tickets