]> git.openstreetmap.org Git - nominatim.git/commitdiff
translate query timeouts into proper HTTP responses
authorSarah Hoffmann <lonvia@denofr.de>
Thu, 24 Aug 2023 13:55:05 +0000 (15:55 +0200)
committerSarah Hoffmann <lonvia@denofr.de>
Fri, 25 Aug 2023 06:50:03 +0000 (08:50 +0200)
Need to use a 503 here because a 408 (Request timeout) will motivate
browsers to immediately resent the request.

nominatim/server/falcon/server.py
nominatim/server/starlette/server.py

index e551e54256f531ddc1e101caa2ada639b09b05e4..f1030f5c82205c85b798e418f3f740343ce04951 100644 (file)
@@ -37,6 +37,17 @@ async def nominatim_error_handler(req: Request, resp: Response, #pylint: disable
     resp.content_type = exception.content_type
 
 
     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.
     """
 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 = 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:
 
     legacy_urls = api.config.get_bool('SERVE_LEGACY_URLS')
     for name, func in api_impl.ROUTES:
index 5567ac9c9b9e9986bafdfd4fdb2855c66e7e220b..19a9943c9ecce33d8184bcabe1a572bb1fe1e1b7 100644 (file)
@@ -7,14 +7,14 @@
 """
 Server implementation using the starlette webserver framework.
 """
 """
 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 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
 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
 
 
         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:
 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))
 
     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,
     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)
                     on_shutdown=[_shutdown])
 
     app.state.API = NominatimAPIAsync(project_dir, environ)