• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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