""" serve HTML page
TODO the RevealServer setting should be available at front end
"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import print_function
import os
import signal
import webbrowser
from tornado import web, ioloop, httpserver, log, gen
from tornado.httpclient import AsyncHTTPClient
from traitlets import Bool, Unicode, Int
from ipypublish.postprocessors.base import IPyPostProcessor
[docs]class ProxyHandler(web.RequestHandler):
"""handler the proxies requests from a local prefix to a CDN"""
[docs] @gen.coroutine
def get(self, prefix, url):
"""proxy a request to a CDN"""
proxy_url = "/".join([self.settings["cdn"], url])
client = self.settings["client"]
client.fetch(proxy_url)
response = yield client.fetch(proxy_url)
for header in [
"Content-Type",
"Cache-Control",
"Date",
"Last-Modified",
"Expires",
]:
if header in response.headers:
self.set_header(header, response.headers[header])
self.finish(response.body)
[docs]class RevealServer(IPyPostProcessor):
"""Post processor designed to serve files
Proxies reveal.js requests to a CDN if no local reveal.js is present
"""
@property
def allowed_mimetypes(self):
return "text/html"
@property
def requires_path(self):
return True
@property
def logger_name(self):
return "reveal-server"
open_in_browser = Bool(
True, help="Should the browser be opened automatically?"
).tag(config=True)
reveal_cdn = Unicode(
"https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.1.0",
help="""URL for reveal.js CDN.""",
).tag(config=True)
reveal_prefix = Unicode("reveal.js", help="URL prefix for reveal.js").tag(
config=True
)
ip = Unicode("127.0.0.1", help="The IP address to listen on.").tag(config=True)
port = Int(8000, help="port for the server to listen on.").tag(config=True)
[docs] def run_postprocess(self, stream, mimetype, filepath, resources):
"""Serve the build directory with a webserver."""
if not filepath.exists():
self.handle_error(
"the target file path does not exist: {}".format(filepath), IOError
)
# TODO rewrite this as pathlib
dirname, filename = os.path.split(str(filepath))
handlers = [
(r"/(.+)", web.StaticFileHandler, {"path": dirname}),
(r"/", web.RedirectHandler, {"url": "/%s" % filename}),
]
if "://" in self.reveal_prefix or self.reveal_prefix.startswith("//"):
# reveal specifically from CDN, nothing to do
pass
elif os.path.isdir(os.path.join(dirname, self.reveal_prefix)):
# reveal prefix exists
self.logger.info("Serving local %s", self.reveal_prefix)
self.logger.info("Serving local %s", self.reveal_prefix)
else:
self.logger.info(
"Redirecting %s requests to %s", self.reveal_prefix, self.reveal_cdn
)
self.logger.info(
"Redirecting %s requests to %s", self.reveal_prefix, self.reveal_cdn
)
handlers.insert(0, (r"/(%s)/(.*)" % self.reveal_prefix, ProxyHandler))
app = web.Application(handlers, cdn=self.reveal_cdn, client=AsyncHTTPClient())
# hook up tornado logging to our self.logger
log.app_log = self.logger
http_server = httpserver.HTTPServer(app)
# find an available port
port_attempts = list(range(10))
for port_attempt in port_attempts:
try:
url = "http://%s:%i/%s" % (self.ip, self.port, filename)
self.logger.info("Attempting to serve at %s" % url)
http_server.listen(self.port, address=self.ip)
break
except IOError:
self.port += 1
if port_attempt == port_attempts[-1]:
self.handle_error(
"no port available to launch slides on, " "try closing some slideshows",
IOError,
)
self.logger.info("Serving your slides at %s" % url)
self.logger.info("Use Control-C to stop this server")
# don't let people press ctrl-z, which leaves port open
def handler(signum, frame):
self.logger.info("Control-Z pressed, but ignored, use Control-C!")
signal.signal(signal.SIGTSTP, handler)
if self.open_in_browser:
# 2 opens the url in a new tab
webbrowser.open(url, new=2)
try:
ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
# dosen't look like line below is necessary
ioloop.IOLoop.instance().stop()
self.logger.info("\nInterrupted")
return stream, filepath, resources