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.http; 17 18 import static java.util.Collections.emptyList; 19 import static java.util.Collections.unmodifiableList; 20 21 import java.util.ArrayList; 22 import java.util.Collection; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Optional; 27 import java.util.function.BiConsumer; 28 import java.util.function.Consumer; 29 import software.amazon.awssdk.annotations.Immutable; 30 import software.amazon.awssdk.annotations.SdkInternalApi; 31 import software.amazon.awssdk.internal.http.LowCopyListMap; 32 import software.amazon.awssdk.utils.CollectionUtils; 33 import software.amazon.awssdk.utils.StringUtils; 34 import software.amazon.awssdk.utils.ToString; 35 import software.amazon.awssdk.utils.Validate; 36 import software.amazon.awssdk.utils.http.SdkHttpUtils; 37 38 /** 39 * Internal implementation of {@link SdkHttpFullRequest}, buildable via {@link SdkHttpFullRequest#builder()}. Provided to HTTP 40 * implementation to execute a request. 41 */ 42 @SdkInternalApi 43 @Immutable 44 final class DefaultSdkHttpFullRequest implements SdkHttpFullRequest { 45 private final String protocol; 46 private final String host; 47 private final Integer port; 48 private final String path; 49 private final LowCopyListMap.ForBuildable queryParameters; 50 private final LowCopyListMap.ForBuildable headers; 51 private final SdkHttpMethod httpMethod; 52 private final ContentStreamProvider contentStreamProvider; 53 DefaultSdkHttpFullRequest(Builder builder)54 private DefaultSdkHttpFullRequest(Builder builder) { 55 this.protocol = standardizeProtocol(builder.protocol); 56 this.host = Validate.paramNotNull(builder.host, "host"); 57 this.port = standardizePort(builder.port); 58 this.path = standardizePath(builder.path); 59 this.httpMethod = Validate.paramNotNull(builder.httpMethod, "method"); 60 this.contentStreamProvider = builder.contentStreamProvider; 61 this.queryParameters = builder.queryParameters.forBuildable(); 62 this.headers = builder.headers.forBuildable(); 63 } 64 standardizeProtocol(String protocol)65 private String standardizeProtocol(String protocol) { 66 Validate.paramNotNull(protocol, "protocol"); 67 68 String standardizedProtocol = StringUtils.lowerCase(protocol); 69 Validate.isTrue(standardizedProtocol.equals("http") || standardizedProtocol.equals("https"), 70 "Protocol must be 'http' or 'https', but was %s", protocol); 71 72 return standardizedProtocol; 73 } 74 standardizePath(String path)75 private String standardizePath(String path) { 76 if (StringUtils.isEmpty(path)) { 77 return ""; 78 } 79 80 StringBuilder standardizedPath = new StringBuilder(); 81 82 // Path must always start with '/' 83 if (!path.startsWith("/")) { 84 standardizedPath.append('/'); 85 } 86 87 standardizedPath.append(path); 88 89 return standardizedPath.toString(); 90 } 91 standardizePort(Integer port)92 private Integer standardizePort(Integer port) { 93 Validate.isTrue(port == null || port >= -1, 94 "Port must be positive (or null/-1 to indicate no port), but was '%s'", port); 95 96 if (port != null && port == -1) { 97 return null; 98 } 99 100 return port; 101 } 102 103 @Override protocol()104 public String protocol() { 105 return protocol; 106 } 107 108 @Override host()109 public String host() { 110 return host; 111 } 112 113 @Override port()114 public int port() { 115 return Optional.ofNullable(port).orElseGet(() -> SdkHttpUtils.standardPort(protocol())); 116 } 117 118 @Override headers()119 public Map<String, List<String>> headers() { 120 return headers.forExternalRead(); 121 } 122 123 @Override matchingHeaders(String header)124 public List<String> matchingHeaders(String header) { 125 return unmodifiableList(headers.forInternalRead().getOrDefault(header, emptyList())); 126 } 127 128 @Override firstMatchingHeader(String headerName)129 public Optional<String> firstMatchingHeader(String headerName) { 130 List<String> headers = this.headers.forInternalRead().get(headerName); 131 if (headers == null || headers.isEmpty()) { 132 return Optional.empty(); 133 } 134 135 String header = headers.get(0); 136 if (StringUtils.isEmpty(header)) { 137 return Optional.empty(); 138 } 139 140 return Optional.of(header); 141 } 142 143 @Override firstMatchingHeader(Collection<String> headersToFind)144 public Optional<String> firstMatchingHeader(Collection<String> headersToFind) { 145 for (String headerName : headersToFind) { 146 Optional<String> header = firstMatchingHeader(headerName); 147 if (header.isPresent()) { 148 return header; 149 } 150 } 151 152 return Optional.empty(); 153 } 154 155 @Override forEachHeader(BiConsumer<? super String, ? super List<String>> consumer)156 public void forEachHeader(BiConsumer<? super String, ? super List<String>> consumer) { 157 headers.forInternalRead().forEach((k, v) -> consumer.accept(k, Collections.unmodifiableList(v))); 158 } 159 160 @Override forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer)161 public void forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer) { 162 queryParameters.forInternalRead().forEach((k, v) -> consumer.accept(k, Collections.unmodifiableList(v))); 163 } 164 165 @Override numHeaders()166 public int numHeaders() { 167 return headers.forInternalRead().size(); 168 } 169 170 @Override numRawQueryParameters()171 public int numRawQueryParameters() { 172 return queryParameters.forInternalRead().size(); 173 } 174 175 @Override encodedQueryParameters()176 public Optional<String> encodedQueryParameters() { 177 return SdkHttpUtils.encodeAndFlattenQueryParameters(queryParameters.forInternalRead()); 178 } 179 180 @Override encodedQueryParametersAsFormData()181 public Optional<String> encodedQueryParametersAsFormData() { 182 return SdkHttpUtils.encodeAndFlattenFormData(queryParameters.forInternalRead()); 183 } 184 185 @Override encodedPath()186 public String encodedPath() { 187 return path; 188 } 189 190 @Override rawQueryParameters()191 public Map<String, List<String>> rawQueryParameters() { 192 return queryParameters.forExternalRead(); 193 } 194 195 @Override firstMatchingRawQueryParameter(String key)196 public Optional<String> firstMatchingRawQueryParameter(String key) { 197 List<String> values = queryParameters.forInternalRead().get(key); 198 return values == null ? Optional.empty() : values.stream().findFirst(); 199 } 200 201 @Override firstMatchingRawQueryParameter(Collection<String> keys)202 public Optional<String> firstMatchingRawQueryParameter(Collection<String> keys) { 203 for (String key : keys) { 204 Optional<String> result = firstMatchingRawQueryParameter(key); 205 if (result.isPresent()) { 206 return result; 207 } 208 } 209 210 return Optional.empty(); 211 } 212 213 @Override firstMatchingRawQueryParameters(String key)214 public List<String> firstMatchingRawQueryParameters(String key) { 215 List<String> values = queryParameters.forInternalRead().get(key); 216 return values == null ? emptyList() : unmodifiableList(values); 217 } 218 219 @Override method()220 public SdkHttpMethod method() { 221 return httpMethod; 222 } 223 224 @Override contentStreamProvider()225 public Optional<ContentStreamProvider> contentStreamProvider() { 226 return Optional.ofNullable(contentStreamProvider); 227 } 228 229 @Override toBuilder()230 public SdkHttpFullRequest.Builder toBuilder() { 231 return new Builder(this); 232 } 233 234 @Override toString()235 public String toString() { 236 return ToString.builder("DefaultSdkHttpFullRequest") 237 .add("httpMethod", httpMethod) 238 .add("protocol", protocol) 239 .add("host", host) 240 .add("port", port) 241 .add("encodedPath", path) 242 .add("headers", headers.forInternalRead().keySet()) 243 .add("queryParameters", queryParameters.forInternalRead().keySet()) 244 .build(); 245 } 246 247 /** 248 * Builder for a {@link DefaultSdkHttpFullRequest}. 249 */ 250 static final class Builder implements SdkHttpFullRequest.Builder { 251 private String protocol; 252 private String host; 253 private Integer port; 254 private String path; 255 private LowCopyListMap.ForBuilder queryParameters; 256 private LowCopyListMap.ForBuilder headers; 257 private SdkHttpMethod httpMethod; 258 private ContentStreamProvider contentStreamProvider; 259 Builder()260 Builder() { 261 queryParameters = LowCopyListMap.emptyQueryParameters(); 262 headers = LowCopyListMap.emptyHeaders(); 263 } 264 Builder(DefaultSdkHttpFullRequest request)265 Builder(DefaultSdkHttpFullRequest request) { 266 queryParameters = request.queryParameters.forBuilder(); 267 headers = request.headers.forBuilder(); 268 protocol = request.protocol; 269 host = request.host; 270 port = request.port; 271 path = request.path; 272 httpMethod = request.httpMethod; 273 contentStreamProvider = request.contentStreamProvider; 274 } 275 276 @Override protocol()277 public String protocol() { 278 return protocol; 279 } 280 281 @Override protocol(String protocol)282 public SdkHttpFullRequest.Builder protocol(String protocol) { 283 this.protocol = protocol; 284 return this; 285 } 286 287 @Override host()288 public String host() { 289 return host; 290 } 291 292 @Override host(String host)293 public SdkHttpFullRequest.Builder host(String host) { 294 this.host = host; 295 return this; 296 } 297 298 @Override port()299 public Integer port() { 300 return port; 301 } 302 303 @Override port(Integer port)304 public SdkHttpFullRequest.Builder port(Integer port) { 305 this.port = port; 306 return this; 307 } 308 309 @Override encodedPath(String path)310 public DefaultSdkHttpFullRequest.Builder encodedPath(String path) { 311 this.path = path; 312 return this; 313 } 314 315 @Override encodedPath()316 public String encodedPath() { 317 return path; 318 } 319 320 @Override putRawQueryParameter(String paramName, List<String> paramValues)321 public DefaultSdkHttpFullRequest.Builder putRawQueryParameter(String paramName, List<String> paramValues) { 322 this.queryParameters.forInternalWrite().put(paramName, new ArrayList<>(paramValues)); 323 return this; 324 } 325 326 @Override appendRawQueryParameter(String paramName, String paramValue)327 public SdkHttpFullRequest.Builder appendRawQueryParameter(String paramName, String paramValue) { 328 this.queryParameters.forInternalWrite().computeIfAbsent(paramName, k -> new ArrayList<>()).add(paramValue); 329 return this; 330 } 331 332 @Override rawQueryParameters(Map<String, List<String>> queryParameters)333 public DefaultSdkHttpFullRequest.Builder rawQueryParameters(Map<String, List<String>> queryParameters) { 334 this.queryParameters.setFromExternal(queryParameters); 335 return this; 336 } 337 338 @Override removeQueryParameter(String paramName)339 public Builder removeQueryParameter(String paramName) { 340 this.queryParameters.forInternalWrite().remove(paramName); 341 return this; 342 } 343 344 @Override clearQueryParameters()345 public Builder clearQueryParameters() { 346 this.queryParameters.forInternalWrite().clear(); 347 return this; 348 } 349 350 @Override rawQueryParameters()351 public Map<String, List<String>> rawQueryParameters() { 352 return CollectionUtils.unmodifiableMapOfLists(queryParameters.forInternalRead()); 353 } 354 355 @Override method(SdkHttpMethod httpMethod)356 public DefaultSdkHttpFullRequest.Builder method(SdkHttpMethod httpMethod) { 357 this.httpMethod = httpMethod; 358 return this; 359 } 360 361 @Override method()362 public SdkHttpMethod method() { 363 return httpMethod; 364 } 365 366 @Override putHeader(String headerName, List<String> headerValues)367 public DefaultSdkHttpFullRequest.Builder putHeader(String headerName, List<String> headerValues) { 368 this.headers.forInternalWrite().put(headerName, new ArrayList<>(headerValues)); 369 return this; 370 } 371 372 @Override appendHeader(String headerName, String headerValue)373 public SdkHttpFullRequest.Builder appendHeader(String headerName, String headerValue) { 374 this.headers.forInternalWrite().computeIfAbsent(headerName, k -> new ArrayList<>()).add(headerValue); 375 return this; 376 } 377 378 @Override headers(Map<String, List<String>> headers)379 public DefaultSdkHttpFullRequest.Builder headers(Map<String, List<String>> headers) { 380 this.headers.setFromExternal(headers); 381 return this; 382 } 383 384 @Override removeHeader(String headerName)385 public SdkHttpFullRequest.Builder removeHeader(String headerName) { 386 this.headers.forInternalWrite().remove(headerName); 387 return this; 388 } 389 390 @Override clearHeaders()391 public SdkHttpFullRequest.Builder clearHeaders() { 392 this.headers.clear(); 393 return this; 394 } 395 396 @Override headers()397 public Map<String, List<String>> headers() { 398 return CollectionUtils.unmodifiableMapOfLists(this.headers.forInternalRead()); 399 } 400 401 @Override matchingHeaders(String header)402 public List<String> matchingHeaders(String header) { 403 return unmodifiableList(headers.forInternalRead().getOrDefault(header, emptyList())); 404 } 405 406 @Override firstMatchingHeader(String headerName)407 public Optional<String> firstMatchingHeader(String headerName) { 408 List<String> headers = this.headers.forInternalRead().get(headerName); 409 if (headers == null || headers.isEmpty()) { 410 return Optional.empty(); 411 } 412 413 String header = headers.get(0); 414 if (StringUtils.isEmpty(header)) { 415 return Optional.empty(); 416 } 417 418 return Optional.of(header); 419 } 420 421 @Override firstMatchingHeader(Collection<String> headersToFind)422 public Optional<String> firstMatchingHeader(Collection<String> headersToFind) { 423 for (String headerName : headersToFind) { 424 Optional<String> header = firstMatchingHeader(headerName); 425 if (header.isPresent()) { 426 return header; 427 } 428 } 429 430 return Optional.empty(); 431 } 432 433 @Override forEachHeader(BiConsumer<? super String, ? super List<String>> consumer)434 public void forEachHeader(BiConsumer<? super String, ? super List<String>> consumer) { 435 headers.forInternalRead().forEach((k, v) -> consumer.accept(k, unmodifiableList(v))); 436 } 437 438 @Override forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer)439 public void forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer) { 440 queryParameters.forInternalRead().forEach((k, v) -> consumer.accept(k, unmodifiableList(v))); 441 } 442 443 @Override numHeaders()444 public int numHeaders() { 445 return headers.forInternalRead().size(); 446 } 447 448 @Override numRawQueryParameters()449 public int numRawQueryParameters() { 450 return queryParameters.forInternalRead().size(); 451 } 452 453 @Override encodedQueryParameters()454 public Optional<String> encodedQueryParameters() { 455 return SdkHttpUtils.encodeAndFlattenQueryParameters(queryParameters.forInternalRead()); 456 } 457 458 @Override contentStreamProvider(ContentStreamProvider contentStreamProvider)459 public DefaultSdkHttpFullRequest.Builder contentStreamProvider(ContentStreamProvider contentStreamProvider) { 460 this.contentStreamProvider = contentStreamProvider; 461 return this; 462 } 463 464 @Override contentStreamProvider()465 public ContentStreamProvider contentStreamProvider() { 466 return contentStreamProvider; 467 } 468 469 @Override copy()470 public SdkHttpFullRequest.Builder copy() { 471 return build().toBuilder(); 472 } 473 474 @Override applyMutation(Consumer<SdkHttpRequest.Builder> mutator)475 public SdkHttpFullRequest.Builder applyMutation(Consumer<SdkHttpRequest.Builder> mutator) { 476 mutator.accept(this); 477 return this; 478 } 479 480 @Override build()481 public DefaultSdkHttpFullRequest build() { 482 return new DefaultSdkHttpFullRequest(this); 483 } 484 } 485 486 } 487