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 software.amazon.awssdk.annotations.SdkInternalApi;
19 import software.amazon.awssdk.annotations.ThreadSafe;
20 import software.amazon.awssdk.core.Response;
21 import software.amazon.awssdk.core.SdkRequest;
22 import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
23 import software.amazon.awssdk.core.exception.SdkClientException;
24 import software.amazon.awssdk.core.http.ExecutionContext;
25 import software.amazon.awssdk.core.http.HttpResponseHandler;
26 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
27 import software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder;
28 import software.amazon.awssdk.core.internal.http.pipeline.stages.AfterExecutionInterceptorsStage;
29 import software.amazon.awssdk.core.internal.http.pipeline.stages.AfterTransmissionExecutionInterceptorsStage;
30 import software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage;
31 import software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage;
32 import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyTransactionIdStage;
33 import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage;
34 import software.amazon.awssdk.core.internal.http.pipeline.stages.BeforeTransmissionExecutionInterceptorsStage;
35 import software.amazon.awssdk.core.internal.http.pipeline.stages.BeforeUnmarshallingExecutionInterceptorsStage;
36 import software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage;
37 import software.amazon.awssdk.core.internal.http.pipeline.stages.HandleResponseStage;
38 import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeHttpRequestStage;
39 import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeRequestImmutableStage;
40 import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeRequestMutableStage;
41 import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomHeadersStage;
42 import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomQueryParamsStage;
43 import software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage;
44 import software.amazon.awssdk.core.internal.http.pipeline.stages.SigningStage;
45 import software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage;
46 import software.amazon.awssdk.core.internal.http.pipeline.stages.UnwrapResponseContainer;
47 import software.amazon.awssdk.http.SdkHttpFullRequest;
48 import software.amazon.awssdk.http.SdkHttpFullResponse;
49 import software.amazon.awssdk.utils.SdkAutoCloseable;
50
51 @ThreadSafe
52 @SdkInternalApi
53 // TODO come up with better name
54 public final class AmazonSyncHttpClient implements SdkAutoCloseable {
55     private final HttpClientDependencies httpClientDependencies;
56
57     public AmazonSyncHttpClient(SdkClientConfiguration clientConfiguration) {
58         this.httpClientDependencies = HttpClientDependencies.builder()
59                                                             .clientConfiguration(clientConfiguration)
60                                                             .build();
61     }
62
63     /**
64      * Shuts down this HTTP client object, releasing any resources that might be held open. This is
65      * an optional method, and callers are not expected to call it, but can if they want to
66      * explicitly release any open resources. Once a client has been shutdown, it cannot be used to
67      * make more requests.
68      */

69     @Override
70     public void close() {
71         httpClientDependencies.close();
72     }
73
74     /**
75      * @return A builder used to configure and execute a HTTP request.
76      */

77     public RequestExecutionBuilder requestExecutionBuilder() {
78         return new RequestExecutionBuilderImpl();
79     }
80
81     /**
82      * Interface to configure a request execution and execute the request.
83      */

84     public interface RequestExecutionBuilder {
85
86         /**
87          * Fluent setter for {@link SdkHttpFullRequest}
88          *
89          * @param request Request object
90          * @return This builder for method chaining.
91          */

92         RequestExecutionBuilder request(SdkHttpFullRequest request);
93
94         RequestExecutionBuilder originalRequest(SdkRequest originalRequest);
95
96         /**
97          * Fluent setter for the execution context
98          *
99          * @param executionContext Execution context
100          * @return This builder for method chaining.
101          */

102         RequestExecutionBuilder executionContext(ExecutionContext executionContext);
103
104         /**
105          * Executes the request with the given configuration.
106          *
107          * @param combinedResponseHandler response handler: converts an http request into a decorated Response object
108          *                               of the appropriate type.
109          * @param <OutputT> Result type
110          * @return Unmarshalled result type.
111          */

112         <OutputT> OutputT execute(HttpResponseHandler<Response<OutputT>> combinedResponseHandler);
113     }
114
115     private static class NoOpResponseHandler<T> implements HttpResponseHandler<T> {
116         @Override
117         public T handle(SdkHttpFullResponse response, ExecutionAttributes executionAttributes) {
118             return null;
119         }
120
121         @Override
122         public boolean needsConnectionLeftOpen() {
123             return false;
124         }
125     }
126
127     private class RequestExecutionBuilderImpl implements RequestExecutionBuilder {
128
129         private SdkHttpFullRequest request;
130         private SdkRequest originalRequest;
131         private ExecutionContext executionContext;
132
133         @Override
134         // This is duplicating information in the interceptor context. Can they be consolidated?
135         public RequestExecutionBuilder request(SdkHttpFullRequest request) {
136             this.request = request;
137             return this;
138         }
139
140         @Override
141         public RequestExecutionBuilder originalRequest(SdkRequest originalRequest) {
142             this.originalRequest = originalRequest;
143             return this;
144         }
145
146         @Override
147         public RequestExecutionBuilder executionContext(ExecutionContext executionContext) {
148             this.executionContext = executionContext;
149             return this;
150         }
151
152         @Override
153         public <OutputT> OutputT execute(HttpResponseHandler<Response<OutputT>> responseHandler) {
154             // TODO: We currently have two ways of passing messages to the HTTP client: through the request or through the
155             // execution interceptor context. We should combine these two methods when we refactor the way request execution
156             // contexts work.
157             if (request != null && executionContext != null) {
158                 executionContext.interceptorContext(
159                     executionContext.interceptorContext().copy(ib -> ib.httpRequest(request)));
160             }
161
162             try {
163                 return RequestPipelineBuilder
164                     // Start of mutating request
165                     .first(RequestPipelineBuilder
166                                .first(MakeRequestMutableStage::new)
167                                .then(ApplyTransactionIdStage::new)
168                                .then(ApplyUserAgentStage::new)
169                                .then(MergeCustomHeadersStage::new)
170                                .then(MergeCustomQueryParamsStage::new)
171                                .then(MakeRequestImmutableStage::new)
172                                // End of mutating request
173                                .then(RequestPipelineBuilder
174                                          .first(SigningStage::new)
175                                          .then(BeforeTransmissionExecutionInterceptorsStage::new)
176                                          .then(MakeHttpRequestStage::new)
177                                          .then(AfterTransmissionExecutionInterceptorsStage::new)
178                                          .then(BeforeUnmarshallingExecutionInterceptorsStage::new)
179                                          .then(() -> new HandleResponseStage<>(responseHandler))
180                                          .wrappedWith(ApiCallAttemptTimeoutTrackingStage::new)
181                                          .wrappedWith(TimeoutExceptionHandlingStage::new)
182                                          .wrappedWith(RetryableStage::new)::build)
183                                .wrappedWith(StreamManagingStage::new)
184                                .wrappedWith(ApiCallTimeoutTrackingStage::new)::build)
185                     .then(() -> new UnwrapResponseContainer<>())
186                     .then(() -> new AfterExecutionInterceptorsStage<>())
187                     .wrappedWith(ExecutionFailureExceptionReportingStage::new)
188                     .build(httpClientDependencies)
189                     .execute(request, createRequestExecutionDependencies());
190             } catch (RuntimeException e) {
191                 throw e;
192             } catch (Exception e) {
193                 throw SdkClientException.builder().cause(e).build();
194             }
195         }
196
197         private RequestExecutionContext createRequestExecutionDependencies() {
198             return RequestExecutionContext.builder()
199                                           .originalRequest(originalRequest)
200                                           .executionContext(executionContext)
201                                           .build();
202         }
203
204     }
205
206 }
207