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))
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))
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)))