diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 9e1ed8d91d..7fffd16141 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -1079,6 +1079,24 @@ public class HttpHeaders implements MultiValueMap, Serializable return getETagValuesAsList(IF_MATCH); } + /** + * Set the time the resource was last changed, as specified by the + * {@code Last-Modified} header. + * @since 5.1.4 + */ + public void setIfModifiedSince(ZonedDateTime ifModifiedSince) { + setZonedDateTime(IF_MODIFIED_SINCE, ifModifiedSince.withZoneSameInstant(GMT)); + } + + /** + * Set the time the resource was last changed, as specified by the + * {@code Last-Modified} header. + * @since 5.1.4 + */ + public void setIfModifiedSince(Instant ifModifiedSince) { + setInstant(IF_MODIFIED_SINCE, ifModifiedSince); + } + /** * Set the (new) value of the {@code If-Modified-Since} header. *

The date should be specified as the number of milliseconds since @@ -1119,6 +1137,24 @@ public class HttpHeaders implements MultiValueMap, Serializable return getETagValuesAsList(IF_NONE_MATCH); } + /** + * Set the time the resource was last changed, as specified by the + * {@code Last-Modified} header. + * @since 5.1.4 + */ + public void setIfUnmodifiedSince(ZonedDateTime ifUnmodifiedSince) { + setZonedDateTime(IF_UNMODIFIED_SINCE, ifUnmodifiedSince.withZoneSameInstant(GMT)); + } + + /** + * Set the time the resource was last changed, as specified by the + * {@code Last-Modified} header. + * @since 5.1.4 + */ + public void setIfUnmodifiedSince(Instant ifUnmodifiedSince) { + setInstant(IF_UNMODIFIED_SINCE, ifUnmodifiedSince); + } + /** * Set the (new) value of the {@code If-Unmodified-Since} header. *

The date should be specified as the number of milliseconds since @@ -1143,11 +1179,10 @@ public class HttpHeaders implements MultiValueMap, Serializable /** * Set the time the resource was last changed, as specified by the * {@code Last-Modified} header. - *

The date should be specified as the number of milliseconds since - * January 1, 1970 GMT. + * @since 5.1.4 */ - public void setLastModified(long lastModified) { - setDate(LAST_MODIFIED, lastModified); + public void setLastModified(ZonedDateTime lastModified) { + setZonedDateTime(LAST_MODIFIED, lastModified.withZoneSameInstant(GMT)); } /** @@ -1162,10 +1197,11 @@ public class HttpHeaders implements MultiValueMap, Serializable /** * Set the time the resource was last changed, as specified by the * {@code Last-Modified} header. - * @since 5.1.4 + *

The date should be specified as the number of milliseconds since + * January 1, 1970 GMT. */ - public void setLastModified(ZonedDateTime lastModified) { - setZonedDateTime(LAST_MODIFIED, lastModified.withZoneSameInstant(GMT)); + public void setLastModified(long lastModified) { + setDate(LAST_MODIFIED, lastModified); } /** @@ -1283,20 +1319,20 @@ public class HttpHeaders implements MultiValueMap, Serializable * Set the given date under the given header name after formatting it as a string * using the RFC-1123 date-time formatter. The equivalent of * {@link #set(String, String)} but for date headers. - * @since 5.1.4 + * @since 5.0 */ - public void setInstant(String headerName, Instant date) { - setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT)); + public void setZonedDateTime(String headerName, ZonedDateTime date) { + set(headerName, DATE_FORMATTERS[0].format(date)); } /** * Set the given date under the given header name after formatting it as a string * using the RFC-1123 date-time formatter. The equivalent of * {@link #set(String, String)} but for date headers. - * @since 5.0 + * @since 5.1.4 */ - public void setZonedDateTime(String headerName, ZonedDateTime date) { - set(headerName, DATE_FORMATTERS[0].format(date)); + public void setInstant(String headerName, Instant date) { + setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT)); } /** @@ -1643,25 +1679,6 @@ public class HttpHeaders implements MultiValueMap, Serializable return formatHeaders(this.headers); } - /** - * Helps to format HTTP header values, as HTTP header values themselves can - * contain comma-separated values, can become confusing with regular - * {@link Map} formatting that also uses commas between entries. - * @param headers the headers to format - * @return the headers to a String - * @since 5.1.4 - */ - public static String formatHeaders(MultiValueMap headers) { - return headers.entrySet().stream() - .map(entry -> { - List values = entry.getValue(); - return entry.getKey() + ":" + (values.size() == 1 ? - "\"" + values.get(0) + "\"" : - values.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", "))); - }) - .collect(Collectors.joining(", ", "[", "]")); - } - /** * Return a {@code HttpHeaders} object that can only be read, not written to. @@ -1689,6 +1706,25 @@ public class HttpHeaders implements MultiValueMap, Serializable } } + /** + * Helps to format HTTP header values, as HTTP header values themselves can + * contain comma-separated values, can become confusing with regular + * {@link Map} formatting that also uses commas between entries. + * @param headers the headers to format + * @return the headers to a String + * @since 5.1.4 + */ + public static String formatHeaders(MultiValueMap headers) { + return headers.entrySet().stream() + .map(entry -> { + List values = entry.getValue(); + return entry.getKey() + ":" + (values.size() == 1 ? + "\"" + values.get(0) + "\"" : + values.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", "))); + }) + .collect(Collectors.joining(", ", "[", "]")); + } + // Package-private: used in ResponseCookie static String formatDate(long date) { Instant instant = Instant.ofEpochMilli(date); diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java index f26ff2d6c7..e32e2095e1 100644 --- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java +++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java @@ -19,6 +19,8 @@ package org.springframework.http; import java.lang.reflect.Type; import java.net.URI; import java.nio.charset.Charset; +import java.time.Instant; +import java.time.ZonedDateTime; import java.util.Arrays; import org.springframework.lang.Nullable; @@ -327,6 +329,20 @@ public class RequestEntity extends HttpEntity { */ B acceptCharset(Charset... acceptableCharsets); + /** + * Set the value of the {@code If-Modified-Since} header. + * @param ifModifiedSince the new value of the header + * @since 5.1.4 + */ + B ifModifiedSince(ZonedDateTime ifModifiedSince); + + /** + * Set the value of the {@code If-Modified-Since} header. + * @param ifModifiedSince the new value of the header + * @since 5.1.4 + */ + B ifModifiedSince(Instant ifModifiedSince); + /** * Set the value of the {@code If-Modified-Since} header. *

The date should be specified as the number of milliseconds since @@ -438,6 +454,18 @@ public class RequestEntity extends HttpEntity { return this; } + @Override + public BodyBuilder ifModifiedSince(ZonedDateTime ifModifiedSince) { + this.headers.setIfModifiedSince(ifModifiedSince); + return this; + } + + @Override + public BodyBuilder ifModifiedSince(Instant ifModifiedSince) { + this.headers.setIfModifiedSince(ifModifiedSince); + return this; + } + @Override public BodyBuilder ifModifiedSince(long ifModifiedSince) { this.headers.setIfModifiedSince(ifModifiedSince); diff --git a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java index c35c3e9e7e..9931b74544 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java @@ -353,13 +353,12 @@ public class ResponseEntity extends HttpEntity { /** * Set the time the resource was last changed, as specified by the * {@code Last-Modified} header. - *

The date should be specified as the number of milliseconds since - * January 1, 1970 GMT. * @param lastModified the last modified date * @return this builder - * @see HttpHeaders#setLastModified(long) + * @since 5.1.4 + * @see HttpHeaders#setLastModified(ZonedDateTime) */ - B lastModified(long lastModified); + B lastModified(ZonedDateTime lastModified); /** * Set the time the resource was last changed, as specified by the @@ -367,19 +366,20 @@ public class ResponseEntity extends HttpEntity { * @param lastModified the last modified date * @return this builder * @since 5.1.4 - * @see HttpHeaders#setLastModified(long) + * @see HttpHeaders#setLastModified(Instant) */ B lastModified(Instant lastModified); /** * Set the time the resource was last changed, as specified by the * {@code Last-Modified} header. + *

The date should be specified as the number of milliseconds since + * January 1, 1970 GMT. * @param lastModified the last modified date * @return this builder - * @since 5.1.4 * @see HttpHeaders#setLastModified(long) */ - B lastModified(ZonedDateTime lastModified); + B lastModified(long lastModified); /** * Set the location of a resource, as specified by the {@code Location} header. @@ -512,7 +512,7 @@ public class ResponseEntity extends HttpEntity { } @Override - public BodyBuilder lastModified(long date) { + public BodyBuilder lastModified(ZonedDateTime date) { this.headers.setLastModified(date); return this; } @@ -524,7 +524,7 @@ public class ResponseEntity extends HttpEntity { } @Override - public BodyBuilder lastModified(ZonedDateTime date) { + public BodyBuilder lastModified(long date) { this.headers.setLastModified(date); return this; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 177d43c9cd..d6b2544036 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -19,9 +19,7 @@ package org.springframework.web.reactive.function.client; import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; @@ -275,9 +273,7 @@ class DefaultWebClient implements WebClient { @Override public DefaultRequestBodyUriSpec ifModifiedSince(ZonedDateTime ifModifiedSince) { - ZonedDateTime gmt = ifModifiedSince.withZoneSameInstant(ZoneId.of("GMT")); - String headerValue = DateTimeFormatter.RFC_1123_DATE_TIME.format(gmt); - getHeaders().set(HttpHeaders.IF_MODIFIED_SINCE, headerValue); + getHeaders().setIfModifiedSince(ifModifiedSince); return this; } @@ -327,13 +323,16 @@ class DefaultWebClient implements WebClient { if (defaultRequest != null) { defaultRequest.accept(this); } - URI uri = (this.uri != null ? this.uri : uriBuilderFactory.expand("")); - return ClientRequest.create(this.httpMethod, uri) + return ClientRequest.create(this.httpMethod, initUri()) .headers(headers -> headers.addAll(initHeaders())) .cookies(cookies -> cookies.addAll(initCookies())) .attributes(attributes -> attributes.putAll(this.attributes)); } + private URI initUri() { + return (this.uri != null ? this.uri : uriBuilderFactory.expand("")); + } + private HttpHeaders initHeaders() { if (CollectionUtils.isEmpty(this.headers)) { return (defaultHeaders != null ? defaultHeaders : new HttpHeaders()); @@ -371,25 +370,21 @@ class DefaultWebClient implements WebClient { private HttpRequest createRequest() { return new HttpRequest() { - - private HttpHeaders headers = initHeaders(); - + private final URI uri = initUri(); + private final HttpHeaders headers = initHeaders(); @Override public HttpMethod getMethod() { return httpMethod; } - @Override public String getMethodValue() { return httpMethod.name(); } - @Override public URI getURI() { - return uri; + return this.uri; } - @Override public HttpHeaders getHeaders() { return this.headers; @@ -410,16 +405,12 @@ class DefaultWebClient implements WebClient { private final List statusHandlers = new ArrayList<>(1); - - DefaultResponseSpec(Mono responseMono, - Supplier requestSupplier) { + DefaultResponseSpec(Mono responseMono, Supplier requestSupplier) { this.responseMono = responseMono; this.requestSupplier = requestSupplier; this.statusHandlers.add(DEFAULT_STATUS_HANDLER); } - - @Override public ResponseSpec onStatus(Predicate statusPredicate, Function> exceptionFunction) { @@ -473,8 +464,7 @@ class DefaultWebClient implements WebClient { return bodyPublisher; } else { - return errorFunction.apply(createResponseException(response, - this.requestSupplier.get())); + return errorFunction.apply(createResponseException(response, this.requestSupplier.get())); } } @@ -486,8 +476,9 @@ class DefaultWebClient implements WebClient { .onErrorResume(ex2 -> Mono.empty()).thenReturn(ex); } - private static Mono createResponseException(ClientResponse response, - HttpRequest request) { + private static Mono createResponseException( + ClientResponse response, HttpRequest request) { + return DataBufferUtils.join(response.body(BodyExtractors.toDataBuffers())) .map(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; @@ -527,7 +518,6 @@ class DefaultWebClient implements WebClient { private final BiFunction> exceptionFunction; - public StatusHandler(Predicate predicate, BiFunction> exceptionFunction) { @@ -537,7 +527,6 @@ class DefaultWebClient implements WebClient { this.exceptionFunction = exceptionFunction; } - public boolean test(HttpStatus status) { return this.predicate.test(status); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java index e501daa41c..6db459c688 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultEntityResponseBuilder.java @@ -158,13 +158,13 @@ class DefaultEntityResponseBuilder implements EntityResponse.Builder { } @Override - public EntityResponse.Builder lastModified(Instant lastModified) { + public EntityResponse.Builder lastModified(ZonedDateTime lastModified) { this.headers.setLastModified(lastModified); return this; } @Override - public EntityResponse.Builder lastModified(ZonedDateTime lastModified) { + public EntityResponse.Builder lastModified(Instant lastModified) { this.headers.setLastModified(lastModified); return this; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java index b41dbac812..10a5995d10 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java @@ -158,13 +158,13 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { } @Override - public ServerResponse.BodyBuilder lastModified(Instant lastModified) { + public ServerResponse.BodyBuilder lastModified(ZonedDateTime lastModified) { this.headers.setLastModified(lastModified); return this; } @Override - public ServerResponse.BodyBuilder lastModified(ZonedDateTime lastModified) { + public ServerResponse.BodyBuilder lastModified(Instant lastModified) { this.headers.setLastModified(lastModified); return this; } @@ -315,7 +315,6 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { @Override public final Mono writeTo(ServerWebExchange exchange, Context context) { writeStatusAndHeaders(exchange.getResponse()); - Instant lastModified = Instant.ofEpochMilli(headers().getLastModified()); HttpMethod httpMethod = exchange.getRequest().getMethod(); if (SAFE_METHODS.contains(httpMethod) && exchange.checkNotModified(headers().getETag(), lastModified)) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java index 89d330b663..b15cb68a59 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/EntityResponse.java @@ -190,10 +190,9 @@ public interface EntityResponse extends ServerResponse { * January 1, 1970 GMT. * @param lastModified the last modified date * @return this builder - * @since 5.1.4 * @see HttpHeaders#setLastModified(long) */ - Builder lastModified(Instant lastModified); + Builder lastModified(ZonedDateTime lastModified); /** * Set the time the resource was last changed, as specified by the @@ -202,9 +201,10 @@ public interface EntityResponse extends ServerResponse { * January 1, 1970 GMT. * @param lastModified the last modified date * @return this builder + * @since 5.1.4 * @see HttpHeaders#setLastModified(long) */ - Builder lastModified(ZonedDateTime lastModified); + Builder lastModified(Instant lastModified); /** * Set the location of a resource, as specified by the {@code Location} header. diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java index d9e545ec82..7f6f620bf9 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerResponse.java @@ -281,19 +281,19 @@ public interface ServerResponse { * {@code Last-Modified} header. * @param lastModified the last modified date * @return this builder - * @since 5.1.4 * @see HttpHeaders#setLastModified(long) */ - B lastModified(Instant lastModified); + B lastModified(ZonedDateTime lastModified); /** * Set the time the resource was last changed, as specified by the * {@code Last-Modified} header. * @param lastModified the last modified date * @return this builder + * @since 5.1.4 * @see HttpHeaders#setLastModified(long) */ - B lastModified(ZonedDateTime lastModified); + B lastModified(Instant lastModified); /** * Set the location of a resource, as specified by the {@code Location} header.