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.handler; 17 18 import java.net.URI; 19 import java.time.Duration; 20 import java.util.Optional; 21 import java.util.function.BiFunction; 22 import software.amazon.awssdk.annotations.SdkInternalApi; 23 import software.amazon.awssdk.core.CredentialType; 24 import software.amazon.awssdk.core.Response; 25 import software.amazon.awssdk.core.SdkRequest; 26 import software.amazon.awssdk.core.SdkResponse; 27 import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; 28 import software.amazon.awssdk.core.client.config.SdkClientConfiguration; 29 import software.amazon.awssdk.core.client.config.SdkClientOption; 30 import software.amazon.awssdk.core.client.handler.ClientExecutionParams; 31 import software.amazon.awssdk.core.exception.SdkClientException; 32 import software.amazon.awssdk.core.http.ExecutionContext; 33 import software.amazon.awssdk.core.http.HttpResponseHandler; 34 import software.amazon.awssdk.core.interceptor.ExecutionAttributes; 35 import software.amazon.awssdk.core.interceptor.ExecutionInterceptorChain; 36 import software.amazon.awssdk.core.interceptor.InterceptorContext; 37 import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; 38 import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; 39 import software.amazon.awssdk.core.internal.InternalCoreExecutionAttribute; 40 import software.amazon.awssdk.core.internal.io.SdkLengthAwareInputStream; 41 import software.amazon.awssdk.core.internal.util.MetricUtils; 42 import software.amazon.awssdk.core.metrics.CoreMetric; 43 import software.amazon.awssdk.core.signer.Signer; 44 import software.amazon.awssdk.core.sync.RequestBody; 45 import software.amazon.awssdk.http.ContentStreamProvider; 46 import software.amazon.awssdk.http.SdkHttpFullRequest; 47 import software.amazon.awssdk.http.SdkHttpFullResponse; 48 import software.amazon.awssdk.http.SdkHttpRequest; 49 import software.amazon.awssdk.metrics.MetricCollector; 50 import software.amazon.awssdk.utils.Pair; 51 import software.amazon.awssdk.utils.StringUtils; 52 53 @SdkInternalApi 54 public abstract class BaseClientHandler { 55 private SdkClientConfiguration clientConfiguration; 56 BaseClientHandler(SdkClientConfiguration clientConfiguration)57 protected BaseClientHandler(SdkClientConfiguration clientConfiguration) { 58 this.clientConfiguration = clientConfiguration; 59 } 60 61 /** 62 * Finalize {@link SdkHttpFullRequest} by running beforeMarshalling, afterMarshalling, 63 * modifyHttpRequest, modifyHttpContent and modifyAsyncHttpContent interceptors 64 */ finalizeSdkHttpFullRequest( ClientExecutionParams<InputT, OutputT> executionParams, ExecutionContext executionContext, InputT inputT, SdkClientConfiguration clientConfiguration)65 static <InputT extends SdkRequest, OutputT> InterceptorContext finalizeSdkHttpFullRequest( 66 ClientExecutionParams<InputT, OutputT> executionParams, 67 ExecutionContext executionContext, InputT inputT, 68 SdkClientConfiguration clientConfiguration) { 69 70 runBeforeMarshallingInterceptors(executionContext); 71 72 Pair<SdkHttpFullRequest, Duration> measuredMarshall = MetricUtils.measureDuration(() -> 73 executionParams.getMarshaller().marshall(inputT)); 74 75 executionContext.metricCollector().reportMetric(CoreMetric.MARSHALLING_DURATION, measuredMarshall.right()); 76 77 SdkHttpFullRequest request = measuredMarshall.left(); 78 79 request = modifyEndpointHostIfNeeded(request, clientConfiguration, executionParams); 80 81 addHttpRequest(executionContext, request); 82 runAfterMarshallingInterceptors(executionContext); 83 return runModifyHttpRequestAndHttpContentInterceptors(executionContext); 84 } 85 runBeforeMarshallingInterceptors(ExecutionContext executionContext)86 private static void runBeforeMarshallingInterceptors(ExecutionContext executionContext) { 87 executionContext.interceptorChain().beforeMarshalling(executionContext.interceptorContext(), 88 executionContext.executionAttributes()); 89 } 90 91 /** 92 * Modifies the given {@link SdkHttpFullRequest} with new host if host prefix is enabled and set. 93 */ modifyEndpointHostIfNeeded(SdkHttpFullRequest originalRequest, SdkClientConfiguration clientConfiguration, ClientExecutionParams executionParams)94 private static SdkHttpFullRequest modifyEndpointHostIfNeeded(SdkHttpFullRequest originalRequest, 95 SdkClientConfiguration clientConfiguration, 96 ClientExecutionParams executionParams) { 97 if (executionParams.discoveredEndpoint() != null) { 98 URI discoveredEndpoint = executionParams.discoveredEndpoint(); 99 executionParams.putExecutionAttribute(SdkInternalExecutionAttribute.IS_DISCOVERED_ENDPOINT, true); 100 return originalRequest.toBuilder().host(discoveredEndpoint.getHost()).port(discoveredEndpoint.getPort()).build(); 101 } 102 103 Boolean disableHostPrefixInjection = clientConfiguration.option(SdkAdvancedClientOption.DISABLE_HOST_PREFIX_INJECTION); 104 if ((disableHostPrefixInjection != null && disableHostPrefixInjection.equals(Boolean.TRUE)) || 105 StringUtils.isEmpty(executionParams.hostPrefixExpression())) { 106 return originalRequest; 107 } 108 109 return originalRequest.toBuilder() 110 .host(executionParams.hostPrefixExpression() + originalRequest.host()) 111 .build(); 112 } 113 addHttpRequest(ExecutionContext executionContext, SdkHttpFullRequest request)114 private static void addHttpRequest(ExecutionContext executionContext, SdkHttpFullRequest request) { 115 InterceptorContext interceptorContext = executionContext.interceptorContext(); 116 117 Optional<ContentStreamProvider> contentStreamProvider = request.contentStreamProvider(); 118 if (contentStreamProvider.isPresent()) { 119 interceptorContext = interceptorContext.copy(b -> b.httpRequest(request) 120 .requestBody(getBody(request))); 121 } else { 122 interceptorContext = interceptorContext.copy(b -> b.httpRequest(request)); 123 } 124 125 executionContext.interceptorContext(interceptorContext); 126 } 127 getBody(SdkHttpFullRequest request)128 private static RequestBody getBody(SdkHttpFullRequest request) { 129 Optional<ContentStreamProvider> contentStreamProviderOptional = request.contentStreamProvider(); 130 if (contentStreamProviderOptional.isPresent()) { 131 Optional<String> contentLengthOptional = request.firstMatchingHeader("Content-Length"); 132 long contentLength = Long.parseLong(contentLengthOptional.orElse("0")); 133 String contentType = request.firstMatchingHeader("Content-Type").orElse(""); 134 135 // Enforce the content length specified only if it was present on the request (and not the default). 136 ContentStreamProvider streamProvider = contentStreamProviderOptional.get(); 137 if (contentLengthOptional.isPresent()) { 138 ContentStreamProvider toWrap = contentStreamProviderOptional.get(); 139 streamProvider = () -> new SdkLengthAwareInputStream(toWrap.newStream(), contentLength); 140 } 141 142 return RequestBody.fromContentProvider(streamProvider, 143 contentLength, 144 contentType); 145 } 146 147 return null; 148 } 149 runAfterMarshallingInterceptors(ExecutionContext executionContext)150 private static void runAfterMarshallingInterceptors(ExecutionContext executionContext) { 151 executionContext.interceptorChain().afterMarshalling(executionContext.interceptorContext(), 152 executionContext.executionAttributes()); 153 } 154 runModifyHttpRequestAndHttpContentInterceptors(ExecutionContext executionContext)155 private static InterceptorContext runModifyHttpRequestAndHttpContentInterceptors(ExecutionContext executionContext) { 156 InterceptorContext interceptorContext = 157 executionContext.interceptorChain().modifyHttpRequestAndHttpContent(executionContext.interceptorContext(), 158 executionContext.executionAttributes()); 159 executionContext.interceptorContext(interceptorContext); 160 return interceptorContext; 161 } 162 163 /** 164 * Run afterUnmarshalling and modifyResponse interceptors. 165 */ 166 private static <OutputT extends SdkResponse> BiFunction<OutputT, SdkHttpFullResponse, OutputT> runAfterUnmarshallingInterceptors(ExecutionContext context)167 runAfterUnmarshallingInterceptors(ExecutionContext context) { 168 169 return (input, httpFullResponse) -> { 170 // Update interceptor context to include response 171 InterceptorContext interceptorContext = 172 context.interceptorContext().copy(b -> b.response(input)); 173 174 context.interceptorChain().afterUnmarshalling(interceptorContext, context.executionAttributes()); 175 176 interceptorContext = context.interceptorChain().modifyResponse(interceptorContext, context.executionAttributes()); 177 178 // Store updated context 179 context.interceptorContext(interceptorContext); 180 181 return (OutputT) interceptorContext.response(); 182 }; 183 } 184 185 private static <OutputT extends SdkResponse> BiFunction<OutputT, SdkHttpFullResponse, OutputT> attachHttpResponseToResult()186 attachHttpResponseToResult() { 187 188 return ((response, httpFullResponse) -> (OutputT) response.toBuilder().sdkHttpResponse(httpFullResponse).build()); 189 } 190 191 // This method is only called from tests, since the subclasses in aws-core override it. 192 protected <InputT extends SdkRequest, OutputT extends SdkResponse> ExecutionContext invokeInterceptorsAndCreateExecutionContext( ClientExecutionParams<InputT, OutputT> params)193 invokeInterceptorsAndCreateExecutionContext( 194 ClientExecutionParams<InputT, OutputT> params) { 195 SdkClientConfiguration clientConfiguration = resolveRequestConfiguration(params); 196 SdkRequest originalRequest = params.getInput(); 197 198 ExecutionAttributes executionAttributes = params.executionAttributes(); 199 executionAttributes 200 .putAttribute(InternalCoreExecutionAttribute.EXECUTION_ATTEMPT, 1) 201 .putAttribute(SdkExecutionAttribute.SERVICE_CONFIG, 202 clientConfiguration.option(SdkClientOption.SERVICE_CONFIGURATION)) 203 .putAttribute(SdkExecutionAttribute.SERVICE_NAME, clientConfiguration.option(SdkClientOption.SERVICE_NAME)) 204 .putAttribute(SdkExecutionAttribute.PROFILE_FILE, 205 clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER) != null ? 206 clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER).get() : null) 207 .putAttribute(SdkExecutionAttribute.PROFILE_FILE_SUPPLIER, 208 clientConfiguration.option(SdkClientOption.PROFILE_FILE_SUPPLIER)) 209 .putAttribute(SdkExecutionAttribute.PROFILE_NAME, clientConfiguration.option(SdkClientOption.PROFILE_NAME)); 210 211 ExecutionInterceptorChain interceptorChain = 212 new ExecutionInterceptorChain(clientConfiguration.option(SdkClientOption.EXECUTION_INTERCEPTORS)); 213 214 InterceptorContext interceptorContext = InterceptorContext.builder() 215 .request(originalRequest) 216 .build(); 217 218 interceptorChain.beforeExecution(interceptorContext, executionAttributes); 219 interceptorContext = interceptorChain.modifyRequest(interceptorContext, executionAttributes); 220 221 MetricCollector metricCollector = resolveMetricCollector(params); 222 223 return ExecutionContext.builder() 224 .interceptorChain(interceptorChain) 225 .interceptorContext(interceptorContext) 226 .executionAttributes(executionAttributes) 227 .signer(clientConfiguration.option(SdkAdvancedClientOption.SIGNER)) 228 .metricCollector(metricCollector) 229 .build(); 230 } 231 isCalculateCrc32FromCompressedData()232 protected boolean isCalculateCrc32FromCompressedData() { 233 return clientConfiguration.option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED); 234 } 235 validateSigningConfiguration(SdkHttpRequest request, Signer signer)236 protected void validateSigningConfiguration(SdkHttpRequest request, Signer signer) { 237 if (signer == null) { 238 return; 239 } 240 241 if (signer.credentialType() != CredentialType.TOKEN) { 242 return; 243 } 244 245 URI endpoint = request.getUri(); 246 if (!"https".equals(endpoint.getScheme())) { 247 throw SdkClientException.create("Cannot use bearer token signer with a plaintext HTTP endpoint: " + endpoint); 248 } 249 } 250 resolveRequestConfiguration(ClientExecutionParams<?, ?> params)251 protected SdkClientConfiguration resolveRequestConfiguration(ClientExecutionParams<?, ?> params) { 252 SdkClientConfiguration config = params.requestConfiguration(); 253 if (config != null) { 254 return config; 255 } 256 return clientConfiguration; 257 } 258 259 /** 260 * Decorate response handlers by running after unmarshalling Interceptors and adding http response metadata. 261 */ decorateResponseHandlers( HttpResponseHandler<OutputT> delegate, ExecutionContext executionContext)262 <OutputT extends SdkResponse> HttpResponseHandler<OutputT> decorateResponseHandlers( 263 HttpResponseHandler<OutputT> delegate, ExecutionContext executionContext) { 264 265 return resultTransformationResponseHandler(delegate, responseTransformations(executionContext)); 266 } 267 decorateSuccessResponseHandlers( HttpResponseHandler<Response<OutputT>> delegate, ExecutionContext executionContext)268 <OutputT extends SdkResponse> HttpResponseHandler<Response<OutputT>> decorateSuccessResponseHandlers( 269 HttpResponseHandler<Response<OutputT>> delegate, ExecutionContext executionContext) { 270 271 return successTransformationResponseHandler(delegate, responseTransformations(executionContext)); 272 } 273 successTransformationResponseHandler( HttpResponseHandler<Response<OutputT>> responseHandler, BiFunction<OutputT, SdkHttpFullResponse, OutputT> successTransformer)274 <OutputT extends SdkResponse> HttpResponseHandler<Response<OutputT>> successTransformationResponseHandler( 275 HttpResponseHandler<Response<OutputT>> responseHandler, 276 BiFunction<OutputT, SdkHttpFullResponse, OutputT> successTransformer) { 277 278 return (response, executionAttributes) -> { 279 Response<OutputT> delegateResponse = responseHandler.handle(response, executionAttributes); 280 281 if (delegateResponse.isSuccess()) { 282 return delegateResponse.toBuilder() 283 .response(successTransformer.apply(delegateResponse.response(), response)) 284 .build(); 285 } else { 286 return delegateResponse; 287 } 288 }; 289 } 290 291 <OutputT extends SdkResponse> HttpResponseHandler<OutputT> resultTransformationResponseHandler( 292 HttpResponseHandler<OutputT> responseHandler, 293 BiFunction<OutputT, SdkHttpFullResponse, OutputT> successTransformer) { 294 295 return (response, executionAttributes) -> { 296 OutputT delegateResponse = responseHandler.handle(response, executionAttributes); 297 return successTransformer.apply(delegateResponse, response); 298 }; 299 } 300 301 static void validateCombinedResponseHandler(ClientExecutionParams<?, ?> executionParams) { 302 if (executionParams.getCombinedResponseHandler() != null) { 303 if (executionParams.getResponseHandler() != null) { 304 throw new IllegalArgumentException("Only one of 'combinedResponseHandler' and 'responseHandler' may " 305 + "be specified in a ClientExecutionParams object"); 306 } 307 308 if (executionParams.getErrorResponseHandler() != null) { 309 throw new IllegalArgumentException("Only one of 'combinedResponseHandler' and 'errorResponseHandler' " 310 + "may be specified in a ClientExecutionParams object"); 311 } 312 } 313 } 314 315 /** 316 * Returns the composition of 'runAfterUnmarshallingInterceptors' and 'attachHttpResponseToResult' response 317 * transformations as a single transformation that should be applied to all responses. 318 */ 319 private static <T extends SdkResponse> BiFunction<T, SdkHttpFullResponse, T> 320 responseTransformations(ExecutionContext executionContext) { 321 322 return composeResponseFunctions(runAfterUnmarshallingInterceptors(executionContext), 323 attachHttpResponseToResult()); 324 } 325 326 /** 327 * Composes two functions passing the result of the first function as the first argument of the second function 328 * and the same second argument to both functions. This is used by response transformers to chain together and 329 * pass through a persistent SdkHttpFullResponse object as a second arg turning them effectively into a single 330 * response transformer. 331 * <p> 332 * So given f1(x, y) and f2(x, y) where x is typically OutputT and y is typically SdkHttpFullResponse the composed 333 * function would be f12(x, y) = f2(f1(x, y), y). 334 */ 335 private static <T, R> BiFunction<T, R, T> composeResponseFunctions(BiFunction<T, R, T> function1, 336 BiFunction<T, R, T> function2) { 337 return (x, y) -> function2.apply(function1.apply(x, y), y); 338 } 339 340 private MetricCollector resolveMetricCollector(ClientExecutionParams<?, ?> params) { 341 MetricCollector metricCollector = params.getMetricCollector(); 342 if (metricCollector == null) { 343 metricCollector = MetricCollector.create("ApiCall"); 344 } 345 return metricCollector; 346 } 347 } 348