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.singletonList;
19
20 import java.net.URI;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Optional;
24 import software.amazon.awssdk.annotations.Immutable;
25 import software.amazon.awssdk.annotations.SdkProtectedApi;
26 import software.amazon.awssdk.utils.builder.CopyableBuilder;
27 import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
28 import software.amazon.awssdk.utils.http.SdkHttpUtils;
29
30 /**
31  * An immutable HTTP request without access to the request body. {@link SdkHttpFullRequest} should be used when access to a
32  * request body stream is required.
33  */

34 @SdkProtectedApi
35 @Immutable
36 public interface SdkHttpRequest extends SdkHttpHeaders, ToCopyableBuilder<SdkHttpRequest.Builder, SdkHttpRequest> {
37
38     /**
39      * @return Builder instance to construct a {@link DefaultSdkHttpFullRequest}.
40      */

41     static Builder builder() {
42         return new DefaultSdkHttpFullRequest.Builder();
43     }
44
45     /**
46      * Returns the protocol that should be used for HTTP communication.
47      *
48      * <p>This will always be "https" or "http" (lowercase).</p>
49      *
50      * @return Either "http" or "https" depending on which protocol should be used.
51      */

52     String protocol();
53
54     /**
55      * Returns the host that should be communicated with.
56      *
57      * <p>This will never be null.</p>
58      *
59      * @return The host to which the request should be sent.
60      */

61     String host();
62
63     /**
64      * The port that should be used for HTTP communication. If this was not configured when the request was created, it will be
65      * derived from the protocol. For "http" it would be 80, and for "https" it would be 443.
66      *
67      * <p>Important Note: AWS signing DOES NOT include the port when the request is signed if the default port for the protocol is
68      * being used. When sending requests via http over port 80 or via https over port 443, the URI or host header MUST NOT include
69      * the port or a signature error will be raised from the service for signed requests. HTTP plugin implementers are encouraged
70      * to use the {@link #getUri()} method for generating the URI to use for communicating with AWS to ensure the URI used in the
71      * request matches the URI used during signing.</p>
72      *
73      * @return The port that should be used for HTTP communication.
74      */

75     int port();
76
77     /**
78      * Returns the URL-encoded path that should be used in the HTTP request.
79      *
80      * <p>If a path is configured, the path will always start with '/' and may or may not end with '/', depending on what the
81      * service might expect. If a path is not configured, this will always return empty-string (ie. ""). Note that '/' is also a
82      * valid path.</p>
83      *
84      * @return The path to the resource being requested.
85      */

86     String encodedPath();
87
88     /**
89      * Returns a map of all non-URL encoded parameters in this request. HTTP plugins can use
90      * {@link SdkHttpUtils#encodeQueryParameters(Map)} to encode parameters into map-form, or
91      * {@link SdkHttpUtils#encodeAndFlattenQueryParameters(Map)} to encode the parameters into uri-formatted string form.
92      *
93      * <p>This will never be null. If there are no parameters an empty map is returned.</p>
94      *
95      * @return An unmodifiable map of all non-encoded parameters in this request.
96      */

97     Map<String, List<String>> rawQueryParameters();
98
99     /**
100      * Convert this HTTP request's protocol, host, port, path and query string into a properly-encoded URI string that matches the
101      * URI string used for AWS request signing.
102      *
103      * <p>The URI's port will be missing (-1) when the {@link #port()} is the default port for the {@link #protocol()}. (80 for
104      * http and 443 for https). This is to reflect the fact that request signature does not include the port.</p>
105      *
106      * @return The URI for this request, formatted in the same way the AWS HTTP request signer uses the URI in the signature.
107      */

108     default URI getUri() {
109         // We can't create a URI by simply passing the query parameters into the URI constructor that takes a query string,
110         // because URI will re-encode them. Because we want to encode them using our encoder, we have to build the URI
111         // ourselves and pass it to the single-argument URI constructor that doesn't perform the encoding.
112
113         String encodedQueryString = SdkHttpUtils.encodeAndFlattenQueryParameters(rawQueryParameters())
114                                                 .map(value -> "?" + value)
115                                                 .orElse("");
116
117         // Do not include the port in the URI when using the default port for the protocol.
118         String portString = SdkHttpUtils.isUsingStandardPort(protocol(), port()) ? "" : ":" + port();
119
120         return URI.create(protocol() + "://" + host() + portString + encodedPath() + encodedQueryString);
121     }
122
123     /**
124      * Returns the HTTP method (GET, POST, etc) to use when sending this request.
125      *
126      * <p>This will never be null.</p>
127      *
128      * @return The HTTP method to use when sending this request.
129      */

130     SdkHttpMethod method();
131
132     /**
133      * A mutable builder for {@link SdkHttpFullRequest}. An instance of this can be created using
134      * {@link SdkHttpFullRequest#builder()}.
135      */

136     interface Builder extends CopyableBuilder<Builder, SdkHttpRequest> {
137         /**
138          * Convenience method to set the {@link #protocol()}, {@link #host()}, {@link #port()}, and
139          * {@link #encodedPath()} from a {@link URI} object.
140          *
141          * @param uri URI containing protocol, host, port and path.
142          * @return This builder for method chaining.
143          */

144         default Builder uri(URI uri) {
145             return this.protocol(uri.getScheme())
146                        .host(uri.getHost())
147                        .port(uri.getPort())
148                        .encodedPath(SdkHttpUtils.appendUri(uri.getRawPath(), encodedPath()));
149         }
150
151         /**
152          * The protocol, exactly as it was configured with {@link #protocol(String)}.
153          */

154         String protocol();
155
156         /**
157          * Configure a {@link SdkHttpRequest#protocol()} to be used in the created HTTP request. This is not validated until the
158          * http request is created.
159          */

160         Builder protocol(String protocol);
161
162         /**
163          * The host, exactly as it was configured with {@link #host(String)}.
164          */

165         String host();
166
167         /**
168          * Configure a {@link SdkHttpRequest#host()} to be used in the created HTTP request. This is not validated until the
169          * http request is created.
170          */

171         Builder host(String host);
172
173         /**
174          * The port, exactly as it was configured with {@link #port(Integer)}.
175          */

176         Integer port();
177
178         /**
179          * Configure a {@link SdkHttpRequest#port()} to be used in the created HTTP request. This is not validated until the
180          * http request is created. In order to simplify mapping from a {@link URI}, "-1" will be treated as "null" when the http
181          * request is created.
182          */

183         Builder port(Integer port);
184
185         /**
186          * The path, exactly as it was configured with {@link #encodedPath(String)}.
187          */

188         String encodedPath();
189
190         /**
191          * Configure an {@link SdkHttpRequest#encodedPath()} to be used in the created HTTP request. This is not validated
192          * until the http request is created. This path MUST be URL encoded.
193          *
194          * <p>Justification of requirements: The path must be encoded when it is configured, because there is no way for the HTTP
195          * implementation to distinguish a "/" that is part of a resource name that should be encoded as "%2F" from a "/" that is
196          * part of the actual path.</p>
197          */

198         Builder encodedPath(String path);
199
200         /**
201          * The query parameters, exactly as they were configured with {@link #rawQueryParameters(Map)},
202          * {@link #putRawQueryParameter(String, String)} and {@link #putRawQueryParameter(String, List)}.
203          */

204         Map<String, List<String>> rawQueryParameters();
205
206         /**
207          * Add a single un-encoded query parameter to be included in the created HTTP request.
208          *
209          * <p>This completely <b>OVERRIDES</b> any values already configured with this parameter name in the builder.</p>
210          *
211          * @param paramName The name of the query parameter to add
212          * @param paramValue The un-encoded value for the query parameter.
213          */

214         default Builder putRawQueryParameter(String paramName, String paramValue) {
215             return putRawQueryParameter(paramName, singletonList(paramValue));
216         }
217
218         /**
219          * Add a single un-encoded query parameter to be included in the created HTTP request.
220          *
221          * <p>This will <b>ADD</b> the value to any existing values already configured with this parameter name in
222          * the builder.</p>
223          *
224          * @param paramName The name of the query parameter to add
225          * @param paramValue The un-encoded value for the query parameter.
226          */

227         Builder appendRawQueryParameter(String paramName, String paramValue);
228
229         /**
230          * Add a single un-encoded query parameter with multiple values to be included in the created HTTP request.
231          *
232          * <p>This completely <b>OVERRIDES</b> any values already configured with this parameter name in the builder.</p>
233          *
234          * @param paramName The name of the query parameter to add
235          * @param paramValues The un-encoded values for the query parameter.
236          */

237         Builder putRawQueryParameter(String paramName, List<String> paramValues);
238
239         /**
240          * Configure an {@link SdkHttpRequest#rawQueryParameters()} to be used in the created HTTP request. This is not validated
241          * until the http request is created. This overrides any values currently configured in the builder. The query parameters
242          * MUST NOT be URL encoded.
243          *
244          * <p>Justification of requirements: The query parameters must not be encoded when they are configured because some HTTP
245          * implementations perform this encoding automatically.</p>
246          */

247         Builder rawQueryParameters(Map<String, List<String>> queryParameters);
248
249         /**
250          * Remove all values for the requested query parameter from this builder.
251          */

252         Builder removeQueryParameter(String paramName);
253
254         /**
255          * Removes all query parameters from this builder.
256          */

257         Builder clearQueryParameters();
258
259         /**
260          * The path, exactly as it was configured with {@link #method(SdkHttpMethod)}.
261          */

262         SdkHttpMethod method();
263
264         /**
265          * Configure an {@link SdkHttpRequest#method()} to be used in the created HTTP request. This is not validated
266          * until the http request is created.
267          */

268         Builder method(SdkHttpMethod httpMethod);
269
270         /**
271          * Perform a case-insensitive search for a particular header in this request, returning the first matching header, if one
272          * is found.
273          *
274          * <p>This is useful for headers like 'Content-Type' or 'Content-Length' of which there is expected to be only one value
275          * present.</p>
276          *
277          * <p>This is equivalent to invoking {@link SdkHttpUtils#firstMatchingHeader(Map, String)}</p>.
278          *
279          * @param header The header to search for (case insensitively).
280          * @return The first header that matched the requested one, or empty if one was not found.
281          */

282         default Optional<String> firstMatchingHeader(String header) {
283             return SdkHttpUtils.firstMatchingHeader(headers(), header);
284         }
285
286         /**
287          * The query parameters, exactly as they were configured with {@link #headers(Map)},
288          * {@link #putHeader(String, String)} and {@link #putHeader(String, List)}.
289          */

290         Map<String, List<String>> headers();
291
292         /**
293          * Add a single header to be included in the created HTTP request.
294          *
295          * <p>This completely <b>OVERRIDES</b> any values already configured with this header name in the builder.</p>
296          *
297          * @param headerName The name of the header to add (eg. "Host")
298          * @param headerValue The value for the header
299          */

300         default Builder putHeader(String headerName, String headerValue) {
301             return putHeader(headerName, singletonList(headerValue));
302         }
303
304         /**
305          * Add a single header with multiple values to be included in the created HTTP request.
306          *
307          * <p>This completely <b>OVERRIDES</b> any values already configured with this header name in the builder.</p>
308          *
309          * @param headerName The name of the header to add
310          * @param headerValues The values for the header
311          */

312         Builder putHeader(String headerName, List<String> headerValues);
313
314         /**
315          * Add a single header to be included in the created HTTP request.
316          *
317          * <p>This will <b>ADD</b> the value to any existing values already configured with this header name in
318          * the builder.</p>
319          *
320          * @param headerName The name of the header to add
321          * @param headerValue The value for the header
322          */

323         Builder appendHeader(String headerName, String headerValue);
324
325         /**
326          * Configure an {@link SdkHttpRequest#headers()} to be used in the created HTTP request. This is not validated
327          * until the http request is created. This overrides any values currently configured in the builder.
328          */

329         Builder headers(Map<String, List<String>> headers);
330
331         /**
332          * Remove all values for the requested header from this builder.
333          */

334         Builder removeHeader(String headerName);
335
336         /**
337          * Removes all headers from this builder.
338          */

339         Builder clearHeaders();
340     }
341 }
342