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.singletonList; 20 21 import java.net.URI; 22 import java.util.Collection; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Optional; 26 import java.util.function.BiConsumer; 27 import software.amazon.awssdk.annotations.Immutable; 28 import software.amazon.awssdk.annotations.SdkProtectedApi; 29 import software.amazon.awssdk.utils.builder.CopyableBuilder; 30 import software.amazon.awssdk.utils.builder.ToCopyableBuilder; 31 import software.amazon.awssdk.utils.http.SdkHttpUtils; 32 33 /** 34 * An immutable HTTP request without access to the request body. {@link SdkHttpFullRequest} should be used when access to a 35 * request body stream is required. 36 */ 37 @SdkProtectedApi 38 @Immutable 39 public interface SdkHttpRequest extends SdkHttpHeaders, ToCopyableBuilder<SdkHttpRequest.Builder, SdkHttpRequest> { 40 41 /** 42 * @return Builder instance to construct a {@link DefaultSdkHttpFullRequest}. 43 */ builder()44 static Builder builder() { 45 return new DefaultSdkHttpFullRequest.Builder(); 46 } 47 48 /** 49 * Returns the protocol that should be used for HTTP communication. 50 * 51 * <p>This will always be "https" or "http" (lowercase).</p> 52 * 53 * @return Either "http" or "https" depending on which protocol should be used. 54 */ protocol()55 String protocol(); 56 57 /** 58 * Returns the host that should be communicated with. 59 * 60 * <p>This will never be null.</p> 61 * 62 * @return The host to which the request should be sent. 63 */ host()64 String host(); 65 66 /** 67 * The port that should be used for HTTP communication. If this was not configured when the request was created, it will be 68 * derived from the protocol. For "http" it would be 80, and for "https" it would be 443. 69 * 70 * <p>Important Note: AWS signing DOES NOT include the port when the request is signed if the default port for the protocol is 71 * being used. When sending requests via http over port 80 or via https over port 443, the URI or host header MUST NOT include 72 * the port or a signature error will be raised from the service for signed requests. HTTP plugin implementers are encouraged 73 * to use the {@link #getUri()} method for generating the URI to use for communicating with AWS to ensure the URI used in the 74 * request matches the URI used during signing.</p> 75 * 76 * @return The port that should be used for HTTP communication. 77 */ port()78 int port(); 79 80 /** 81 * Returns the URL-encoded path that should be used in the HTTP request. 82 * 83 * <p>If a path is configured, the path will always start with '/' and may or may not end with '/', depending on what the 84 * service might expect. If a path is not configured, this will always return empty-string (ie. ""). Note that '/' is also a 85 * valid path.</p> 86 * 87 * @return The path to the resource being requested. 88 */ encodedPath()89 String encodedPath(); 90 91 /** 92 * Returns a map of all non-URL encoded parameters in this request. HTTP plugins can use 93 * {@link SdkHttpUtils#encodeQueryParameters(Map)} to encode parameters into map-form, or 94 * {@link SdkHttpUtils#encodeAndFlattenQueryParameters(Map)} to encode the parameters into uri-formatted string form. 95 * 96 * <p>This will never be null. If there are no parameters an empty map is returned.</p> 97 * 98 * @return An unmodifiable map of all non-encoded parameters in this request. 99 */ rawQueryParameters()100 Map<String, List<String>> rawQueryParameters(); 101 firstMatchingRawQueryParameter(String key)102 default Optional<String> firstMatchingRawQueryParameter(String key) { 103 List<String> values = rawQueryParameters().get(key); 104 return values == null ? Optional.empty() : values.stream().findFirst(); 105 } 106 firstMatchingRawQueryParameter(Collection<String> keys)107 default Optional<String> firstMatchingRawQueryParameter(Collection<String> keys) { 108 for (String key : keys) { 109 Optional<String> result = firstMatchingRawQueryParameter(key); 110 if (result.isPresent()) { 111 return result; 112 } 113 } 114 115 return Optional.empty(); 116 } 117 firstMatchingRawQueryParameters(String key)118 default List<String> firstMatchingRawQueryParameters(String key) { 119 List<String> values = rawQueryParameters().get(key); 120 return values == null ? emptyList() : values; 121 } 122 forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer)123 default void forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer) { 124 rawQueryParameters().forEach(consumer); 125 } 126 numRawQueryParameters()127 default int numRawQueryParameters() { 128 return rawQueryParameters().size(); 129 } 130 encodedQueryParameters()131 default Optional<String> encodedQueryParameters() { 132 return SdkHttpUtils.encodeAndFlattenQueryParameters(rawQueryParameters()); 133 } 134 encodedQueryParametersAsFormData()135 default Optional<String> encodedQueryParametersAsFormData() { 136 return SdkHttpUtils.encodeAndFlattenFormData(rawQueryParameters()); 137 } 138 139 /** 140 * Convert this HTTP request's protocol, host, port, path and query string into a properly-encoded URI string that matches the 141 * URI string used for AWS request signing. 142 * 143 * <p>The URI's port will be missing (-1) when the {@link #port()} is the default port for the {@link #protocol()}. (80 for 144 * http and 443 for https). This is to reflect the fact that request signature does not include the port.</p> 145 * 146 * @return The URI for this request, formatted in the same way the AWS HTTP request signer uses the URI in the signature. 147 */ getUri()148 default URI getUri() { 149 // We can't create a URI by simply passing the query parameters into the URI constructor that takes a query string, 150 // because URI will re-encode them. Because we want to encode them using our encoder, we have to build the URI 151 // ourselves and pass it to the single-argument URI constructor that doesn't perform the encoding. 152 String encodedQueryString = encodedQueryParameters().map(value -> "?" + value).orElse(""); 153 154 // Do not include the port in the URI when using the default port for the protocol. 155 String portString = SdkHttpUtils.isUsingStandardPort(protocol(), port()) ? "" : ":" + port(); 156 157 return URI.create(protocol() + "://" + host() + portString + encodedPath() + encodedQueryString); 158 } 159 160 /** 161 * Returns the HTTP method (GET, POST, etc) to use when sending this request. 162 * 163 * <p>This will never be null.</p> 164 * 165 * @return The HTTP method to use when sending this request. 166 */ method()167 SdkHttpMethod method(); 168 169 /** 170 * A mutable builder for {@link SdkHttpFullRequest}. An instance of this can be created using 171 * {@link SdkHttpFullRequest#builder()}. 172 */ 173 interface Builder extends CopyableBuilder<Builder, SdkHttpRequest>, SdkHttpHeaders { 174 /** 175 * Convenience method to set the {@link #protocol()}, {@link #host()}, {@link #port()}, 176 * {@link #encodedPath()} and extracts query parameters from a {@link URI} object. 177 * 178 * @param uri URI containing protocol, host, port and path. 179 * @return This builder for method chaining. 180 */ uri(URI uri)181 default Builder uri(URI uri) { 182 Builder builder = this.protocol(uri.getScheme()) 183 .host(uri.getHost()) 184 .port(uri.getPort()) 185 .encodedPath(SdkHttpUtils.appendUri(uri.getRawPath(), encodedPath())); 186 if (uri.getRawQuery() != null) { 187 builder.clearQueryParameters(); 188 SdkHttpUtils.uriParams(uri) 189 .forEach(this::putRawQueryParameter); 190 } 191 return builder; 192 } 193 194 /** 195 * The protocol, exactly as it was configured with {@link #protocol(String)}. 196 */ protocol()197 String protocol(); 198 199 /** 200 * Configure a {@link SdkHttpRequest#protocol()} to be used in the created HTTP request. This is not validated until the 201 * http request is created. 202 */ protocol(String protocol)203 Builder protocol(String protocol); 204 205 /** 206 * The host, exactly as it was configured with {@link #host(String)}. 207 */ host()208 String host(); 209 210 /** 211 * Configure a {@link SdkHttpRequest#host()} to be used in the created HTTP request. This is not validated until the 212 * http request is created. 213 */ host(String host)214 Builder host(String host); 215 216 /** 217 * The port, exactly as it was configured with {@link #port(Integer)}. 218 */ port()219 Integer port(); 220 221 /** 222 * Configure a {@link SdkHttpRequest#port()} to be used in the created HTTP request. This is not validated until the 223 * http request is created. In order to simplify mapping from a {@link URI}, "-1" will be treated as "null" when the http 224 * request is created. 225 */ port(Integer port)226 Builder port(Integer port); 227 228 /** 229 * The path, exactly as it was configured with {@link #encodedPath(String)}. 230 */ encodedPath()231 String encodedPath(); 232 233 /** 234 * Configure an {@link SdkHttpRequest#encodedPath()} to be used in the created HTTP request. This is not validated 235 * until the http request is created. This path MUST be URL encoded. 236 * 237 * <p>Justification of requirements: The path must be encoded when it is configured, because there is no way for the HTTP 238 * implementation to distinguish a "/" that is part of a resource name that should be encoded as "%2F" from a "/" that is 239 * part of the actual path.</p> 240 */ encodedPath(String path)241 Builder encodedPath(String path); 242 243 /** 244 * The query parameters, exactly as they were configured with {@link #rawQueryParameters(Map)}, 245 * {@link #putRawQueryParameter(String, String)} and {@link #putRawQueryParameter(String, List)}. 246 */ rawQueryParameters()247 Map<String, List<String>> rawQueryParameters(); 248 249 /** 250 * Add a single un-encoded query parameter to be included in the created HTTP request. 251 * 252 * <p>This completely <b>OVERRIDES</b> any values already configured with this parameter name in the builder.</p> 253 * 254 * @param paramName The name of the query parameter to add 255 * @param paramValue The un-encoded value for the query parameter. 256 */ putRawQueryParameter(String paramName, String paramValue)257 default Builder putRawQueryParameter(String paramName, String paramValue) { 258 return putRawQueryParameter(paramName, singletonList(paramValue)); 259 } 260 261 /** 262 * Add a single un-encoded query parameter to be included in the created HTTP request. 263 * 264 * <p>This will <b>ADD</b> the value to any existing values already configured with this parameter name in 265 * the builder.</p> 266 * 267 * @param paramName The name of the query parameter to add 268 * @param paramValue The un-encoded value for the query parameter. 269 */ appendRawQueryParameter(String paramName, String paramValue)270 Builder appendRawQueryParameter(String paramName, String paramValue); 271 272 /** 273 * Add a single un-encoded query parameter with multiple values to be included in the created HTTP request. 274 * 275 * <p>This completely <b>OVERRIDES</b> any values already configured with this parameter name in the builder.</p> 276 * 277 * @param paramName The name of the query parameter to add 278 * @param paramValues The un-encoded values for the query parameter. 279 */ putRawQueryParameter(String paramName, List<String> paramValues)280 Builder putRawQueryParameter(String paramName, List<String> paramValues); 281 282 /** 283 * Configure an {@link SdkHttpRequest#rawQueryParameters()} to be used in the created HTTP request. This is not validated 284 * until the http request is created. This overrides any values currently configured in the builder. The query parameters 285 * MUST NOT be URL encoded. 286 * 287 * <p>Justification of requirements: The query parameters must not be encoded when they are configured because some HTTP 288 * implementations perform this encoding automatically.</p> 289 */ rawQueryParameters(Map<String, List<String>> queryParameters)290 Builder rawQueryParameters(Map<String, List<String>> queryParameters); 291 292 /** 293 * Remove all values for the requested query parameter from this builder. 294 */ removeQueryParameter(String paramName)295 Builder removeQueryParameter(String paramName); 296 297 /** 298 * Removes all query parameters from this builder. 299 */ clearQueryParameters()300 Builder clearQueryParameters(); 301 forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer)302 default void forEachRawQueryParameter(BiConsumer<? super String, ? super List<String>> consumer) { 303 rawQueryParameters().forEach(consumer); 304 } 305 numRawQueryParameters()306 default int numRawQueryParameters() { 307 return rawQueryParameters().size(); 308 } 309 encodedQueryParameters()310 default Optional<String> encodedQueryParameters() { 311 return SdkHttpUtils.encodeAndFlattenQueryParameters(rawQueryParameters()); 312 } 313 314 /** 315 * The path, exactly as it was configured with {@link #method(SdkHttpMethod)}. 316 */ method()317 SdkHttpMethod method(); 318 319 /** 320 * Configure an {@link SdkHttpRequest#method()} to be used in the created HTTP request. This is not validated 321 * until the http request is created. 322 */ method(SdkHttpMethod httpMethod)323 Builder method(SdkHttpMethod httpMethod); 324 325 /** 326 * The query parameters, exactly as they were configured with {@link #headers(Map)}, 327 * {@link #putHeader(String, String)} and {@link #putHeader(String, List)}. 328 */ headers()329 Map<String, List<String>> headers(); 330 331 /** 332 * Add a single header to be included in the created HTTP request. 333 * 334 * <p>This completely <b>OVERRIDES</b> any values already configured with this header name in the builder.</p> 335 * 336 * @param headerName The name of the header to add (eg. "Host") 337 * @param headerValue The value for the header 338 */ putHeader(String headerName, String headerValue)339 default Builder putHeader(String headerName, String headerValue) { 340 return putHeader(headerName, singletonList(headerValue)); 341 } 342 343 /** 344 * Add a single header with multiple values to be included in the created HTTP request. 345 * 346 * <p>This completely <b>OVERRIDES</b> any values already configured with this header name in the builder.</p> 347 * 348 * @param headerName The name of the header to add 349 * @param headerValues The values for the header 350 */ putHeader(String headerName, List<String> headerValues)351 Builder putHeader(String headerName, List<String> headerValues); 352 353 /** 354 * Add a single header to be included in the created HTTP request. 355 * 356 * <p>This will <b>ADD</b> the value to any existing values already configured with this header name in 357 * the builder.</p> 358 * 359 * @param headerName The name of the header to add 360 * @param headerValue The value for the header 361 */ appendHeader(String headerName, String headerValue)362 Builder appendHeader(String headerName, String headerValue); 363 364 /** 365 * Configure an {@link SdkHttpRequest#headers()} to be used in the created HTTP request. This is not validated 366 * until the http request is created. This overrides any values currently configured in the builder. 367 */ headers(Map<String, List<String>> headers)368 Builder headers(Map<String, List<String>> headers); 369 370 /** 371 * Remove all values for the requested header from this builder. 372 */ removeHeader(String headerName)373 Builder removeHeader(String headerName); 374 375 /** 376 * Removes all headers from this builder. 377 */ clearHeaders()378 Builder clearHeaders(); 379 } 380 } 381