|
10 | 10 | import re |
11 | 11 | import sys |
12 | 12 | import traceback |
| 13 | +import types |
| 14 | +import warnings |
13 | 15 | try: |
14 | 16 | # py3 |
15 | 17 | from http.client import responses |
@@ -450,6 +452,34 @@ def prepare(self): |
450 | 452 | raise web.HTTPError(404) |
451 | 453 | return super(APIHandler, self).prepare() |
452 | 454 |
|
| 455 | + def write_error(self, status_code, **kwargs): |
| 456 | + """APIHandler errors are JSON, not human pages""" |
| 457 | + self.set_header('Content-Type', 'application/json') |
| 458 | + message = responses.get(status_code, 'Unknown HTTP Error') |
| 459 | + reply = { |
| 460 | + 'message': message, |
| 461 | + } |
| 462 | + exc_info = kwargs.get('exc_info') |
| 463 | + if exc_info: |
| 464 | + e = exc_info[1] |
| 465 | + if isinstance(e, HTTPError): |
| 466 | + reply['message'] = e.log_message or message |
| 467 | + else: |
| 468 | + reply['message'] = 'Unhandled error' |
| 469 | + reply['traceback'] = ''.join(traceback.format_exception(*exc_info)) |
| 470 | + self.log.warning(reply['message']) |
| 471 | + self.finish(json.dumps(reply)) |
| 472 | + |
| 473 | + def get_current_user(self): |
| 474 | + """Raise 403 on API handlers instead of redirecting to human login page""" |
| 475 | + # preserve _user_cache so we don't raise more than once |
| 476 | + if hasattr(self, '_user_cache'): |
| 477 | + return self._user_cache |
| 478 | + self._user_cache = user = super(APIHandler, self).get_current_user() |
| 479 | + if user is None: |
| 480 | + raise web.HTTPError(403) |
| 481 | + return user |
| 482 | + |
453 | 483 | @property |
454 | 484 | def content_security_policy(self): |
455 | 485 | csp = '; '.join([ |
@@ -547,32 +577,14 @@ def json_errors(method): |
547 | 577 | 2. Create and return a JSON body with a message field describing |
548 | 578 | the error in a human readable form. |
549 | 579 | """ |
| 580 | + warnings.warn('@json_errors is deprecated in notebook 5.2.0. Subclass APIHandler instead.', |
| 581 | + DeprecationWarning, |
| 582 | + stacklevel=2, |
| 583 | + ) |
550 | 584 | @functools.wraps(method) |
551 | | - @gen.coroutine |
552 | 585 | def wrapper(self, *args, **kwargs): |
553 | | - try: |
554 | | - result = yield gen.maybe_future(method(self, *args, **kwargs)) |
555 | | - except web.HTTPError as e: |
556 | | - self.set_header('Content-Type', 'application/json') |
557 | | - status = e.status_code |
558 | | - message = e.log_message |
559 | | - self.log.warning(message) |
560 | | - self.set_status(e.status_code) |
561 | | - reply = dict(message=message, reason=e.reason) |
562 | | - self.finish(json.dumps(reply)) |
563 | | - except Exception: |
564 | | - self.set_header('Content-Type', 'application/json') |
565 | | - self.log.error("Unhandled error in API request", exc_info=True) |
566 | | - status = 500 |
567 | | - message = "Unknown server error" |
568 | | - t, value, tb = sys.exc_info() |
569 | | - self.set_status(status) |
570 | | - tb_text = ''.join(traceback.format_exception(t, value, tb)) |
571 | | - reply = dict(message=message, reason=None, traceback=tb_text) |
572 | | - self.finish(json.dumps(reply)) |
573 | | - else: |
574 | | - # FIXME: can use regular return in generators in py3 |
575 | | - raise gen.Return(result) |
| 586 | + self.write_error = types.MethodType(APIHandler.write_error, self) |
| 587 | + return method(self, *args, **kwargs) |
576 | 588 | return wrapper |
577 | 589 |
|
578 | 590 |
|
@@ -643,7 +655,6 @@ def validate_absolute_path(self, root, absolute_path): |
643 | 655 |
|
644 | 656 | class APIVersionHandler(APIHandler): |
645 | 657 |
|
646 | | - @json_errors |
647 | 658 | def get(self): |
648 | 659 | # not authenticated, so give as few info as possible |
649 | 660 | self.finish(json.dumps({"version":notebook.__version__})) |
|
0 commit comments