def serve_fileobj(fileobj, content_type=None, disposition=None, name=None, debug=False): response = cherrypy.serving.response try: st = os.fstat(fileobj.fileno()) except AttributeError: if debug: cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC') content_length = None else: response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() content_length = st.st_size if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: cd = disposition else: cd = '%s; filename="%s"' % (disposition, name) response.headers['Content-Disposition'] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def serve_fileobj(fileobj, content_type = None, disposition = None, name = None, debug = False): response = cherrypy.serving.response try: st = os.fstat(fileobj.fileno()) except AttributeError: if debug: cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC') content_length = None else: response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() content_length = st.st_size if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: cd = disposition else: cd = '%s; filename="%s"' % (disposition, name) response.headers['Content-Disposition'] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def get(invalid_methods=("POST", "PUT", "DELETE"), cache_class=MemoryCache): """Try to obtain cached output. If fresh enough, raise HTTPError(304). If POST, PUT, or DELETE: * invalidates (deletes) any cached response for this resource * sets request.cached = False * sets request.cacheable = False else if a cached copy exists: * sets request.cached = True * sets request.cacheable = False * sets response.headers to the cached values * checks the cached Last-Modified response header against the current If-(Un)Modified-Since request headers; raises 304 if necessary. * sets response.status and response.body to the cached values * returns True otherwise: * sets request.cached = False * sets request.cacheable = True * returns False """ if not hasattr(cherrypy, "_cache"): cherrypy._cache = cache_class() request = cherrypy.request # POST, PUT, DELETE should invalidate (delete) the cached copy. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. if request.method in invalid_methods: cherrypy._cache.delete() request.cached = False request.cacheable = False return False cache_data = cherrypy._cache.get() request.cached = c = bool(cache_data) request.cacheable = not c if c: response = cherrypy.response s, response.headers, b, create_time = cache_data # Add the required Age header response.headers["Age"] = str(int(response.time - create_time)) try: # Note that validate_since depends on a Last-Modified header; # this was put into the cached copy, and should have been # resurrected just above (response.headers = cache_data[1]). cptools.validate_since() except cherrypy.HTTPError, x: if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise # serve it & get out from the request response.status = s response.body = b
def serve_fileobj(fileobj, content_type=None, disposition=None, name=None, debug=False): """Set status, headers, and body in order to serve the given file object. The Content-Type header will be set to the content_type arg, if provided. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, 'filename' will not be set. If disposition is None, no Content-Disposition header will be written. CAUTION: If the request contains a 'Range' header, one or more seek()s will be performed on the file object. This may cause undesired behavior if the file object is not seekable. It could also produce undesired results if the caller set the read position of the file object prior to calling serve_fileobj(), expecting that the data would be served starting from that position. """ response = cherrypy.serving.response request = cherrypy.serving.request try: st = os.fstat(fileobj.fileno()) except AttributeError: if debug: cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC') content_length = None else: # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() content_length = st.st_size if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: cd = disposition else: if 'Firefox' in request.headers.get('User-Agent'): cd = '%s; filename*="%s"' % (disposition, name) else: cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def serve_file(path, content_type=None, disposition=None, name=None, debug=False): """Set status, headers, and body in order to serve the given path. The Content-Type header will be set to the content_type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ response = cherrypy.serving.response if not os.path.isabs(path): msg = "'%s' is not an absolute path." % path if debug: cherrypy.log(msg, 'TOOLS.STATICFILE') raise ValueError(msg) try: st = os.stat(path) except OSError: if debug: cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() if stat.S_ISDIR(st.st_mode): if debug: cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: ext = '' i = path.rfind('.') if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, None) if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers['Content-Disposition'] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') content_length = st.st_size fileobj = open(path, 'rb') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def process_wait_history_json(self): try: stationinfo = self.monitor.dbquery.getStation(self.stationname) except KeyError: return self.monitor.errorPage("Station '%s' not found" % self.stationname) with self.lock: cherrypy.response.headers['Last-Modified'] = httputil.HTTPDate(time.mktime(stationinfo.summaryupdatetime.timetuple())) cptools.validate_since() # output: time, nwaiting, nactive, median wait waithistory = [ (makeJSTime(tm), wt, ac, md if md else 0.0) for (tm, wt, ac, md) in stationinfo.waithistory ] cherrypy.response.headers['Content-Type'] = 'text/json' return json.dumps(waithistory)
def serve_fileobj(fileobj, content_type=None, disposition=None, name=None, debug=False): """Set status, headers, and body in order to serve the given file object. The Content-Type header will be set to the content_type arg, if provided. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, 'filename' will not be set. If disposition is None, no Content-Disposition header will be written. CAUTION: If the request contains a 'Range' header, one or more seek()s will be performed on the file object. This may cause undesired behavior if the file object is not seekable. It could also produce undesired results if the caller set the read position of the file object prior to calling serve_fileobj(), expecting that the data would be served starting from that position. """ response = cherrypy.serving.response try: st = os.fstat(fileobj.fileno()) except AttributeError: if debug: cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC') content_length = None except UnsupportedOperation: content_length = None else: # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() content_length = st.st_size if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: cd = disposition else: cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def serve_file(path, content_type=None, disposition=None, name=None, debug=False): response = cherrypy.serving.response if not os.path.isabs(path): msg = "'%s' is not an absolute path." % path if debug: cherrypy.log(msg, 'TOOLS.STATICFILE') raise ValueError(msg) try: st = os.stat(path) except OSError: if debug: cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() if stat.S_ISDIR(st.st_mode): if debug: cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: ext = '' i = path.rfind('.') if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, None) if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers['Content-Disposition'] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') content_length = st.st_size fileobj = open(path, 'rb') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def serve_file(path, content_type = None, disposition = None, name = None, debug = False): response = cherrypy.serving.response if not os.path.isabs(path): msg = "'%s' is not an absolute path." % path if debug: cherrypy.log(msg, 'TOOLS.STATICFILE') raise ValueError(msg) try: st = os.stat(path) except OSError: if debug: cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() if stat.S_ISDIR(st.st_mode): if debug: cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: ext = '' i = path.rfind('.') if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, None) if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers['Content-Disposition'] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') content_length = st.st_size fileobj = open(path, 'rb') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def serve_file(path, content_type=None, disposition=None, name=None, debug=False): """Set status, headers, and body in order to serve the given path. The Content-Type header will be set to the content_type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ response = cherrypy.serving.response # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using tools.staticdir, you can make your relative # paths become absolute by supplying a value for "tools.staticdir.root". if not os.path.isabs(path): msg = "'%s' is not an absolute path." % path if debug: cherrypy.log(msg, 'TOOLS.STATICFILE') raise ValueError(msg) try: st = os.stat(path) except (OSError, TypeError, ValueError): # OSError when file fails to stat # TypeError on Python 2 when there's a null byte # ValueError on Python 3 when there's a null byte if debug: cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. if debug: cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: # Set content-type based on filename extension ext = "" i = path.rfind('.') if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, None) if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory content_length = st.st_size fileobj = open(path, 'rb') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def serve_raw_file(self, file, content_type=None, disposition=None, name=None): # Adapted from CherryPy's serve_file(), modified to work with file-like # objects response = cherrypy.response st = None if isinstance(file, str): path = file if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: raise cherrypy.NotFound() if stat.S_ISDIR(st.st_mode): raise cherrypy.NotFound() response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() file = open(path, "rb") else: path = getattr(file, "name", None) if path: try: st = os.stat(path) except OSError: pass else: if not hasattr(file, "read"): raise ValueError( "Expected a file-like object, got %r instead " "(object has no read() method)" % file) if not hasattr(file, "seek"): raise ValueError("Can't serve file-like object %r " "(object has no seek() method)" % file) if not hasattr(file, "tell"): raise ValueError("Can't serve file-like object %r " "(object has no tell() method)" % file) # Set the content type if content_type is None: if path: content_type = mimetypes.guess_type(path)[0] if not content_type: content_type = "text/plain" response.headers["Content-Type"] = content_type # Set the content disposition if disposition is not None: cd = disposition if not name and path: name = os.path.basename(path) if name: cd = rfc6266.build_header(name, cd) response.headers["Content-Disposition"] = cd if self.use_xsendfile and path: response.headers["X-Sendfile"] = path return "" # Find the size of the file if st is None: start = file.tell() file.seek(0, 2) # Move to the end of the file c_len = file.tell() - start file.seek(start) else: c_len = st.st_size # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if cherrypy.request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = httputil.get_ranges(cherrypy.request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len message = "Invalid Range (first-byte-pos greater than Content-Length)" raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > c_len: stop = c_len r_len = stop - start response.status = "206 Partial Content" response.headers['Content-Range'] = ( "bytes %s-%s/%s" % (start, stop - 1, c_len)) response.headers['Content-Length'] = r_len file.seek(start) response.body = file_generator_limited(file, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" import mimetools boundary = mimetools.choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if "Content-Length" in response.headers: # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % content_type yield ( "\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) file.seek(start) for chunk in file_generator_limited( file, stop - start): yield chunk yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() else: response.headers['Content-Length'] = c_len response.body = file else: response.headers['Content-Length'] = c_len response.body = file return response.body
def serve_file(path, content_type=None, disposition=None, name=None): """Set status, headers, and body in order to serve the given file. The Content-Type header will be set to the content_type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ response = cherrypy.response # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using tools.static, you can make your relative # paths become absolute by supplying a value for "tools.static.root". if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: raise cherrypy.NotFound() # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. raise cherrypy.NotFound() # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers["Last-Modified"] = http.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: # Set content-type based on filename extension ext = "" i = path.rfind(".") if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, "text/plain") response.headers["Content-Type"] = content_type if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory c_len = st.st_size bodyfile = open(path, "rb") # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if cherrypy.request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = http.get_ranges(cherrypy.request.headers.get("Range"), c_len) if r == []: response.headers["Content-Range"] = "bytes */%s" % c_len message = "Invalid Range (first-byte-pos greater than Content-Length)" raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > c_len: stop = c_len r_len = stop - start response.status = "206 Partial Content" response.headers["Content-Range"] = "bytes %s-%s/%s" % (start, stop - 1, c_len) response.headers["Content-Length"] = r_len bodyfile.seek(start) response.body = file_generator_limited(bodyfile, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" import mimetools boundary = mimetools.choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers["Content-Type"] = ct if response.headers.has_key("Content-Length"): # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % content_type yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) bodyfile.seek(start) for chunk in file_generator_limited(bodyfile, stop - start): yield chunk yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() else: response.headers["Content-Length"] = c_len response.body = bodyfile else: response.headers["Content-Length"] = c_len response.body = bodyfile return response.body
def get(invalid_methods=("POST", "PUT", "DELETE"), **kwargs): """Try to obtain cached output. If fresh enough, raise HTTPError(304). If POST, PUT, or DELETE: * invalidates (deletes) any cached response for this resource * sets request.cached = False * sets request.cacheable = False else if a cached copy exists: * sets request.cached = True * sets request.cacheable = False * sets response.headers to the cached values * checks the cached Last-Modified response header against the current If-(Un)Modified-Since request headers; raises 304 if necessary. * sets response.status and response.body to the cached values * returns True otherwise: * sets request.cached = False * sets request.cacheable = True * returns False """ request = cherrypy.request # POST, PUT, DELETE should invalidate (delete) the cached copy. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. if request.method in invalid_methods: cherrypy._cache.delete() request.cached = False request.cacheable = False return False cache_data = cherrypy._cache.get() request.cached = c = bool(cache_data) request.cacheable = not c if c: response = cherrypy.response s, h, b, create_time, original_req_headers = cache_data # Check 'Vary' selecting headers. If any headers mentioned in "Vary" # differ between the cached and current request, bail out and # let the rest of CP handle the request. This should properly # mimic the behavior of isolated caches as RFC 2616 assumes: # "If the selecting request header fields for the cached entry # do not match the selecting request header fields of the new # request, then the cache MUST NOT use a cached entry to satisfy # the request unless it first relays the new request to the origin # server in a conditional request and the server responds with # 304 (Not Modified), including an entity tag or Content-Location # that indicates the entity to be used. # TODO: can we store multiple variants based on Vary'd headers? for header_element in h.elements('Vary'): key = header_element.value if original_req_headers[key] != request.headers.get(key, 'missing'): request.cached = False request.cacheable = True return False # Copy the response headers. See http://www.cherrypy.org/ticket/721. response.headers = rh = http.HeaderMap() for k in h: dict.__setitem__(rh, k, dict.__getitem__(h, k)) # Add the required Age header response.headers["Age"] = str(int(response.time - create_time)) try: # Note that validate_since depends on a Last-Modified header; # this was put into the cached copy, and should have been # resurrected just above (response.headers = cache_data[1]). cptools.validate_since() except cherrypy.HTTPRedirect, x: if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise # serve it & get out from the request response.status = s response.body = b
def get(invalid_methods=("POST", "PUT", "DELETE"), cache_class=MemoryCache, **kwargs): """Try to obtain cached output. If fresh enough, raise HTTPError(304). If POST, PUT, or DELETE: * invalidates (deletes) any cached response for this resource * sets request.cached = False * sets request.cacheable = False else if a cached copy exists: * sets request.cached = True * sets request.cacheable = False * sets response.headers to the cached values * checks the cached Last-Modified response header against the current If-(Un)Modified-Since request headers; raises 304 if necessary. * sets response.status and response.body to the cached values * returns True otherwise: * sets request.cached = False * sets request.cacheable = True * returns False """ if not hasattr(cherrypy, "_cache"): cherrypy._cache = cache_class() request = cherrypy.request # POST, PUT, DELETE should invalidate (delete) the cached copy. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. if request.method in invalid_methods: cherrypy._cache.delete() request.cached = False request.cacheable = False return False cache_data = cherrypy._cache.get() request.cached = c = bool(cache_data) request.cacheable = not c if c: response = cherrypy.response s, h, b, create_time, original_req_headers = cache_data # Check 'Vary' selecting headers. If any headers mentioned in "Vary" # differ between the cached and current request, bail out and # let the rest of CP handle the request. This should properly # mimic the behavior of isolated caches as RFC 2616 assumes: # "If the selecting request header fields for the cached entry # do not match the selecting request header fields of the new # request, then the cache MUST NOT use a cached entry to satisfy # the request unless it first relays the new request to the origin # server in a conditional request and the server responds with # 304 (Not Modified), including an entity tag or Content-Location # that indicates the entity to be used. # TODO: can we store multiple variants based on Vary'd headers? for header_element in h.elements('Vary'): key = header_element.value if original_req_headers[key] != request.headers.get( key, 'missing'): request.cached = False request.cacheable = True return False # Add the required Age header response.headers = h response.headers["Age"] = str(int(response.time - create_time)) try: # Note that validate_since depends on a Last-Modified header; # this was put into the cached copy, and should have been # resurrected just above (response.headers = cache_data[1]). cptools.validate_since() except cherrypy.HTTPError, x: if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise # serve it & get out from the request response.status = s response.body = b
def staticdirindex( section, dir, root="", match="", content_types=None, index="", indexlistermatch="", indexlister=None, **kwargs): """Serve a directory index listing for a dir. Compatibility alert: staticdirindex is built on and is dependent on staticdir and its configurations. staticdirindex only works effectively in locations where staticdir is also configured. staticdirindex is coded to allow easy integration with staticdir, if demand warrants. indexlister must be configured, or no function is performed. indexlister should be a callable that accepts the following parameters: section: same as for staticdir (and implicitly calculated for it) dir: same as for staticdir, but already combined with root path: combination of section and dir Other parameters that are configured for staticdirindex will be passed on to indexlister. Should use priorty > than that of staticdir, so that only directories not served by staticdir, call staticdirindex. """ # first call old staticdir, and see if it does anything sdret = staticdir(section, dir, root, match, content_types, index) if sdret: return True # if not, then see if we are configured to do anything if indexlister is None: return False req = cherrypy.request response = cherrypy.response match = indexlistermatch # N.B. filename ending in a slash or not does not imply a directory # the following block of code directly copied from static.py staticdir if match and not re.search(match, cherrypy.request.path_info): return False # Allow the use of '~' to refer to a user's home directory. dir = os.path.expanduser(dir) # If dir is relative, make absolute using "root". if not os.path.isabs(dir): if not root: msg = "Static dir requires an absolute dir (or root)." raise ValueError(msg) dir = os.path.join(root, dir) # Determine where we are in the object tree relative to 'section' # (where the static tool was defined). if section == 'global': section = "/" section = section.rstrip(r"\/") branch = cherrypy.request.path_info[len(section) + 1:] branch = urllib.unquote(branch.lstrip(r"\/")) # If branch is "", filename will end in a slash filename = os.path.join(dir, branch) # There's a chance that the branch pulled from the URL might # have ".." or similar uplevel attacks in it. Check that the final # filename is a child of dir. if not os.path.normpath(filename).startswith(os.path.normpath(dir)): raise cherrypy.HTTPError(403) # Forbidden # the above block of code directly copied from static.py staticdir # N.B. filename ending in a slash or not does not imply a directory # Check if path is a directory. path = filename # The following block of code copied from static.py serve_file # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using tools.static, you can make your relative # paths become absolute by supplying a value for "tools.static.root". if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: # The above block of code copied from static.py serve_file return False if stat.S_ISDIR(st.st_mode): # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = http.HTTPDate(st.st_mtime) cptools.validate_since() response.body = indexlister( section=section, dir=dir, path=path, **kwargs) response.headers['Content-Type'] = 'text/html' req.is_index = True return True return False
def staticdirindex(section, dir, root="", match="", content_types=None, index="", indexlistermatch="", indexlister=None, **kwargs): """Serve a directory index listing for a dir. Compatibility alert: staticdirindex is built on and is dependent on staticdir and its configurations. staticdirindex only works effectively in locations where staticdir is also configured. staticdirindex is coded to allow easy integration with staticdir, if demand warrants. indexlister must be configured, or no function is performed. indexlister should be a callable that accepts the following parameters: section: same as for staticdir (and implicitly calculated for it) dir: same as for staticdir, but already combined with root path: combination of section and dir Other parameters that are configured for staticdirindex will be passed on to indexlister. Should use priorty > than that of staticdir, so that only directories not served by staticdir, call staticdirindex. """ # first call old staticdir, and see if it does anything sdret = staticdir(section, dir, root, match, content_types, index) if sdret: return True # if not, then see if we are configured to do anything if indexlister is None: return False req = cherrypy.request response = cherrypy.response match = indexlistermatch # N.B. filename ending in a slash or not does not imply a directory # the following block of code directly copied from static.py staticdir if match and not re.search(match, cherrypy.request.path_info): return False # Allow the use of '~' to refer to a user's home directory. dir = os.path.expanduser(dir) # If dir is relative, make absolute using "root". if not os.path.isabs(dir): if not root: msg = "Static dir requires an absolute dir (or root)." raise ValueError(msg) dir = os.path.join(root, dir) # Determine where we are in the object tree relative to 'section' # (where the static tool was defined). if section == 'global': section = "/" section = section.rstrip(r"\/") branch = cherrypy.request.path_info[len(section) + 1:] branch = urllib.unquote(branch.lstrip(r"\/")) # If branch is "", filename will end in a slash filename = os.path.join(dir, branch) # There's a chance that the branch pulled from the URL might # have ".." or similar uplevel attacks in it. Check that the final # filename is a child of dir. if not os.path.normpath(filename).startswith(os.path.normpath(dir)): raise cherrypy.HTTPError(403) # Forbidden # the above block of code directly copied from static.py staticdir # N.B. filename ending in a slash or not does not imply a directory # Check if path is a directory. path = filename # The following block of code copied from static.py serve_file # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using tools.static, you can make your relative # paths become absolute by supplying a value for "tools.static.root". if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: # The above block of code copied from static.py serve_file return False if stat.S_ISDIR(st.st_mode): # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = http.HTTPDate(st.st_mtime) cptools.validate_since() response.body = indexlister(section=section, dir=dir, path=path, **kwargs) response.headers['Content-Type'] = 'text/html' req.is_index = True return True return False
def serve_file(path, content_type=None, disposition=None, name=None, debug=False): """Set status, headers, and body in order to serve the given path. The Content-Type header will be set to the content_type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ response = cherrypy.serving.response # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using tools.staticdir, you can make your relative # paths become absolute by supplying a value for "tools.staticdir.root". if not os.path.isabs(path): msg = "'%s' is not an absolute path." % path if debug: cherrypy.log(msg, 'TOOLS.STATICFILE') raise ValueError(msg) try: st = os.stat(path) except (OSError, TypeError, ValueError): # OSError when file fails to stat # TypeError on Python 2 when there's a null byte # ValueError on Python 3 when there's a null byte if debug: cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. if debug: cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC') raise cherrypy.NotFound() # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: # Set content-type based on filename extension ext = '' i = path.rfind('.') if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, None) if content_type is not None: response.headers['Content-Type'] = content_type if debug: cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') cd = None if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers['Content-Disposition'] = cd if debug: cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory content_length = st.st_size fileobj = open(path, 'rb') return _serve_fileobj(fileobj, content_type, content_length, debug=debug)
def serve_file(path, content_type=None, disposition=None, name=None): """Set status, headers, and body in order to serve the given file. The Content-Type header will be set to the content_type arg, if provided. If not provided, the Content-Type will be guessed by the file extension of the 'path' argument. If disposition is not None, the Content-Disposition header will be set to "<disposition>; filename=<name>". If name is None, it will be set to the basename of path. If disposition is None, no Content-Disposition header will be written. """ response = cherrypy.response # If path is relative, users should fix it by making path absolute. # That is, CherryPy should not guess where the application root is. # It certainly should *not* use cwd (since CP may be invoked from a # variety of paths). If using tools.staticdir, you can make your relative # paths become absolute by supplying a value for "tools.staticdir.root". if not os.path.isabs(path): raise ValueError("'%s' is not an absolute path." % path) try: st = os.stat(path) except OSError: raise cherrypy.NotFound() # Check if path is a directory. if stat.S_ISDIR(st.st_mode): # Let the caller deal with it as they like. raise cherrypy.NotFound() # Set the Last-Modified response header, so that # modified-since validation code can work. response.headers['Last-Modified'] = http.HTTPDate(st.st_mtime) cptools.validate_since() if content_type is None: # Set content-type based on filename extension ext = "" i = path.rfind('.') if i != -1: ext = path[i:].lower() content_type = mimetypes.types_map.get(ext, "text/plain") response.headers['Content-Type'] = content_type if disposition is not None: if name is None: name = os.path.basename(path) cd = '%s; filename="%s"' % (disposition, name) response.headers["Content-Disposition"] = cd # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory c_len = st.st_size bodyfile = open(path, 'rb') # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code if cherrypy.request.protocol >= (1, 1): response.headers["Accept-Ranges"] = "bytes" r = http.get_ranges(cherrypy.request.headers.get('Range'), c_len) if r == []: response.headers['Content-Range'] = "bytes */%s" % c_len message = "Invalid Range (first-byte-pos greater than Content-Length)" raise cherrypy.HTTPError(416, message) if r: if len(r) == 1: # Return a single-part response. start, stop = r[0] if stop > c_len: stop = c_len r_len = stop - start response.status = "206 Partial Content" response.headers['Content-Range'] = ("bytes %s-%s/%s" % (start, stop - 1, c_len)) response.headers['Content-Length'] = r_len bodyfile.seek(start) response.body = file_generator_limited(bodyfile, r_len) else: # Return a multipart/byteranges response. response.status = "206 Partial Content" import mimetools boundary = mimetools.choose_boundary() ct = "multipart/byteranges; boundary=%s" % boundary response.headers['Content-Type'] = ct if response.headers.has_key("Content-Length"): # Delete Content-Length header so finalize() recalcs it. del response.headers["Content-Length"] def file_ranges(): # Apache compatibility: yield "\r\n" for start, stop in r: yield "--" + boundary yield "\r\nContent-type: %s" % content_type yield ("\r\nContent-range: bytes %s-%s/%s\r\n\r\n" % (start, stop - 1, c_len)) bodyfile.seek(start) for chunk in file_generator_limited( bodyfile, stop - start): yield chunk yield "\r\n" # Final boundary yield "--" + boundary + "--" # Apache compatibility: yield "\r\n" response.body = file_ranges() return response.body response.headers['Content-Length'] = c_len response.body = bodyfile return response.body
def get(invalid_methods = ('POST', 'PUT', 'DELETE'), debug = False, **kwargs): request = cherrypy.serving.request response = cherrypy.serving.response if not hasattr(cherrypy, '_cache'): cherrypy._cache = kwargs.pop('cache_class', MemoryCache)() for k, v in kwargs.items(): setattr(cherrypy._cache, k, v) cherrypy._cache.debug = debug if request.method in invalid_methods: if debug: cherrypy.log('request.method %r in invalid_methods %r' % (request.method, invalid_methods), 'TOOLS.CACHING') cherrypy._cache.delete() request.cached = False request.cacheable = False return False if 'no-cache' in [ e.value for e in request.headers.elements('Pragma') ]: request.cached = False request.cacheable = True return False cache_data = cherrypy._cache.get() request.cached = bool(cache_data) request.cacheable = not request.cached if request.cached: max_age = cherrypy._cache.delay for v in [ e.value for e in request.headers.elements('Cache-Control') ]: atoms = v.split('=', 1) directive = atoms.pop(0) if directive == 'max-age': if len(atoms) != 1 or not atoms[0].isdigit(): raise cherrypy.HTTPError(400, 'Invalid Cache-Control header') max_age = int(atoms[0]) break elif directive == 'no-cache': if debug: cherrypy.log('Ignoring cache due to Cache-Control: no-cache', 'TOOLS.CACHING') request.cached = False request.cacheable = True return False if debug: cherrypy.log('Reading response from cache', 'TOOLS.CACHING') s, h, b, create_time = cache_data age = int(response.time - create_time) if age > max_age: if debug: cherrypy.log('Ignoring cache due to age > %d' % max_age, 'TOOLS.CACHING') request.cached = False request.cacheable = True return False response.headers = rh = httputil.HeaderMap() for k in h: dict.__setitem__(rh, k, dict.__getitem__(h, k)) response.headers['Age'] = str(age) try: cptools.validate_since() except cherrypy.HTTPRedirect: x = sys.exc_info()[1] if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise response.status = s response.body = b elif debug: cherrypy.log('request is not cached', 'TOOLS.CACHING') return request.cached
def get(invalid_methods=("POST", "PUT", "DELETE"), debug=False, **kwargs): """Try to obtain cached output. If fresh enough, raise HTTPError(304). If POST, PUT, or DELETE: * invalidates (deletes) any cached response for this resource * sets request.cached = False * sets request.cacheable = False else if a cached copy exists: * sets request.cached = True * sets request.cacheable = False * sets response.headers to the cached values * checks the cached Last-Modified response header against the current If-(Un)Modified-Since request headers; raises 304 if necessary. * sets response.status and response.body to the cached values * returns True otherwise: * sets request.cached = False * sets request.cacheable = True * returns False """ request = cherrypy.serving.request response = cherrypy.serving.response if not hasattr(cherrypy, "_cache"): # Make a process-wide Cache object. cherrypy._cache = kwargs.pop("cache_class", MemoryCache)() # Take all remaining kwargs and set them on the Cache object. for k, v in kwargs.items(): setattr(cherrypy._cache, k, v) cherrypy._cache.debug = debug # POST, PUT, DELETE should invalidate (delete) the cached copy. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. if request.method in invalid_methods: if debug: cherrypy.log('request.method %r in invalid_methods %r' % (request.method, invalid_methods), 'TOOLS.CACHING') cherrypy._cache.delete() request.cached = False request.cacheable = False return False if 'no-cache' in [e.value for e in request.headers.elements('Pragma')]: request.cached = False request.cacheable = True return False cache_data = cherrypy._cache.get() request.cached = bool(cache_data) request.cacheable = not request.cached if request.cached: # Serve the cached copy. max_age = cherrypy._cache.delay for v in [e.value for e in request.headers.elements('Cache-Control')]: atoms = v.split('=', 1) directive = atoms.pop(0) if directive == 'max-age': if len(atoms) != 1 or not atoms[0].isdigit(): raise cherrypy.HTTPError(400, "Invalid Cache-Control header") max_age = int(atoms[0]) break elif directive == 'no-cache': if debug: cherrypy.log('Ignoring cache due to Cache-Control: no-cache', 'TOOLS.CACHING') request.cached = False request.cacheable = True return False if debug: cherrypy.log('Reading response from cache', 'TOOLS.CACHING') s, h, b, create_time = cache_data age = int(response.time - create_time) if (age > max_age): if debug: cherrypy.log('Ignoring cache due to age > %d' % max_age, 'TOOLS.CACHING') request.cached = False request.cacheable = True return False # Copy the response headers. See http://www.cherrypy.org/ticket/721. response.headers = rh = httputil.HeaderMap() for k in h: dict.__setitem__(rh, k, dict.__getitem__(h, k)) # Add the required Age header response.headers["Age"] = str(age) try: # Note that validate_since depends on a Last-Modified header; # this was put into the cached copy, and should have been # resurrected just above (response.headers = cache_data[1]). cptools.validate_since() except cherrypy.HTTPRedirect: x = sys.exc_info()[1] if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise # serve it & get out from the request response.status = s response.body = b else: if debug: cherrypy.log('request is not cached', 'TOOLS.CACHING') return request.cached
def get(invalid_methods=('POST', 'PUT', 'DELETE'), debug=False, **kwargs): request = cherrypy.serving.request response = cherrypy.serving.response if not hasattr(cherrypy, '_cache'): cherrypy._cache = kwargs.pop('cache_class', MemoryCache)() for k, v in kwargs.items(): setattr(cherrypy._cache, k, v) cherrypy._cache.debug = debug if request.method in invalid_methods: if debug: cherrypy.log( 'request.method %r in invalid_methods %r' % (request.method, invalid_methods), 'TOOLS.CACHING') cherrypy._cache.delete() request.cached = False request.cacheable = False return False if 'no-cache' in [e.value for e in request.headers.elements('Pragma')]: request.cached = False request.cacheable = True return False cache_data = cherrypy._cache.get() request.cached = bool(cache_data) request.cacheable = not request.cached if request.cached: max_age = cherrypy._cache.delay for v in [e.value for e in request.headers.elements('Cache-Control')]: atoms = v.split('=', 1) directive = atoms.pop(0) if directive == 'max-age': if len(atoms) != 1 or not atoms[0].isdigit(): raise cherrypy.HTTPError(400, 'Invalid Cache-Control header') max_age = int(atoms[0]) break elif directive == 'no-cache': if debug: cherrypy.log( 'Ignoring cache due to Cache-Control: no-cache', 'TOOLS.CACHING') request.cached = False request.cacheable = True return False if debug: cherrypy.log('Reading response from cache', 'TOOLS.CACHING') s, h, b, create_time = cache_data age = int(response.time - create_time) if age > max_age: if debug: cherrypy.log('Ignoring cache due to age > %d' % max_age, 'TOOLS.CACHING') request.cached = False request.cacheable = True return False response.headers = rh = httputil.HeaderMap() for k in h: dict.__setitem__(rh, k, dict.__getitem__(h, k)) response.headers['Age'] = str(age) try: cptools.validate_since() except cherrypy.HTTPRedirect: x = sys.exc_info()[1] if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise response.status = s response.body = b elif debug: cherrypy.log('request is not cached', 'TOOLS.CACHING') return request.cached
def get(invalid_methods=('POST', 'PUT', 'DELETE'), debug=False, **kwargs): """Try to obtain cached output. If fresh enough, raise HTTPError(304). If POST, PUT, or DELETE: * invalidates (deletes) any cached response for this resource * sets request.cached = False * sets request.cacheable = False else if a cached copy exists: * sets request.cached = True * sets request.cacheable = False * sets response.headers to the cached values * checks the cached Last-Modified response header against the current If-(Un)Modified-Since request headers; raises 304 if necessary. * sets response.status and response.body to the cached values * returns True otherwise: * sets request.cached = False * sets request.cacheable = True * returns False """ request = cherrypy.serving.request response = cherrypy.serving.response if not hasattr(cherrypy, '_cache'): # Make a process-wide Cache object. cherrypy._cache = kwargs.pop('cache_class', MemoryCache)() # Take all remaining kwargs and set them on the Cache object. for k, v in kwargs.items(): setattr(cherrypy._cache, k, v) cherrypy._cache.debug = debug # POST, PUT, DELETE should invalidate (delete) the cached copy. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. if request.method in invalid_methods: if debug: cherrypy.log( 'request.method %r in invalid_methods %r' % (request.method, invalid_methods), 'TOOLS.CACHING') cherrypy._cache.delete() request.cached = False request.cacheable = False return False if 'no-cache' in [e.value for e in request.headers.elements('Pragma')]: request.cached = False request.cacheable = True return False cache_data = cherrypy._cache.get() request.cached = bool(cache_data) request.cacheable = not request.cached if request.cached: # Serve the cached copy. max_age = cherrypy._cache.delay for v in [e.value for e in request.headers.elements('Cache-Control')]: atoms = v.split('=', 1) directive = atoms.pop(0) if directive == 'max-age': if len(atoms) != 1 or not atoms[0].isdigit(): raise cherrypy.HTTPError(400, 'Invalid Cache-Control header') max_age = int(atoms[0]) break elif directive == 'no-cache': if debug: cherrypy.log( 'Ignoring cache due to Cache-Control: no-cache', 'TOOLS.CACHING') request.cached = False request.cacheable = True return False if debug: cherrypy.log('Reading response from cache', 'TOOLS.CACHING') s, h, b, create_time = cache_data age = int(response.time - create_time) if (age > max_age): if debug: cherrypy.log('Ignoring cache due to age > %d' % max_age, 'TOOLS.CACHING') request.cached = False request.cacheable = True return False # Copy the response headers. See # https://github.com/cherrypy/cherrypy/issues/721. response.headers = rh = httputil.HeaderMap() for k in h: dict.__setitem__(rh, k, dict.__getitem__(h, k)) # Add the required Age header response.headers['Age'] = str(age) try: # Note that validate_since depends on a Last-Modified header; # this was put into the cached copy, and should have been # resurrected just above (response.headers = cache_data[1]). cptools.validate_since() except cherrypy.HTTPRedirect: x = sys.exc_info()[1] if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise # serve it & get out from the request response.status = s response.body = b else: if debug: cherrypy.log('request is not cached', 'TOOLS.CACHING') return request.cached
def get(invalid_methods = ('POST', 'PUT', 'DELETE'), debug = False, **kwargs): """Try to obtain cached output. If fresh enough, raise HTTPError(304). If POST, PUT, or DELETE: * invalidates (deletes) any cached response for this resource * sets request.cached = False * sets request.cacheable = False else if a cached copy exists: * sets request.cached = True * sets request.cacheable = False * sets response.headers to the cached values * checks the cached Last-Modified response header against the current If-(Un)Modified-Since request headers; raises 304 if necessary. * sets response.status and response.body to the cached values * returns True otherwise: * sets request.cached = False * sets request.cacheable = True * returns False """ request = cherrypy.serving.request response = cherrypy.serving.response if not hasattr(cherrypy, '_cache'): cherrypy._cache = kwargs.pop('cache_class', MemoryCache)() for k, v in kwargs.items(): setattr(cherrypy._cache, k, v) cherrypy._cache.debug = debug if request.method in invalid_methods: if debug: cherrypy.log('request.method %r in invalid_methods %r' % (request.method, invalid_methods), 'TOOLS.CACHING') cherrypy._cache.delete() request.cached = False request.cacheable = False return False if 'no-cache' in [ e.value for e in request.headers.elements('Pragma') ]: request.cached = False request.cacheable = True return False cache_data = cherrypy._cache.get() request.cached = bool(cache_data) request.cacheable = not request.cached if request.cached: max_age = cherrypy._cache.delay for v in [ e.value for e in request.headers.elements('Cache-Control') ]: atoms = v.split('=', 1) directive = atoms.pop(0) if directive == 'max-age': if len(atoms) != 1 or not atoms[0].isdigit(): raise cherrypy.HTTPError(400, 'Invalid Cache-Control header') max_age = int(atoms[0]) break elif directive == 'no-cache': if debug: cherrypy.log('Ignoring cache due to Cache-Control: no-cache', 'TOOLS.CACHING') request.cached = False request.cacheable = True return False if debug: cherrypy.log('Reading response from cache', 'TOOLS.CACHING') s, h, b, create_time = cache_data age = int(response.time - create_time) if age > max_age: if debug: cherrypy.log('Ignoring cache due to age > %d' % max_age, 'TOOLS.CACHING') request.cached = False request.cacheable = True return False response.headers = rh = httputil.HeaderMap() for k in h: dict.__setitem__(rh, k, dict.__getitem__(h, k)) response.headers['Age'] = str(age) try: cptools.validate_since() except cherrypy.HTTPRedirect: x = sys.exc_info()[1] if x.status == 304: cherrypy._cache.tot_non_modified += 1 raise response.status = s response.body = b elif debug: cherrypy.log('request is not cached', 'TOOLS.CACHING') return request.cached