|
15 | 15 |
|
16 | 16 | package software.amazon.awssdk.services.s3.internal.crt;
|
17 | 17 |
|
| 18 | +import static software.amazon.awssdk.core.http.HttpResponseHandler.X_AMZN_REQUEST_ID_HEADER_ALTERNATE; |
| 19 | +import static software.amazon.awssdk.core.http.HttpResponseHandler.X_AMZ_ID_2_HEADER; |
18 | 20 | import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError;
|
19 | 21 |
|
20 | 22 | import java.nio.ByteBuffer;
|
|
25 | 27 | import java.util.concurrent.TimeoutException;
|
26 | 28 | import software.amazon.awssdk.annotations.SdkInternalApi;
|
27 | 29 | import software.amazon.awssdk.annotations.SdkTestInternalApi;
|
| 30 | +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; |
| 31 | +import software.amazon.awssdk.awscore.exception.AwsServiceException; |
| 32 | +import software.amazon.awssdk.core.SdkBytes; |
28 | 33 | import software.amazon.awssdk.core.async.listener.PublisherListener;
|
29 | 34 | import software.amazon.awssdk.core.exception.SdkClientException;
|
30 | 35 | import software.amazon.awssdk.crt.CRT;
|
|
34 | 39 | import software.amazon.awssdk.crt.s3.S3MetaRequestResponseHandler;
|
35 | 40 | import software.amazon.awssdk.http.SdkHttpResponse;
|
36 | 41 | import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler;
|
| 42 | +import software.amazon.awssdk.services.s3.model.S3Exception; |
37 | 43 | import software.amazon.awssdk.utils.Logger;
|
38 | 44 | import software.amazon.awssdk.utils.async.SimplePublisher;
|
39 | 45 |
|
@@ -172,21 +178,66 @@ private void handleError(S3FinishedResponseContext context) {
|
172 | 178 | int responseStatus = context.getResponseStatus();
|
173 | 179 | byte[] errorPayload = context.getErrorPayload();
|
174 | 180 |
|
175 |
| - if (isErrorResponse(responseStatus) && errorPayload != null) { |
176 |
| - SdkHttpResponse.Builder errorResponse = populateSdkHttpResponse(SdkHttpResponse.builder(), |
177 |
| - responseStatus, headers); |
178 |
| - initiateResponseHandling(errorResponse.build()); |
179 |
| - onErrorResponseComplete(errorPayload); |
| 181 | + if (isServiceError(responseStatus) && errorPayload != null) { |
| 182 | + handleServiceError(responseStatus, headers, errorPayload); |
180 | 183 | } else {
|
181 |
| - Throwable cause = context.getCause(); |
| 184 | + handleIoError(context, crtCode); |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | + private void handleIoError(S3FinishedResponseContext context, int crtCode) { |
| 189 | + Throwable cause = context.getCause(); |
| 190 | + |
| 191 | + SdkClientException sdkClientException = |
| 192 | + SdkClientException.create("Failed to send the request: " + |
| 193 | + CRT.awsErrorString(crtCode), cause); |
| 194 | + failResponseHandlerAndFuture(sdkClientException); |
| 195 | + } |
| 196 | + |
| 197 | + private void handleServiceError(int responseStatus, HttpHeader[] headers, byte[] errorPayload) { |
| 198 | + SdkHttpResponse.Builder errorResponse = populateSdkHttpResponse(SdkHttpResponse.builder(), |
| 199 | + responseStatus, headers); |
| 200 | + if (requestFailedMidwayOfOtherError(responseStatus)) { |
| 201 | + AwsServiceException s3Exception = buildS3Exception(responseStatus, errorPayload, errorResponse); |
182 | 202 |
|
183 | 203 | SdkClientException sdkClientException =
|
184 |
| - SdkClientException.create("Failed to send the request: " + |
185 |
| - CRT.awsErrorString(crtCode), cause); |
186 |
| - failResponseHandlerAndFuture(sdkClientException); |
| 204 | + SdkClientException.create("Request failed during the transfer due to an error returned from S3"); |
| 205 | + s3Exception.addSuppressed(sdkClientException); |
| 206 | + failResponseHandlerAndFuture(s3Exception); |
| 207 | + } else { |
| 208 | + initiateResponseHandling(errorResponse.build()); |
| 209 | + onErrorResponseComplete(errorPayload); |
187 | 210 | }
|
188 | 211 | }
|
189 | 212 |
|
| 213 | + private static AwsServiceException buildS3Exception(int responseStatus, |
| 214 | + byte[] errorPayload, |
| 215 | + SdkHttpResponse.Builder errorResponse) { |
| 216 | + String requestId = errorResponse.firstMatchingHeader(X_AMZN_REQUEST_ID_HEADER_ALTERNATE) |
| 217 | + .orElse(null); |
| 218 | + String extendedRequestId = errorResponse.firstMatchingHeader(X_AMZ_ID_2_HEADER) |
| 219 | + .orElse(null); |
| 220 | + return S3Exception.builder() |
| 221 | + .requestId(requestId) |
| 222 | + .extendedRequestId(extendedRequestId) |
| 223 | + .statusCode(responseStatus) |
| 224 | + .message(errorResponse.statusText()) |
| 225 | + .awsErrorDetails(AwsErrorDetails.builder() |
| 226 | + .sdkHttpResponse(errorResponse.build()) |
| 227 | + .rawResponse(SdkBytes.fromByteArray(errorPayload)) |
| 228 | + .build()) |
| 229 | + .build(); |
| 230 | + } |
| 231 | + |
| 232 | + /** |
| 233 | + * Whether request failed midway or it failed due to a different error than the initial response. |
| 234 | + * For example, this could happen if an object got deleted after download was initiated (200 |
| 235 | + * was received). |
| 236 | + */ |
| 237 | + private boolean requestFailedMidwayOfOtherError(int responseStatus) { |
| 238 | + return responseHandlingInitiated && initialHeadersResponse.statusCode() != responseStatus; |
| 239 | + } |
| 240 | + |
190 | 241 | private void initiateResponseHandling(SdkHttpResponse response) {
|
191 | 242 | if (!responseHandlingInitiated) {
|
192 | 243 | responseHandlingInitiated = true;
|
@@ -214,7 +265,7 @@ private void failResponseHandlerAndFuture(Throwable exception) {
|
214 | 265 | resultFuture.completeExceptionally(exception);
|
215 | 266 | }
|
216 | 267 |
|
217 |
| - private static boolean isErrorResponse(int responseStatus) { |
| 268 | + private static boolean isServiceError(int responseStatus) { |
218 | 269 | return responseStatus != 0;
|
219 | 270 | }
|
220 | 271 |
|
|
0 commit comments