Core: Allow request decompression (#6726)

This commit is contained in:
Bert Blommers 2023-08-26 07:14:40 +00:00 committed by GitHub
parent 18e382c8ca
commit 59ebe7d6a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 0 deletions

View File

@ -14,6 +14,7 @@ from moto.core.common_types import TYPE_RESPONSE, TYPE_IF_NONE
from moto.core.exceptions import DryRunClientError from moto.core.exceptions import DryRunClientError
from moto.core.utils import ( from moto.core.utils import (
camelcase_to_underscores, camelcase_to_underscores,
gzip_decompress,
method_names_from_class, method_names_from_class,
params_sort_function, params_sort_function,
) )
@ -232,6 +233,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
def __init__(self, service_name: Optional[str] = None): def __init__(self, service_name: Optional[str] = None):
super().__init__() super().__init__()
self.service_name = service_name self.service_name = service_name
self.allow_request_decompression = True
@classmethod @classmethod
def dispatch(cls, *args: Any, **kwargs: Any) -> Any: # type: ignore[misc] def dispatch(cls, *args: Any, **kwargs: Any) -> Any: # type: ignore[misc]
@ -262,6 +264,15 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
querystring[key] = [value] querystring[key] = [value]
raw_body = self.body raw_body = self.body
# https://github.com/getmoto/moto/issues/6692
# Content coming from SDK's can be GZipped for performance reasons
if (
headers.get("Content-Encoding", "") == "gzip"
and self.allow_request_decompression
):
self.body = gzip_decompress(self.body)
if isinstance(self.body, bytes) and not use_raw_body: if isinstance(self.body, bytes) and not use_raw_body:
self.body = self.body.decode("utf-8") self.body = self.body.decode("utf-8")

View File

@ -3,6 +3,7 @@ import inspect
import re import re
import unicodedata import unicodedata
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
from gzip import decompress
from typing import Any, Optional, List, Callable, Dict, Tuple from typing import Any, Optional, List, Callable, Dict, Tuple
from urllib.parse import urlparse, unquote from urllib.parse import urlparse, unquote
from .common_types import TYPE_RESPONSE from .common_types import TYPE_RESPONSE
@ -416,3 +417,7 @@ def _unquote_hex_characters(path: str) -> str:
) )
char_offset += (combo_end - combo_start) + len(character) - 1 char_offset += (combo_end - combo_start) + len(character) - 1
return path return path
def gzip_decompress(body: bytes) -> bytes:
return decompress(body)

View File

@ -158,6 +158,11 @@ def parse_key_name(pth: str) -> str:
class S3Response(BaseResponse): class S3Response(BaseResponse):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__(service_name="s3") super().__init__(service_name="s3")
# Whatever format requests come in, we should never touch them
# There are some nuances here - this decision should be method-specific, instead of service-specific
# E.G.: we don't want to touch put_object(), but we might have to decompress put_object_configuration()
# Taking the naive approach to never decompress anything from S3 for now
self.allow_request_decompression = False
def get_safe_path_from_url(self, url: ParseResult) -> str: def get_safe_path_from_url(self, url: ParseResult) -> str:
return self.get_safe_path(url.path) return self.get_safe_path(url.path)

View File

@ -2,11 +2,13 @@ import datetime
from unittest import SkipTest, mock from unittest import SkipTest, mock
from collections import OrderedDict from collections import OrderedDict
from gzip import compress as gzip_compress
from botocore.awsrequest import AWSPreparedRequest from botocore.awsrequest import AWSPreparedRequest
from moto.core.responses import AWSServiceSpec, BaseResponse from moto.core.responses import AWSServiceSpec, BaseResponse
from moto.core.responses import flatten_json_request_body from moto.core.responses import flatten_json_request_body
from moto.s3.responses import S3Response
from moto import settings from moto import settings
from freezegun import freeze_time from freezegun import freeze_time
@ -244,3 +246,38 @@ def test_response_metadata():
assert "date" in bc.response_headers assert "date" in bc.response_headers
if not settings.TEST_SERVER_MODE: if not settings.TEST_SERVER_MODE:
assert bc.response_headers["date"] == "Sat, 20 May 2023 10:20:30 GMT" assert bc.response_headers["date"] == "Sat, 20 May 2023 10:20:30 GMT"
def test_compression_gzip():
body = '{"key": "%D0"}, "C": "#0 = :0"}'
request = AWSPreparedRequest(
"GET",
url="http://request",
headers={"Content-Encoding": "gzip"},
body=_gzip_compress_body(body),
stream_output=False,
)
response = BaseResponse()
response.setup_class(request, request.url, request.headers)
assert body == response.body
def test_compression_gzip_in_s3():
body = b"some random data"
request = AWSPreparedRequest(
"GET",
url="http://request",
headers={"Content-Encoding": "gzip"},
body=body,
stream_output=False,
)
response = S3Response()
response.setup_class(request, request.url, request.headers)
assert body == response.body.encode("utf-8")
def _gzip_compress_body(body: str):
assert isinstance(body, str)
return gzip_compress(data=body.encode("utf-8"))