1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15
16 package software.amazon.awssdk.core.internal.http;
17
18 import java.io.IOException;
19 import java.util.Optional;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 import software.amazon.awssdk.annotations.SdkInternalApi;
23 import software.amazon.awssdk.core.Response;
24 import software.amazon.awssdk.core.SdkStandardLogger;
25 import software.amazon.awssdk.core.exception.RetryableException;
26 import software.amazon.awssdk.core.exception.SdkClientException;
27 import software.amazon.awssdk.core.exception.SdkException;
28 import software.amazon.awssdk.core.http.HttpResponseHandler;
29 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
30 import software.amazon.awssdk.http.SdkHttpFullResponse;
31 import software.amazon.awssdk.utils.IoUtils;
32
33 /**
34  * Unmarshalls an HTTP response into either a successful response POJO, or into a (possibly modeled) exception based
35  * on the status code of the HTTP response. Returns a wrapper {@link Response} object which may contain either the
36  * unmarshalled success POJO, or the unmarshalled exception. Can be used with streaming or non-streaming requests.
37  *
38  * @param <OutputT> Type of successful unmarshalled POJO.
39  */

40 @SdkInternalApi
41 public class CombinedResponseHandler<OutputT> implements HttpResponseHandler<Response<OutputT>> {
42     private static final Logger log = LoggerFactory.getLogger(CombinedResponseHandler.class);
43
44     private final HttpResponseHandler<OutputT> successResponseHandler;
45     private final HttpResponseHandler<? extends SdkException> errorResponseHandler;
46
47     public CombinedResponseHandler(HttpResponseHandler<OutputT> successResponseHandler,
48                                    HttpResponseHandler<? extends SdkException> errorResponseHandler) {
49         this.successResponseHandler = successResponseHandler;
50         this.errorResponseHandler = errorResponseHandler;
51     }
52
53     @Override
54     public Response<OutputT> handle(SdkHttpFullResponse httpResponse, ExecutionAttributes executionAttributes)
55             throws Exception {
56
57         boolean didRequestFail = true;
58         try {
59             Response<OutputT> response = handleResponse(httpResponse, executionAttributes);
60             didRequestFail = !response.isSuccess();
61             return response;
62         } finally {
63             closeInputStreamIfNeeded(httpResponse, didRequestFail);
64         }
65     }
66
67     private Response<OutputT> handleResponse(SdkHttpFullResponse httpResponse,
68                                              ExecutionAttributes executionAttributes)
69             throws IOException, InterruptedException {
70
71         if (httpResponse.isSuccessful()) {
72             OutputT response = handleSuccessResponse(httpResponse, executionAttributes);
73             return Response.<OutputT>builder().httpResponse(httpResponse)
74                                               .response(response)
75                                               .isSuccess(true)
76                                               .build();
77         } else {
78             return Response.<OutputT>builder().httpResponse(httpResponse)
79                                               .exception(handleErrorResponse(httpResponse, executionAttributes))
80                                               .isSuccess(false)
81                                               .build();
82         }
83     }
84
85     /**
86      * Handles a successful response from a service call by unmarshalling the results using the
87      * specified response handler.
88      *
89      * @return The contents of the response, unmarshalled using the specified response handler.
90      * @throws IOException If any problems were encountered reading the response contents from
91      *                     the HTTP method object.
92      */

93     private OutputT handleSuccessResponse(SdkHttpFullResponse httpResponse, ExecutionAttributes executionAttributes)
94             throws IOException, InterruptedException {
95         try {
96             SdkStandardLogger.REQUEST_LOGGER.debug(() -> "Received successful response: " + httpResponse.statusCode());
97             return successResponseHandler.handle(httpResponse, executionAttributes);
98         } catch (IOException | InterruptedException | RetryableException e) {
99             throw e;
100         } catch (Exception e) {
101             if (e instanceof SdkException && ((SdkException) e).retryable()) {
102                 throw (SdkException) e;
103             }
104
105             String errorMessage =
106                     "Unable to unmarshall response (" + e.getMessage() + "). Response Code: "
107                     + httpResponse.statusCode() + ", Response Text: " + httpResponse.statusText().orElse(null);
108             throw SdkClientException.builder().message(errorMessage).cause(e).build();
109         }
110     }
111
112     /**
113      * Responsible for handling an error response, including unmarshalling the error response
114      * into the most specific exception type possible, and throwing the exception.
115      *
116      * @throws IOException If any problems are encountering reading the error response.
117      */

118     private SdkException handleErrorResponse(SdkHttpFullResponse httpResponse,
119                                              ExecutionAttributes executionAttributes)
120             throws IOException, InterruptedException {
121         try {
122             SdkException exception = errorResponseHandler.handle(httpResponse, executionAttributes);
123             exception.fillInStackTrace();
124             SdkStandardLogger.REQUEST_LOGGER.debug(() -> "Received error response: " + exception);
125             return exception;
126         } catch (InterruptedException | IOException e) {
127             throw e;
128         } catch (Exception e) {
129             String errorMessage = String.format("Unable to unmarshall error response (%s). " +
130                                                 "Response Code: %d, Response Text: %s", e.getMessage(),
131                                                 httpResponse.statusCode(), httpResponse.statusText().orElse("null"));
132             throw SdkClientException.builder().message(errorMessage).cause(e).build();
133         }
134     }
135
136     /**
137      * Close the input stream if required.
138      */

139     private void closeInputStreamIfNeeded(SdkHttpFullResponse httpResponse,
140                                           boolean didRequestFail) {
141         // Always close on failed requests. Close on successful requests unless it needs connection left open
142         if (didRequestFail || !successResponseHandler.needsConnectionLeftOpen()) {
143             Optional.ofNullable(httpResponse)
144                     .flatMap(SdkHttpFullResponse::content) // If no content, no need to close
145                     .ifPresent(s -> IoUtils.closeQuietly(s, log));
146         }
147     }
148 }
149