Skip to content

Commit

Permalink
Support ETag generation on ResourceWebHandler
Browse files Browse the repository at this point in the history
This commit replicates the ETag generation option now available on
`ResourceHttpRequestHandler` but for its WebFlux counterpart.

See gh-29031
  • Loading branch information
bclozel committed Oct 25, 2023
1 parent ff14c51 commit 9ac5eb0
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand Down Expand Up @@ -123,6 +124,9 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {

private boolean useLastModified = true;

@Nullable
private Function<Resource, String> etagGenerator;

private boolean optimizeLocations = false;


Expand Down Expand Up @@ -275,6 +279,29 @@ public boolean isUseLastModified() {
return this.useLastModified;
}

/**
* Configure a generator function that will be used to create the ETag information,
* given a {@link Resource} that is about to be written to the response.
* <p>This function should return a String that will be used as an argument in
* {@link ServerWebExchange#checkNotModified(String)}, or {@code null} if no value
* can be generated for the given resource.
* @param etagGenerator the HTTP ETag generator function to use.
* @since 6.1
*/
public void setEtagGenerator(@Nullable Function<Resource, String> etagGenerator) {
this.etagGenerator = etagGenerator;
}

/**
* Return the HTTP ETag generator function to be used when serving resources.
* @return the HTTP ETag generator function
* @since 6.1
*/
@Nullable
public Function<Resource, String> getEtagGenerator() {
return this.etagGenerator;
}

/**
* Set whether to optimize the specified locations through an existence
* check on startup, filtering non-existing directories upfront so that
Expand Down Expand Up @@ -418,7 +445,9 @@ public Mono<Void> handle(ServerWebExchange exchange) {
}

// Header phase
if (isUseLastModified() && exchange.checkNotModified(Instant.ofEpochMilli(resource.lastModified()))) {
String eTagValue = (this.getEtagGenerator() != null) ? this.getEtagGenerator().apply(resource) : null;
Instant lastModified = isUseLastModified() ? Instant.ofEpochMilli(resource.lastModified()) : Instant.MIN;
if (exchange.checkNotModified(eTagValue, lastModified)) {
logger.trace(exchange.getLogPrefix() + "Resource not modified");
return Mono.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,48 @@ void shouldRespondWithModifiedResource() throws Exception {
}

@Test
// SPR-14005
void shouldRespondWithNotModifiedWhenEtag() throws Exception {
this.handler.setEtagGenerator(resource -> "testEtag");
this.handler.afterPropertiesSet();
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("").ifNoneMatch( "\"testEtag\""));

setPathWithinHandlerMapping(exchange, "foo.css");
setBestMachingPattern(exchange, "/**");
this.handler.handle(exchange).block(TIMEOUT);
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
}

@Test
void shouldRespondWithModifiedResourceWhenEtagNoMatch() throws Exception {
this.handler.setEtagGenerator(resource -> "noMatch");
this.handler.afterPropertiesSet();
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("").ifNoneMatch( "\"testEtag\""));

setPathWithinHandlerMapping(exchange, "foo.css");
setBestMachingPattern(exchange, "/**");
this.handler.handle(exchange).block(TIMEOUT);
assertThat((Object) exchange.getResponse().getStatusCode()).isNull();
assertResponseBody(exchange, "h1 { color:red; }");
}

@Test
void shouldRespondWithNotModifiedWhenEtagAndLastModified() throws Exception {
this.handler.setEtagGenerator(resource -> "testEtag");
this.handler.afterPropertiesSet();
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.get("")
.ifModifiedSince(resourceLastModified("test/foo.css"))
.ifNoneMatch( "\"testEtag\""));

setPathWithinHandlerMapping(exchange, "foo.css");
setBestMachingPattern(exchange, "/**");
this.handler.handle(exchange).block(TIMEOUT);
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.NOT_MODIFIED);
}

@Test // SPR-14005
void doOverwriteExistingCacheControlHeaders() throws Exception {
this.handler.setCacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS));
this.handler.afterPropertiesSet();
Expand Down

0 comments on commit 9ac5eb0

Please sign in to comment.