diff --git a/CHANGELOG.md b/CHANGELOG.md index ca740b1..d2045a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ### Unreleased -N/A +- Allow virtual hosting by specifying an alternate hostname in the application + route pattern. +- Jetforce will no longer raise an exception when attempting to log dropped + connections or other malformed requests. ### v0.2.0 (2012-01-21) diff --git a/examples/virtualhost.py b/examples/virtualhost.py new file mode 100644 index 0000000..c74449f --- /dev/null +++ b/examples/virtualhost.py @@ -0,0 +1,31 @@ +""" +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.py b/jetforce.py index cbf484e..4ffa5ad 100755 --- a/jetforce.py +++ b/jetforce.py @@ -152,6 +152,7 @@ class RoutePattern: path: str = "" scheme: str = "gemini" + hostname: typing.Optional[str] = None strict_hostname: bool = True strict_port: bool = True @@ -161,7 +162,10 @@ class RoutePattern: """ Check if the given request URL matches this route pattern. """ - server_hostname = request.environ["HOSTNAME"] + if self.hostname is None: + server_hostname = request.environ["HOSTNAME"] + else: + server_hostname = self.hostname server_port = int(request.environ["SERVER_PORT"]) if self.strict_hostname and request.hostname != server_hostname: @@ -225,6 +229,7 @@ class JetforceApplication: self, path: str = "", scheme: str = "gemini", + hostname: typing.Optional[str] = None, strict_hostname: bool = True, strict_trailing_slash: bool = False, ) -> typing.Callable: @@ -238,7 +243,7 @@ class JetforceApplication: return Response(Status.SUCCESS, 'text/plain', 'Hello world!') """ route_pattern = RoutePattern( - path, scheme, strict_hostname, strict_trailing_slash + path, scheme, hostname, strict_hostname, strict_trailing_slash ) def wrap(func: typing.Callable) -> typing.Callable: @@ -591,14 +596,18 @@ class GeminiRequestHandler: """ Log a gemini request using a format derived from the Common Log Format. """ - self.server.log_message( - f"{self.remote_addr} " - f"[{time.strftime(self.TIMESTAMP_FORMAT, self.received_timestamp)}] " - f'"{self.url}" ' - f"{self.status} " - f'"{self.meta}" ' - f"{self.response_size}" - ) + try: + self.server.log_message( + f"{self.remote_addr} " + f"[{time.strftime(self.TIMESTAMP_FORMAT, self.received_timestamp)}] " + f'"{self.url}" ' + f"{self.status} " + f'"{self.meta}" ' + f"{self.response_size}" + ) + except AttributeError: + # Malformed request or dropped connection + pass class GeminiServer: