_exception_handler.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import typing
  2. from starlette._utils import is_async_callable
  3. from starlette.concurrency import run_in_threadpool
  4. from starlette.exceptions import HTTPException
  5. from starlette.requests import Request
  6. from starlette.types import (
  7. ASGIApp,
  8. ExceptionHandler,
  9. HTTPExceptionHandler,
  10. Message,
  11. Receive,
  12. Scope,
  13. Send,
  14. WebSocketExceptionHandler,
  15. )
  16. from starlette.websockets import WebSocket
  17. ExceptionHandlers = typing.Dict[typing.Any, ExceptionHandler]
  18. StatusHandlers = typing.Dict[int, ExceptionHandler]
  19. def _lookup_exception_handler(
  20. exc_handlers: ExceptionHandlers, exc: Exception
  21. ) -> typing.Optional[ExceptionHandler]:
  22. for cls in type(exc).__mro__:
  23. if cls in exc_handlers:
  24. return exc_handlers[cls]
  25. return None
  26. def wrap_app_handling_exceptions(
  27. app: ASGIApp, conn: typing.Union[Request, WebSocket]
  28. ) -> ASGIApp:
  29. exception_handlers: ExceptionHandlers
  30. status_handlers: StatusHandlers
  31. try:
  32. exception_handlers, status_handlers = conn.scope["starlette.exception_handlers"]
  33. except KeyError:
  34. exception_handlers, status_handlers = {}, {}
  35. async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None:
  36. response_started = False
  37. async def sender(message: Message) -> None:
  38. nonlocal response_started
  39. if message["type"] == "http.response.start":
  40. response_started = True
  41. await send(message)
  42. try:
  43. await app(scope, receive, sender)
  44. except Exception as exc:
  45. handler = None
  46. if isinstance(exc, HTTPException):
  47. handler = status_handlers.get(exc.status_code)
  48. if handler is None:
  49. handler = _lookup_exception_handler(exception_handlers, exc)
  50. if handler is None:
  51. raise exc
  52. if response_started:
  53. msg = "Caught handled exception, but response already started."
  54. raise RuntimeError(msg) from exc
  55. if scope["type"] == "http":
  56. nonlocal conn
  57. handler = typing.cast(HTTPExceptionHandler, handler)
  58. conn = typing.cast(Request, conn)
  59. if is_async_callable(handler):
  60. response = await handler(conn, exc)
  61. else:
  62. response = await run_in_threadpool(handler, conn, exc)
  63. await response(scope, receive, sender)
  64. elif scope["type"] == "websocket":
  65. handler = typing.cast(WebSocketExceptionHandler, handler)
  66. conn = typing.cast(WebSocket, conn)
  67. if is_async_callable(handler):
  68. await handler(conn, exc)
  69. else:
  70. await run_in_threadpool(handler, conn, exc)
  71. return wrapped_app