1212import requests
1313from botocore .awsrequest import AWSPreparedRequest
1414from botocore .model import OperationModel
15- from localstack import config
1615from localstack import config as localstack_config
17- from localstack .aws .api import HttpRequest
1816from localstack .aws .protocol .parser import create_parser
1917from localstack .aws .spec import load_service
2018from localstack .config import external_service_url
2119from localstack .constants import AWS_REGION_US_EAST_1 , DOCKER_IMAGE_NAME_PRO
22- from localstack .http import Request
23- from localstack .utils .aws .aws_responses import requests_response
2420from localstack .utils .bootstrap import setup_logging
2521from localstack .utils .collections import select_attributes
2622from localstack .utils .container_utils .container_client import PortMappings
2723from localstack .utils .docker_utils import DOCKER_CLIENT , reserve_available_container_port
2824from localstack .utils .files import new_tmp_file , save_file
2925from localstack .utils .functions import run_safe
3026from localstack .utils .net import get_docker_host_from_container , get_free_tcp_port
31- from localstack .utils .server .http2_server import run_server
3227from localstack .utils .serving import Server
3328from localstack .utils .strings import short_uid , to_bytes , to_str , truncate
3429from localstack_ext .bootstrap .licensingv2 import ENV_LOCALSTACK_API_KEY , ENV_LOCALSTACK_AUTH_TOKEN
35- from requests import Response
30+ from werkzeug import Request , Response
31+ from werkzeug import serving as werkzeug_serving
3632
3733from aws_replicator import config as repl_config
3834from aws_replicator .client .utils import truncate_content
4137
4238LOG = logging .getLogger (__name__ )
4339LOG .setLevel (logging .INFO )
44- if config .DEBUG :
40+ if localstack_config .DEBUG :
4541 LOG .setLevel (logging .DEBUG )
4642
4743# TODO make configurable
@@ -62,17 +58,31 @@ def __init__(self, config: ProxyConfig, port: int = None):
6258 self .config = config
6359 port = port or get_free_tcp_port ()
6460 super ().__init__ (port = port )
61+ self ._server = None
6562
6663 def do_run (self ):
6764 self .register_in_instance ()
6865 bind_host = self .config .get ("bind_host" ) or DEFAULT_BIND_HOST
69- proxy = run_server (port = self .port , bind_addresses = [bind_host ], handler = self .proxy_request )
70- proxy .join ()
7166
72- def proxy_request (self , request : Request , data : bytes ) -> Response :
67+ # werkzeug uses under the hood a stdlib ``http.server.HTTPServer``, which should be enough to serve
68+ # a simple AWS proxy. if HTTP2 and websockets are ever needed, then we should move to a more
69+ # sophisticated runtime (like hypercorn or twisted)
70+ self ._server = werkzeug_serving .make_server (bind_host , self .port , self ._wsgi_app )
71+ self ._server .serve_forever ()
72+
73+ def do_shutdown (self ):
74+ if self ._server :
75+ self ._server .shutdown ()
76+
77+ @Request .application
78+ def _wsgi_app (self , request : Request ) -> Response :
79+ """A wsgi-compatible interface for serving the proxy server."""
80+ return self .proxy_request (request )
81+
82+ def proxy_request (self , request : Request ) -> Response :
7383 parsed = self ._extract_region_and_service (request .headers )
7484 if not parsed :
75- return requests_response ( "" , status_code = 400 )
85+ return Response ( status = 400 )
7686 region_name , service_name = parsed
7787 query_string = to_str (request .query_string or "" )
7888
@@ -85,13 +95,6 @@ def proxy_request(self, request: Request, data: bytes) -> Response:
8595 query_string ,
8696 )
8797
88- request = HttpRequest (
89- body = data ,
90- method = request .method ,
91- headers = request .headers ,
92- path = request .path ,
93- query_string = query_string ,
94- )
9598 session = boto3 .Session ()
9699 client = session .client (service_name , region_name = region_name )
97100
@@ -119,24 +122,23 @@ def proxy_request(self, request: Request, data: bytes) -> Response:
119122 # send request to upstream AWS
120123 result = client ._endpoint .make_request (operation_model , request_dict )
121124
122- # create response object - TODO: to be replaced with localstack.http.Response over time
123- response = requests_response (
125+ response = Response (
124126 result [0 ].content ,
125- status_code = result [0 ].status_code ,
127+ status = result [0 ].status_code ,
126128 headers = dict (result [0 ].headers ),
127129 )
128130
129131 LOG .debug (
130132 "Received response for service %s from AWS: %s - %s" ,
131133 service_name ,
132134 response .status_code ,
133- truncate_content (response .content , max_length = 500 ),
135+ truncate_content (response .data , max_length = 500 ),
134136 )
135137 return response
136138 except Exception as e :
137139 if LOG .isEnabledFor (logging .DEBUG ):
138140 LOG .exception ("Error when making request to AWS service %s: %s" , service_name , e )
139- return requests_response ( "" , status_code = 400 )
141+ return Response ( status = 400 )
140142
141143 def register_in_instance (self ):
142144 port = getattr (self , "port" , None )
@@ -156,7 +158,7 @@ def register_in_instance(self):
156158 raise
157159
158160 def _parse_aws_request (
159- self , request : HttpRequest , service_name : str , region_name : str , client
161+ self , request : Request , service_name : str , region_name : str , client
160162 ) -> Tuple [OperationModel , AWSPreparedRequest , Dict ]:
161163 parser = create_parser (load_service (service_name ))
162164 operation_model , parsed_request = parser .parse (request )
@@ -245,7 +247,7 @@ def _adjust_request_dict(self, service_name: str, request_dict: Dict):
245247 req_json ["QueueOwnerAWSAccountId" ] = account_id
246248 request_dict ["body" ] = to_bytes (json .dumps (req_json ))
247249
248- def _fix_headers (self , request : HttpRequest , service_name : str ):
250+ def _fix_headers (self , request : Request , service_name : str ):
249251 if service_name == "s3" :
250252 # fix the Host header, to avoid bucket addressing issues
251253 host = request .headers .get ("Host" ) or ""
0 commit comments