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

Update error handling on APIHandlers #2853

Merged
merged 4 commits into from
Sep 25, 2017
Merged
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
61 changes: 36 additions & 25 deletions notebook/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import re
import sys
import traceback
import types
import warnings
try:
# py3
from http.client import responses
Expand Down Expand Up @@ -450,6 +452,34 @@ def prepare(self):
raise web.HTTPError(404)
return super(APIHandler, self).prepare()

def write_error(self, status_code, **kwargs):
"""APIHandler errors are JSON, not human pages"""
self.set_header('Content-Type', 'application/json')
message = responses.get(status_code, 'Unknown HTTP Error')
reply = {
'message': message,
}
exc_info = kwargs.get('exc_info')
if exc_info:
e = exc_info[1]
if isinstance(e, HTTPError):
reply['message'] = e.log_message or message
else:
reply['message'] = 'Unhandled error'
reply['traceback'] = ''.join(traceback.format_exception(*exc_info))
self.log.warning(reply['message'])
self.finish(json.dumps(reply))

def get_current_user(self):
"""Raise 403 on API handlers instead of redirecting to human login page"""
# preserve _user_cache so we don't raise more than once
if hasattr(self, '_user_cache'):
return self._user_cache
self._user_cache = user = super(APIHandler, self).get_current_user()
if user is None:
raise web.HTTPError(403)
return user

@property
def content_security_policy(self):
csp = '; '.join([
Expand Down Expand Up @@ -547,32 +577,14 @@ def json_errors(method):
2. Create and return a JSON body with a message field describing
the error in a human readable form.
"""
warnings.warn('@json_errors is deprecated in notebook 5.2.0. Subclass APIHandler instead.',
DeprecationWarning,
stacklevel=2,
)
@functools.wraps(method)
@gen.coroutine
def wrapper(self, *args, **kwargs):
try:
result = yield gen.maybe_future(method(self, *args, **kwargs))
except web.HTTPError as e:
self.set_header('Content-Type', 'application/json')
status = e.status_code
message = e.log_message
self.log.warning(message)
self.set_status(e.status_code)
reply = dict(message=message, reason=e.reason)
self.finish(json.dumps(reply))
except Exception:
self.set_header('Content-Type', 'application/json')
self.log.error("Unhandled error in API request", exc_info=True)
status = 500
message = "Unknown server error"
t, value, tb = sys.exc_info()
self.set_status(status)
tb_text = ''.join(traceback.format_exception(t, value, tb))
reply = dict(message=message, reason=None, traceback=tb_text)
self.finish(json.dumps(reply))
else:
# FIXME: can use regular return in generators in py3
raise gen.Return(result)
self.write_error = types.MethodType(APIHandler.write_error, self)
return method(self, *args, **kwargs)
return wrapper


Expand Down Expand Up @@ -643,7 +655,6 @@ def validate_absolute_path(self, root, absolute_path):

class APIVersionHandler(APIHandler):

@json_errors
def get(self):
# not authenticated, so give as few info as possible
self.finish(json.dumps({"version":notebook.__version__}))
Expand Down
3 changes: 1 addition & 2 deletions notebook/services/api/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from tornado import gen, web

from ...base.handlers import IPythonHandler, APIHandler, json_errors
from ...base.handlers import IPythonHandler, APIHandler
from notebook._tz import utcfromtimestamp, isoformat

import os
Expand All @@ -28,7 +28,6 @@ class APIStatusHandler(APIHandler):

_track_activity = False

@json_errors
@web.authenticated
@gen.coroutine
def get(self):
Expand Down
5 changes: 1 addition & 4 deletions notebook/services/config/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@
from tornado import web

from ipython_genutils.py3compat import PY3
from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler

class ConfigHandler(APIHandler):

@json_errors
@web.authenticated
def get(self, section_name):
self.set_header("Content-Type", 'application/json')
self.finish(json.dumps(self.config_manager.get(section_name)))

@json_errors
@web.authenticated
def put(self, section_name):
data = self.get_json_body() # Will raise 400 if content is not valid JSON
self.config_manager.set(section_name, data)
self.set_status(204)

@json_errors
@web.authenticated
def patch(self, section_name):
new_data = self.get_json_body()
Expand Down
12 changes: 1 addition & 11 deletions notebook/services/contents/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from jupyter_client.jsonutil import date_default

from notebook.base.handlers import (
IPythonHandler, APIHandler, json_errors, path_regex,
IPythonHandler, APIHandler, path_regex,
)


Expand Down Expand Up @@ -87,7 +87,6 @@ def _finish_model(self, model, location=True):
self.set_header('Content-Type', 'application/json')
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def get(self, path=''):
Expand Down Expand Up @@ -115,7 +114,6 @@ def get(self, path=''):
validate_model(model, expect_content=content)
self._finish_model(model, location=False)

@json_errors
@web.authenticated
@gen.coroutine
def patch(self, path=''):
Expand Down Expand Up @@ -168,7 +166,6 @@ def _save(self, model, path):
validate_model(model, expect_content=False)
self._finish_model(model)

@json_errors
@web.authenticated
@gen.coroutine
def post(self, path=''):
Expand Down Expand Up @@ -204,7 +201,6 @@ def post(self, path=''):
else:
yield self._new_untitled(path)

@json_errors
@web.authenticated
@gen.coroutine
def put(self, path=''):
Expand All @@ -230,7 +226,6 @@ def put(self, path=''):
else:
yield gen.maybe_future(self._new_untitled(path))

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, path=''):
Expand All @@ -244,7 +239,6 @@ def delete(self, path=''):

class CheckpointsHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self, path=''):
Expand All @@ -254,7 +248,6 @@ def get(self, path=''):
data = json.dumps(checkpoints, default=date_default)
self.finish(data)

@json_errors
@web.authenticated
@gen.coroutine
def post(self, path=''):
Expand All @@ -271,7 +264,6 @@ def post(self, path=''):

class ModifyCheckpointsHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def post(self, path, checkpoint_id):
Expand All @@ -281,7 +273,6 @@ def post(self, path, checkpoint_id):
self.set_status(204)
self.finish()

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, path, checkpoint_id):
Expand Down Expand Up @@ -310,7 +301,6 @@ def get(self, path):
class TrustNotebooksHandler(IPythonHandler):
""" Handles trust/signing of notebooks """

@json_errors
@web.authenticated
@gen.coroutine
def post(self,path=''):
Expand Down
7 changes: 1 addition & 6 deletions notebook/services/kernels/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,20 @@
from ipython_genutils.py3compat import cast_unicode
from notebook.utils import url_path_join, url_escape

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from ...base.zmqhandlers import AuthenticatedZMQStreamHandler, deserialize_binary_message

from jupyter_client import protocol_version as client_protocol_version

class MainKernelHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self):
km = self.kernel_manager
kernels = yield gen.maybe_future(km.list_kernels())
self.finish(json.dumps(kernels, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def post(self):
Expand All @@ -56,15 +54,13 @@ def post(self):

class KernelHandler(APIHandler):

@json_errors
@web.authenticated
def get(self, kernel_id):
km = self.kernel_manager
km._check_kernel_id(kernel_id)
model = km.kernel_model(kernel_id)
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, kernel_id):
Expand All @@ -76,7 +72,6 @@ def delete(self, kernel_id):

class KernelActionHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def post(self, kernel_id, action):
Expand Down
4 changes: 1 addition & 3 deletions notebook/services/kernelspecs/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from tornado import web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from ...utils import url_path_join, url_unescape

def kernelspec_model(handler, name):
Expand Down Expand Up @@ -45,7 +45,6 @@ def kernelspec_model(handler, name):

class MainKernelSpecHandler(APIHandler):

@json_errors
@web.authenticated
def get(self):
ksm = self.kernel_spec_manager
Expand All @@ -66,7 +65,6 @@ def get(self):

class KernelSpecHandler(APIHandler):

@json_errors
@web.authenticated
def get(self, kernel_name):
try:
Expand Down
3 changes: 1 addition & 2 deletions notebook/services/nbconvert/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from tornado import web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler

class NbconvertRootHandler(APIHandler):

@json_errors
@web.authenticated
def get(self):
try:
Expand Down
3 changes: 1 addition & 2 deletions notebook/services/security/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from tornado import web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from . import csp_report_uri

class CSPReportHandler(APIHandler):
Expand All @@ -21,7 +21,6 @@ def check_xsrf_cookie(self):
# don't check XSRF for CSP reports
return

@json_errors
@web.authenticated
def post(self):
'''Log a content security policy violation report'''
Expand Down
7 changes: 1 addition & 6 deletions notebook/services/sessions/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

from tornado import gen, web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from jupyter_client.jsonutil import date_default
from notebook.utils import url_path_join
from jupyter_client.kernelspec import NoSuchKernel


class SessionRootHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self):
Expand All @@ -28,7 +27,6 @@ def get(self):
sessions = yield gen.maybe_future(sm.list_sessions())
self.finish(json.dumps(sessions, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def post(self):
Expand Down Expand Up @@ -90,7 +88,6 @@ def post(self):

class SessionHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self, session_id):
Expand All @@ -99,7 +96,6 @@ def get(self, session_id):
model = yield gen.maybe_future(sm.get_session(session_id=session_id))
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def patch(self, session_id):
Expand Down Expand Up @@ -153,7 +149,6 @@ def patch(self, session_id):
)
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, session_id):
Expand Down
Loading