From: Sarah Hoffmann Date: Thu, 24 Aug 2023 13:55:05 +0000 (+0200) Subject: translate query timeouts into proper HTTP responses X-Git-Tag: v4.3.0~15^2~2 X-Git-Url: https://git.openstreetmap.org./nominatim.git/commitdiff_plain/5a2ebfcd4afcf43b82baaadf443922f7711bed5b translate query timeouts into proper HTTP responses Need to use a 503 here because a 408 (Request timeout) will motivate browsers to immediately resent the request. --- diff --git a/nominatim/server/falcon/server.py b/nominatim/server/falcon/server.py index e551e542..f1030f5c 100644 --- a/nominatim/server/falcon/server.py +++ b/nominatim/server/falcon/server.py @@ -37,6 +37,17 @@ async def nominatim_error_handler(req: Request, resp: Response, #pylint: disable resp.content_type = exception.content_type +async def timeout_error_handler(req: Request, resp: Response, #pylint: disable=unused-argument + exception: TimeoutError, #pylint: disable=unused-argument + _: Any) -> None: + """ Special error handler that passes message and content type as + per exception info. + """ + resp.status = 503 + resp.text = "Query took too long to process." + resp.content_type = 'text/plain; charset=utf-8' + + class ParamWrapper(api_impl.ASGIAdaptor): """ Adaptor class for server glue to Falcon framework. """ @@ -139,6 +150,7 @@ def get_application(project_dir: Path, app = App(cors_enable=api.config.get_bool('CORS_NOACCESSCONTROL'), middleware=middleware) app.add_error_handler(HTTPNominatimError, nominatim_error_handler) + app.add_error_handler(TimeoutError, timeout_error_handler) legacy_urls = api.config.get_bool('SERVE_LEGACY_URLS') for name, func in api_impl.ROUTES: diff --git a/nominatim/server/starlette/server.py b/nominatim/server/starlette/server.py index 5567ac9c..19a9943c 100644 --- a/nominatim/server/starlette/server.py +++ b/nominatim/server/starlette/server.py @@ -7,14 +7,14 @@ """ Server implementation using the starlette webserver framework. """ -from typing import Any, Optional, Mapping, Callable, cast, Coroutine +from typing import Any, Optional, Mapping, Callable, cast, Coroutine, Dict, Awaitable from pathlib import Path import datetime as dt from starlette.applications import Starlette from starlette.routing import Route from starlette.exceptions import HTTPException -from starlette.responses import Response +from starlette.responses import Response, PlainTextResponse from starlette.requests import Request from starlette.middleware import Middleware from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint @@ -110,6 +110,13 @@ class FileLoggingMiddleware(BaseHTTPMiddleware): return response +async def timeout_error(request: Request, #pylint: disable=unused-argument + _: Exception) -> Response: + """ Error handler for query timeouts. + """ + return PlainTextResponse("Query took too long to process.", status_code=503) + + def get_application(project_dir: Path, environ: Optional[Mapping[str, str]] = None, debug: bool = True) -> Starlette: @@ -136,10 +143,15 @@ def get_application(project_dir: Path, if log_file: middleware.append(Middleware(FileLoggingMiddleware, file_name=log_file)) + exceptions: Dict[Any, Callable[[Request, Exception], Awaitable[Response]]] = { + TimeoutError: timeout_error + } + async def _shutdown() -> None: await app.state.API.close() app = Starlette(debug=debug, routes=routes, middleware=middleware, + exception_handlers=exceptions, on_shutdown=[_shutdown]) app.state.API = NominatimAPIAsync(project_dir, environ)