From: Sarah Hoffmann Date: Thu, 1 Dec 2022 16:58:22 +0000 (+0100) Subject: add sanic development server implementation X-Git-Tag: v4.3.0~118^2~14 X-Git-Url: https://git.openstreetmap.org./nominatim.git/commitdiff_plain/23dabad0b0c8c1ffd9b4973bfc9e1d739e6128d7?ds=inline add sanic development server implementation --- diff --git a/nominatim/cli.py b/nominatim/cli.py index 56ed6a07..c134ca18 100644 --- a/nominatim/cli.py +++ b/nominatim/cli.py @@ -197,10 +197,15 @@ class AdminServe: """\ Start a simple web server for serving the API. - This command starts the built-in PHP webserver to serve the website + This command starts a built-in webserver to serve the website from the current project directory. This webserver is only suitable for testing and development. Do not use it in production setups! + There are different webservers available. The default 'php' engine + runs the classic PHP frontend. 'sanic' and 'falcon' are Python servers + which run the new Python frontend code. This is highly experimental + at the moment and may not include the full API. + By the default, the webserver can be accessed at: http://127.0.0.1:8088 """ @@ -208,10 +213,31 @@ class AdminServe: group = parser.add_argument_group('Server arguments') group.add_argument('--server', default='127.0.0.1:8088', help='The address the server will listen to.') + group.add_argument('--engine', default='php', + choices=('php', 'sanic', 'falcon'), + help='Webserver framework to run. (default: php)') def run(self, args: NominatimArgs) -> int: - run_php_server(args.server, args.project_dir / 'website') + if args.engine == 'php': + run_php_server(args.server, args.project_dir / 'website') + elif args.engine == 'sanic': + import nominatim.server.sanic.server + + app = nominatim.server.sanic.server.get_application(args.project_dir) + + server_info = args.server.split(':', 1) + host = server_info[0] + if len(server_info) > 1: + if not server_info[1].isdigit(): + raise UsageError('Invalid format for --server parameter. Use :') + port = int(server_info[1]) + else: + port = 8088 + app.run(host=host, port=port, debug=True) + elif args.engine == 'falcon': + raise NotImplementedError('Support for falcon not yet available.') + return 0 diff --git a/nominatim/clicmd/args.py b/nominatim/clicmd/args.py index 15de72a5..e47287b3 100644 --- a/nominatim/clicmd/args.py +++ b/nominatim/clicmd/args.py @@ -127,6 +127,7 @@ class NominatimArgs: # Arguments to 'serve' server: str + engine: str # Arguments to 'special-phrases import_from_wiki: bool diff --git a/nominatim/result_formatter/base.py b/nominatim/result_formatter/base.py index 88f4d918..d77f4db8 100644 --- a/nominatim/result_formatter/base.py +++ b/nominatim/result_formatter/base.py @@ -28,6 +28,12 @@ class ResultFormatter(Generic[T]): return list(self.functions.keys()) + def supports_format(self, fmt: str) -> bool: + """ Check if the given format is supported by this formatter. + """ + return fmt in self.functions + + def format(self, result: T, fmt: str) -> str: """ Convert the given result into a string using the given format. diff --git a/nominatim/result_formatter/v1.py b/nominatim/result_formatter/v1.py index d14e3f6c..081f451e 100644 --- a/nominatim/result_formatter/v1.py +++ b/nominatim/result_formatter/v1.py @@ -28,7 +28,7 @@ def _format_status_json(result: StatusResult) -> str: out['status'] = result.status out['message'] = result.message if result.data_updated is not None: - out['data_updated'] = result.data_updated + out['data_updated'] = result.data_updated.isoformat() out['software_version'] = result.software_version if result.database_version is not None: out['database_version'] = result.database_version diff --git a/nominatim/server/__init__.py b/nominatim/server/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nominatim/server/sanic/__init__.py b/nominatim/server/sanic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nominatim/server/sanic/server.py b/nominatim/server/sanic/server.py new file mode 100644 index 00000000..9c75327e --- /dev/null +++ b/nominatim/server/sanic/server.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# This file is part of Nominatim. (https://nominatim.org) +# +# Copyright (C) 2022 by the Nominatim developer community. +# For a full list of authors see the git log. +""" +Server implementation using the sanic webserver framework. +""" +from pathlib import Path + +import sanic + +from nominatim.api import NominatimAPIAsync +from nominatim.apicmd.status import StatusResult +import nominatim.result_formatter.v1 as formatting + +api = sanic.Blueprint('NominatimAPI') + +CONTENT_TYPE = { + 'text': 'text/plain; charset=utf-8', + 'xml': 'text/xml; charset=utf-8' +} + +def usage_error(msg): + return sanic.response.text(msg, status=400) + + +def api_response(request, result): + body = request.ctx.formatter.format(result, request.ctx.format) + return sanic.response.text(body, + content_type=CONTENT_TYPE.get(request.ctx.format, 'application/json')) + + +@api.on_request +async def extract_format(request): + request.ctx.formatter = request.app.ctx.formatters[request.route.ctx.result_type] + + request.ctx.format = request.args.get('format', request.route.ctx.default_format) + if not request.ctx.formatter.supports_format(request.ctx.format): + return usage_error("Parameter 'format' must be one of: " + + ', '.join(request.ctx.formatter.list_formats())) + + +@api.get('/status', ctx_result_type=StatusResult, ctx_default_format='text') +async def status(request): + return api_response(request,await request.app.ctx.api.status()) + + +def get_application(project_dir: Path) -> sanic.Sanic: + app = sanic.Sanic("NominatimInstance") + + app.ctx.api = NominatimAPIAsync(project_dir) + app.ctx.formatters = {} + for rtype in (StatusResult, ): + app.ctx.formatters[rtype] = formatting.create(rtype) + + app.blueprint(api) + + return app + +