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}