web/views/staticcontrollers.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
--- a/web/views/staticcontrollers.py	Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,272 +0,0 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Set of static resources controllers for :
-
-- /data/...
-- /static/...
-- /fckeditor/...
-"""
-
-import os
-import os.path as osp
-import hashlib
-import mimetypes
-import threading
-import tempfile
-from time import mktime
-from datetime import datetime, timedelta
-from logging import getLogger
-
-from cubicweb import Forbidden
-from cubicweb.web import NotFound, Redirect
-from cubicweb.web.http_headers import generateDateTime
-from cubicweb.web.controller import Controller
-from cubicweb.web.views.urlrewrite import URLRewriter
-
-
-
-class StaticFileController(Controller):
-    """an abtract class to serve static file
-
-    Make sure to add your subclass to the STATIC_CONTROLLERS list"""
-    __abstract__ = True
-    directory_listing_allowed = False
-
-    def max_age(self, path):
-        """max cache TTL"""
-        return 60*60*24*7
-
-    def static_file(self, path):
-        """Return full content of a static file.
-
-        XXX iterable content would be better
-        """
-        debugmode = self._cw.vreg.config.debugmode
-        if osp.isdir(path):
-            if self.directory_listing_allowed:
-                return u''
-            raise Forbidden(path)
-        if not osp.isfile(path):
-            raise NotFound()
-        if not debugmode:
-            # XXX: Don't provide additional resource information to error responses
-            #
-            # the HTTP RFC recommends not going further than 1 year ahead
-            expires = datetime.now() + timedelta(seconds=self.max_age(path))
-            self._cw.set_header('Expires', generateDateTime(mktime(expires.timetuple())))
-            self._cw.set_header('Cache-Control', 'max-age=%s' % self.max_age(path))
-
-        # XXX system call to os.stats could be cached once and for all in
-        # production mode (where static files are not expected to change)
-        #
-        # Note that: we do a osp.isdir + osp.isfile before and a potential
-        # os.read after. Improving this specific call will not help
-        #
-        # Real production environment should use dedicated static file serving.
-        self._cw.set_header('last-modified', generateDateTime(os.stat(path).st_mtime))
-        if self._cw.is_client_cache_valid():
-            return ''
-        # XXX elif uri.startswith('/https/'): uri = uri[6:]
-        mimetype, encoding = mimetypes.guess_type(path)
-        if mimetype is None:
-            mimetype = 'application/octet-stream'
-        self._cw.set_content_type(mimetype, osp.basename(path), encoding)
-        with open(path, 'rb') as resource:
-            return resource.read()
-
-    @property
-    def relpath(self):
-        """path of a requested file relative to the controller"""
-        path = self._cw.form.get('static_relative_path')
-        if path is None:
-            path = self._cw.relative_path(includeparams=True)
-        return path
-
-
-class ConcatFilesHandler(object):
-    """Emulating the behavior of modconcat
-
-    this serve multiple file as a single one.
-    """
-
-    def __init__(self, config):
-        self._resources = {}
-        self.config = config
-        self.logger = getLogger('cubicweb.web')
-        self.lock = threading.Lock()
-
-    def _resource(self, path):
-        """get the resouce"""
-        try:
-            return self._resources[path]
-        except KeyError:
-            self._resources[path] = self.config.locate_resource(path)
-            return self._resources[path]
-
-    def _up_to_date(self, filepath, paths):
-        """
-        The concat-file is considered up-to-date if it exists.
-        In debug mode, an additional check is performed to make sure that
-        concat-file is more recent than all concatenated files
-        """
-        if not osp.isfile(filepath):
-            return False
-        if self.config.debugmode:
-            concat_lastmod = os.stat(filepath).st_mtime
-            for path in paths:
-                dirpath, rid = self._resource(path)
-                if rid is None:
-                    raise NotFound(path)
-                path = osp.join(dirpath, rid)
-                if os.stat(path).st_mtime > concat_lastmod:
-                    return False
-        return True
-
-    def build_filepath(self, paths):
-        """return the filepath that will be used to cache concatenation of `paths`
-        """
-        _, ext = osp.splitext(paths[0])
-        fname = 'cache_concat_' + hashlib.md5((';'.join(paths)).encode('ascii')).hexdigest() + ext
-        return osp.join(self.config.appdatahome, 'uicache', fname)
-
-    def concat_cached_filepath(self, paths):
-        filepath = self.build_filepath(paths)
-        if not self._up_to_date(filepath, paths):
-            with self.lock:
-                if self._up_to_date(filepath, paths):
-                    # first check could have raced with some other thread
-                    # updating the file
-                    return filepath
-                fd, tmpfile = tempfile.mkstemp(dir=os.path.dirname(filepath))
-                try:
-                    f = os.fdopen(fd, 'wb')
-                    for path in paths:
-                        dirpath, rid = self._resource(path)
-                        if rid is None:
-                            # In production mode log an error, do not return a 404
-                            # XXX the erroneous content is cached anyway
-                            self.logger.error('concatenated data url error: %r file '
-                                              'does not exist', path)
-                            if self.config.debugmode:
-                                raise NotFound(path)
-                        else:
-                            with open(osp.join(dirpath, rid), 'rb') as source:
-                                for line in source:
-                                    f.write(line)
-                            f.write(b'\n')
-                    f.close()
-                except:
-                    os.remove(tmpfile)
-                    raise
-                else:
-                    os.rename(tmpfile, filepath)
-        return filepath
-
-
-class DataController(StaticFileController):
-    """Controller in charge of serving static files in /data/
-
-    Handles mod_concat-like URLs.
-    """
-
-    __regid__ = 'data'
-
-    def __init__(self, *args, **kwargs):
-        super(DataController, self).__init__(*args, **kwargs)
-        config = self._cw.vreg.config
-        self.base_datapath = config.data_relpath()
-        self.data_modconcat_basepath = '%s??' % self.base_datapath
-        self.concat_files_registry = ConcatFilesHandler(config)
-
-    def publish(self, rset=None):
-        config = self._cw.vreg.config
-        # includeparams=True for modconcat-like urls
-        relpath = self.relpath
-        if relpath.startswith(self.data_modconcat_basepath):
-            paths = relpath[len(self.data_modconcat_basepath):].split(',')
-            filepath = self.concat_files_registry.concat_cached_filepath(paths)
-        else:
-            if not relpath.startswith(self.base_datapath):
-                # /data/foo, redirect to /data/{hash}/foo
-                prefix = 'data/'
-                relpath = relpath[len(prefix):]
-                raise Redirect(self._cw.data_url(relpath), 302)
-            # skip leading '/data/{hash}/' and url params
-            prefix = self.base_datapath
-            relpath = relpath[len(prefix):]
-            relpath = relpath.split('?', 1)[0]
-            dirpath, rid = config.locate_resource(relpath)
-            if dirpath is None:
-                raise NotFound()
-            filepath = osp.join(dirpath, rid)
-        return self.static_file(filepath)
-
-
-class FCKEditorController(StaticFileController):
-    """Controller in charge of serving FCKEditor related file
-
-    The motivational for a dedicated controller have been lost.
-    """
-
-    __regid__ = 'fckeditor'
-
-    def publish(self, rset=None):
-        config = self._cw.vreg.config
-        if self._cw.https:
-            uiprops = config.https_uiprops
-        else:
-            uiprops = config.uiprops
-        relpath = self.relpath
-        if relpath.startswith('fckeditor/'):
-            relpath = relpath[len('fckeditor/'):]
-        relpath = relpath.split('?', 1)[0]
-        return self.static_file(osp.join(uiprops['FCKEDITOR_PATH'], relpath))
-
-
-class StaticDirectoryController(StaticFileController):
-    """Controller in charge of serving static file in /static/
-    """
-    __regid__ = 'static'
-
-    def publish(self, rset=None):
-        staticdir = self._cw.vreg.config.static_directory
-        relpath = self.relpath[len(self.__regid__) + 1:]
-        return self.static_file(osp.join(staticdir, relpath))
-
-STATIC_CONTROLLERS = [DataController, FCKEditorController,
-                      StaticDirectoryController]
-
-class StaticControlerRewriter(URLRewriter):
-    """a quick and dirty rewritter in charge of server static file.
-
-    This is a work around the flatness of url handling in cubicweb."""
-
-    __regid__ = 'static'
-
-    priority = 10
-
-    def rewrite(self, req, uri):
-        for ctrl in STATIC_CONTROLLERS:
-            if uri.startswith('/%s/' % ctrl.__regid__):
-                break
-        else:
-            self.debug("not a static file uri: %s", uri)
-            raise KeyError(uri)
-        relpath = self._cw.relative_path(includeparams=False)
-        self._cw.form['static_relative_path'] = self._cw.relative_path(includeparams=True)
-        return ctrl.__regid__, None