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