Support ETag generation on ResourceHttpRequestHandler
Prior to this commit, the `ResourceHttpRequestHandler` would support HTTP caching when serving resources, but only driving it through the `Resource#lastModified()` information. This commit introduces an ETag generator function that can be configured on the `ResourceHttpRequestHandler` to dynamically generate an ETag value for the Resource that is going to be served. Closes gh-29031
This commit is contained in:
parent
ebfa009f18
commit
7582bd8667
|
|
@ -25,6 +25,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
|
@ -141,6 +142,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
|||
|
||||
private boolean useLastModified = true;
|
||||
|
||||
@Nullable
|
||||
private Function<Resource, String> etagGenerator;
|
||||
|
||||
private boolean optimizeLocations = false;
|
||||
|
||||
@Nullable
|
||||
|
|
@ -383,6 +387,29 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
|||
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 ServletWebRequest#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
|
||||
|
|
@ -567,7 +594,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
|||
checkRequest(request);
|
||||
|
||||
// Header phase
|
||||
if (isUseLastModified() && new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
|
||||
String eTagValue = (this.getEtagGenerator() != null) ? this.getEtagGenerator().apply(resource) : null;
|
||||
long lastModified = (this.isUseLastModified()) ? resource.lastModified() : -1;
|
||||
if (new ServletWebRequest(request, response).checkNotModified(eTagValue, lastModified)) {
|
||||
logger.trace("Resource not modified");
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -478,7 +478,7 @@ class ResourceHttpRequestHandlerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void shouldRespondWithNotModified() throws Exception {
|
||||
void shouldRespondWithNotModifiedWhenModifiedSince() throws Exception {
|
||||
this.handler.afterPropertiesSet();
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.request.addHeader("If-Modified-Since", resourceLastModified("test/foo.css"));
|
||||
|
|
@ -496,6 +496,38 @@ class ResourceHttpRequestHandlerTests {
|
|||
assertThat(this.response.getContentAsString()).isEqualTo("h1 { color:red; }");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRespondWithNotModifiedWhenEtag() throws Exception {
|
||||
this.handler.setEtagGenerator(resource -> "testEtag");
|
||||
this.handler.afterPropertiesSet();
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.request.addHeader("If-None-Match", "\"testEtag\"");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRespondWithModifiedResourceWhenEtagNoMatch() throws Exception {
|
||||
this.handler.setEtagGenerator(resource -> "noMatch");
|
||||
this.handler.afterPropertiesSet();
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.request.addHeader("If-None-Match", "\"testEtag\"");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
assertThat(this.response.getContentAsString()).isEqualTo("h1 { color:red; }");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRespondWithNotModifiedWhenEtagAndLastModified() throws Exception {
|
||||
this.handler.setEtagGenerator(resource -> "testEtag");
|
||||
this.handler.afterPropertiesSet();
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.request.addHeader("If-None-Match", "\"testEtag\"");
|
||||
this.request.addHeader("If-Modified-Since", resourceLastModified("test/foo.css"));
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_NOT_MODIFIED);
|
||||
}
|
||||
|
||||
@Test // SPR-14005
|
||||
void overwritesExistingCacheControlHeaders() throws Exception {
|
||||
this.handler.setCacheSeconds(3600);
|
||||
|
|
|
|||
Loading…
Reference in New Issue