From 8f3a05650499dc5b621d8c6a155f0c9aa2f3ee5a Mon Sep 17 00:00:00 2001 From: Om Singhal Date: Tue, 22 Sep 2026 15:27:05 -0400 Subject: [PATCH] fix(event_handler): skip compression when the response has no body --- .../event_handler/api_gateway.py | 5 ++ .../required_dependencies/test_api_gateway.py | 66 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/aws_lambda_powertools/event_handler/api_gateway.py b/aws_lambda_powertools/event_handler/api_gateway.py index a764f0ca3a6..d5af0b33a98 100644 --- a/aws_lambda_powertools/event_handler/api_gateway.py +++ b/aws_lambda_powertools/event_handler/api_gateway.py @@ -810,6 +810,11 @@ def _has_compression_enabled( def _compress(self): """Compress the response body, but only if `Accept-Encoding` headers includes gzip.""" + # A response without a body, such as 204 No Content or 304 Not Modified, has nothing to compress + if self.response.body is None: + logger.debug("Response has no body; skipping compression") + return + self.response.headers["Content-Encoding"] = "gzip" if isinstance(self.response.body, str): logger.debug("Converting string response to bytes before compressing it") diff --git a/tests/functional/event_handler/required_dependencies/test_api_gateway.py b/tests/functional/event_handler/required_dependencies/test_api_gateway.py index e5ed7b7cb78..dec2e6d3b2b 100644 --- a/tests/functional/event_handler/required_dependencies/test_api_gateway.py +++ b/tests/functional/event_handler/required_dependencies/test_api_gateway.py @@ -606,6 +606,72 @@ def return_text() -> Response: assert result["body"] == expected_value +@pytest.mark.parametrize("status_code", [204, 304]) +def test_compress_route_with_body_less_response(status_code: int): + # GIVEN a function with compress=True returning a Response without a body + # AND an event with an "Accept-Encoding" that includes gzip + app = ApiGatewayResolver() + mock_event = {"path": "/my/path", "httpMethod": "GET", "headers": {"Accept-Encoding": "deflate, gzip"}} + + @app.get("/my/path", compress=True) + def no_content() -> Response: + return Response(status_code=status_code) + + # WHEN calling the event handler + result = app(mock_event, None) + + # THEN don't perform any gzip compression + assert result["statusCode"] == status_code + assert result["body"] is None + assert result["isBase64Encoded"] is False + assert "Content-Encoding" not in result["multiValueHeaders"] + + +def test_compress_response_with_body_less_response(): + # GIVEN a function returning a Response with compress=True and no body + # AND an event with an "Accept-Encoding" that includes gzip + app = ApiGatewayResolver() + mock_event = {"path": "/my/path", "httpMethod": "GET", "headers": {"Accept-Encoding": "deflate, gzip"}} + + @app.get("/my/path") + def no_content() -> Response: + return Response(status_code=204, compress=True) + + # WHEN calling the event handler + result = app(mock_event, None) + + # THEN don't perform any gzip compression + assert result["statusCode"] == 204 + assert result["body"] is None + assert result["isBase64Encoded"] is False + assert "Content-Encoding" not in result["multiValueHeaders"] + + +def test_compress_exception_handler_with_body_less_response(): + # GIVEN a function with compress=True whose exception handler returns a Response without a body + # AND an event with an "Accept-Encoding" that includes gzip + app = ApiGatewayResolver() + mock_event = {"path": "/my/path", "httpMethod": "GET", "headers": {"Accept-Encoding": "deflate, gzip"}} + + @app.exception_handler(ValueError) + def handle_value_error(ex: ValueError): + return Response(status_code=410) + + @app.get("/my/path", compress=True) + def raise_value_error() -> Response: + raise ValueError("Foo!") + + # WHEN calling the event handler + # AND a ValueError is raised + result = app(mock_event, None) + + # THEN call the exception_handler and don't perform any gzip compression + assert result["statusCode"] == 410 + assert result["body"] is None + assert result["isBase64Encoded"] is False + assert "Content-Encoding" not in result["multiValueHeaders"] + + def test_cache_control_200(): # GIVEN a function with cache_control set app = ApiGatewayResolver()