Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
0
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
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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
Ejemplo n.º 22
0
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
Ejemplo n.º 23
0
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