Skip to content

Commit a240cfc

Browse files
committed
Consistent support for if-(un)modified-since as ZonedDateTime/Instant
Includes DefaultRequestBodyUriSpec pre-resolving URI for HttpRequest. Issue: SPR-17571
1 parent 199be6a commit a240cfc

File tree

8 files changed

+129
-77
lines changed

8 files changed

+129
-77
lines changed

spring-web/src/main/java/org/springframework/http/HttpHeaders.java

+68-32
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,24 @@ public List<String> getIfMatch() {
10791079
return getETagValuesAsList(IF_MATCH);
10801080
}
10811081

1082+
/**
1083+
* Set the time the resource was last changed, as specified by the
1084+
* {@code Last-Modified} header.
1085+
* @since 5.1.4
1086+
*/
1087+
public void setIfModifiedSince(ZonedDateTime ifModifiedSince) {
1088+
setZonedDateTime(IF_MODIFIED_SINCE, ifModifiedSince.withZoneSameInstant(GMT));
1089+
}
1090+
1091+
/**
1092+
* Set the time the resource was last changed, as specified by the
1093+
* {@code Last-Modified} header.
1094+
* @since 5.1.4
1095+
*/
1096+
public void setIfModifiedSince(Instant ifModifiedSince) {
1097+
setInstant(IF_MODIFIED_SINCE, ifModifiedSince);
1098+
}
1099+
10821100
/**
10831101
* Set the (new) value of the {@code If-Modified-Since} header.
10841102
* <p>The date should be specified as the number of milliseconds since
@@ -1119,6 +1137,24 @@ public List<String> getIfNoneMatch() {
11191137
return getETagValuesAsList(IF_NONE_MATCH);
11201138
}
11211139

1140+
/**
1141+
* Set the time the resource was last changed, as specified by the
1142+
* {@code Last-Modified} header.
1143+
* @since 5.1.4
1144+
*/
1145+
public void setIfUnmodifiedSince(ZonedDateTime ifUnmodifiedSince) {
1146+
setZonedDateTime(IF_UNMODIFIED_SINCE, ifUnmodifiedSince.withZoneSameInstant(GMT));
1147+
}
1148+
1149+
/**
1150+
* Set the time the resource was last changed, as specified by the
1151+
* {@code Last-Modified} header.
1152+
* @since 5.1.4
1153+
*/
1154+
public void setIfUnmodifiedSince(Instant ifUnmodifiedSince) {
1155+
setInstant(IF_UNMODIFIED_SINCE, ifUnmodifiedSince);
1156+
}
1157+
11221158
/**
11231159
* Set the (new) value of the {@code If-Unmodified-Since} header.
11241160
* <p>The date should be specified as the number of milliseconds since
@@ -1143,11 +1179,10 @@ public long getIfUnmodifiedSince() {
11431179
/**
11441180
* Set the time the resource was last changed, as specified by the
11451181
* {@code Last-Modified} header.
1146-
* <p>The date should be specified as the number of milliseconds since
1147-
* January 1, 1970 GMT.
1182+
* @since 5.1.4
11481183
*/
1149-
public void setLastModified(long lastModified) {
1150-
setDate(LAST_MODIFIED, lastModified);
1184+
public void setLastModified(ZonedDateTime lastModified) {
1185+
setZonedDateTime(LAST_MODIFIED, lastModified.withZoneSameInstant(GMT));
11511186
}
11521187

11531188
/**
@@ -1162,10 +1197,11 @@ public void setLastModified(Instant lastModified) {
11621197
/**
11631198
* Set the time the resource was last changed, as specified by the
11641199
* {@code Last-Modified} header.
1165-
* @since 5.1.4
1200+
* <p>The date should be specified as the number of milliseconds since
1201+
* January 1, 1970 GMT.
11661202
*/
1167-
public void setLastModified(ZonedDateTime lastModified) {
1168-
setZonedDateTime(LAST_MODIFIED, lastModified.withZoneSameInstant(GMT));
1203+
public void setLastModified(long lastModified) {
1204+
setDate(LAST_MODIFIED, lastModified);
11691205
}
11701206

11711207
/**
@@ -1283,20 +1319,20 @@ public List<String> getVary() {
12831319
* Set the given date under the given header name after formatting it as a string
12841320
* using the RFC-1123 date-time formatter. The equivalent of
12851321
* {@link #set(String, String)} but for date headers.
1286-
* @since 5.1.4
1322+
* @since 5.0
12871323
*/
1288-
public void setInstant(String headerName, Instant date) {
1289-
setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT));
1324+
public void setZonedDateTime(String headerName, ZonedDateTime date) {
1325+
set(headerName, DATE_FORMATTERS[0].format(date));
12901326
}
12911327

12921328
/**
12931329
* Set the given date under the given header name after formatting it as a string
12941330
* using the RFC-1123 date-time formatter. The equivalent of
12951331
* {@link #set(String, String)} but for date headers.
1296-
* @since 5.0
1332+
* @since 5.1.4
12971333
*/
1298-
public void setZonedDateTime(String headerName, ZonedDateTime date) {
1299-
set(headerName, DATE_FORMATTERS[0].format(date));
1334+
public void setInstant(String headerName, Instant date) {
1335+
setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT));
13001336
}
13011337

13021338
/**
@@ -1643,25 +1679,6 @@ public String toString() {
16431679
return formatHeaders(this.headers);
16441680
}
16451681

1646-
/**
1647-
* Helps to format HTTP header values, as HTTP header values themselves can
1648-
* contain comma-separated values, can become confusing with regular
1649-
* {@link Map} formatting that also uses commas between entries.
1650-
* @param headers the headers to format
1651-
* @return the headers to a String
1652-
* @since 5.1.4
1653-
*/
1654-
public static String formatHeaders(MultiValueMap<String, String> headers) {
1655-
return headers.entrySet().stream()
1656-
.map(entry -> {
1657-
List<String> values = entry.getValue();
1658-
return entry.getKey() + ":" + (values.size() == 1 ?
1659-
"\"" + values.get(0) + "\"" :
1660-
values.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
1661-
})
1662-
.collect(Collectors.joining(", ", "[", "]"));
1663-
}
1664-
16651682

16661683
/**
16671684
* Return a {@code HttpHeaders} object that can only be read, not written to.
@@ -1689,6 +1706,25 @@ public static HttpHeaders writableHttpHeaders(HttpHeaders headers) {
16891706
}
16901707
}
16911708

1709+
/**
1710+
* Helps to format HTTP header values, as HTTP header values themselves can
1711+
* contain comma-separated values, can become confusing with regular
1712+
* {@link Map} formatting that also uses commas between entries.
1713+
* @param headers the headers to format
1714+
* @return the headers to a String
1715+
* @since 5.1.4
1716+
*/
1717+
public static String formatHeaders(MultiValueMap<String, String> headers) {
1718+
return headers.entrySet().stream()
1719+
.map(entry -> {
1720+
List<String> values = entry.getValue();
1721+
return entry.getKey() + ":" + (values.size() == 1 ?
1722+
"\"" + values.get(0) + "\"" :
1723+
values.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
1724+
})
1725+
.collect(Collectors.joining(", ", "[", "]"));
1726+
}
1727+
16921728
// Package-private: used in ResponseCookie
16931729
static String formatDate(long date) {
16941730
Instant instant = Instant.ofEpochMilli(date);

spring-web/src/main/java/org/springframework/http/RequestEntity.java

+28
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.lang.reflect.Type;
2020
import java.net.URI;
2121
import java.nio.charset.Charset;
22+
import java.time.Instant;
23+
import java.time.ZonedDateTime;
2224
import java.util.Arrays;
2325

2426
import org.springframework.lang.Nullable;
@@ -327,6 +329,20 @@ public interface HeadersBuilder<B extends HeadersBuilder<B>> {
327329
*/
328330
B acceptCharset(Charset... acceptableCharsets);
329331

332+
/**
333+
* Set the value of the {@code If-Modified-Since} header.
334+
* @param ifModifiedSince the new value of the header
335+
* @since 5.1.4
336+
*/
337+
B ifModifiedSince(ZonedDateTime ifModifiedSince);
338+
339+
/**
340+
* Set the value of the {@code If-Modified-Since} header.
341+
* @param ifModifiedSince the new value of the header
342+
* @since 5.1.4
343+
*/
344+
B ifModifiedSince(Instant ifModifiedSince);
345+
330346
/**
331347
* Set the value of the {@code If-Modified-Since} header.
332348
* <p>The date should be specified as the number of milliseconds since
@@ -438,6 +454,18 @@ public BodyBuilder contentType(MediaType contentType) {
438454
return this;
439455
}
440456

457+
@Override
458+
public BodyBuilder ifModifiedSince(ZonedDateTime ifModifiedSince) {
459+
this.headers.setIfModifiedSince(ifModifiedSince);
460+
return this;
461+
}
462+
463+
@Override
464+
public BodyBuilder ifModifiedSince(Instant ifModifiedSince) {
465+
this.headers.setIfModifiedSince(ifModifiedSince);
466+
return this;
467+
}
468+
441469
@Override
442470
public BodyBuilder ifModifiedSince(long ifModifiedSince) {
443471
this.headers.setIfModifiedSince(ifModifiedSince);

spring-web/src/main/java/org/springframework/http/ResponseEntity.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -353,33 +353,33 @@ public interface HeadersBuilder<B extends HeadersBuilder<B>> {
353353
/**
354354
* Set the time the resource was last changed, as specified by the
355355
* {@code Last-Modified} header.
356-
* <p>The date should be specified as the number of milliseconds since
357-
* January 1, 1970 GMT.
358356
* @param lastModified the last modified date
359357
* @return this builder
360-
* @see HttpHeaders#setLastModified(long)
358+
* @since 5.1.4
359+
* @see HttpHeaders#setLastModified(ZonedDateTime)
361360
*/
362-
B lastModified(long lastModified);
361+
B lastModified(ZonedDateTime lastModified);
363362

364363
/**
365364
* Set the time the resource was last changed, as specified by the
366365
* {@code Last-Modified} header.
367366
* @param lastModified the last modified date
368367
* @return this builder
369368
* @since 5.1.4
370-
* @see HttpHeaders#setLastModified(long)
369+
* @see HttpHeaders#setLastModified(Instant)
371370
*/
372371
B lastModified(Instant lastModified);
373372

374373
/**
375374
* Set the time the resource was last changed, as specified by the
376375
* {@code Last-Modified} header.
376+
* <p>The date should be specified as the number of milliseconds since
377+
* January 1, 1970 GMT.
377378
* @param lastModified the last modified date
378379
* @return this builder
379-
* @since 5.1.4
380380
* @see HttpHeaders#setLastModified(long)
381381
*/
382-
B lastModified(ZonedDateTime lastModified);
382+
B lastModified(long lastModified);
383383

384384
/**
385385
* Set the location of a resource, as specified by the {@code Location} header.
@@ -512,7 +512,7 @@ public BodyBuilder eTag(String etag) {
512512
}
513513

514514
@Override
515-
public BodyBuilder lastModified(long date) {
515+
public BodyBuilder lastModified(ZonedDateTime date) {
516516
this.headers.setLastModified(date);
517517
return this;
518518
}
@@ -524,7 +524,7 @@ public BodyBuilder lastModified(Instant date) {
524524
}
525525

526526
@Override
527-
public BodyBuilder lastModified(ZonedDateTime date) {
527+
public BodyBuilder lastModified(long date) {
528528
this.headers.setLastModified(date);
529529
return this;
530530
}

spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java

+14-25
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
import java.net.URI;
2020
import java.nio.charset.Charset;
2121
import java.nio.charset.StandardCharsets;
22-
import java.time.ZoneId;
2322
import java.time.ZonedDateTime;
24-
import java.time.format.DateTimeFormatter;
2523
import java.util.ArrayList;
2624
import java.util.Arrays;
2725
import java.util.LinkedHashMap;
@@ -275,9 +273,7 @@ public DefaultRequestBodyUriSpec cookies(Consumer<MultiValueMap<String, String>>
275273

276274
@Override
277275
public DefaultRequestBodyUriSpec ifModifiedSince(ZonedDateTime ifModifiedSince) {
278-
ZonedDateTime gmt = ifModifiedSince.withZoneSameInstant(ZoneId.of("GMT"));
279-
String headerValue = DateTimeFormatter.RFC_1123_DATE_TIME.format(gmt);
280-
getHeaders().set(HttpHeaders.IF_MODIFIED_SINCE, headerValue);
276+
getHeaders().setIfModifiedSince(ifModifiedSince);
281277
return this;
282278
}
283279

@@ -327,13 +323,16 @@ private ClientRequest.Builder initRequestBuilder() {
327323
if (defaultRequest != null) {
328324
defaultRequest.accept(this);
329325
}
330-
URI uri = (this.uri != null ? this.uri : uriBuilderFactory.expand(""));
331-
return ClientRequest.create(this.httpMethod, uri)
326+
return ClientRequest.create(this.httpMethod, initUri())
332327
.headers(headers -> headers.addAll(initHeaders()))
333328
.cookies(cookies -> cookies.addAll(initCookies()))
334329
.attributes(attributes -> attributes.putAll(this.attributes));
335330
}
336331

332+
private URI initUri() {
333+
return (this.uri != null ? this.uri : uriBuilderFactory.expand(""));
334+
}
335+
337336
private HttpHeaders initHeaders() {
338337
if (CollectionUtils.isEmpty(this.headers)) {
339338
return (defaultHeaders != null ? defaultHeaders : new HttpHeaders());
@@ -371,25 +370,21 @@ public ResponseSpec retrieve() {
371370

372371
private HttpRequest createRequest() {
373372
return new HttpRequest() {
374-
375-
private HttpHeaders headers = initHeaders();
376-
373+
private final URI uri = initUri();
374+
private final HttpHeaders headers = initHeaders();
377375

378376
@Override
379377
public HttpMethod getMethod() {
380378
return httpMethod;
381379
}
382-
383380
@Override
384381
public String getMethodValue() {
385382
return httpMethod.name();
386383
}
387-
388384
@Override
389385
public URI getURI() {
390-
return uri;
386+
return this.uri;
391387
}
392-
393388
@Override
394389
public HttpHeaders getHeaders() {
395390
return this.headers;
@@ -410,16 +405,12 @@ private static class DefaultResponseSpec implements ResponseSpec {
410405

411406
private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
412407

413-
414-
DefaultResponseSpec(Mono<ClientResponse> responseMono,
415-
Supplier<HttpRequest> requestSupplier) {
408+
DefaultResponseSpec(Mono<ClientResponse> responseMono, Supplier<HttpRequest> requestSupplier) {
416409
this.responseMono = responseMono;
417410
this.requestSupplier = requestSupplier;
418411
this.statusHandlers.add(DEFAULT_STATUS_HANDLER);
419412
}
420413

421-
422-
423414
@Override
424415
public ResponseSpec onStatus(Predicate<HttpStatus> statusPredicate,
425416
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
@@ -473,8 +464,7 @@ private <T extends Publisher<?>> T handleBody(ClientResponse response,
473464
return bodyPublisher;
474465
}
475466
else {
476-
return errorFunction.apply(createResponseException(response,
477-
this.requestSupplier.get()));
467+
return errorFunction.apply(createResponseException(response, this.requestSupplier.get()));
478468
}
479469
}
480470

@@ -486,8 +476,9 @@ private <T> Mono<T> drainBody(ClientResponse response, Throwable ex) {
486476
.onErrorResume(ex2 -> Mono.empty()).thenReturn(ex);
487477
}
488478

489-
private static Mono<WebClientResponseException> createResponseException(ClientResponse response,
490-
HttpRequest request) {
479+
private static Mono<WebClientResponseException> createResponseException(
480+
ClientResponse response, HttpRequest request) {
481+
491482
return DataBufferUtils.join(response.body(BodyExtractors.toDataBuffers()))
492483
.map(dataBuffer -> {
493484
byte[] bytes = new byte[dataBuffer.readableByteCount()];
@@ -527,7 +518,6 @@ private static class StatusHandler {
527518

528519
private final BiFunction<ClientResponse, HttpRequest, Mono<? extends Throwable>> exceptionFunction;
529520

530-
531521
public StatusHandler(Predicate<HttpStatus> predicate,
532522
BiFunction<ClientResponse, HttpRequest, Mono<? extends Throwable>> exceptionFunction) {
533523

@@ -537,7 +527,6 @@ public StatusHandler(Predicate<HttpStatus> predicate,
537527
this.exceptionFunction = exceptionFunction;
538528
}
539529

540-
541530
public boolean test(HttpStatus status) {
542531
return this.predicate.test(status);
543532
}

0 commit comments

Comments
 (0)