|
| 1 | +# Note/disclosure: This file has been partially modified by an AI agent. |
1 | 2 | import json |
2 | 3 | import logging |
3 | 4 | import re |
4 | 5 | from typing import Dict, Optional |
| 6 | +from urllib.parse import urlencode |
5 | 7 |
|
6 | 8 | import requests |
| 9 | +from botocore.serialize import create_serializer |
7 | 10 | from localstack.aws.api import RequestContext |
8 | 11 | from localstack.aws.chain import Handler, HandlerChain |
9 | 12 | from localstack.constants import APPLICATION_JSON, LOCALHOST, LOCALHOST_HOSTNAME |
@@ -193,6 +196,12 @@ def forward_request( |
193 | 196 | data = request.form |
194 | 197 | elif request.data: |
195 | 198 | data = request.data |
| 199 | + |
| 200 | + # Fallback: if data is empty and we have parsed service_request, |
| 201 | + # reconstruct the request body (handles cases where form data was consumed) |
| 202 | + if not data and context.service_request: |
| 203 | + data = self._reconstruct_request_body(context, ctype) |
| 204 | + |
196 | 205 | LOG.debug( |
197 | 206 | "Forward request: %s %s - %s - %s", |
198 | 207 | request.method, |
@@ -287,3 +296,29 @@ def _get_canonical_service_name(cls, service_name: str) -> str: |
287 | 296 | "monitoring": "cloudwatch", |
288 | 297 | } |
289 | 298 | return mapping.get(service_name, service_name) |
| 299 | + |
| 300 | + def _reconstruct_request_body( |
| 301 | + self, context: RequestContext, content_type: str |
| 302 | + ) -> bytes: |
| 303 | + """ |
| 304 | + Reconstruct the request body from the parsed service_request. |
| 305 | + This is used when the original request body was consumed during parsing. |
| 306 | + """ |
| 307 | + try: |
| 308 | + protocol = context.service.protocol |
| 309 | + if protocol == "query" or "x-www-form-urlencoded" in (content_type or ""): |
| 310 | + # For Query protocol, serialize using botocore serializer |
| 311 | + serializer = create_serializer(protocol) |
| 312 | + operation_model = context.operation |
| 313 | + serialized = serializer.serialize_to_request( |
| 314 | + context.service_request, operation_model |
| 315 | + ) |
| 316 | + body = serialized.get("body", {}) |
| 317 | + if isinstance(body, dict): |
| 318 | + return urlencode(body, doseq=True) |
| 319 | + return body |
| 320 | + elif protocol == "json" or protocol == "rest-json": |
| 321 | + return json.dumps(context.service_request) |
| 322 | + except Exception as e: |
| 323 | + LOG.debug("Failed to reconstruct request body: %s", e) |
| 324 | + return b"" |
0 commit comments