]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/server/starlette/server.py
These days the OSM wikipedia tab no longer contains URLs
[nominatim.git] / nominatim / server / starlette / server.py
1 # SPDX-License-Identifier: GPL-2.0-only
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2023 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Server implementation using the starlette webserver framework.
9 """
10 from typing import Any, Optional, Mapping, Callable, cast, Coroutine
11 from pathlib import Path
12
13 from starlette.applications import Starlette
14 from starlette.routing import Route
15 from starlette.exceptions import HTTPException
16 from starlette.responses import Response
17 from starlette.requests import Request
18 from starlette.middleware import Middleware
19 from starlette.middleware.cors import CORSMiddleware
20
21 from nominatim.api import NominatimAPIAsync
22 import nominatim.api.v1 as api_impl
23 from nominatim.config import Configuration
24
25 class ParamWrapper(api_impl.ASGIAdaptor):
26     """ Adaptor class for server glue to Starlette framework.
27     """
28
29     def __init__(self, request: Request) -> None:
30         self.request = request
31
32
33     def get(self, name: str, default: Optional[str] = None) -> Optional[str]:
34         return self.request.query_params.get(name, default=default)
35
36
37     def get_header(self, name: str, default: Optional[str] = None) -> Optional[str]:
38         return self.request.headers.get(name, default)
39
40
41     def error(self, msg: str, status: int = 400) -> HTTPException:
42         return HTTPException(status, detail=msg,
43                              headers={'content-type': self.content_type})
44
45
46     def create_response(self, status: int, output: str) -> Response:
47         return Response(output, status_code=status, media_type=self.content_type)
48
49
50     def config(self) -> Configuration:
51         return cast(Configuration, self.request.app.state.API.config)
52
53
54 def _wrap_endpoint(func: api_impl.EndpointFunc)\
55         -> Callable[[Request], Coroutine[Any, Any, Response]]:
56     async def _callback(request: Request) -> Response:
57         return cast(Response, await func(request.app.state.API, ParamWrapper(request)))
58
59     return _callback
60
61
62 def get_application(project_dir: Path,
63                     environ: Optional[Mapping[str, str]] = None,
64                     debug: bool = True) -> Starlette:
65     """ Create a Nominatim falcon ASGI application.
66     """
67     config = Configuration(project_dir, environ)
68
69     routes = []
70     legacy_urls = config.get_bool('SERVE_LEGACY_URLS')
71     for name, func in api_impl.ROUTES:
72         endpoint = _wrap_endpoint(func)
73         routes.append(Route(f"/{name}", endpoint=endpoint))
74         if legacy_urls:
75             routes.append(Route(f"/{name}.php", endpoint=endpoint))
76
77     middleware = []
78     if config.get_bool('CORS_NOACCESSCONTROL'):
79         middleware.append(Middleware(CORSMiddleware, allow_origins=['*']))
80
81     async def _shutdown() -> None:
82         await app.state.API.close()
83
84     app = Starlette(debug=debug, routes=routes, middleware=middleware,
85                     on_shutdown=[_shutdown])
86
87     app.state.API = NominatimAPIAsync(project_dir, environ)
88
89     return app
90
91
92 def run_wsgi() -> Starlette:
93     """ Entry point for uvicorn.
94     """
95     return get_application(Path('.'), debug=False)