diff --git a/localstack/aws/client.py b/localstack/aws/client.py index 726473c63b70c..37d18820380af 100644 --- a/localstack/aws/client.py +++ b/localstack/aws/client.py @@ -6,9 +6,11 @@ from botocore.model import OperationModel from botocore.parsers import ResponseParser, ResponseParserFactory -from werkzeug import Response +from werkzeug import Request, Response from localstack.aws.api import CommonServiceException, ServiceException, ServiceResponse +from localstack.aws.gateway import Gateway +from localstack.http.client import HttpClient from localstack.runtime import hooks from localstack.utils.patch import patch @@ -220,3 +222,21 @@ def raise_service_exception(response: Response, parsed_response: Dict) -> None: """ if service_exception := parse_service_exception(response, parsed_response): raise service_exception + + +class GatewayClient(HttpClient): + """ + HttpClient implementation that injects the request directly into the Gateway. + """ + + gateway: Gateway + + def __init__(self, gateway: Gateway): + self.gateway = gateway + + def request(self, request: Request, server: str | None = None) -> Response: + from localstack.http.response import Response + + response = Response() + self.gateway.process(request, response) + return response diff --git a/localstack/aws/serving/edge.py b/localstack/aws/serving/edge.py index f93441e077851..234585425eea1 100644 --- a/localstack/aws/serving/edge.py +++ b/localstack/aws/serving/edge.py @@ -1,6 +1,5 @@ from localstack.http.hypercorn import GatewayServer from localstack.runtime.shutdown import SHUTDOWN_HANDLERS -from localstack.services.plugins import SERVICE_PLUGINS def serve_gateway(bind_address, port, use_ssl, asynchronous=False): @@ -8,12 +7,10 @@ def serve_gateway(bind_address, port, use_ssl, asynchronous=False): Implementation of the edge.do_start_edge_proxy interface to start a Hypercorn server instance serving the LocalstackAwsGateway. """ - from localstack.aws.app import LocalstackAwsGateway - - gateway = LocalstackAwsGateway(SERVICE_PLUGINS) + from localstack.runtime import components # start serving gateway - server = GatewayServer(gateway, port, bind_address, use_ssl) + server = GatewayServer(components.gateway, port, bind_address, use_ssl) server.start() # with the current way the infrastructure is started, this is the easiest way to shut down the server correctly diff --git a/localstack/runtime/components.py b/localstack/runtime/components.py new file mode 100644 index 0000000000000..c7893e0cfb655 --- /dev/null +++ b/localstack/runtime/components.py @@ -0,0 +1,57 @@ +""" +Core components of the runtime available as global singletons. +""" +from functools import cached_property +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from localstack.aws.app import LocalstackAwsGateway + from localstack.services.plugins import ServicePluginManager + +gateway: "LocalstackAwsGateway" +"""The Gateway processes incoming HTTP request and dispatches them to AWS services or internal resources""" + +service_plugin_manager: "ServicePluginManager" +"""The plugin manager """ + + +class RuntimeContainer: + """ + Class that provides the core runtime components as singleton factories. + """ + + @cached_property + def service_plugin_manager(self) -> "ServicePluginManager": + # TODO: migrate creation here + from localstack.services.plugins import SERVICE_PLUGINS + + return SERVICE_PLUGINS + + @cached_property + def gateway(self) -> "LocalstackAwsGateway": + from localstack.aws.app import LocalstackAwsGateway + + return LocalstackAwsGateway(self.service_plugin_manager) + + +_container = RuntimeContainer() + + +def __getattr__(name) -> Any: + """ + Provides dynamic and lazy access to runtime components. Can be used like this:: + + from localstack.runtime import components + + assert isinstance(components.gateway, LocalstackAwsGateway) + + or even:: + + from localstack.runtime.components import gateway + + assert isinstance(gateway, LocalstackAwsGateway) + + :param name: the component to get + :return: the component if it exists + """ + return getattr(_container, name) diff --git a/localstack/services/s3/virtual_host.py b/localstack/services/s3/virtual_host.py index b9ad819af46a1..ad12e70578b3d 100644 --- a/localstack/services/s3/virtual_host.py +++ b/localstack/services/s3/virtual_host.py @@ -3,9 +3,9 @@ from urllib.parse import urlsplit, urlunsplit from localstack import config +from localstack.aws.client import GatewayClient from localstack.constants import LOCALHOST_HOSTNAME from localstack.http import Request, Response -from localstack.http.client import SimpleStreamingRequestsClient from localstack.http.proxy import Proxy from localstack.runtime import hooks from localstack.services.edge import ROUTER @@ -54,15 +54,18 @@ def __call__(self, request: Request, **kwargs) -> Response: def _create_proxy(self) -> Proxy: """ - Factory for creating proxy instance used when proxying s3 calls. + Factory for creating proxy instance used when proxying s3 calls, injects directly into the handler + chain. :return: a proxy instance """ + from localstack.runtime import components + return Proxy( forward_base_url=config.get_edge_url(), # do not preserve the Host when forwarding (to avoid an endless loop) preserve_host=False, - client=SimpleStreamingRequestsClient(), + client=GatewayClient(components.gateway), ) @staticmethod