Skip to content

Commit

Permalink
Merge pull request #184 from parente/static-files
Browse files Browse the repository at this point in the history
Option to serve static files under /public
  • Loading branch information
Nitin Dahyabhai committed Jul 20, 2016
2 parents 2b7b8c7 + f861ea5 commit 0b611e5
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 11 deletions.
41 changes: 32 additions & 9 deletions kernel_gateway/notebook_http/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,20 @@
"""Notebook HTTP personality for the Kernel Gateway"""

import os
import tornado
from ..base.handlers import default_handlers as default_base_handlers
from ..services.kernels.pool import ManagedKernelPool
from .cell.parser import APICellParser
from .swagger.handlers import SwaggerSpecHandler
from .handlers import NotebookAPIHandler, parameterize_path, NotebookDownloadHandler
from notebook.utils import url_path_join
from traitlets import Bool, default
from traitlets import Bool, Unicode, default
from traitlets.config.configurable import LoggingConfigurable

class NotebookHTTPPersonality(LoggingConfigurable):
"""Personality for notebook-http support, creating REST endpoints
based on the notebook's annotated cells
"""
def __init__(self, *args, **kwargs):
super(NotebookHTTPPersonality, self).__init__(*args, **kwargs)
self.api_parser = APICellParser(self.parent.kernel_manager.seed_kernelspec)

allow_notebook_download_env = 'KG_ALLOW_NOTEBOOK_DOWNLOAD'
allow_notebook_download = Bool(
config=True,
Expand All @@ -29,7 +26,19 @@ def __init__(self, *args, **kwargs):
def allow_notebook_download_default(self):
return os.getenv(self.allow_notebook_download_env, 'False') == 'True'

static_path_env = 'KG_STATIC_PATH'
static_path = Unicode(None,
config=True,
allow_none=True,
help="Serve static files on disk in the given path as /public, defaults to not serve"
)
@default('static_path')
def static_path_default(self):
return os.getenv(self.static_path_env)

def init_configurables(self):
"""Create a managed kernel pool and cell parser."""
self.api_parser = APICellParser(self.parent.kernel_manager.seed_kernelspec)
self.kernel_pool = ManagedKernelPool(
self.parent.prespawn_count,
self.parent.kernel_manager
Expand All @@ -43,12 +52,24 @@ def create_request_handlers(self):
handlers = []
# Register the NotebookDownloadHandler if configuration allows
if self.allow_notebook_download:
path = url_path_join('/', self.parent.base_url, r'/_api/source')
self.log.info('Registering resource: {}, methods: (GET)'.format(path))
handlers.append((
url_path_join('/', self.parent.base_url, r'/_api/source'),
path,
NotebookDownloadHandler,
{'path': self.parent.seed_uri}
))

# Register a static path handler if configuration allows
if self.static_path is not None:
path = url_path_join('/', self.parent.base_url, r'/public/(.*)')
self.log.info('Registering resource: {}, methods: (GET)'.format(path))
handlers.append((
path,
tornado.web.StaticFileHandler,
{'path': self.static_path}
))

# Discover the notebook endpoints and their implementations
endpoints = self.api_parser.endpoints(self.parent.kernel_manager.seed_source)
response_sources = self.api_parser.endpoint_responses(self.parent.kernel_manager.seed_source)
Expand All @@ -59,7 +80,7 @@ def create_request_handlers(self):
for endpoint_path, verb_source_map in endpoints:
parameterized_path = parameterize_path(endpoint_path)
parameterized_path = url_path_join('/', self.parent.base_url, parameterized_path)
self.log.info('Registering endpoint_path: {}, methods: ({})'.format(
self.log.info('Registering resource: {}, methods: ({})'.format(
parameterized_path,
list(verb_source_map.keys())
))
Expand All @@ -72,13 +93,15 @@ def create_request_handlers(self):
handlers.append((parameterized_path, NotebookAPIHandler, handler_args))

# Register the swagger API spec handler
handlers.append(
(url_path_join('/', self.parent.base_url, r'/_api/spec/swagger.json'),
path = url_path_join('/', self.parent.base_url, r'/_api/spec/swagger.json')
handlers.append((
path,
SwaggerSpecHandler, {
'notebook_path' : self.parent.seed_uri,
'source_cells': self.parent.kernel_manager.seed_source,
'kernel_spec' : self.parent.kernel_manager.seed_kernelspec
}))
self.log.info('Registering resource: {}, methods: (GET)'.format(path))

# Add the 404 catch-all last
handlers.append(default_base_handlers[-1])
Expand Down
9 changes: 9 additions & 0 deletions kernel_gateway/tests/resources/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
40 changes: 38 additions & 2 deletions kernel_gateway/tests/test_notebook_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,26 @@ def test_format_request_code_escaped_integration(self):
@gen_test
def test_blocked_download_notebook_source(self):
"""Notebook source should not exist under the path /_api/source when
`allow_notebook` download is False or not configured.
`allow_notebook_download` is False or not configured.
"""
response = yield self.http_client.fetch(
self.get_url('/_api/source'),
method='GET',
raise_error=False
)
self.assertEqual(response.code, 404, "/_api/source did not block as allow_notebook_download is false")
self.assertEqual(response.code, 404, "/_api/source found when allow_notebook_download is false")

@gen_test
def test_blocked_public(self):
"""Public static assets should not exist under the path /public when
`static_path` is False or not configured.
"""
response = yield self.http_client.fetch(
self.get_url('/public'),
method='GET',
raise_error=False
)
self.assertEqual(response.code, 404, "/public found when static_path is false")

@gen_test
def test_api_returns_execute_result(self):
Expand Down Expand Up @@ -232,6 +244,30 @@ def test_kernel_gateway_environment_set(self):
self.assertEqual(response.code, 200, 'GET endpoint did not return 200.')
self.assertEqual(response.body, b'KERNEL_GATEWAY is 1\n', 'Unexpected body in response to GET.')

class TestPublicStatic(TestGatewayAppBase):
"""Tests gateway behavior when public static assets are enabled."""
def setup_app(self):
"""Sets the notebook-http mode and points to a local test notebook as
the basis for the API.
"""
self.app.api = 'kernel_gateway.notebook_http'
self.app.seed_uri = os.path.join(RESOURCES,
'kernel_api{}.ipynb'.format(sys.version_info.major))

def setup_configurables(self):
"""Configures the static path at the root of the resources/public folder."""
self.app.personality.static_path = os.path.join(RESOURCES, 'public')

@gen_test
def test_get_public(self):
"""index.html should exist under `/public/index.html`."""
response = yield self.http_client.fetch(
self.get_url('/public/index.html'),
method='GET',
raise_error=False
)
self.assertEqual(response.code, 200)
self.assertEqual(response.headers.get('Content-Type'), 'text/html')

class TestSourceDownload(TestGatewayAppBase):
"""Tests gateway behavior when notebook download is allowed."""
Expand Down

0 comments on commit 0b611e5

Please sign in to comment.