diff --git a/examples/vhost.py b/examples/vhost.py new file mode 100644 index 0000000..985a7bb --- /dev/null +++ b/examples/vhost.py @@ -0,0 +1,36 @@ +""" +A server that implements virtual hosting for multiple subdomains. + +This is a basic example of you how can run multiple apps from the same server +by creating a composite application. + +> jetforce-client gemini://apple.localhost --host localhost +> jetforce-client gemini://banana.localhost --host localhost +""" +from jetforce import GeminiServer, JetforceApplication, Response, Status +from jetforce.app.composite import CompositeApplication + +apple = JetforceApplication() + + +@apple.route() +def index(request): + return Response(Status.SUCCESS, "text/plain", "apple!") + + +banana = JetforceApplication() + + +@banana.route() +def index(request): + return Response(Status.SUCCESS, "text/plain", "banana!") + + +composite_app = CompositeApplication( + {"apple.localhost": apple, "banana.localhost": banana} +) + + +if __name__ == "__main__": + server = GeminiServer(composite_app) + server.run() diff --git a/examples/virtualhost.py b/examples/virtualhost.py deleted file mode 100644 index c74449f..0000000 --- a/examples/virtualhost.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -This is an example of using virtual hosting to serve URLs for multiple -subdomains from a single jetforce server. -""" -import asyncio - -import jetforce -from jetforce import Response, Status - -app = jetforce.JetforceApplication() - - -@app.route(hostname="apple.localhost") -def serve_apple_domain(request): - return Response(Status.SUCCESS, "text/plain", f"apple\n{request.path}") - - -@app.route(hostname="banana.localhost") -def serve_banana_domain(request): - return Response(Status.SUCCESS, "text/plain", f"banana\n{request.path}") - - -if __name__ == "__main__": - args = jetforce.command_line_parser().parse_args() - ssl_context = jetforce.make_ssl_context( - args.hostname, args.certfile, args.keyfile, args.cafile, args.capath - ) - server = jetforce.GeminiServer( - host=args.host, port=args.port, ssl_context=ssl_context, app=app - ) - asyncio.run(server.run()) diff --git a/jetforce/app/composite.py b/jetforce/app/composite.py new file mode 100644 index 0000000..1f52c53 --- /dev/null +++ b/jetforce/app/composite.py @@ -0,0 +1,50 @@ +import typing + +from .base import Request, Status + + +class CompositeApplication: + """ + Route requests between multiple applications by looking at the URL hostname. + + The primary intention of this class is enable virtual hosting by serving + two or more applications behind a single jetforce server. + """ + + def __init__(self, application_map: typing.Dict[typing.Optional[str], typing.Any]): + """ + Initialize the application by providing a mapping of hostname -> app + key pairs. A hostname of `None` is a special key that can be used as + a default if none of the others match. + + Example: + app = CompositeApplication( + { + "cats.com": cats_app, + "dogs.com": dogs_app, + None: other_animals_app, + } + ) + """ + self.application_map = application_map + + def __call__( + self, environ: dict, send_status: typing.Callable + ) -> typing.Iterator[bytes]: + try: + request = Request(environ) + except Exception: + send_status(Status.BAD_REQUEST, "Unrecognized URL format") + return + + if request.hostname in self.application_map: + environ["HOSTNAME"] = request.hostname + app = self.application_map[request.hostname] + yield from app(environ, send_status) + + elif None in self.application_map: + app = self.application_map[None] + yield from app(environ, send_status) + + else: + send_status(Status.PROXY_REQUEST_REFUSED, "Invalid hostname")