Add composite application helper

This commit is contained in:
Michael Lazar 2020-05-18 23:52:05 -04:00
parent f02ea37762
commit 43907a0e44
3 changed files with 86 additions and 31 deletions

36
examples/vhost.py Normal file
View File

@ -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()

View File

@ -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())

50
jetforce/app/composite.py Normal file
View File

@ -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")