1
15
16 package software.amazon.awssdk.http;
17
18 import static software.amazon.awssdk.utils.CollectionUtils.deepCopyMap;
19 import static software.amazon.awssdk.utils.CollectionUtils.deepUnmodifiableMap;
20
21 import java.util.ArrayList;
22 import java.util.LinkedHashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.TreeMap;
27 import java.util.function.Consumer;
28 import software.amazon.awssdk.annotations.Immutable;
29 import software.amazon.awssdk.annotations.SdkInternalApi;
30 import software.amazon.awssdk.utils.CollectionUtils;
31 import software.amazon.awssdk.utils.StringUtils;
32 import software.amazon.awssdk.utils.ToString;
33 import software.amazon.awssdk.utils.Validate;
34 import software.amazon.awssdk.utils.http.SdkHttpUtils;
35
36
40 @SdkInternalApi
41 @Immutable
42 final class DefaultSdkHttpFullRequest implements SdkHttpFullRequest {
43 private final String protocol;
44 private final String host;
45 private final Integer port;
46 private final String path;
47 private final Map<String, List<String>> queryParameters;
48 private final SdkHttpMethod httpMethod;
49 private final Map<String, List<String>> headers;
50 private final ContentStreamProvider contentStreamProvider;
51
52 private DefaultSdkHttpFullRequest(Builder builder) {
53 this.protocol = standardizeProtocol(builder.protocol);
54 this.host = Validate.paramNotNull(builder.host, "host");
55 this.port = standardizePort(builder.port);
56 this.path = standardizePath(builder.path);
57 this.httpMethod = Validate.paramNotNull(builder.httpMethod, "method");
58 this.contentStreamProvider = builder.contentStreamProvider;
59
60 this.queryParameters = builder.queryParametersAreFromToBuilder
61 ? builder.queryParameters
62 : deepUnmodifiableMap(builder.queryParameters, () -> new LinkedHashMap<>());
63 this.headers = builder.headersAreFromToBuilder
64 ? builder.headers
65 : deepUnmodifiableMap(builder.headers, () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER));
66 }
67
68 private String standardizeProtocol(String protocol) {
69 Validate.paramNotNull(protocol, "protocol");
70
71 String standardizedProtocol = StringUtils.lowerCase(protocol);
72 Validate.isTrue(standardizedProtocol.equals("http") || standardizedProtocol.equals("https"),
73 "Protocol must be 'http' or 'https', but was %s", protocol);
74
75 return standardizedProtocol;
76 }
77
78 private String standardizePath(String path) {
79 if (StringUtils.isEmpty(path)) {
80 return "";
81 }
82
83 StringBuilder standardizedPath = new StringBuilder();
84
85
86 if (!path.startsWith("/")) {
87 standardizedPath.append('/');
88 }
89
90 standardizedPath.append(path);
91
92 return standardizedPath.toString();
93 }
94
95 private Integer standardizePort(Integer port) {
96 Validate.isTrue(port == null || port >= -1,
97 "Port must be positive (or null/-1 to indicate no port), but was '%s'", port);
98
99 if (port != null && port == -1) {
100 return null;
101 }
102
103 return port;
104 }
105
106 @Override
107 public String protocol() {
108 return protocol;
109 }
110
111 @Override
112 public String host() {
113 return host;
114 }
115
116 @Override
117 public int port() {
118 return Optional.ofNullable(port).orElseGet(() -> SdkHttpUtils.standardPort(protocol()));
119 }
120
121 @Override
122 public Map<String, List<String>> headers() {
123 return headers;
124 }
125
126 @Override
127 public String encodedPath() {
128 return path;
129 }
130
131 @Override
132 public Map<String, List<String>> rawQueryParameters() {
133 return queryParameters;
134 }
135
136 @Override
137 public SdkHttpMethod method() {
138 return httpMethod;
139 }
140
141 @Override
142 public Optional<ContentStreamProvider> contentStreamProvider() {
143 return Optional.ofNullable(contentStreamProvider);
144 }
145
146 @Override
147 public SdkHttpFullRequest.Builder toBuilder() {
148 return new Builder(this);
149 }
150
151 @Override
152 public String toString() {
153 return ToString.builder("DefaultSdkHttpFullRequest")
154 .add("httpMethod", httpMethod)
155 .add("protocol", protocol)
156 .add("host", host)
157 .add("port", port)
158 .add("encodedPath", path)
159 .add("headers", headers.keySet())
160 .add("queryParameters", queryParameters.keySet())
161 .build();
162 }
163
164
167 static final class Builder implements SdkHttpFullRequest.Builder {
168 private String protocol;
169 private String host;
170 private Integer port;
171 private String path;
172
173 private boolean queryParametersAreFromToBuilder;
174 private Map<String, List<String>> queryParameters;
175
176 private SdkHttpMethod httpMethod;
177
178 private boolean headersAreFromToBuilder;
179 private Map<String, List<String>> headers;
180
181 private ContentStreamProvider contentStreamProvider;
182
183 Builder() {
184 queryParameters = new LinkedHashMap<>();
185 queryParametersAreFromToBuilder = false;
186 headers = new LinkedHashMap<>();
187 headersAreFromToBuilder = false;
188 }
189
190 Builder(DefaultSdkHttpFullRequest request) {
191 queryParameters = request.queryParameters;
192 queryParametersAreFromToBuilder = true;
193 headers = request.headers;
194 headersAreFromToBuilder = true;
195 protocol = request.protocol;
196 host = request.host;
197 port = request.port;
198 path = request.path;
199 httpMethod = request.httpMethod;
200 contentStreamProvider = request.contentStreamProvider;
201 }
202
203 @Override
204 public String protocol() {
205 return protocol;
206 }
207
208 @Override
209 public SdkHttpFullRequest.Builder protocol(String protocol) {
210 this.protocol = protocol;
211 return this;
212 }
213
214 @Override
215 public String host() {
216 return host;
217 }
218
219 @Override
220 public SdkHttpFullRequest.Builder host(String host) {
221 this.host = host;
222 return this;
223 }
224
225 @Override
226 public Integer port() {
227 return port;
228 }
229
230 @Override
231 public SdkHttpFullRequest.Builder port(Integer port) {
232 this.port = port;
233 return this;
234 }
235
236 @Override
237 public DefaultSdkHttpFullRequest.Builder encodedPath(String path) {
238 this.path = path;
239 return this;
240 }
241
242 @Override
243 public String encodedPath() {
244 return path;
245 }
246
247 @Override
248 public DefaultSdkHttpFullRequest.Builder putRawQueryParameter(String paramName, List<String> paramValues) {
249 copyQueryParamsIfNeeded();
250 this.queryParameters.put(paramName, new ArrayList<>(paramValues));
251 return this;
252 }
253
254 @Override
255 public SdkHttpFullRequest.Builder appendRawQueryParameter(String paramName, String paramValue) {
256 copyQueryParamsIfNeeded();
257 this.queryParameters.computeIfAbsent(paramName, k -> new ArrayList<>()).add(paramValue);
258 return this;
259 }
260
261 @Override
262 public DefaultSdkHttpFullRequest.Builder rawQueryParameters(Map<String, List<String>> queryParameters) {
263 this.queryParameters = CollectionUtils.deepCopyMap(queryParameters, () -> new LinkedHashMap<>());
264 queryParametersAreFromToBuilder = false;
265 return this;
266 }
267
268 @Override
269 public Builder removeQueryParameter(String paramName) {
270 copyQueryParamsIfNeeded();
271 this.queryParameters.remove(paramName);
272 return this;
273 }
274
275 @Override
276 public Builder clearQueryParameters() {
277 this.queryParameters = new LinkedHashMap<>();
278 queryParametersAreFromToBuilder = false;
279 return this;
280 }
281
282 private void copyQueryParamsIfNeeded() {
283 if (queryParametersAreFromToBuilder) {
284 queryParametersAreFromToBuilder = false;
285 this.queryParameters = deepCopyMap(queryParameters);
286 }
287 }
288
289 @Override
290 public Map<String, List<String>> rawQueryParameters() {
291 return CollectionUtils.unmodifiableMapOfLists(queryParameters);
292 }
293
294 @Override
295 public DefaultSdkHttpFullRequest.Builder method(SdkHttpMethod httpMethod) {
296 this.httpMethod = httpMethod;
297 return this;
298 }
299
300 @Override
301 public SdkHttpMethod method() {
302 return httpMethod;
303 }
304
305 @Override
306 public DefaultSdkHttpFullRequest.Builder putHeader(String headerName, List<String> headerValues) {
307 copyHeadersIfNeeded();
308 this.headers.put(headerName, new ArrayList<>(headerValues));
309 return this;
310 }
311
312 @Override
313 public SdkHttpFullRequest.Builder appendHeader(String headerName, String headerValue) {
314 copyHeadersIfNeeded();
315 this.headers.computeIfAbsent(headerName, k -> new ArrayList<>()).add(headerValue);
316 return this;
317 }
318
319 @Override
320 public DefaultSdkHttpFullRequest.Builder headers(Map<String, List<String>> headers) {
321 this.headers = CollectionUtils.deepCopyMap(headers);
322 headersAreFromToBuilder = false;
323 return this;
324 }
325
326 @Override
327 public SdkHttpFullRequest.Builder removeHeader(String headerName) {
328 copyHeadersIfNeeded();
329 this.headers.remove(headerName);
330 return this;
331 }
332
333 @Override
334 public SdkHttpFullRequest.Builder clearHeaders() {
335 this.headers = new LinkedHashMap<>();
336 headersAreFromToBuilder = false;
337 return this;
338 }
339
340 @Override
341 public Map<String, List<String>> headers() {
342 return CollectionUtils.unmodifiableMapOfLists(this.headers);
343 }
344
345 private void copyHeadersIfNeeded() {
346 if (headersAreFromToBuilder) {
347 headersAreFromToBuilder = false;
348 this.headers = deepCopyMap(headers);
349 }
350 }
351
352 @Override
353 public DefaultSdkHttpFullRequest.Builder contentStreamProvider(ContentStreamProvider contentStreamProvider) {
354 this.contentStreamProvider = contentStreamProvider;
355 return this;
356 }
357
358 public ContentStreamProvider contentStreamProvider() {
359 return contentStreamProvider;
360 }
361
362 @Override
363 public SdkHttpFullRequest.Builder copy() {
364 return build().toBuilder();
365 }
366
367 @Override
368 public SdkHttpFullRequest.Builder applyMutation(Consumer<SdkHttpRequest.Builder> mutator) {
369 mutator.accept(this);
370 return this;
371 }
372
373 @Override
374 public DefaultSdkHttpFullRequest build() {
375 return new DefaultSdkHttpFullRequest(this);
376 }
377 }
378
379 }
380