From 30d401ce7fb062578893bdbf880e6e581fdfd5a9 Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Wed, 24 Jun 2026 22:14:50 +0530 Subject: [PATCH] fix(crt): fail request on HTTP 200 with error body Ensure the CRT-backed S3AsyncClient fails the future instead of silently reporting success when S3 returns an HTTP 2xx response but with an XML error payload (e.g. for complete multipart upload failures). --- .../internal/crt/S3CrtResponseHandlerAdapter.java | 6 +++++- .../crt/S3CrtResponseHandlerAdapterTest.java | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapter.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapter.java index 55531992a92c..e0d15be4d9d3 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapter.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapter.java @@ -212,7 +212,7 @@ private void notifyResponsePublisherErrorIfNeeded(Throwable error) { private void handleServiceError(int responseStatus, HttpHeader[] headers, byte[] errorPayload) { SdkHttpResponse.Builder errorResponse = populateSdkHttpResponse(SdkHttpResponse.builder(), responseStatus, headers); - if (requestFailedMidwayOfOtherError(responseStatus)) { + if (requestFailedMidwayOfOtherError(responseStatus) || isSuccessStatus(responseStatus)) { AwsServiceException s3Exception = buildS3Exception(responseStatus, errorPayload, errorResponse); SdkClientException sdkClientException = @@ -226,6 +226,10 @@ private void handleServiceError(int responseStatus, HttpHeader[] headers, byte[] } } + private static boolean isSuccessStatus(int responseStatus) { + return responseStatus >= 200 && responseStatus < 300; + } + private static AwsServiceException buildS3Exception(int responseStatus, byte[] errorPayload, SdkHttpResponse.Builder errorResponse) { diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapterTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapterTest.java index 89e3301738f1..06e261f28ea8 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapterTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/S3CrtResponseHandlerAdapterTest.java @@ -149,6 +149,21 @@ public void errorResponse_shouldCompleteFutureSuccessfully() { verify(sdkResponseHandler).onHeaders(any(SdkHttpResponse.class)); } + @Test + public void errorResponseWith200Status_shouldCompleteFutureExceptionally() { + int statusCode = 200; + responseHandlerAdapter.onResponseHeaders(statusCode, new HttpHeader[0]); + + byte[] errorPayload = "errorResponse".getBytes(StandardCharsets.UTF_8); + responseHandlerAdapter.onFinished(stubResponseContext(1, statusCode, errorPayload)); + + Throwable actualException = sdkResponseHandler.error; + assertThat(actualException).isInstanceOf(S3Exception.class); + assertThat(((S3Exception) actualException).statusCode()).isEqualTo(200); + assertThat(future).isCompletedExceptionally(); + verify(s3MetaRequest).close(); + } + @Test public void requestFailed_shouldCompleteFutureExceptionally() {