exceptions.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import typing
  2. from starlette._exception_handler import (
  3. ExceptionHandlers,
  4. StatusHandlers,
  5. wrap_app_handling_exceptions,
  6. )
  7. from starlette.exceptions import HTTPException, WebSocketException
  8. from starlette.requests import Request
  9. from starlette.responses import PlainTextResponse, Response
  10. from starlette.types import ASGIApp, Receive, Scope, Send
  11. from starlette.websockets import WebSocket
  12. class ExceptionMiddleware:
  13. def __init__(
  14. self,
  15. app: ASGIApp,
  16. handlers: typing.Optional[
  17. typing.Mapping[typing.Any, typing.Callable[[Request, Exception], Response]]
  18. ] = None,
  19. debug: bool = False,
  20. ) -> None:
  21. self.app = app
  22. self.debug = debug # TODO: We ought to handle 404 cases if debug is set.
  23. self._status_handlers: StatusHandlers = {}
  24. self._exception_handlers: ExceptionHandlers = {
  25. HTTPException: self.http_exception,
  26. WebSocketException: self.websocket_exception,
  27. }
  28. if handlers is not None:
  29. for key, value in handlers.items():
  30. self.add_exception_handler(key, value)
  31. def add_exception_handler(
  32. self,
  33. exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
  34. handler: typing.Callable[[Request, Exception], Response],
  35. ) -> None:
  36. if isinstance(exc_class_or_status_code, int):
  37. self._status_handlers[exc_class_or_status_code] = handler
  38. else:
  39. assert issubclass(exc_class_or_status_code, Exception)
  40. self._exception_handlers[exc_class_or_status_code] = handler
  41. async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
  42. if scope["type"] not in ("http", "websocket"):
  43. await self.app(scope, receive, send)
  44. return
  45. scope["starlette.exception_handlers"] = (
  46. self._exception_handlers,
  47. self._status_handlers,
  48. )
  49. conn: typing.Union[Request, WebSocket]
  50. if scope["type"] == "http":
  51. conn = Request(scope, receive, send)
  52. else:
  53. conn = WebSocket(scope, receive, send)
  54. await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  55. def http_exception(self, request: Request, exc: Exception) -> Response:
  56. assert isinstance(exc, HTTPException)
  57. if exc.status_code in {204, 304}:
  58. return Response(status_code=exc.status_code, headers=exc.headers)
  59. return PlainTextResponse(
  60. exc.detail, status_code=exc.status_code, headers=exc.headers
  61. )
  62. async def websocket_exception(self, websocket: WebSocket, exc: Exception) -> None:
  63. assert isinstance(exc, WebSocketException)
  64. await websocket.close(code=exc.code, reason=exc.reason) # pragma: no cover