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