|
12 | 12 | import boto3 |
13 | 13 | import requests |
14 | 14 | from botocore.awsrequest import AWSPreparedRequest |
15 | | -from botocore.model import OperationModel |
| 15 | +from botocore.model import OperationModel, ServiceModel |
| 16 | +from botocore.session import get_session as get_botocore_session |
| 17 | +from localstack.aws.protocol.parser import create_parser |
| 18 | +from localstack.aws.spec import load_service |
16 | 19 | from localstack import config as localstack_config |
17 | 20 | from localstack.config import external_service_url |
18 | 21 | from localstack.constants import ( |
|
43 | 46 | from aws_proxy import config as repl_config |
44 | 47 | from aws_proxy.client.utils import truncate_content |
45 | 48 | from aws_proxy.config import HANDLER_PATH_PROXIES |
46 | | -from aws_proxy.shared.constants import HEADER_HOST_ORIGINAL |
| 49 | +from aws_proxy.shared.constants import HEADER_HOST_ORIGINAL, SERVICE_NAME_MAPPING |
47 | 50 | from aws_proxy.shared.models import AddProxyRequest, ProxyConfig |
48 | 51 |
|
49 | 52 | LOG = logging.getLogger(__name__) |
50 | 53 | LOG.setLevel(logging.INFO) |
51 | 54 | if localstack_config.DEBUG: |
52 | 55 | LOG.setLevel(logging.DEBUG) |
53 | 56 |
|
54 | | -# Mapping from AWS service signing names to boto3 client names |
55 | | -SERVICE_NAME_MAPPING = { |
56 | | - "monitoring": "cloudwatch", |
57 | | -} |
58 | 57 |
|
59 | 58 | # TODO make configurable |
60 | 59 | CLI_PIP_PACKAGE = "localstack-extension-aws-proxy" |
@@ -197,13 +196,30 @@ def deregister_from_instance(self): |
197 | 196 | def _parse_aws_request( |
198 | 197 | self, request: Request, service_name: str, region_name: str, client |
199 | 198 | ) -> Tuple[OperationModel, AWSPreparedRequest, Dict]: |
200 | | - from localstack.aws.protocol.parser import create_parser |
| 199 | + # Get botocore's service model for making the actual AWS request |
| 200 | + botocore_service_model = self._get_botocore_service_model(service_name) |
| 201 | + |
| 202 | + # Check if request uses JSON protocol (X-Amz-Target header) while service model |
| 203 | + # uses RPC v2 CBOR. In this case, we need to parse the request manually since |
| 204 | + # create_parser would reject the X-Amz-Target header for RPC v2 services. |
| 205 | + x_amz_target = request.headers.get("X-Amz-Target") or request.headers.get( |
| 206 | + "X-Amzn-Target" |
| 207 | + ) |
| 208 | + if x_amz_target and botocore_service_model.protocol == "smithy-rpc-v2-cbor": |
| 209 | + # Extract operation name from X-Amz-Target (format: "ServiceName.OperationName") |
| 210 | + operation_name = x_amz_target.split(".")[-1] |
| 211 | + operation_model = botocore_service_model.operation_model(operation_name) |
| 212 | + # Parse JSON body |
| 213 | + parsed_request = json.loads(to_str(request.data)) if request.data else {} |
| 214 | + else: |
| 215 | + # Use LocalStack's parser for other protocols |
| 216 | + localstack_service_model = load_service(service_name) |
| 217 | + parser = create_parser(localstack_service_model) |
| 218 | + ls_operation_model, parsed_request = parser.parse(request) |
| 219 | + operation_model = botocore_service_model.operation_model( |
| 220 | + ls_operation_model.name |
| 221 | + ) |
201 | 222 |
|
202 | | - # Use botocore's service model to ensure protocol compatibility |
203 | | - # (LocalStack's load_service may return newer protocol versions that don't match the client) |
204 | | - service_model = self._get_botocore_service_model(service_name) |
205 | | - parser = create_parser(service_model) |
206 | | - operation_model, parsed_request = parser.parse(request) |
207 | 223 | request_context = { |
208 | 224 | "client_region": region_name, |
209 | 225 | "has_streaming_input": operation_model.has_streaming_input, |
@@ -348,10 +364,7 @@ def _get_botocore_service_model(service_name: str): |
348 | 364 | load_service() to ensure protocol compatibility, as LocalStack may use newer protocol |
349 | 365 | versions (e.g., smithy-rpc-v2-cbor) while clients use older protocols (e.g., query). |
350 | 366 | """ |
351 | | - import botocore.session |
352 | | - from botocore.model import ServiceModel |
353 | | - |
354 | | - session = botocore.session.get_session() |
| 367 | + session = get_botocore_session() |
355 | 368 | loader = session.get_component("data_loader") |
356 | 369 | api_data = loader.load_service_model(service_name, "service-2") |
357 | 370 | return ServiceModel(api_data) |
|
0 commit comments