From 5e0b2f3041a62d3e1fe8978503f21d452c28349a Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Thu, 20 Sep 2018 09:08:23 +0200 Subject: [PATCH] (Temporary) caching cdn handler until we sort out bundling --- js/loader.js | 3 +- voila/app.py | 4 +- voila/requirehandler.py | 85 +++++++++++++++++++++++++++++++++++++++ voila/server_extension.py | 6 ++- voila/static/main.js | 5 ++- 5 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 voila/requirehandler.py diff --git a/js/loader.js b/js/loader.js index 3eb88d4ff..ae912bd08 100644 --- a/js/loader.js +++ b/js/loader.js @@ -12,7 +12,8 @@ var requirePromise = function (pkg) { }; export function requireLoader(moduleName, moduleVersion) { - return requirePromise(["" + moduleName]).catch(function (err) { + var path = `/voila/require/${moduleName}@${moduleVersion}` + return requirePromise([path]).catch(function (err) { var failedId = err.requireModules && err.requireModules[0]; if (failedId) { console.log(`Falling back to unpkg.com for ${moduleName}@${moduleVersion}`); diff --git a/voila/app.py b/voila/app.py index 3d0505281..8929b0a36 100644 --- a/voila/app.py +++ b/voila/app.py @@ -26,6 +26,7 @@ from .handler import VoilaHandler from .treehandler import VoilaTreeHandler from .watchdog import WatchDogHandler +from .requirehandler import RequireHandler _kernel_id_regex = r"(?P\w+-\w+-\w+-\w+-\w+)" @@ -110,7 +111,9 @@ def start(self): ] ) + _require_regex = '(.*)' handlers = [ + (r'/voila/require/%s' % _require_regex, RequireHandler), (r'/api/kernels/%s' % _kernel_id_regex, KernelHandler), (r'/api/kernels/%s/channels' % _kernel_id_regex, ZMQChannelsHandler), ( @@ -148,7 +151,6 @@ def start(self): contents_manager = LargeFileManager() # TODO: make this configurable like notebook - app = tornado.web.Application( handlers, kernel_manager=kernel_manager, diff --git a/voila/requirehandler.py b/voila/requirehandler.py new file mode 100644 index 000000000..ebf3638a1 --- /dev/null +++ b/voila/requirehandler.py @@ -0,0 +1,85 @@ +import requests +import logging +import tornado.web +import os + +from jupyter_server.base.handlers import JupyterHandler +from jupyter_core.paths import ( + jupyter_data_dir, jupyter_config_path, jupyter_path, + SYSTEM_JUPYTER_PATH, ENV_JUPYTER_PATH, +) + +logger = logging.getLogger('Voila.require') + +whitelist = ['jupyter-leaflet', 'ipyvolume', 'bqplot', 'threejs'] + + +class RequireHandler(JupyterHandler): + cdn = 'https://unpkg.com/{module}@{version}/dist/index.js' + + def initialize(self, cache_directories=None): + self.cache_directories = cache_directories + if self.cache_directories is None: + self.cache_directories = [os.path.join(k, 'voila_cache') for k in [ENV_JUPYTER_PATH[0], jupyter_data_dir()]] + logging.info('Using %r for caching directories', self.cache_directories) + + @tornado.web.authenticated + @tornado.gen.coroutine + def get(self, path=None): + if path: + path = path.strip('/') # remove leading / + if '@' not in path: + raise tornado.web.HTTPError(500) + module, version = path.rsplit('@', 1) + if module not in whitelist: + logger.error('Module %r not in whitelist, will not cache', module) + raise tornado.web.HTTPError(404) + + url = self.cdn.format(module=module, version=version) + content = self.get_from_cache(module, version) + if not content: + logger.info('Request %s', url) + response = requests.get(url) + if response.ok: + self.put_in_cache(module, version, response.text) + content = response.text + else: + logger.error('Could not get: %r', path) + raise tornado.web.HTTPError(500) + + self.set_header('Content-Type', 'text/javascript') + self.write(content) + + def get_module_path(self, module, version): + return '{module}/{version}'.format(module=module, version=version) + + def get_from_cache(self, module, version): + path = self.get_module_path(module, version) + for directory_path in self.cache_directories: + cache_path = os.path.join(directory_path, path) + try: + logger.info('Try opening cache file: %s', cache_path) + with open(cache_path) as f: + logger.info('Found cache file: %s', cache_path) + return f.read() + except FileNotFoundError: + pass + + def put_in_cache(self, module, version, value): + path = self.get_module_path(module, version) + for directory_path in self.cache_directories: + cache_path = os.path.join(directory_path, path) + directory_path = os.path.dirname(cache_path) + if not os.path.exists(directory_path): + try: + os.makedirs(directory_path) + except: + pass + try: + logger.info('Try writing cache file: %s', cache_path) + with open(cache_path, 'w') as f: + f.write(value) + logger.info('Wrote cache file: %s', cache_path) + return + except FileNotFoundError: + logger.info('Failed writing cache file: %s', cache_path) diff --git a/voila/server_extension.py b/voila/server_extension.py index 491dbd03a..518f0e5a1 100644 --- a/voila/server_extension.py +++ b/voila/server_extension.py @@ -1,3 +1,4 @@ +import logging import os import gettext from pathlib import Path @@ -14,6 +15,7 @@ from .treehandler import VoilaTreeHandler from .watchdog import WatchDogHandler +logger = logging.getLogger('Voila.server_extension') def load_jupyter_server_extension(server_app): web_app = server_app.web_app @@ -34,4 +36,6 @@ def load_jupyter_server_extension(server_app): (url_path_join(web_app.settings['base_url'], '/voila/static/(.*)'), tornado.web.StaticFileHandler, {'path': str(STATIC_ROOT)}) - ]) \ No newline at end of file + ]) + voila_root = web_app.settings['base_url'] + 'voila' + server_app.log.info('Voila extension running at %s', voila_root) diff --git a/voila/static/main.js b/voila/static/main.js index 930f05e1c..31a61bcf3 100644 --- a/voila/static/main.js +++ b/voila/static/main.js @@ -9,7 +9,10 @@ Array.prototype.forEach.call(scripts, (script) => { }) requirejs.config({ - baseUrl: '/voila/static/dist' + baseUrl: '/voila/require/', + paths: { + 'libwidgets': '/voila/static/dist/libwidgets' + } }) require(['libwidgets'], function(lib) {