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.http; 018 019import java.lang.reflect.Type; 020import java.net.URI; 021import java.nio.charset.Charset; 022import java.time.Instant; 023import java.time.ZonedDateTime; 024import java.util.Arrays; 025import java.util.function.Consumer; 026 027import org.springframework.lang.Nullable; 028import org.springframework.util.MultiValueMap; 029import org.springframework.util.ObjectUtils; 030 031/** 032 * Extension of {@link HttpEntity} that adds a {@linkplain HttpMethod method} and 033 * {@linkplain URI uri}. Used in {@code RestTemplate} and {@code @Controller} methods. 034 * 035 * <p>In {@code RestTemplate}, this class is used as parameter in 036 * {@link org.springframework.web.client.RestTemplate#exchange(RequestEntity, Class) exchange()}: 037 * <pre class="code"> 038 * MyRequest body = ... 039 * RequestEntity<MyRequest> request = RequestEntity 040 * .post(new URI("https://example.com/bar")) 041 * .accept(MediaType.APPLICATION_JSON) 042 * .body(body); 043 * ResponseEntity<MyResponse> response = template.exchange(request, MyResponse.class); 044 * </pre> 045 * 046 * <p>If you would like to provide a URI template with variables, consider using 047 * {@link org.springframework.web.util.DefaultUriBuilderFactory DefaultUriBuilderFactory}: 048 * <pre class="code"> 049 * // Create shared factory 050 * UriBuilderFactory factory = new DefaultUriBuilderFactory(); 051 * 052 * // Use factory to create URL from template 053 * URI uri = factory.uriString("https://example.com/{foo}").build("bar"); 054 * RequestEntity<MyRequest> request = RequestEntity.post(uri).accept(MediaType.APPLICATION_JSON).body(body); 055 * </pre> 056 * 057 * <p>Can also be used in Spring MVC, as a parameter in a @Controller method: 058 * <pre class="code"> 059 * @RequestMapping("/handle") 060 * public void handle(RequestEntity<String> request) { 061 * HttpMethod method = request.getMethod(); 062 * URI url = request.getUrl(); 063 * String body = request.getBody(); 064 * } 065 * </pre> 066 * 067 * @author Arjen Poutsma 068 * @author Sebastien Deleuze 069 * @since 4.1 070 * @param <T> the body type 071 * @see #getMethod() 072 * @see #getUrl() 073 * @see org.springframework.web.client.RestOperations#exchange(RequestEntity, Class) 074 * @see ResponseEntity 075 */ 076public class RequestEntity<T> extends HttpEntity<T> { 077 078 @Nullable 079 private final HttpMethod method; 080 081 private final URI url; 082 083 @Nullable 084 private final Type type; 085 086 087 /** 088 * Constructor with method and URL but without body nor headers. 089 * @param method the method 090 * @param url the URL 091 */ 092 public RequestEntity(HttpMethod method, URI url) { 093 this(null, null, method, url, null); 094 } 095 096 /** 097 * Constructor with method, URL and body but without headers. 098 * @param body the body 099 * @param method the method 100 * @param url the URL 101 */ 102 public RequestEntity(@Nullable T body, HttpMethod method, URI url) { 103 this(body, null, method, url, null); 104 } 105 106 /** 107 * Constructor with method, URL, body and type but without headers. 108 * @param body the body 109 * @param method the method 110 * @param url the URL 111 * @param type the type used for generic type resolution 112 * @since 4.3 113 */ 114 public RequestEntity(@Nullable T body, HttpMethod method, URI url, Type type) { 115 this(body, null, method, url, type); 116 } 117 118 /** 119 * Constructor with method, URL and headers but without body. 120 * @param headers the headers 121 * @param method the method 122 * @param url the URL 123 */ 124 public RequestEntity(MultiValueMap<String, String> headers, HttpMethod method, URI url) { 125 this(null, headers, method, url, null); 126 } 127 128 /** 129 * Constructor with method, URL, headers and body. 130 * @param body the body 131 * @param headers the headers 132 * @param method the method 133 * @param url the URL 134 */ 135 public RequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, 136 @Nullable HttpMethod method, URI url) { 137 138 this(body, headers, method, url, null); 139 } 140 141 /** 142 * Constructor with method, URL, headers, body and type. 143 * @param body the body 144 * @param headers the headers 145 * @param method the method 146 * @param url the URL 147 * @param type the type used for generic type resolution 148 * @since 4.3 149 */ 150 public RequestEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, 151 @Nullable HttpMethod method, URI url, @Nullable Type type) { 152 153 super(body, headers); 154 this.method = method; 155 this.url = url; 156 this.type = type; 157 } 158 159 160 /** 161 * Return the HTTP method of the request. 162 * @return the HTTP method as an {@code HttpMethod} enum value 163 */ 164 @Nullable 165 public HttpMethod getMethod() { 166 return this.method; 167 } 168 169 /** 170 * Return the URL of the request. 171 * @return the URL as a {@code URI} 172 */ 173 public URI getUrl() { 174 return this.url; 175 } 176 177 /** 178 * Return the type of the request's body. 179 * @return the request's body type, or {@code null} if not known 180 * @since 4.3 181 */ 182 @Nullable 183 public Type getType() { 184 if (this.type == null) { 185 T body = getBody(); 186 if (body != null) { 187 return body.getClass(); 188 } 189 } 190 return this.type; 191 } 192 193 194 @Override 195 public boolean equals(@Nullable Object other) { 196 if (this == other) { 197 return true; 198 } 199 if (!super.equals(other)) { 200 return false; 201 } 202 RequestEntity<?> otherEntity = (RequestEntity<?>) other; 203 return (ObjectUtils.nullSafeEquals(getMethod(), otherEntity.getMethod()) && 204 ObjectUtils.nullSafeEquals(getUrl(), otherEntity.getUrl())); 205 } 206 207 @Override 208 public int hashCode() { 209 int hashCode = super.hashCode(); 210 hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.method); 211 hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.url); 212 return hashCode; 213 } 214 215 @Override 216 public String toString() { 217 StringBuilder builder = new StringBuilder("<"); 218 builder.append(getMethod()); 219 builder.append(' '); 220 builder.append(getUrl()); 221 builder.append(','); 222 T body = getBody(); 223 HttpHeaders headers = getHeaders(); 224 if (body != null) { 225 builder.append(body); 226 builder.append(','); 227 } 228 builder.append(headers); 229 builder.append('>'); 230 return builder.toString(); 231 } 232 233 234 // Static builder methods 235 236 /** 237 * Create a builder with the given method and url. 238 * @param method the HTTP method (GET, POST, etc) 239 * @param url the URL 240 * @return the created builder 241 */ 242 public static BodyBuilder method(HttpMethod method, URI url) { 243 return new DefaultBodyBuilder(method, url); 244 } 245 246 /** 247 * Create an HTTP GET builder with the given url. 248 * @param url the URL 249 * @return the created builder 250 */ 251 public static HeadersBuilder<?> get(URI url) { 252 return method(HttpMethod.GET, url); 253 } 254 255 /** 256 * Create an HTTP HEAD builder with the given url. 257 * @param url the URL 258 * @return the created builder 259 */ 260 public static HeadersBuilder<?> head(URI url) { 261 return method(HttpMethod.HEAD, url); 262 } 263 264 /** 265 * Create an HTTP POST builder with the given url. 266 * @param url the URL 267 * @return the created builder 268 */ 269 public static BodyBuilder post(URI url) { 270 return method(HttpMethod.POST, url); 271 } 272 273 /** 274 * Create an HTTP PUT builder with the given url. 275 * @param url the URL 276 * @return the created builder 277 */ 278 public static BodyBuilder put(URI url) { 279 return method(HttpMethod.PUT, url); 280 } 281 282 /** 283 * Create an HTTP PATCH builder with the given url. 284 * @param url the URL 285 * @return the created builder 286 */ 287 public static BodyBuilder patch(URI url) { 288 return method(HttpMethod.PATCH, url); 289 } 290 291 /** 292 * Create an HTTP DELETE builder with the given url. 293 * @param url the URL 294 * @return the created builder 295 */ 296 public static HeadersBuilder<?> delete(URI url) { 297 return method(HttpMethod.DELETE, url); 298 } 299 300 /** 301 * Creates an HTTP OPTIONS builder with the given url. 302 * @param url the URL 303 * @return the created builder 304 */ 305 public static HeadersBuilder<?> options(URI url) { 306 return method(HttpMethod.OPTIONS, url); 307 } 308 309 310 /** 311 * Defines a builder that adds headers to the request entity. 312 * @param <B> the builder subclass 313 */ 314 public interface HeadersBuilder<B extends HeadersBuilder<B>> { 315 316 /** 317 * Add the given, single header value under the given name. 318 * @param headerName the header name 319 * @param headerValues the header value(s) 320 * @return this builder 321 * @see HttpHeaders#add(String, String) 322 */ 323 B header(String headerName, String... headerValues); 324 325 /** 326 * Copy the given headers into the entity's headers map. 327 * @param headers the existing HttpHeaders to copy from 328 * @return this builder 329 * @since 5.2 330 * @see HttpHeaders#add(String, String) 331 */ 332 B headers(@Nullable HttpHeaders headers); 333 334 /** 335 * Manipulate this entity's headers with the given consumer. The 336 * headers provided to the consumer are "live", so that the consumer can be used to 337 * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, 338 * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other 339 * {@link HttpHeaders} methods. 340 * @param headeonsumes the {@code HttpHeaders} 341 * @return this builder 342 * @since 5.2 343 */ 344 B headers(Consumer<HttpHeaders> headersConsumer); 345 346 /** 347 * Set the list of acceptable {@linkplain MediaType media types}, as 348 * specified by the {@code Accept} header. 349 * @param acceptableMediaTypes the acceptable media types 350 */ 351 B accept(MediaType... acceptableMediaTypes); 352 353 /** 354 * Set the list of acceptable {@linkplain Charset charsets}, as specified 355 * by the {@code Accept-Charset} header. 356 * @param acceptableCharsets the acceptable charsets 357 */ 358 B acceptCharset(Charset... acceptableCharsets); 359 360 /** 361 * Set the value of the {@code If-Modified-Since} header. 362 * @param ifModifiedSince the new value of the header 363 * @since 5.1.4 364 */ 365 B ifModifiedSince(ZonedDateTime ifModifiedSince); 366 367 /** 368 * Set the value of the {@code If-Modified-Since} header. 369 * @param ifModifiedSince the new value of the header 370 * @since 5.1.4 371 */ 372 B ifModifiedSince(Instant ifModifiedSince); 373 374 /** 375 * Set the value of the {@code If-Modified-Since} header. 376 * <p>The date should be specified as the number of milliseconds since 377 * January 1, 1970 GMT. 378 * @param ifModifiedSince the new value of the header 379 */ 380 B ifModifiedSince(long ifModifiedSince); 381 382 /** 383 * Set the values of the {@code If-None-Match} header. 384 * @param ifNoneMatches the new value of the header 385 */ 386 B ifNoneMatch(String... ifNoneMatches); 387 388 /** 389 * Builds the request entity with no body. 390 * @return the request entity 391 * @see BodyBuilder#body(Object) 392 */ 393 RequestEntity<Void> build(); 394 } 395 396 397 /** 398 * Defines a builder that adds a body to the response entity. 399 */ 400 public interface BodyBuilder extends HeadersBuilder<BodyBuilder> { 401 402 /** 403 * Set the length of the body in bytes, as specified by the 404 * {@code Content-Length} header. 405 * @param contentLength the content length 406 * @return this builder 407 * @see HttpHeaders#setContentLength(long) 408 */ 409 BodyBuilder contentLength(long contentLength); 410 411 /** 412 * Set the {@linkplain MediaType media type} of the body, as specified 413 * by the {@code Content-Type} header. 414 * @param contentType the content type 415 * @return this builder 416 * @see HttpHeaders#setContentType(MediaType) 417 */ 418 BodyBuilder contentType(MediaType contentType); 419 420 /** 421 * Set the body of the request entity and build the RequestEntity. 422 * @param <T> the type of the body 423 * @param body the body of the request entity 424 * @return the built request entity 425 */ 426 <T> RequestEntity<T> body(T body); 427 428 /** 429 * Set the body and type of the request entity and build the RequestEntity. 430 * @param <T> the type of the body 431 * @param body the body of the request entity 432 * @param type the type of the body, useful for generic type resolution 433 * @return the built request entity 434 * @since 4.3 435 */ 436 <T> RequestEntity<T> body(T body, Type type); 437 } 438 439 440 private static class DefaultBodyBuilder implements BodyBuilder { 441 442 private final HttpMethod method; 443 444 private final URI url; 445 446 private final HttpHeaders headers = new HttpHeaders(); 447 448 public DefaultBodyBuilder(HttpMethod method, URI url) { 449 this.method = method; 450 this.url = url; 451 } 452 453 @Override 454 public BodyBuilder header(String headerName, String... headerValues) { 455 for (String headerValue : headerValues) { 456 this.headers.add(headerName, headerValue); 457 } 458 return this; 459 } 460 461 @Override 462 public BodyBuilder headers(@Nullable HttpHeaders headers) { 463 if (headers != null) { 464 this.headers.putAll(headers); 465 } 466 return this; 467 } 468 469 @Override 470 public BodyBuilder headers(Consumer<HttpHeaders> headersConsumer) { 471 headersConsumer.accept(this.headers); 472 return this; 473 } 474 475 @Override 476 public BodyBuilder accept(MediaType... acceptableMediaTypes) { 477 this.headers.setAccept(Arrays.asList(acceptableMediaTypes)); 478 return this; 479 } 480 481 @Override 482 public BodyBuilder acceptCharset(Charset... acceptableCharsets) { 483 this.headers.setAcceptCharset(Arrays.asList(acceptableCharsets)); 484 return this; 485 } 486 487 @Override 488 public BodyBuilder contentLength(long contentLength) { 489 this.headers.setContentLength(contentLength); 490 return this; 491 } 492 493 @Override 494 public BodyBuilder contentType(MediaType contentType) { 495 this.headers.setContentType(contentType); 496 return this; 497 } 498 499 @Override 500 public BodyBuilder ifModifiedSince(ZonedDateTime ifModifiedSince) { 501 this.headers.setIfModifiedSince(ifModifiedSince); 502 return this; 503 } 504 505 @Override 506 public BodyBuilder ifModifiedSince(Instant ifModifiedSince) { 507 this.headers.setIfModifiedSince(ifModifiedSince); 508 return this; 509 } 510 511 @Override 512 public BodyBuilder ifModifiedSince(long ifModifiedSince) { 513 this.headers.setIfModifiedSince(ifModifiedSince); 514 return this; 515 } 516 517 @Override 518 public BodyBuilder ifNoneMatch(String... ifNoneMatches) { 519 this.headers.setIfNoneMatch(Arrays.asList(ifNoneMatches)); 520 return this; 521 } 522 523 @Override 524 public RequestEntity<Void> build() { 525 return new RequestEntity<>(this.headers, this.method, this.url); 526 } 527 528 @Override 529 public <T> RequestEntity<T> body(T body) { 530 return new RequestEntity<>(body, this.headers, this.method, this.url); 531 } 532 533 @Override 534 public <T> RequestEntity<T> body(T body, Type type) { 535 return new RequestEntity<>(body, this.headers, this.method, this.url, type); 536 } 537 } 538 539}