From b62da236604352d6c8d693569652ab2c2d068d02 Mon Sep 17 00:00:00 2001 From: MinRK Date: Sun, 22 Dec 2013 10:15:38 -0800 Subject: [PATCH 1/8] render custom HTML for error pages --- IPython/html/base/handlers.py | 54 +++++++++++++++++++++++++++++------ IPython/html/notebookapp.py | 3 ++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index f63438a7f..c9333804f 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -24,7 +24,13 @@ import os import stat import sys import traceback +try: + # py3 + from http.client import responses +except ImportError: + from httplib import responses +from jinja2 import TemplateNotFound from tornado import web try: @@ -44,14 +50,7 @@ UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768) # Top-level handlers #----------------------------------------------------------------------------- -class RequestHandler(web.RequestHandler): - """RequestHandler with default variable setting.""" - - def render(*args, **kwargs): - kwargs.setdefault('message', '') - return web.RequestHandler.render(*args, **kwargs) - -class AuthenticatedHandler(RequestHandler): +class AuthenticatedHandler(web.RequestHandler): """A RequestHandler with an authenticated user.""" def clear_login_cookie(self): @@ -209,6 +208,45 @@ class IPythonHandler(AuthenticatedHandler): raise web.HTTPError(400, u'Invalid JSON in body of request') return model + def get_error_html(self, status_code, **kwargs): + """render custom error pages""" + exception = kwargs.get('exception') + message = '' + status_message = responses.get(status_code, 'Unknown') + if exception: + # get the custom message, if defined + try: + message = exception.log_message % exception.args + except Exception: + pass + + # construct the custom reason, if defined + reason = getattr(exception, 'reason', '') + if reason: + status_message = reason + + # build template namespace + ns = dict( + status_code=status_code, + status_message=status_message, + message=message, + exception=exception, + ) + + # render the template + try: + html = self.render_template('%s.html' % status_code, **ns) + except TemplateNotFound: + self.log.debug("No template for %d", status_code) + html = self.render_template('error.html', **ns) + return html + + +class Template404(IPythonHandler): + """Render our 404 template""" + def prepare(self): + raise web.HTTPError(404) + class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler): """static files should only be accessible when logged in""" diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index 0dc0dc258..85b3fc26f 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -61,6 +61,7 @@ from tornado import web # Our own libraries from IPython.html import DEFAULT_STATIC_FILES_PATH +from .base.handlers import Template404 from .services.kernels.kernelmanager import MappingKernelManager from .services.notebooks.nbmanager import NotebookManager @@ -208,6 +209,8 @@ class NotebookWebApplication(web.Application): pattern = url_path_join(settings['base_project_url'], handler[0]) new_handler = tuple([pattern] + list(handler[1:])) new_handlers.append(new_handler) + # add 404 on the end, which will catch everything that falls through + new_handlers.append((r'(.*)', Template404)) return new_handlers From f9dc2f7b7ee11bbed9e97b9d501ce61b2120aed5 Mon Sep 17 00:00:00 2001 From: MinRK Date: Sun, 22 Dec 2013 17:46:53 -0800 Subject: [PATCH 2/8] catch pandoc failures in nbconvert handlers --- IPython/html/nbconvert/handlers.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/IPython/html/nbconvert/handlers.py b/IPython/html/nbconvert/handlers.py index b7197a717..9a65a023c 100644 --- a/IPython/html/nbconvert/handlers.py +++ b/IPython/html/nbconvert/handlers.py @@ -62,8 +62,11 @@ class NbconvertFileHandler(IPythonHandler): info = os.stat(os_path) self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime)) - - output, resources = exporter.from_filename(os_path) + + try: + output, resources = exporter.from_filename(os_path) + except Exception as e: + raise web.HTTPError(500, "nbconvert failed: %s" % e) if respond_zip(self, name, output, resources): return @@ -90,8 +93,11 @@ class NbconvertPostHandler(IPythonHandler): model = self.get_json_body() nbnode = to_notebook_json(model['content']) - - output, resources = exporter.from_notebook_node(nbnode) + + try: + output, resources = exporter.from_notebook_node(nbnode) + except Exception as e: + raise web.HTTPError(500, "nbconvert failed: %s" % e) if respond_zip(self, nbnode.metadata.name, output, resources): return From 84df1a5e267a2f0928f754556852627a19694537 Mon Sep 17 00:00:00 2001 From: MinRK Date: Sun, 22 Dec 2013 17:58:15 -0800 Subject: [PATCH 3/8] add error page templates --- IPython/html/templates/404.html | 5 +++++ IPython/html/templates/error.html | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 IPython/html/templates/404.html create mode 100644 IPython/html/templates/error.html diff --git a/IPython/html/templates/404.html b/IPython/html/templates/404.html new file mode 100644 index 000000000..733505185 --- /dev/null +++ b/IPython/html/templates/404.html @@ -0,0 +1,5 @@ +{% extends "error.html" %} +{% block error_detail %} +

You are requesting a page that does not exist!

+{% endblock %} + diff --git a/IPython/html/templates/error.html b/IPython/html/templates/error.html new file mode 100644 index 000000000..2a28dae36 --- /dev/null +++ b/IPython/html/templates/error.html @@ -0,0 +1,31 @@ +{% extends "page.html" %} + +{% block login_widget %} +{% endblock %} + +{% block stylesheet %} +{{super()}} + +{% endblock %} +{% block site %} + +
+ {% block h1_error %} +

{{status_code}} : {{status_message}}

+ {% endblock h1_error %} + {% block error_detail %} + {% if message %} +

The error was:

+
+
{{message | replace('\n', '
')}}
+
+ {% endif %} + {% endblock %} + + +{% endblock %} From 21632ac564c13e9b89647688bc55a963cf3f5adc Mon Sep 17 00:00:00 2001 From: MinRK Date: Sun, 22 Dec 2013 17:58:27 -0800 Subject: [PATCH 4/8] add error css --- IPython/html/static/base/less/error.less | 20 ++++++++++++++++++++ IPython/html/static/base/less/style.less | 2 +- IPython/html/static/style/ipython.min.css | 4 ++++ IPython/html/static/style/style.min.css | 4 ++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 IPython/html/static/base/less/error.less diff --git a/IPython/html/static/base/less/error.less b/IPython/html/static/base/less/error.less new file mode 100644 index 000000000..0a1eadb0d --- /dev/null +++ b/IPython/html/static/base/less/error.less @@ -0,0 +1,20 @@ +div.error { + margin: 2em; + text-align: center; +} + +div.error > h1 { + font-size: 500%; + line-height: normal; +} + +div.error > p { + font-size: 200%; + line-height: normal; +} + +div.traceback-wrapper { + text-align: left; + max-width: 800px; + margin: auto; +} diff --git a/IPython/html/static/base/less/style.less b/IPython/html/static/base/less/style.less index 18af1959d..40c5b965a 100644 --- a/IPython/html/static/base/less/style.less +++ b/IPython/html/static/base/less/style.less @@ -1,4 +1,4 @@ @import "variables.less"; @import "mixins.less"; @import "flexbox.less"; - +@import "error.less"; diff --git a/IPython/html/static/style/ipython.min.css b/IPython/html/static/style/ipython.min.css index a26bd1f65..00d99e2dc 100644 --- a/IPython/html/static/style/ipython.min.css +++ b/IPython/html/static/style/ipython.min.css @@ -18,6 +18,10 @@ .start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;} .end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;} .center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;} +div.error{margin:2em;text-align:center;} +div.error>h1{font-size:500%;line-height:normal;} +div.error>p{font-size:200%;line-height:normal;} +div.traceback-wrapper{text-align:left;max-width:800px;margin:auto;} .center-nav{display:inline-block;margin-bottom:-4px;} .alternate_upload{background-color:none;display:inline;} .alternate_upload.form{padding:0;margin:0;} diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css index 19f14f54d..46e64d584 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -1385,6 +1385,10 @@ ul.icons-ul{list-style-type:none;text-indent:-0.7142857142857143em;margin-left:2 .start{-webkit-box-pack:start;-moz-box-pack:start;box-pack:start;} .end{-webkit-box-pack:end;-moz-box-pack:end;box-pack:end;} .center{-webkit-box-pack:center;-moz-box-pack:center;box-pack:center;} +div.error{margin:2em;text-align:center;} +div.error>h1{font-size:500%;line-height:normal;} +div.error>p{font-size:200%;line-height:normal;} +div.traceback-wrapper{text-align:left;max-width:800px;margin:auto;} body{background-color:white;position:absolute;left:0px;right:0px;top:0px;bottom:0px;overflow:visible;} div#header{display:none;} #ipython_notebook{padding-left:16px;} From 91d332692357c8bfe20323cee266868d810349c7 Mon Sep 17 00:00:00 2001 From: MinRK Date: Mon, 23 Dec 2013 12:35:51 -0800 Subject: [PATCH 5/8] allow notebook to start without nbconvert catches import / key errors and turns them into proper http errors --- IPython/html/nbconvert/handlers.py | 23 ++++++++++++++++++--- IPython/html/services/nbconvert/handlers.py | 5 ++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/IPython/html/nbconvert/handlers.py b/IPython/html/nbconvert/handlers.py index 9a65a023c..dc4d2e48c 100644 --- a/IPython/html/nbconvert/handlers.py +++ b/IPython/html/nbconvert/handlers.py @@ -6,7 +6,7 @@ from tornado import web from ..base.handlers import IPythonHandler, notebook_path_regex from IPython.nbformat.current import to_notebook_json -from IPython.nbconvert.exporters.export import exporter_map + from IPython.utils import tz from IPython.utils.py3compat import cast_bytes @@ -47,13 +47,30 @@ def respond_zip(handler, name, output, resources): handler.finish(buffer.getvalue()) return True +def get_exporter(format, **kwargs): + """get an exporter, raising appropriate errors""" + # if this fails, will raise 500 + try: + from IPython.nbconvert.exporters.export import exporter_map + except ImportError as e: + raise web.HTTPError(500, "Could not import nbconvert: %s" % e) + + try: + Exporter = exporter_map[format] + except KeyError: + # should this be 400? + raise web.HTTPError(404, u"No exporter for format: %s" % format) + + return Exporter(**kwargs) + class NbconvertFileHandler(IPythonHandler): SUPPORTED_METHODS = ('GET',) @web.authenticated def get(self, format, path='', name=None): - exporter = exporter_map[format](config=self.config) + + exporter = get_exporter(format, config=self.config) path = path.strip('/') os_path = self.notebook_manager.get_os_path(name, path) @@ -89,7 +106,7 @@ class NbconvertPostHandler(IPythonHandler): @web.authenticated def post(self, format): - exporter = exporter_map[format](config=self.config) + exporter = get_exporter(format, config=self.config) model = self.get_json_body() nbnode = to_notebook_json(model['content']) diff --git a/IPython/html/services/nbconvert/handlers.py b/IPython/html/services/nbconvert/handlers.py index e2ced712b..fb79aa54d 100644 --- a/IPython/html/services/nbconvert/handlers.py +++ b/IPython/html/services/nbconvert/handlers.py @@ -3,7 +3,10 @@ import json from tornado import web from ...base.handlers import IPythonHandler, json_errors -from IPython.nbconvert.exporters.export import exporter_map +try: + from IPython.nbconvert.exporters.export import exporter_map +except ImportError: + exporter_map = {} class NbconvertRootHandler(IPythonHandler): SUPPORTED_METHODS = ('GET',) From 7ae363f6d1bd4da7fb2751d7c352aedbb75f4456 Mon Sep 17 00:00:00 2001 From: MinRK Date: Tue, 7 Jan 2014 15:15:57 -0800 Subject: [PATCH 6/8] turn missing dependencies in nbconvert to 500 errors pygments is the only such example at this time --- IPython/html/nbconvert/handlers.py | 5 ++++- IPython/html/services/nbconvert/handlers.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/IPython/html/nbconvert/handlers.py b/IPython/html/nbconvert/handlers.py index dc4d2e48c..2fb6f4586 100644 --- a/IPython/html/nbconvert/handlers.py +++ b/IPython/html/nbconvert/handlers.py @@ -61,7 +61,10 @@ def get_exporter(format, **kwargs): # should this be 400? raise web.HTTPError(404, u"No exporter for format: %s" % format) - return Exporter(**kwargs) + try: + return Exporter(**kwargs) + except Exception as e: + raise web.HTTPError(500, "Could not construct Exporter: %s" % e) class NbconvertFileHandler(IPythonHandler): diff --git a/IPython/html/services/nbconvert/handlers.py b/IPython/html/services/nbconvert/handlers.py index fb79aa54d..1c74de5d6 100644 --- a/IPython/html/services/nbconvert/handlers.py +++ b/IPython/html/services/nbconvert/handlers.py @@ -3,10 +3,6 @@ import json from tornado import web from ...base.handlers import IPythonHandler, json_errors -try: - from IPython.nbconvert.exporters.export import exporter_map -except ImportError: - exporter_map = {} class NbconvertRootHandler(IPythonHandler): SUPPORTED_METHODS = ('GET',) @@ -14,6 +10,10 @@ class NbconvertRootHandler(IPythonHandler): @web.authenticated @json_errors def get(self): + try: + from IPython.nbconvert.exporters.export import exporter_map + except ImportError as e: + raise web.HTTPError(500, "Could not import nbconvert: %s" % e) res = {} for format, exporter in exporter_map.items(): res[format] = info = {} From b7563aec7cfb2b22537c17d03355384d346a201d Mon Sep 17 00:00:00 2001 From: MinRK Date: Tue, 7 Jan 2014 15:16:13 -0800 Subject: [PATCH 7/8] be more specific about unknown status codes per review --- IPython/html/base/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index c9333804f..14b9ee410 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -212,7 +212,7 @@ class IPythonHandler(AuthenticatedHandler): """render custom error pages""" exception = kwargs.get('exception') message = '' - status_message = responses.get(status_code, 'Unknown') + status_message = responses.get(status_code, 'Unknown HTTP Error') if exception: # get the custom message, if defined try: From 5c9dfd9b5802e785306d50624f7d4646cb7b9d02 Mon Sep 17 00:00:00 2001 From: MinRK Date: Wed, 8 Jan 2014 15:03:28 -0800 Subject: [PATCH 8/8] remove unnecessary conversion of newline to br tag relic from when I was using div, not pre --- IPython/html/templates/error.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/templates/error.html b/IPython/html/templates/error.html index 2a28dae36..bedf06c2e 100644 --- a/IPython/html/templates/error.html +++ b/IPython/html/templates/error.html @@ -22,7 +22,7 @@ div#header, div#site { {% if message %}

The error was:

-
{{message | replace('\n', '
')}}
+
{{message}}
{% endif %} {% endblock %}