diff --git a/pywwt/jupyter_server.py b/pywwt/jupyter_server.py index a8c22fd..81246c7 100644 --- a/pywwt/jupyter_server.py +++ b/pywwt/jupyter_server.py @@ -1,3 +1,22 @@ +"""Routines for the pywwt notebook server extension. + +In order to make files available to the WWT engine, we need to serve them over +HTTP. Here we extend the Notebook server to be able to server both static +pywwt HTML/JS assets and custom local files specified by the user. + +The tricky part is that the Notebook server and the kernel are in separate +processes, and there is no super convenient way for them to communicate. Here, +we assume that they share a home directory, and enable communications by +writing JSON to a file in $HOME. + +The recently added code in :func:`_compute_notebook_server_base_url` +demonstrates how the kernel can figure out a URL by which to communicate with +the server. So, in principle, we could replace this local-file communication +with REST API calls. I'm not aware of any better way for the kernel and server +to talk to each other. (Note that the notebook "comms" API is for the kernel +to talk to the JavaScript frontend, not the notebook server.) + +""" import os import json import mimetypes @@ -43,8 +62,60 @@ class WWTFileHandler(IPythonHandler): self.finish(content) -def serve_file(path, extension=''): +def _compute_notebook_server_base_url(): + """Figure out the base_url of the current Jupyter notebook server. + Copied from + https://github.com/jupyter/notebook/issues/3156#issuecomment-401119433 + with miniscule changes. This is gross, but appears to be the best + available option right now. + + """ + import ipykernel + import json + from notebook.notebookapp import list_running_servers + import re + import requests + + # First, find our ID. + kernel_id = re.search( + 'kernel-(.*).json', + ipykernel.connect.get_connection_file() + ).group(1) + + # Now, check all of the running servers known on this machine. We have to + # talk to each server to figure out if it's ours or somebody else's. + for s in list_running_servers(): + response = requests.get( + requests.compat.urljoin(s['url'], 'api/sessions'), + params = {'token': s.get('token', '')} + ) + + for n in json.loads(response.text): + if n['kernel']['id'] == kernel_id: + return s['base_url'] # Found it! + + raise Exception('cannot locate our notebook server; is this code running in a Jupyter kernel?') + + +_server_base_url = None + +def get_notebook_server_base_url(): + """Get the "base_url" of the current Jupyter notebook server. + + """ + global _server_base_url + if _server_base_url is None: + _server_base_url = _compute_notebook_server_base_url() + return _server_base_url + + +def serve_file(path, extension=''): + """Given a path to a file on local disk, instruct the notebook server + to serve it up over HTTP. Returns a relative URL that can be used to + access the file. + + """ if not os.path.exists(path): raise ValueError("Path {0} does not exist".format(path)) @@ -60,7 +131,7 @@ def serve_file(path, extension=''): with open(CONFIG, 'w') as f: json.dump(config, f) - return '/wwt/' + hash + return url_path_join(get_notebook_server_base_url(), '/wwt/' + hash) def load_jupyter_server_extension(nb_server_app):