From aa5c0dcd72cf9b4150a04286959207e05a16344b Mon Sep 17 00:00:00 2001 From: James Yuzawa Date: Mon, 17 Mar 2025 21:10:23 -0400 Subject: [PATCH] Add caching headers to unmodified static resources per https://www.rfc-editor.org/rfc/rfc7232#section-4.1 The server generating a 304 response MUST generate any of the following header fields that would have been sent in a 200 (OK) response to the same request: Cache-Control, Content-Location, Date, ETag, Expires, and Vary. Closes gh-34614 Signed-off-by: James Yuzawa --- .../web/servlet/resource/ResourceHttpRequestHandler.java | 8 ++++---- .../servlet/resource/ResourceHttpRequestHandlerTests.java | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 2a21b956a79..e2dc0f72e0c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -569,7 +569,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator * If the resource exists, the request will be checked for the presence of the * {@code Last-Modified} header, and its value will be compared against the last-modified * timestamp of the given resource, returning a {@code 304} status code if the - * {@code Last-Modified} value is greater. If the resource is newer than the + * {@code Last-Modified} value is greater. If the resource is newer than the * {@code Last-Modified} value, or the header is not present, the content resource * of the resource will be written to the response with caching headers * set to expire one year in the future. @@ -593,6 +593,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator // Supported methods and required session checkRequest(request); + // Apply cache settings, if any + prepareResponse(response); + // Header phase String eTagValue = (this.getEtagGenerator() != null) ? this.getEtagGenerator().apply(resource) : null; long lastModified = (this.isUseLastModified()) ? resource.lastModified() : -1; @@ -601,9 +604,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator return; } - // Apply cache settings, if any - prepareResponse(response); - // Check the media type for the resource MediaType mediaType = getMediaType(request, resource); setHeaders(response, resource, mediaType); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java index 6e203e300be..923a2b6a717 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -479,11 +479,13 @@ class ResourceHttpRequestHandlerTests { @Test void shouldRespondWithNotModifiedWhenModifiedSince() throws Exception { + this.handler.setCacheSeconds(3600); this.handler.afterPropertiesSet(); this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); 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); + assertThat(this.response.getHeader("Cache-Control")).isEqualTo("max-age=3600"); } @Test @@ -498,12 +500,14 @@ class ResourceHttpRequestHandlerTests { @Test void shouldRespondWithNotModifiedWhenEtag() throws Exception { + this.handler.setCacheSeconds(3600); 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); + assertThat(this.response.getHeader("Cache-Control")).isEqualTo("max-age=3600"); } @Test