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.pipeline.stages; 17 18 import java.util.List; 19 import software.amazon.awssdk.annotations.SdkInternalApi; 20 import software.amazon.awssdk.core.ApiName; 21 import software.amazon.awssdk.core.ClientType; 22 import software.amazon.awssdk.core.SdkSystemSetting; 23 import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; 24 import software.amazon.awssdk.core.client.config.SdkClientConfiguration; 25 import software.amazon.awssdk.core.client.config.SdkClientOption; 26 import software.amazon.awssdk.core.internal.http.HttpClientDependencies; 27 import software.amazon.awssdk.core.internal.http.RequestExecutionContext; 28 import software.amazon.awssdk.core.internal.http.pipeline.MutableRequestToRequestPipeline; 29 import software.amazon.awssdk.core.retry.RetryPolicy; 30 import software.amazon.awssdk.core.util.SdkUserAgent; 31 import software.amazon.awssdk.http.SdkHttpClient; 32 import software.amazon.awssdk.http.SdkHttpFullRequest; 33 import software.amazon.awssdk.http.async.SdkAsyncHttpClient; 34 import software.amazon.awssdk.utils.Logger; 35 import software.amazon.awssdk.utils.StringUtils; 36 import software.amazon.awssdk.utils.http.SdkHttpUtils; 37 38 /** 39 * Apply any custom user agent supplied, otherwise instrument the user agent with info about the SDK and environment. 40 */ 41 @SdkInternalApi 42 public class ApplyUserAgentStage implements MutableRequestToRequestPipeline { 43 private static final Logger log = Logger.loggerFor(ApplyUserAgentStage.class); 44 45 private static final String COMMA = ", "; 46 private static final String SPACE = " "; 47 48 private static final String IO = "io"; 49 private static final String HTTP = "http"; 50 private static final String CONFIG = "cfg"; 51 private static final String RETRY_MODE = "retry-mode"; 52 53 private static final String AWS_EXECUTION_ENV_PREFIX = "exec-env/"; 54 55 private static final String HEADER_USER_AGENT = "User-Agent"; 56 57 private final SdkClientConfiguration clientConfig; 58 ApplyUserAgentStage(HttpClientDependencies dependencies)59 public ApplyUserAgentStage(HttpClientDependencies dependencies) { 60 this.clientConfig = dependencies.clientConfiguration(); 61 } 62 resolveClientUserAgent(String userAgentPrefix, String internalUserAgent, ClientType clientType, SdkHttpClient syncHttpClient, SdkAsyncHttpClient asyncHttpClient, RetryPolicy retryPolicy)63 public static String resolveClientUserAgent(String userAgentPrefix, 64 String internalUserAgent, 65 ClientType clientType, 66 SdkHttpClient syncHttpClient, 67 SdkAsyncHttpClient asyncHttpClient, 68 RetryPolicy retryPolicy) { 69 String awsExecutionEnvironment = SdkSystemSetting.AWS_EXECUTION_ENV.getStringValue().orElse(null); 70 71 StringBuilder userAgent = new StringBuilder(128); 72 73 userAgent.append(StringUtils.trimToEmpty(userAgentPrefix)); 74 75 String systemUserAgent = SdkUserAgent.create().userAgent(); 76 if (!systemUserAgent.equals(userAgentPrefix)) { 77 userAgent.append(COMMA).append(systemUserAgent); 78 } 79 80 String trimmedInternalUserAgent = StringUtils.trimToEmpty(internalUserAgent); 81 if (!trimmedInternalUserAgent.isEmpty()) { 82 userAgent.append(SPACE).append(trimmedInternalUserAgent); 83 } 84 85 if (!StringUtils.isEmpty(awsExecutionEnvironment)) { 86 userAgent.append(SPACE).append(AWS_EXECUTION_ENV_PREFIX).append(awsExecutionEnvironment.trim()); 87 } 88 89 if (clientType == null) { 90 clientType = ClientType.UNKNOWN; 91 } 92 93 userAgent.append(SPACE) 94 .append(IO) 95 .append("/") 96 .append(StringUtils.lowerCase(clientType.name())); 97 98 userAgent.append(SPACE) 99 .append(HTTP) 100 .append("/") 101 .append(SdkHttpUtils.urlEncode(clientName(clientType, syncHttpClient, asyncHttpClient))); 102 103 String retryMode = retryPolicy.retryMode().toString(); 104 105 userAgent.append(SPACE) 106 .append(CONFIG) 107 .append("/") 108 .append(RETRY_MODE) 109 .append("/") 110 .append(StringUtils.lowerCase(retryMode)); 111 112 return userAgent.toString(); 113 } 114 115 @Override execute(SdkHttpFullRequest.Builder request, RequestExecutionContext context)116 public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, RequestExecutionContext context) 117 throws Exception { 118 return request.putHeader(HEADER_USER_AGENT, getUserAgent(clientConfig, context.requestConfig().apiNames())); 119 } 120 getUserAgent(SdkClientConfiguration config, List<ApiName> requestApiNames)121 private String getUserAgent(SdkClientConfiguration config, List<ApiName> requestApiNames) { 122 String clientUserAgent = clientConfig.option(SdkClientOption.CLIENT_USER_AGENT); 123 if (clientUserAgent == null) { 124 log.warn(() -> "Client user agent configuration is missing, so request user agent will be incomplete."); 125 clientUserAgent = ""; 126 } 127 StringBuilder userAgent = new StringBuilder(clientUserAgent); 128 129 if (!requestApiNames.isEmpty()) { 130 requestApiNames.forEach(apiName -> { 131 userAgent.append(SPACE).append(apiName.name()).append("/").append(apiName.version()); 132 }); 133 } 134 135 String userDefinedSuffix = config.option(SdkAdvancedClientOption.USER_AGENT_SUFFIX); 136 if (!StringUtils.isEmpty(userDefinedSuffix)) { 137 userAgent.append(COMMA).append(userDefinedSuffix.trim()); 138 } 139 140 return userAgent.toString(); 141 } 142 clientName(ClientType clientType, SdkHttpClient syncHttpClient, SdkAsyncHttpClient asyncHttpClient)143 private static String clientName(ClientType clientType, SdkHttpClient syncHttpClient, SdkAsyncHttpClient asyncHttpClient) { 144 if (clientType == ClientType.SYNC) { 145 return syncHttpClient == null ? "null" : syncHttpClient.clientName(); 146 } 147 148 if (clientType == ClientType.ASYNC) { 149 return asyncHttpClient == null ? "null" : asyncHttpClient.clientName(); 150 } 151 152 return ClientType.UNKNOWN.name(); 153 } 154 } 155