Add ServerRequest::checkNotModified
This commit adds the checkNotModified method to ServerRequest in both WebFlux.fn and WebMvc.fn. Unlike other checkNotModified methods found in the framework, this method does not return a boolean, but rather a response wrapped in a Mono/Optional. If the resource has not been changed, the not-modified response can be returned directly; if the resource has changed, the user can create a corresponding response using switchIfEmpty/orElse(Get). Closes gh-24173
This commit is contained in:
parent
73e39726cd
commit
0dc1c7eb8b
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,6 +20,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -44,6 +45,7 @@ import org.springframework.http.codec.multipart.Part;
|
|||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
|
@ -86,6 +88,23 @@ class DefaultServerRequest implements ServerRequest {
|
|||
this.headers = new DefaultHeaders();
|
||||
}
|
||||
|
||||
static Mono<ServerResponse> checkNotModified(ServerWebExchange exchange, @Nullable Instant lastModified,
|
||||
@Nullable String etag) {
|
||||
|
||||
if (lastModified == null) {
|
||||
lastModified = Instant.MIN;
|
||||
}
|
||||
|
||||
if (exchange.checkNotModified(etag, lastModified)) {
|
||||
Integer statusCode = exchange.getResponse().getRawStatusCode();
|
||||
return ServerResponse.status(statusCode != null ? statusCode : 200)
|
||||
.headers(headers -> headers.addAll(exchange.getResponse().getHeaders()))
|
||||
.build();
|
||||
}
|
||||
else {
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String methodName() {
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -43,6 +44,7 @@ import org.springframework.http.codec.multipart.Part;
|
|||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
|
@ -287,6 +289,108 @@ public interface ServerRequest {
|
|||
*/
|
||||
ServerWebExchange exchange();
|
||||
|
||||
/**
|
||||
* Check whether the requested resource has been modified given the
|
||||
* supplied last-modified timestamp (as determined by the application).
|
||||
* If not modified, this method returns a response with corresponding
|
||||
* status code and headers, otherwise an empty result.
|
||||
* <p>Typical usage:
|
||||
* <pre class="code">
|
||||
* public Mono<ServerResponse> myHandleMethod(ServerRequest request) {
|
||||
* Instant lastModified = // application-specific calculation
|
||||
* return request.checkNotModified(lastModified)
|
||||
* .switchIfEmpty(Mono.defer(() -> {
|
||||
* // further request processing, actually building content
|
||||
* return ServerResponse.ok().body(...);
|
||||
* }));
|
||||
* }</pre>
|
||||
* <p>This method works with conditional GET/HEAD requests, but
|
||||
* also with conditional POST/PUT/DELETE requests.
|
||||
* <p><strong>Note:</strong> you can use either
|
||||
* this {@code #checkNotModified(Instant)} method; or
|
||||
* {@link #checkNotModified(String)}. If you want enforce both
|
||||
* a strong entity tag and a Last-Modified value,
|
||||
* as recommended by the HTTP specification,
|
||||
* then you should use {@link #checkNotModified(Instant, String)}.
|
||||
* @param lastModified the last-modified timestamp that the
|
||||
* application determined for the underlying resource
|
||||
* @return a corresponding response if the request qualifies as not
|
||||
* modified, or an empty result otherwise.
|
||||
* @since 5.2.5
|
||||
*/
|
||||
default Mono<ServerResponse> checkNotModified(Instant lastModified) {
|
||||
Assert.notNull(lastModified, "LastModified must not be null");
|
||||
return DefaultServerRequest.checkNotModified(exchange(), lastModified, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the requested resource has been modified given the
|
||||
* supplied {@code ETag} (entity tag), as determined by the application.
|
||||
* If not modified, this method returns a response with corresponding
|
||||
* status code and headers, otherwise an empty result.
|
||||
* <p>Typical usage:
|
||||
* <pre class="code">
|
||||
* public Mono<ServerResponse> myHandleMethod(ServerRequest request) {
|
||||
* String eTag = // application-specific calculation
|
||||
* return request.checkNotModified(eTag)
|
||||
* .switchIfEmpty(Mono.defer(() -> {
|
||||
* // further request processing, actually building content
|
||||
* return ServerResponse.ok().body(...);
|
||||
* }));
|
||||
* }</pre>
|
||||
* <p>This method works with conditional GET/HEAD requests, but
|
||||
* also with conditional POST/PUT/DELETE requests.
|
||||
* <p><strong>Note:</strong> you can use either
|
||||
* this {@link #checkNotModified(Instant)} method; or
|
||||
* {@code #checkNotModified(String)}. If you want enforce both
|
||||
* a strong entity tag and a Last-Modified value,
|
||||
* as recommended by the HTTP specification,
|
||||
* then you should use {@link #checkNotModified(Instant, String)}.
|
||||
* @param etag the entity tag that the application determined
|
||||
* for the underlying resource. This parameter will be padded
|
||||
* with quotes (") if necessary.
|
||||
* @return a corresponding response if the request qualifies as not
|
||||
* modified, or an empty result otherwise.
|
||||
* @since 5.2.5
|
||||
*/
|
||||
default Mono<ServerResponse> checkNotModified(String etag) {
|
||||
Assert.notNull(etag, "Etag must not be null");
|
||||
return DefaultServerRequest.checkNotModified(exchange(), null, etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the requested resource has been modified given the
|
||||
* supplied {@code ETag} (entity tag) and last-modified timestamp,
|
||||
* as determined by the application.
|
||||
* If not modified, this method returns a response with corresponding
|
||||
* status code and headers, otherwise an empty result.
|
||||
* <p>Typical usage:
|
||||
* <pre class="code">
|
||||
* public Mono<ServerResponse> myHandleMethod(ServerRequest request) {
|
||||
* Instant lastModified = // application-specific calculation
|
||||
* String eTag = // application-specific calculation
|
||||
* return request.checkNotModified(lastModified, eTag)
|
||||
* .switchIfEmpty(Mono.defer(() -> {
|
||||
* // further request processing, actually building content
|
||||
* return ServerResponse.ok().body(...);
|
||||
* }));
|
||||
* }</pre>
|
||||
* <p>This method works with conditional GET/HEAD requests, but
|
||||
* also with conditional POST/PUT/DELETE requests.
|
||||
* @param lastModified the last-modified timestamp that the
|
||||
* application determined for the underlying resource
|
||||
* @param etag the entity tag that the application determined
|
||||
* for the underlying resource. This parameter will be padded
|
||||
* with quotes (") if necessary.
|
||||
* @return a corresponding response if the request qualifies as not
|
||||
* modified, or an empty result otherwise.
|
||||
* @since 5.2.5
|
||||
*/
|
||||
default Mono<ServerResponse> checkNotModified(Instant lastModified, String etag) {
|
||||
Assert.notNull(lastModified, "LastModified must not be null");
|
||||
Assert.notNull(etag, "Etag must not be null");
|
||||
return DefaultServerRequest.checkNotModified(exchange(), lastModified, etag);
|
||||
}
|
||||
|
||||
// Static builder methods
|
||||
|
||||
|
|
|
@ -16,12 +16,18 @@
|
|||
|
||||
package org.springframework.web.reactive.function.server;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -30,6 +36,8 @@ import java.util.Optional;
|
|||
import java.util.OptionalLong;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
@ -43,6 +51,7 @@ import org.springframework.http.HttpCookie;
|
|||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.DecoderHttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
|
@ -438,4 +447,260 @@ public class DefaultServerRequestTests {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedTimestamp(String method) throws Exception {
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfModifiedSince(now);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(now);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.assertNext(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getLastModified()).isEqualTo(now.toEpochMilli());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedTimestamp(String method) {
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
Instant oneMinuteAgo = now.minus(1, ChronoUnit.MINUTES);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfModifiedSince(oneMinuteAgo);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(now);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETag(String method) {
|
||||
String eTag = "\"Foo\"";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(eTag);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.assertNext(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETagWithSeparatorChars(String method) {
|
||||
String eTag = "\"Foo, Bar\"";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(eTag);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.assertNext(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedETag(String method) {
|
||||
String currentETag = "\"Foo\"";
|
||||
String oldEtag = "Bar";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(oldEtag);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(currentETag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedUnpaddedETag(String method) {
|
||||
String eTag = "Foo";
|
||||
String paddedEtag = String.format("\"%s\"", eTag);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(paddedEtag);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.assertNext(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(paddedEtag);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedUnpaddedETag(String method) {
|
||||
String currentETag = "Foo";
|
||||
String oldEtag = "Bar";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(oldEtag);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(currentETag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedWildcardIsIgnored(String method) {
|
||||
String eTag = "\"Foo\"";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch("*");
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETagAndTimestamp(String method) {
|
||||
String eTag = "\"Foo\"";
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(eTag);
|
||||
headers.setIfModifiedSince(now);
|
||||
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(now, eTag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.assertNext(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
assertThat(serverResponse.headers().getLastModified()).isEqualTo(now.toEpochMilli());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETagAndModifiedTimestamp(String method) {
|
||||
String eTag = "\"Foo\"";
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
Instant oneMinuteAgo = now.minus(1, ChronoUnit.MINUTES);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(eTag);
|
||||
headers.setIfModifiedSince(oneMinuteAgo);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(now, eTag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.assertNext(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
assertThat(serverResponse.headers().getLastModified()).isEqualTo(now.toEpochMilli());
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedETagAndNotModifiedTimestamp(String method) throws Exception {
|
||||
String currentETag = "\"Foo\"";
|
||||
String oldEtag = "\"Bar\"";
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setIfNoneMatch(oldEtag);
|
||||
headers.setIfModifiedSince(now);
|
||||
MockServerHttpRequest mockRequest = MockServerHttpRequest
|
||||
.method(HttpMethod.valueOf(method), "/")
|
||||
.headers(headers)
|
||||
.build();
|
||||
|
||||
DefaultServerRequest request =
|
||||
new DefaultServerRequest(MockServerWebExchange.from(mockRequest), Collections.emptyList());
|
||||
|
||||
Mono<ServerResponse> result = request.checkNotModified(now, currentETag);
|
||||
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@ValueSource(strings = {"GET", "HEAD"})
|
||||
@interface ParameterizedHttpMethodTest {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,15 +17,18 @@
|
|||
package org.springframework.web.servlet.function;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -36,8 +39,10 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
|
@ -47,11 +52,14 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
|
@ -246,6 +254,26 @@ class DefaultServerRequest implements ServerRequest {
|
|||
return Optional.ofNullable(this.serverHttpRequest.getPrincipal());
|
||||
}
|
||||
|
||||
static Optional<ServerResponse> checkNotModified(HttpServletRequest servletRequest, @Nullable Instant lastModified,
|
||||
@Nullable String etag) {
|
||||
|
||||
long lastModifiedTimestamp = -1;
|
||||
if (lastModified != null && lastModified.isAfter(Instant.EPOCH)) {
|
||||
lastModifiedTimestamp = lastModified.toEpochMilli();
|
||||
}
|
||||
|
||||
CheckNotModifiedResponse response = new CheckNotModifiedResponse();
|
||||
WebRequest webRequest = new ServletWebRequest(servletRequest, response);
|
||||
if (webRequest.checkNotModified(etag, lastModifiedTimestamp)) {
|
||||
return Optional.of(ServerResponse.status(response.status).
|
||||
headers(headers -> headers.addAll(response.headers))
|
||||
.build());
|
||||
}
|
||||
else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link Headers}.
|
||||
*/
|
||||
|
@ -419,4 +447,207 @@ class DefaultServerRequest implements ServerRequest {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple implementation of {@link HttpServletResponse} used by
|
||||
* {@link #checkNotModified(HttpServletRequest, Instant, String)} to record status and headers set by
|
||||
* {@link ServletWebRequest#checkNotModified(String, long)}. Throws an {@code UnsupportedOperationException}
|
||||
* for other methods.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private static final class CheckNotModifiedResponse implements HttpServletResponse {
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private int status = 200;
|
||||
|
||||
@Override
|
||||
public boolean containsHeader(String name) {
|
||||
return this.headers.containsKey(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDateHeader(String name, long date) {
|
||||
this.headers.setDate(name, date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
this.headers.set(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value) {
|
||||
this.headers.add(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int sc) {
|
||||
this.status = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int sc, String sm) {
|
||||
this.status = sc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return this.headers.getFirst(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getHeaders(String name) {
|
||||
List<String> result = this.headers.get(name);
|
||||
return result != null ? result : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getHeaderNames() {
|
||||
return this.headers.keySet();
|
||||
}
|
||||
|
||||
|
||||
// Unsupported
|
||||
@Override
|
||||
public void addCookie(Cookie cookie) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeURL(String url) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeRedirectURL(String url) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeUrl(String url) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encodeRedirectUrl(String url) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc, String msg) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int sc) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendRedirect(String location) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDateHeader(String name, long date) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIntHeader(String name, int value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntHeader(String name, int value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getCharacterEncoding() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterEncoding(String charset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentLength(int len) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentLengthLong(long len) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentType(String type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferSize(int size) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSize() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBuffer() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBuffer() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocale(Locale loc) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
|||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -1017,6 +1018,21 @@ public abstract class RequestPredicates {
|
|||
return this.request.servletRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerResponse> checkNotModified(Instant lastModified) {
|
||||
return this.request.checkNotModified(lastModified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerResponse> checkNotModified(String etag) {
|
||||
return this.request.checkNotModified(etag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerResponse> checkNotModified(Instant lastModified, String etag) {
|
||||
return this.request.checkNotModified(lastModified, etag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return method() + " " + path();
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.net.InetSocketAddress;
|
|||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -42,6 +43,7 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
|
@ -224,6 +226,109 @@ public interface ServerRequest {
|
|||
*/
|
||||
HttpServletRequest servletRequest();
|
||||
|
||||
/**
|
||||
* Check whether the requested resource has been modified given the
|
||||
* supplied last-modified timestamp (as determined by the application).
|
||||
* If not modified, this method returns a response with corresponding
|
||||
* status code and headers, otherwise an empty result.
|
||||
* <p>Typical usage:
|
||||
* <pre class="code">
|
||||
* public ServerResponse myHandleMethod(ServerRequest request) {
|
||||
* Instant lastModified = // application-specific calculation
|
||||
* return request.checkNotModified(lastModified)
|
||||
* .orElseGet(() -> {
|
||||
* // further request processing, actually building content
|
||||
* return ServerResponse.ok().body(...);
|
||||
* });
|
||||
* }</pre>
|
||||
* <p>This method works with conditional GET/HEAD requests, but
|
||||
* also with conditional POST/PUT/DELETE requests.
|
||||
* <p><strong>Note:</strong> you can use either
|
||||
* this {@code #checkNotModified(Instant)} method; or
|
||||
* {@link #checkNotModified(String)}. If you want enforce both
|
||||
* a strong entity tag and a Last-Modified value,
|
||||
* as recommended by the HTTP specification,
|
||||
* then you should use {@link #checkNotModified(Instant, String)}.
|
||||
* @param lastModified the last-modified timestamp that the
|
||||
* application determined for the underlying resource
|
||||
* @return a corresponding response if the request qualifies as not
|
||||
* modified, or an empty result otherwise.
|
||||
* @since 5.2.5
|
||||
*/
|
||||
default Optional<ServerResponse> checkNotModified(Instant lastModified) {
|
||||
Assert.notNull(lastModified, "LastModified must not be null");
|
||||
return DefaultServerRequest.checkNotModified(servletRequest(), lastModified, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the requested resource has been modified given the
|
||||
* supplied {@code ETag} (entity tag), as determined by the application.
|
||||
* If not modified, this method returns a response with corresponding
|
||||
* status code and headers, otherwise an empty result.
|
||||
* <p>Typical usage:
|
||||
* <pre class="code">
|
||||
* public ServerResponse myHandleMethod(ServerRequest request) {
|
||||
* String eTag = // application-specific calculation
|
||||
* return request.checkNotModified(eTag)
|
||||
* .orElseGet(() -> {
|
||||
* // further request processing, actually building content
|
||||
* return ServerResponse.ok().body(...);
|
||||
* });
|
||||
* }</pre>
|
||||
* <p>This method works with conditional GET/HEAD requests, but
|
||||
* also with conditional POST/PUT/DELETE requests.
|
||||
* <p><strong>Note:</strong> you can use either
|
||||
* this {@link #checkNotModified(Instant)} method; or
|
||||
* {@code #checkNotModified(String)}. If you want enforce both
|
||||
* a strong entity tag and a Last-Modified value,
|
||||
* as recommended by the HTTP specification,
|
||||
* then you should use {@link #checkNotModified(Instant, String)}.
|
||||
* @param etag the entity tag that the application determined
|
||||
* for the underlying resource. This parameter will be padded
|
||||
* with quotes (") if necessary.
|
||||
* @return a corresponding response if the request qualifies as not
|
||||
* modified, or an empty result otherwise.
|
||||
* @since 5.2.5
|
||||
*/
|
||||
default Optional<ServerResponse> checkNotModified(String etag) {
|
||||
Assert.notNull(etag, "Etag must not be null");
|
||||
return DefaultServerRequest.checkNotModified(servletRequest(), null, etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the requested resource has been modified given the
|
||||
* supplied {@code ETag} (entity tag) and last-modified timestamp,
|
||||
* as determined by the application.
|
||||
* If not modified, this method returns a response with corresponding
|
||||
* status code and headers, otherwise an empty result.
|
||||
* <p>Typical usage:
|
||||
* <pre class="code">
|
||||
* public ServerResponse myHandleMethod(ServerRequest request) {
|
||||
* Instant lastModified = // application-specific calculation
|
||||
* String eTag = // application-specific calculation
|
||||
* return request.checkNotModified(lastModified, eTag)
|
||||
* .orElseGet(() -> {
|
||||
* // further request processing, actually building content
|
||||
* return ServerResponse.ok().body(...);
|
||||
* });
|
||||
* }</pre>
|
||||
* <p>This method works with conditional GET/HEAD requests, but
|
||||
* also with conditional POST/PUT/DELETE requests.
|
||||
* @param lastModified the last-modified timestamp that the
|
||||
* application determined for the underlying resource
|
||||
* @param etag the entity tag that the application determined
|
||||
* for the underlying resource. This parameter will be padded
|
||||
* with quotes (") if necessary.
|
||||
* @return a corresponding response if the request qualifies as not
|
||||
* modified, or an empty result otherwise.
|
||||
* @since 5.2.5
|
||||
*/
|
||||
default Optional<ServerResponse> checkNotModified(Instant lastModified, String etag) {
|
||||
Assert.notNull(lastModified, "LastModified must not be null");
|
||||
Assert.notNull(etag, "Etag must not be null");
|
||||
return DefaultServerRequest.checkNotModified(servletRequest(), lastModified, etag);
|
||||
}
|
||||
|
||||
|
||||
// Static methods
|
||||
|
||||
|
@ -248,6 +353,7 @@ public interface ServerRequest {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Represents the headers of the HTTP request.
|
||||
* @see ServerRequest#headers()
|
||||
|
|
|
@ -16,10 +16,16 @@
|
|||
|
||||
package org.springframework.web.servlet.function;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -29,11 +35,14 @@ import java.util.OptionalLong;
|
|||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
|
@ -41,6 +50,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
|
|||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpSession;
|
||||
|
||||
|
@ -48,6 +58,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest.get;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
|
@ -300,7 +311,193 @@ public class DefaultServerRequestTests {
|
|||
this.messageConverters);
|
||||
|
||||
assertThat(request.principal().get()).isEqualTo(principal);
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedTimestamp(String method) throws Exception {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, now.toEpochMilli());
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(now, "");
|
||||
|
||||
assertThat(result).hasValueSatisfying(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getLastModified()).isEqualTo(now.toEpochMilli());
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedTimestamp(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
Instant oneMinuteAgo = now.minus(1, ChronoUnit.MINUTES);
|
||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, oneMinuteAgo.toEpochMilli());
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(now, "");
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETag(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String eTag = "\"Foo\"";
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
assertThat(result).hasValueSatisfying(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETagWithSeparatorChars(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String eTag = "\"Foo, Bar\"";
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
assertThat(result).hasValueSatisfying(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedETag(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String currentETag = "\"Foo\"";
|
||||
String oldEtag = "Bar";
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, oldEtag);
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(currentETag);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedUnpaddedETag(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String eTag = "Foo";
|
||||
String paddedEtag = String.format("\"%s\"", eTag);
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, paddedEtag);
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
assertThat(result).hasValueSatisfying(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(paddedEtag);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedUnpaddedETag(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String currentETag = "Foo";
|
||||
String oldEtag = "Bar";
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, oldEtag);
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(currentETag);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedWildcardIsIgnored(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String eTag = "\"Foo\"";
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, "*");
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(eTag);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETagAndTimestamp(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String eTag = "\"Foo\"";
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, now.toEpochMilli());
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(now, eTag);
|
||||
|
||||
assertThat(result).hasValueSatisfying(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
assertThat(serverResponse.headers().getLastModified()).isEqualTo(now.toEpochMilli());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkNotModifiedETagAndModifiedTimestamp(String method) {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String eTag = "\"Foo\"";
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
Instant oneMinuteAgo = now.minus(1, ChronoUnit.MINUTES);
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, eTag);
|
||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, oneMinuteAgo.toEpochMilli());
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(get("/")
|
||||
.ifNoneMatch(eTag)
|
||||
.ifModifiedSince(oneMinuteAgo.toEpochMilli())
|
||||
);
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(now, eTag);
|
||||
|
||||
assertThat(result).hasValueSatisfying(serverResponse -> {
|
||||
assertThat(serverResponse.statusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
|
||||
assertThat(serverResponse.headers().getETag()).isEqualTo(eTag);
|
||||
assertThat(serverResponse.headers().getLastModified()).isEqualTo(now.toEpochMilli());
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedHttpMethodTest
|
||||
void checkModifiedETagAndNotModifiedTimestamp(String method) throws Exception {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest(method, "/");
|
||||
String currentETag = "\"Foo\"";
|
||||
String oldEtag = "\"Bar\"";
|
||||
Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
|
||||
servletRequest.addHeader(HttpHeaders.IF_NONE_MATCH, oldEtag);
|
||||
servletRequest.addHeader(HttpHeaders.IF_MODIFIED_SINCE, now.toEpochMilli());
|
||||
|
||||
DefaultServerRequest request = new DefaultServerRequest(servletRequest, this.messageConverters);
|
||||
|
||||
Optional<ServerResponse> result = request.checkNotModified(now, currentETag);
|
||||
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@ValueSource(strings = { "GET", "HEAD" })
|
||||
@interface ParameterizedHttpMethodTest {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue