001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.function;
018
019import java.io.IOException;
020import java.net.InetSocketAddress;
021import java.net.URI;
022import java.nio.charset.Charset;
023import java.security.Principal;
024import java.time.Instant;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028import java.util.Optional;
029import java.util.OptionalLong;
030import java.util.function.Consumer;
031
032import javax.servlet.ServletException;
033import javax.servlet.http.Cookie;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpSession;
036
037import org.springframework.core.ParameterizedTypeReference;
038import org.springframework.core.io.buffer.DataBuffer;
039import org.springframework.http.HttpHeaders;
040import org.springframework.http.HttpMethod;
041import org.springframework.http.HttpRange;
042import org.springframework.http.MediaType;
043import org.springframework.http.converter.HttpMessageConverter;
044import org.springframework.http.server.PathContainer;
045import org.springframework.lang.Nullable;
046import org.springframework.util.Assert;
047import org.springframework.util.CollectionUtils;
048import org.springframework.util.MultiValueMap;
049import org.springframework.web.util.UriBuilder;
050
051/**
052 * Represents a server-side HTTP request, as handled by a {@code HandlerFunction}.
053 * Access to headers and body is offered by {@link Headers} and
054 * {@link #body(Class)}, respectively.
055 *
056 * @author Arjen Poutsma
057 * @since 5.2
058 */
059public interface ServerRequest {
060
061        /**
062         * Get the HTTP method.
063         * @return the HTTP method as an HttpMethod enum value, or {@code null}
064         * if not resolvable (e.g. in case of a non-standard HTTP method)
065         */
066        @Nullable
067        default HttpMethod method() {
068                return HttpMethod.resolve(methodName());
069        }
070
071        /**
072         * Get the name of the HTTP method.
073         * @return the HTTP method as a String
074         */
075        String methodName();
076
077        /**
078         * Get the request URI.
079         */
080        URI uri();
081
082        /**
083         * Get a {@code UriBuilderComponents} from the URI associated with this
084         * {@code ServerRequest}.
085         *
086         * @return a URI builder
087         */
088        UriBuilder uriBuilder();
089
090        /**
091         * Get the request path.
092         */
093        default String path() {
094                return uri().getRawPath();
095        }
096
097        /**
098         * Get the request path as a {@code PathContainer}.
099         */
100        default PathContainer pathContainer() {
101                return PathContainer.parsePath(path());
102        }
103
104        /**
105         * Get the headers of this request.
106         */
107        Headers headers();
108
109        /**
110         * Get the cookies of this request.
111         */
112        MultiValueMap<String, Cookie> cookies();
113
114        /**
115         * Get the remote address to which this request is connected, if available.
116         */
117        Optional<InetSocketAddress> remoteAddress();
118
119        /**
120         * Get the readers used to convert the body of this request.
121         */
122        List<HttpMessageConverter<?>> messageConverters();
123
124        /**
125         * Extract the body as an object of the given type.
126         * @param bodyType the type of return value
127         * @param <T> the body type
128         * @return the body
129         */
130        <T> T body(Class<T> bodyType) throws ServletException, IOException;
131
132        /**
133         * Extract the body as an object of the given type.
134         * @param bodyType the type of return value
135         * @param <T> the body type
136         * @return the body
137         */
138        <T> T body(ParameterizedTypeReference<T> bodyType) throws ServletException, IOException;
139
140        /**
141         * Get the request attribute value if present.
142         * @param name the attribute name
143         * @return the attribute value
144         */
145        default Optional<Object> attribute(String name) {
146                Map<String, Object> attributes = attributes();
147                if (attributes.containsKey(name)) {
148                        return Optional.of(attributes.get(name));
149                }
150                else {
151                        return Optional.empty();
152                }
153        }
154
155        /**
156         * Get a mutable map of request attributes.
157         * @return the request attributes
158         */
159        Map<String, Object> attributes();
160
161        /**
162         * Get the first parameter with the given name, if present. Servlet
163         * parameters are contained in the query string or posted form data.
164         * @param name the parameter name
165         * @return the parameter value
166         * @see HttpServletRequest#getParameter(String)
167         */
168        default Optional<String> param(String name) {
169                List<String> paramValues = params().get(name);
170                if (CollectionUtils.isEmpty(paramValues)) {
171                        return Optional.empty();
172                }
173                else {
174                        String value = paramValues.get(0);
175                        if (value == null) {
176                                value = "";
177                        }
178                        return Optional.of(value);
179                }
180        }
181
182        /**
183         * Get all parameters for this request. Servlet parameters are contained
184         * in the query string or posted form data.
185         * @see HttpServletRequest#getParameterMap()
186         */
187        MultiValueMap<String, String> params();
188
189        /**
190         * Get the path variable with the given name, if present.
191         * @param name the variable name
192         * @return the variable value
193         * @throws IllegalArgumentException if there is no path variable with the given name
194         */
195        default String pathVariable(String name) {
196                Map<String, String> pathVariables = pathVariables();
197                if (pathVariables.containsKey(name)) {
198                        return pathVariables().get(name);
199                }
200                else {
201                        throw new IllegalArgumentException("No path variable with name \"" + name + "\" available");
202                }
203        }
204
205        /**
206         * Get all path variables for this request.
207         */
208        Map<String, String> pathVariables();
209
210        /**
211         * Get the web session for this request. Always guaranteed to
212         * return an instance either matching to the session id requested by the
213         * client, or with a new session id either because the client did not
214         * specify one or because the underlying session had expired. Use of this
215         * method does not automatically create a session.
216         */
217        HttpSession session();
218
219        /**
220         * Get the authenticated user for the request, if any.
221         */
222        Optional<Principal> principal();
223
224        /**
225         * Get the servlet request that this request is based on.
226         */
227        HttpServletRequest servletRequest();
228
229        /**
230         * Check whether the requested resource has been modified given the
231         * supplied last-modified timestamp (as determined by the application).
232         * If not modified, this method returns a response with corresponding
233         * status code and headers, otherwise an empty result.
234         * <p>Typical usage:
235         * <pre class="code">
236         * public ServerResponse myHandleMethod(ServerRequest request) {
237         *   Instant lastModified = // application-specific calculation
238         *       return request.checkNotModified(lastModified)
239         *         .orElseGet(() -> {
240         *           // further request processing, actually building content
241         *               return ServerResponse.ok().body(...);
242         *         });
243         * }</pre>
244         * <p>This method works with conditional GET/HEAD requests, but
245         * also with conditional POST/PUT/DELETE requests.
246         * <p><strong>Note:</strong> you can use either
247         * this {@code #checkNotModified(Instant)} method; or
248         * {@link #checkNotModified(String)}. If you want enforce both
249         * a strong entity tag and a Last-Modified value,
250         * as recommended by the HTTP specification,
251         * then you should use {@link #checkNotModified(Instant, String)}.
252         * @param lastModified the last-modified timestamp that the
253         * application determined for the underlying resource
254         * @return a corresponding response if the request qualifies as not
255         * modified, or an empty result otherwise.
256         * @since 5.2.5
257         */
258        default Optional<ServerResponse> checkNotModified(Instant lastModified) {
259                Assert.notNull(lastModified, "LastModified must not be null");
260                return DefaultServerRequest.checkNotModified(servletRequest(), lastModified, null);
261        }
262
263        /**
264         * Check whether the requested resource has been modified given the
265         * supplied {@code ETag} (entity tag), as determined by the application.
266         * If not modified, this method returns a response with corresponding
267         * status code and headers, otherwise an empty result.
268         * <p>Typical usage:
269         * <pre class="code">
270         * public ServerResponse myHandleMethod(ServerRequest request) {
271         *   String eTag = // application-specific calculation
272         *       return request.checkNotModified(eTag)
273         *         .orElseGet(() -> {
274         *           // further request processing, actually building content
275         *               return ServerResponse.ok().body(...);
276         *         });
277         * }</pre>
278         * <p>This method works with conditional GET/HEAD requests, but
279         * also with conditional POST/PUT/DELETE requests.
280         * <p><strong>Note:</strong> you can use either
281         * this {@link #checkNotModified(Instant)} method; or
282         * {@code #checkNotModified(String)}. If you want enforce both
283         * a strong entity tag and a Last-Modified value,
284         * as recommended by the HTTP specification,
285         * then you should use {@link #checkNotModified(Instant, String)}.
286         * @param etag the entity tag that the application determined
287         * for the underlying resource. This parameter will be padded
288         * with quotes (") if necessary.
289         * @return a corresponding response if the request qualifies as not
290         * modified, or an empty result otherwise.
291         * @since 5.2.5
292         */
293        default Optional<ServerResponse> checkNotModified(String etag) {
294                Assert.notNull(etag, "Etag must not be null");
295                return DefaultServerRequest.checkNotModified(servletRequest(), null, etag);
296        }
297
298        /**
299         * Check whether the requested resource has been modified given the
300         * supplied {@code ETag} (entity tag) and last-modified timestamp,
301         * as determined by the application.
302         * If not modified, this method returns a response with corresponding
303         * status code and headers, otherwise an empty result.
304         * <p>Typical usage:
305         * <pre class="code">
306         * public ServerResponse myHandleMethod(ServerRequest request) {
307         *   Instant lastModified = // application-specific calculation
308         *   String eTag = // application-specific calculation
309         *       return request.checkNotModified(lastModified, eTag)
310         *         .orElseGet(() -> {
311         *           // further request processing, actually building content
312         *               return ServerResponse.ok().body(...);
313         *         });
314         * }</pre>
315         * <p>This method works with conditional GET/HEAD requests, but
316         * also with conditional POST/PUT/DELETE requests.
317         * @param lastModified the last-modified timestamp that the
318         * application determined for the underlying resource
319         * @param etag the entity tag that the application determined
320         * for the underlying resource. This parameter will be padded
321         * with quotes (") if necessary.
322         * @return a corresponding response if the request qualifies as not
323         * modified, or an empty result otherwise.
324         * @since 5.2.5
325         */
326        default Optional<ServerResponse> checkNotModified(Instant lastModified, String etag) {
327                Assert.notNull(lastModified, "LastModified must not be null");
328                Assert.notNull(etag, "Etag must not be null");
329                return DefaultServerRequest.checkNotModified(servletRequest(), lastModified, etag);
330        }
331
332
333        // Static methods
334
335        /**
336         * Create a new {@code ServerRequest} based on the given {@code HttpServletRequest} and
337         * message converters.
338         * @param servletRequest the request
339         * @param messageReaders the message readers
340         * @return the created {@code ServerRequest}
341         */
342        static ServerRequest create(HttpServletRequest servletRequest, List<HttpMessageConverter<?>> messageReaders) {
343                return new DefaultServerRequest(servletRequest, messageReaders);
344        }
345
346        /**
347         * Create a builder with the status, headers, and cookies of the given request.
348         * @param other the response to copy the status, headers, and cookies from
349         * @return the created builder
350         */
351        static Builder from(ServerRequest other) {
352                return new DefaultServerRequestBuilder(other);
353        }
354
355
356
357        /**
358         * Represents the headers of the HTTP request.
359         * @see ServerRequest#headers()
360         */
361        interface Headers {
362
363                /**
364                 * Get the list of acceptable media types, as specified by the {@code Accept}
365                 * header.
366                 * <p>Returns an empty list if the acceptable media types are unspecified.
367                 */
368                List<MediaType> accept();
369
370                /**
371                 * Get the list of acceptable charsets, as specified by the
372                 * {@code Accept-Charset} header.
373                 */
374                List<Charset> acceptCharset();
375
376                /**
377                 * Get the list of acceptable languages, as specified by the
378                 * {@code Accept-Language} header.
379                 */
380                List<Locale.LanguageRange> acceptLanguage();
381
382                /**
383                 * Get the length of the body in bytes, as specified by the
384                 * {@code Content-Length} header.
385                 */
386                OptionalLong contentLength();
387
388                /**
389                 * Get the media type of the body, as specified by the
390                 * {@code Content-Type} header.
391                 */
392                Optional<MediaType> contentType();
393
394                /**
395                 * Get the value of the {@code Host} header, if available.
396                 * <p>If the header value does not contain a port, the
397                 * {@linkplain InetSocketAddress#getPort() port} in the returned address will
398                 * be {@code 0}.
399                 */
400                @Nullable
401                InetSocketAddress host();
402
403                /**
404                 * Get the value of the {@code Range} header.
405                 * <p>Returns an empty list when the range is unknown.
406                 */
407                List<HttpRange> range();
408
409                /**
410                 * Get the header value(s), if any, for the header of the given name.
411                 * <p>Returns an empty list if no header values are found.
412                 * @param headerName the header name
413                 */
414                List<String> header(String headerName);
415
416                /**
417                 * Get the first header value, if any, for the header for the given name.
418                 * <p>Returns {@code null} if no header values are found.
419                 * @param headerName the header name
420                 * @since 5.2.5
421                 */
422                @Nullable
423                default String firstHeader(String headerName) {
424                        List<String> list = header(headerName);
425                        return list.isEmpty() ? null : list.get(0);
426                }
427
428                /**
429                 * Get the headers as an instance of {@link HttpHeaders}.
430                 */
431                HttpHeaders asHttpHeaders();
432        }
433
434
435        /**
436         * Defines a builder for a request.
437         */
438        interface Builder {
439
440                /**
441                 * Set the method of the request.
442                 * @param method the new method
443                 * @return this builder
444                 */
445                Builder method(HttpMethod method);
446
447                /**
448                 * Set the URI of the request.
449                 * @param uri the new URI
450                 * @return this builder
451                 */
452                Builder uri(URI uri);
453
454                /**
455                 * Add the given header value(s) under the given name.
456                 * @param headerName  the header name
457                 * @param headerValues the header value(s)
458                 * @return this builder
459                 * @see HttpHeaders#add(String, String)
460                 */
461                Builder header(String headerName, String... headerValues);
462
463                /**
464                 * Manipulate this request's headers with the given consumer.
465                 * <p>The headers provided to the consumer are "live", so that the consumer can be used to
466                 * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values,
467                 * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other
468                 * {@link HttpHeaders} methods.
469                 * @param headersConsumer a function that consumes the {@code HttpHeaders}
470                 * @return this builder
471                 */
472                Builder headers(Consumer<HttpHeaders> headersConsumer);
473
474                /**
475                 * Add a cookie with the given name and value(s).
476                 * @param name the cookie name
477                 * @param values the cookie value(s)
478                 * @return this builder
479                 */
480                Builder cookie(String name, String... values);
481
482                /**
483                 * Manipulate this request's cookies with the given consumer.
484                 * <p>The map provided to the consumer is "live", so that the consumer can be used to
485                 * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing cookies,
486                 * {@linkplain MultiValueMap#remove(Object) remove} cookies, or use any of the other
487                 * {@link MultiValueMap} methods.
488                 * @param cookiesConsumer a function that consumes the cookies map
489                 * @return this builder
490                 */
491                Builder cookies(Consumer<MultiValueMap<String, Cookie>> cookiesConsumer);
492
493                /**
494                 * Set the body of the request.
495                 * <p>Calling this methods will
496                 * {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
497                 * the existing body of the builder.
498                 * @param body the new body
499                 * @return this builder
500                 */
501                Builder body(byte[] body);
502
503                /**
504                 * Set the body of the request to the UTF-8 encoded bytes of the given string.
505                 * <p>Calling this methods will
506                 * {@linkplain org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer) release}
507                 * the existing body of the builder.
508                 * @param body the new body
509                 * @return this builder
510                 */
511                Builder body(String body);
512
513                /**
514                 * Add an attribute with the given name and value.
515                 * @param name  the attribute name
516                 * @param value the attribute value
517                 * @return this builder
518                 */
519                Builder attribute(String name, Object value);
520
521                /**
522                 * Manipulate this request's attributes with the given consumer.
523                 * <p>The map provided to the consumer is "live", so that the consumer can be used
524                 * to {@linkplain Map#put(Object, Object) overwrite} existing attributes,
525                 * {@linkplain Map#remove(Object) remove} attributes, or use any of the other
526                 * {@link Map} methods.
527                 * @param attributesConsumer a function that consumes the attributes map
528                 * @return this builder
529                 */
530                Builder attributes(Consumer<Map<String, Object>> attributesConsumer);
531
532                /**
533                 * Build the request.
534                 * @return the built request
535                 */
536                ServerRequest build();
537        }
538
539}