Add Last-Modified header to 304 response when ETag is not present

.

This commit adds the Last-Modified header to the 304 Not Modified response when the ETag header is not present in the cache entry. This aligns the behavior with the recommendations in RFC 7232 and helps clients that rely on the Last-Modified header for cache updates when
This commit is contained in:
Arturo Bernal 2023-04-22 21:21:31 +02:00 committed by Oleg Kalnichevski
parent e1cfb2add6
commit b9e2bbc778
3 changed files with 40 additions and 0 deletions

View File

@ -139,6 +139,18 @@ class CachedHttpResponseGenerator {
response.addHeader(varyHeader);
}
//Since the goal of a 304 response is to minimize information transfer
//when the recipient already has one or more cached representations, a
//sender SHOULD NOT generate representation metadata other than the
//above listed fields unless said metadata exists for the purpose of
//guiding cache updates (e.g., Last-Modified might be useful if the
//response does not have an ETag field).
if (etagHeader == null) {
final Header lastModifiedHeader = entry.getFirstHeader(HttpHeaders.LAST_MODIFIED);
if (lastModifiedHeader != null) {
response.addHeader(lastModifiedHeader);
}
}
return response;
}

View File

@ -391,6 +391,7 @@ public class TestCachingExecChain {
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
resp1.setHeader("Cache-control", "max-age=600");
resp1.setHeader("Expires", DateUtils.formatStandardDate(inTenMinutes));
resp1.setHeader("ETag", "\"etag\"");
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
@ -403,6 +404,32 @@ public class TestCachingExecChain {
Mockito.verify(mockExecChain).proceed(Mockito.any(), Mockito.any());
}
@Test
public void testReturns304ForIfModifiedSinceHeaderIf304ResponseInCacheWithLastModified() throws Exception {
final Instant now = Instant.now();
final Instant oneHourAgo = now.minus(1, ChronoUnit.HOURS);
final Instant inTenMinutes = now.plus(10, ChronoUnit.MINUTES);
final ClassicHttpRequest req1 = new HttpGet("http://foo.example.com/");
req1.addHeader("If-Modified-Since", DateUtils.formatStandardDate(oneHourAgo));
final ClassicHttpRequest req2 = new HttpGet("http://foo.example.com/");
req2.addHeader("If-Modified-Since", DateUtils.formatStandardDate(oneHourAgo));
final ClassicHttpResponse resp1 = HttpTestUtils.make304Response();
resp1.setHeader("Date", DateUtils.formatStandardDate(now));
resp1.setHeader("Cache-control", "max-age=600");
resp1.setHeader("Expires", DateUtils.formatStandardDate(inTenMinutes));
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
execute(req1);
final ClassicHttpResponse result = execute(req2);
Assertions.assertEquals(HttpStatus.SC_NOT_MODIFIED, result.getCode());
Assertions.assertTrue(result.containsHeader("Last-Modified"));
Mockito.verify(mockExecChain).proceed(Mockito.any(), Mockito.any());
}
@Test
public void testReturns200ForIfModifiedSinceDateIsLess() throws Exception {
final Instant now = Instant.now();

View File

@ -175,6 +175,7 @@ public class TestProtocolRecommendations {
resp1.setHeader("Cache-Control","max-age=3600");
resp1.setHeader(validatorHeader, validator);
resp1.setHeader(headerName, headerValue);
resp1.setHeader("ETag", "\"etag\"");
Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);