12
12
import requests
13
13
from botocore .awsrequest import AWSPreparedRequest
14
14
from botocore .model import OperationModel
15
- from localstack import config
16
15
from localstack import config as localstack_config
17
- from localstack .aws .api import HttpRequest
18
16
from localstack .aws .protocol .parser import create_parser
19
17
from localstack .aws .spec import load_service
20
18
from localstack .config import external_service_url
21
19
from 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
24
20
from localstack .utils .bootstrap import setup_logging
25
21
from localstack .utils .collections import select_attributes
26
22
from localstack .utils .container_utils .container_client import PortMappings
27
23
from localstack .utils .docker_utils import DOCKER_CLIENT , reserve_available_container_port
28
24
from localstack .utils .files import new_tmp_file , save_file
29
25
from localstack .utils .functions import run_safe
30
26
from localstack .utils .net import get_docker_host_from_container , get_free_tcp_port
31
- from localstack .utils .server .http2_server import run_server
32
27
from localstack .utils .serving import Server
33
28
from localstack .utils .strings import short_uid , to_bytes , to_str , truncate
34
29
from 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
36
32
37
33
from aws_replicator import config as repl_config
38
34
from aws_replicator .client .utils import truncate_content
41
37
42
38
LOG = logging .getLogger (__name__ )
43
39
LOG .setLevel (logging .INFO )
44
- if config .DEBUG :
40
+ if localstack_config .DEBUG :
45
41
LOG .setLevel (logging .DEBUG )
46
42
47
43
# TODO make configurable
@@ -62,17 +58,31 @@ def __init__(self, config: ProxyConfig, port: int = None):
62
58
self .config = config
63
59
port = port or get_free_tcp_port ()
64
60
super ().__init__ (port = port )
61
+ self ._server = None
65
62
66
63
def do_run (self ):
67
64
self .register_in_instance ()
68
65
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 ()
71
66
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 :
73
83
parsed = self ._extract_region_and_service (request .headers )
74
84
if not parsed :
75
- return requests_response ( "" , status_code = 400 )
85
+ return Response ( status = 400 )
76
86
region_name , service_name = parsed
77
87
query_string = to_str (request .query_string or "" )
78
88
@@ -85,13 +95,6 @@ def proxy_request(self, request: Request, data: bytes) -> Response:
85
95
query_string ,
86
96
)
87
97
88
- request = HttpRequest (
89
- body = data ,
90
- method = request .method ,
91
- headers = request .headers ,
92
- path = request .path ,
93
- query_string = query_string ,
94
- )
95
98
session = boto3 .Session ()
96
99
client = session .client (service_name , region_name = region_name )
97
100
@@ -119,24 +122,23 @@ def proxy_request(self, request: Request, data: bytes) -> Response:
119
122
# send request to upstream AWS
120
123
result = client ._endpoint .make_request (operation_model , request_dict )
121
124
122
- # create response object - TODO: to be replaced with localstack.http.Response over time
123
- response = requests_response (
125
+ response = Response (
124
126
result [0 ].content ,
125
- status_code = result [0 ].status_code ,
127
+ status = result [0 ].status_code ,
126
128
headers = dict (result [0 ].headers ),
127
129
)
128
130
129
131
LOG .debug (
130
132
"Received response for service %s from AWS: %s - %s" ,
131
133
service_name ,
132
134
response .status_code ,
133
- truncate_content (response .content , max_length = 500 ),
135
+ truncate_content (response .data , max_length = 500 ),
134
136
)
135
137
return response
136
138
except Exception as e :
137
139
if LOG .isEnabledFor (logging .DEBUG ):
138
140
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 )
140
142
141
143
def register_in_instance (self ):
142
144
port = getattr (self , "port" , None )
@@ -156,7 +158,7 @@ def register_in_instance(self):
156
158
raise
157
159
158
160
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
160
162
) -> Tuple [OperationModel , AWSPreparedRequest , Dict ]:
161
163
parser = create_parser (load_service (service_name ))
162
164
operation_model , parsed_request = parser .parse (request )
@@ -245,7 +247,7 @@ def _adjust_request_dict(self, service_name: str, request_dict: Dict):
245
247
req_json ["QueueOwnerAWSAccountId" ] = account_id
246
248
request_dict ["body" ] = to_bytes (json .dumps (req_json ))
247
249
248
- def _fix_headers (self , request : HttpRequest , service_name : str ):
250
+ def _fix_headers (self , request : Request , service_name : str ):
249
251
if service_name == "s3" :
250
252
# fix the Host header, to avoid bucket addressing issues
251
253
host = request .headers .get ("Host" ) or ""
0 commit comments