Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Temporary) caching cdn handler until we sort out bundling #4

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion js/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
4 changes: 3 additions & 1 deletion voila/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<kernel_id>\w+-\w+-\w+-\w+-\w+)"

Expand Down Expand Up @@ -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),
(
Expand Down Expand Up @@ -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,
Expand Down
85 changes: 85 additions & 0 deletions voila/requirehandler.py
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 5 additions & 1 deletion voila/server_extension.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import os
import gettext
from pathlib import Path
Expand All @@ -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
Expand All @@ -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)})

])
])
voila_root = web_app.settings['base_url'] + 'voila'
server_app.log.info('Voila extension running at %s', voila_root)
5 changes: 4 additions & 1 deletion voila/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down