Exemple #1
0
  def bind_urls(self, runtime):

    '''  '''

    from canteen import url, handler

    ## asset handler
    def make_responder(asset_type, path_prefix=None):

      '''  '''

      class AssetResponder(handler.Handler):

        '''  '''

        content_types = {
          'css': 'text/css',
          'js': 'application/javascript',
          'svg': 'image/svg+xml',
          'woff': 'font/woff',
          'png': 'image/png',
          'gif': 'image/gif',
          'jpeg': 'image/jpeg',
          'jpg': 'image/jpeg',
          'webp': 'image/webp',
          'webm': 'video/webm',
          'avi': 'video/avi',
          'mpeg': 'video/mpeg',
          'mp4': 'video/mp4',
          'flv': 'video/x-flv',
          'appcache': 'text/cache-manifest'
        }

        def GET(self, asset):

          '''  '''

          fullpath = os.path.join(path_prefix, asset) if path_prefix else os.path.join(self.assets.path, asset_type, asset)
          if fullpath in self.assets.__handles__:

            # extract cached handle/modtime/content
            modtime, handle, contents, fingerprint = self.assets.__handles__[fullpath]

            if os.path.getmtime(fullpath) > modtime:
              modtime, handle, contents, fingerprint = self.open_and_serve(fullpath)  # need to refresh cache

          else:
            modtime, handle, contents, fingerprint = self.open_and_serve(fullpath)  # need to prime cache in first place

          # try to serve a 304, if possible
          if 'If-None-Match' in self.request.headers:
            if self.request.headers['If-None-Match'] == fingerprint:  # fingerprint matches, serve a 304
              return self.http.new_response(status='304 Not Modified', headers=[('ETag', self.request.headers['If-None-Match'])])

          # resolve content type by file extension, if possible
          content_type = self.content_types.get(fullpath.split('.')[-1])
          if not content_type:

            # try to guess with `mimetypes`
            content_type, encoding = mimetypes.guess_type(fullpath)
            if not content_type: content_type = 'application/octet-stream'

          return self.http.new_response(contents, headers=[('ETag', fingerprint)], content_type=content_type)  # can return content directly

        def open_and_serve(self, filepath):

          '''  '''

          if os.path.exists(filepath):
            try:
              with open(filepath, 'rb') as fhandle:

                # assign to cache location by file path
                contents = fhandle.read()
                self.assets.__handles__[filepath] = (os.path.getmtime(filepath), fhandle, contents, hashlib.md5(contents).hexdigest())
                return self.assets.__handles__[filepath]

            except IOError as e:
              if __debug__: raise
              self.error(404)

            except Exception as e:
              if __debug__: raise
              self.error(500)

          else:
            return self.error(404)

      return AssetResponder

    # set default asset prefixes
    asset_prefixes = self.__prefixes__ = {
      'style': 'assets/style',
      'image': 'assets/img',
      'script': 'assets/script',
      'font': 'assets/font',
      'video': 'assets/video',
      'other': 'assets/ext'
    } if 'asset_prefix' not in self.config else self.config['asset_prefix']

    for category, prefix in asset_prefixes.iteritems():
      url("%s-assets" % category, "/%s/<path:asset>" % prefix)(make_responder(asset_type=category))

    if 'extra_assets' in self.config:
      for name, ext_cfg in self.config['extra_assets'].iteritems():
        prefix, path = ext_cfg
        url("%s-extra-assets" % name, "%s/<path:asset>" % prefix)(make_responder(asset_type=name, path_prefix=path))
Exemple #2
0
    def bind_urls(self, runtime=None):
        '''  '''

        from canteen import url
        from canteen import handler

        ## asset handler
        def make_responder(asset_type, path_prefix=None):
            '''  '''
            class AssetResponder(handler.Handler):
                '''  '''

                content_types = {
                    'css': 'text/css',
                    'js': 'application/javascript',
                    'svg': 'image/svg+xml',
                    'woff': 'font/woff',
                    'png': 'image/png',
                    'gif': 'image/gif',
                    'jpeg': 'image/jpeg',
                    'jpg': 'image/jpeg',
                    'webp': 'image/webp'
                }

                def GET(self, asset):
                    '''  '''

                    if not path_prefix:
                        fullpath = os.path.join(self.assets.path, asset_type,
                                                asset)
                    else:
                        fullpath = os.path.join(path_prefix, asset)

                    if fullpath in self.assets.__handles__:

                        # extract cached handle/modtime/content
                        modtime, handle, contents, fingerprint = self.assets.__handles__[
                            fullpath]

                        if os.path.getmtime(fullpath) > modtime:
                            modtime, handle, contents, fingerprint = self.open_and_serve(
                                fullpath)  # need to refresh cache

                    else:
                        modtime, handle, contents, fingerprint = self.open_and_serve(
                            fullpath)  # need to prime cache in first place

                    # try to serve a 304, if possible
                    if 'If-None-Match' in self.request.headers:
                        if self.request.headers[
                                'If-None-Match'] == fingerprint:  # fingerprint matches, serve a 304
                            return self.response(
                                status='304 Not Modified',
                                headers=[
                                    ('ETag',
                                     self.request.headers['If-None-Match'])
                                ])

                    # resolve content type by file extension, if possible
                    content_type = self.content_types.get(
                        fullpath.split('.')[-1])
                    if not content_type:

                        # try to guess with `mimetypes`
                        content_type, encoding = mimetypes.guess_type(fullpath)
                        if not content_type:
                            content_type = 'application/octet-stream'

                    return self.response(contents,
                                         headers=[('ETag', fingerprint)],
                                         content_type=content_type
                                         )  # can return content directly

                def open_and_serve(self, filepath):
                    '''  '''

                    if os.path.exists(filepath):
                        try:
                            with open(filepath, 'rb') as fhandle:

                                # assign to cache location by file path
                                contents = fhandle.read()
                                self.assets.__handles__[filepath] = (
                                    os.path.getmtime(filepath), fhandle,
                                    contents,
                                    hashlib.md5(contents).hexdigest())
                                return self.assets.__handles__[filepath]

                        except IOError as e:
                            if __debug__: raise
                            self.error(404)

                        except Exception as e:
                            if __debug__: raise
                            self.error(500)

                    else:
                        return self.error(404)

            return AssetResponder

        # map asset prefixes to asset responder
        if 'asset_prefix' not in self.config:

            # set default asset prefixes
            asset_prefixes = {
                'style': 'assets/style',
                'image': 'assets/img',
                'script': 'assets/script',
                'font': 'assets/font'
            }

        else:
            asset_prefixes = self.config['asset_prefix']

        for category, prefix in asset_prefixes.iteritems():
            url("%s-assets" % category, "/%s/<path:asset>" % prefix)(
                make_responder(asset_type=category))

        self.__prefixes__ = asset_prefixes

        if 'extra_assets' in self.config:
            for name, ext_cfg in self.config['extra_assets'].iteritems():
                prefix, path = ext_cfg
                url("%s-extra-assets" % name, "%s/<path:asset>" % prefix)(
                    make_responder(asset_type=name, path_prefix=path))
Exemple #3
0
  def bind_urls(self):

    """ Bind static asset URLs, if Canteen is instructed to handle requests for
        static assets. Constructs handlers according to URLs and paths from
        application configuration. """

    from canteen import url, handler

    ## asset handler
    def make_responder(asset_type, path_prefix=None):

      """ Internal utility function to make an ``AssetResponder`` handler which
          can handler assets of type ``asset_type`` at prefix ``path_prefix``.

          :param asset_type: Type of asset we're binding this handler for.
          :param path_prefix: URL path prefix that we should respond to.

          :returns: :py:class:`AssetResponder` instance, which is a Canteen
            :py:class:`base.Handler`, preconfigured to handle asset URLs. """


      class AssetResponder(handler.Handler):

        """ Internal :py:class:`canteen.base.Handler` implementation for binding
            to and fulfilling static asset URLs, such as those for CSS, images,
            JavaScript files and SVGs. """

        content_types = {
          'css': 'text/css',
          'js': 'application/javascript',
          'svg': 'image/svg+xml',
          'woff': 'font/woff',
          'png': 'image/png',
          'gif': 'image/gif',
          'jpeg': 'image/jpeg',
          'jpg': 'image/jpeg',
          'webp': 'image/webp',
          'webm': 'video/webm',
          'avi': 'video/avi',
          'mpeg': 'video/mpeg',
          'mp4': 'video/mp4',
          'flv': 'video/x-flv',
          'appcache': 'text/cache-manifest'}

        def GET(self, asset):

          """ Fulfill HTTP GET requests for a particular kind of ``asset_type``
              and ``path_prefix``, which are provided by the outer closure that
              constructs this handler.

              :param asset: Asset to serve. This is a relative path that should
                be translated into a local file and served.

              :returns: Response containing the headers and content to be served
                for the resource specified at ``asset``. """

          fullpath = (
            os.path.join(path_prefix, asset) if path_prefix else (
              os.path.join(self.assets.path, asset_type, asset)))
          if fullpath in self.assets.__handles__:

            # extract cached handle/modtime/content
            modtime, handle, contents, fingerprint = (
              self.assets.__handles__[fullpath])

            if os.path.getmtime(fullpath) > modtime:
              modtime, handle, contents, fingerprint = (
                self.open_and_serve(fullpath))  # need to refresh cache

          else:
            modtime, handle, contents, fingerprint = (
              self.open_and_serve(fullpath))  # need to prime cache

          # try to serve a 304, if possible
          if 'If-None-Match' in self.request.headers:

            # fingerprint matches, serve a 304
            if self.request.headers['If-None-Match'] == fingerprint:
              etag_header = ('ETag', self.request.headers['If-None-Match'])
              return self.http.new_response(
                        status='304 Not Modified',
                        headers=[etag_header])

          # resolve content type by file extension, if possible
          content_type = self.content_types.get(fullpath.split('.')[-1])
          if not content_type:

            # try to guess with `mimetypes`
            content_type, encoding = mimetypes.guess_type(fullpath)
            if not content_type: content_type = 'application/octet-stream'

          # can return content directly
          return self.http.new_response(contents,
                                        headers=[('ETag', fingerprint)],
                                        content_type=content_type)

        # noinspection PyBroadException
        def open_and_serve(self, filepath):

          """ Utility function to open a static asset file and serve its
              contents. Takes a filepath and properly handles MIME/content-type
              negotiation before responding with the asset.

              :param filepath: Path to the static asset to be served.

              :returns: Structure (``tuple``) containing:
                - a timestamp for when the asset was last modified, provided by
                  ``getmtime`` from the OS
                - original handle to the file, which should be closed after
                  exiting this function
                - the contents of the file, as a string
                - an MD5 hash of the contents of the file, as a hex digest """

          if os.path.exists(filepath):
            try:
              with open(filepath, 'rb') as fhandle:

                # assign to cache location by file path
                contents = fhandle.read()
                self.assets.__handles__[filepath] = (
                  os.path.getmtime(filepath),
                  fhandle,
                  contents,
                  hashlib.md5(contents).hexdigest())
                return self.assets.__handles__[filepath]

            except IOError:
              if __debug__: raise
              self.error(404)

            except Exception:
              if __debug__: raise
              self.error(500)

          else:
            return self.error(404)

      return AssetResponder

    # set default asset prefixes
    asset_prefixes = self.__prefixes__ = {
      'style': 'assets/style',
      'image': 'assets/img',
      'script': 'assets/script',
      'font': 'assets/font',
      'video': 'assets/video',
      'other': 'assets/ext'
    } if 'asset_prefix' not in self.config else self.config['asset_prefix']

    for category, prefix in asset_prefixes.iteritems():
      url("%s-assets" % category, "/%s/<path:asset>" % prefix)((
        make_responder(asset_type=category)))

    if 'extra_assets' in self.config:
      for name, ext_cfg in self.config['extra_assets'].iteritems():
        prefix, path = ext_cfg
        url("%s-extra-assets" % name, "%s/<path:asset>" % prefix)((
          make_responder(asset_type=name, path_prefix=path)))