Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/middleware.py: 68%
34 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1from fastapi import FastAPI, Request, status
2from fastapi.encoders import jsonable_encoder
3from fastapi.middleware.cors import CORSMiddleware
4from fastapi.responses import JSONResponse
5from opal_common.config import opal_common_config
6from opal_common.logger import logger
7from pydantic import BaseModel
10class ErrorResponse(BaseModel):
11 error: str
14def get_response() -> JSONResponse:
15 error = ErrorResponse(error="Uncaught server exception")
16 json_error = jsonable_encoder(error.dict())
17 return JSONResponse(
18 content=json_error, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
19 )
22def register_default_server_exception_handler(app: FastAPI):
23 """Registers a default exception handler for HTTP 500 exceptions.
25 Since fastapi does not include CORS headers by default in 500
26 exceptions, we need to include them manually. Otherwise the frontend
27 cries on the wrong issue.
28 """
30 @app.exception_handler(status.HTTP_500_INTERNAL_SERVER_ERROR)
31 async def default_server_exception_handler(request: Request, exception: Exception):
32 response = get_response()
33 logger.exception("Uncaught server exception: {exc}", exc=exception)
35 # Since the CORSMiddleware is not executed when an unhandled server exception
36 # occurs, we need to manually set the CORS headers ourselves if we want the FE
37 # to receive a proper JSON 500, opposed to a CORS error.
38 # Setting CORS headers on server errors is a bit of a philosophical topic of
39 # discussion in many frameworks, and it is currently not handled in FastAPI.
40 # See dotnet core for a recent discussion, where ultimately it was
41 # decided to return CORS headers on server failures:
42 # https://github.com/dotnet/aspnetcore/issues/2378
43 origin = request.headers.get("origin")
45 if origin: 45 ↛ 48line 45 didn't jump to line 48 because the condition on line 45 was never true
46 # Have the middleware do the heavy lifting for us to parse
47 # all the config, then update our response headers
48 cors = CORSMiddleware(
49 app=app,
50 allow_origins=opal_common_config.ALLOWED_ORIGINS,
51 allow_credentials=True,
52 allow_methods=["*"],
53 allow_headers=["*"],
54 )
56 # Logic directly from Starlette's CORSMiddleware:
57 # https://github.com/encode/starlette/blob/master/starlette/middleware/cors.py#L152
59 response.headers.update(cors.simple_headers)
60 has_cookie = "cookie" in request.headers
62 # If request includes any cookie headers, then we must respond
63 # with the specific origin instead of '*'.
64 if cors.allow_all_origins and has_cookie:
65 response.headers["Access-Control-Allow-Origin"] = origin
67 # If we only allow specific origins, then we have to mirror back
68 # the Origin header in the response.
69 elif not cors.allow_all_origins and cors.is_allowed_origin(origin=origin):
70 response.headers["Access-Control-Allow-Origin"] = origin
71 response.headers.add_vary_header("Origin")
73 return response
76def configure_cors_middleware(app: FastAPI):
77 app.add_middleware(
78 CORSMiddleware,
79 allow_origins=opal_common_config.ALLOWED_ORIGINS,
80 allow_credentials=True,
81 allow_methods=["*"],
82 allow_headers=["*"],
83 )
86def configure_middleware(app: FastAPI):
87 register_default_server_exception_handler(app)
88 configure_cors_middleware(app)