Improve Last-Modified & ETag support

Prior to this change, the `"Last-Modified"` and "`Etag`" support had
been improved with SPR-11324: HTTP response headers are now
automatically added for conditional requests and more.

This commit fixes the format of the "`Last-Modified`" and "`ETag`"
values, which were using an epoch timestamp rather than an HTTP-date
format defined in RFC 7231 section 7.1.1.1.

Also, Conditional responses are only applied when the given response
applies, i.e. when it has an compatible HTTP status (2xx).

Issue: SPR-13090
This commit is contained in:
Brian Clozel
2015-06-05 14:30:09 +02:00
parent 75edb3979e
commit 0175068cab
9 changed files with 129 additions and 49 deletions

View File

@@ -460,7 +460,7 @@ public class ComplexWebApplicationContext extends StaticWebApplicationContext {
@Override
public long lastModified() {
return 99;
return 1427846401000L;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -177,7 +177,7 @@ public class DispatcherServletTests extends TestCase {
MockHttpServletResponse response = new MockHttpServletResponse();
simpleDispatcherServlet.service(request, response);
assertTrue("Not forwarded", response.getForwardedUrl() == null);
assertEquals("98", response.getHeader("Last-Modified"));
assertEquals("Wed, 01 Apr 2015 00:00:00 GMT", response.getHeader("Last-Modified"));
}
public void testUnknownRequest() throws Exception {
@@ -205,7 +205,7 @@ public class DispatcherServletTests extends TestCase {
assertTrue(request.getAttribute("test3") != null);
assertTrue(request.getAttribute("test3x") != null);
assertTrue(request.getAttribute("test3y") != null);
assertEquals("99", response.getHeader("Last-Modified"));
assertEquals("Wed, 01 Apr 2015 00:00:01 GMT", response.getHeader("Last-Modified"));
}
public void testExistingMultipartRequest() throws Exception {

View File

@@ -90,7 +90,7 @@ public class SimpleWebApplicationContext extends StaticWebApplicationContext {
@Override
public long getLastModified(HttpServletRequest request) {
return 98;
return 1427846400000L;
}
}

View File

@@ -23,9 +23,12 @@ import static org.springframework.web.servlet.HandlerMapping.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +64,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/
public class HttpEntityMethodProcessorMockTests {
private SimpleDateFormat dateFormat;
private HttpEntityMethodProcessor processor;
private HttpMessageConverter<String> messageConverter;
@@ -87,6 +92,9 @@ public class HttpEntityMethodProcessorMockTests {
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
messageConverter = mock(HttpMessageConverter.class);
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
@@ -341,7 +349,7 @@ public class HttpEntityMethodProcessorMockTests {
assertTrue(mavContainer.isRequestHandled());
assertEquals(HttpStatus.NOT_MODIFIED.value(), servletResponse.getStatus());
assertEquals(oneMinuteAgo/1000 * 1000, Long.parseLong(servletResponse.getHeader(HttpHeaders.LAST_MODIFIED)));
assertEquals(dateFormat.format(oneMinuteAgo), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
assertEquals(0, servletResponse.getContentAsByteArray().length);
}
@@ -385,7 +393,7 @@ public class HttpEntityMethodProcessorMockTests {
assertTrue(mavContainer.isRequestHandled());
assertEquals(HttpStatus.NOT_MODIFIED.value(), servletResponse.getStatus());
assertEquals(oneMinuteAgo/1000 * 1000, Long.parseLong(servletResponse.getHeader(HttpHeaders.LAST_MODIFIED)));
assertEquals(dateFormat.format(oneMinuteAgo), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
assertEquals(etagValue, servletResponse.getHeader(HttpHeaders.ETAG));
assertEquals(0, servletResponse.getContentAsByteArray().length);
}
@@ -411,7 +419,7 @@ public class HttpEntityMethodProcessorMockTests {
assertTrue(mavContainer.isRequestHandled());
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
assertEquals(oneMinuteAgo/1000 * 1000, Long.parseLong(servletResponse.getHeader(HttpHeaders.LAST_MODIFIED)));
assertEquals(dateFormat.format(oneMinuteAgo), servletResponse.getHeader(HttpHeaders.LAST_MODIFIED));
assertEquals(changedEtagValue, servletResponse.getHeader(HttpHeaders.ETAG));
assertEquals(0, servletResponse.getContentAsByteArray().length);
}

View File

@@ -23,9 +23,13 @@ import static org.mockito.Mockito.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.HttpServletResponse;
@@ -52,6 +56,8 @@ import org.springframework.web.servlet.HandlerMapping;
*/
public class ResourceHttpRequestHandlerTests {
private SimpleDateFormat dateFormat;
private ResourceHttpRequestHandler handler;
private MockHttpServletRequest request;
@@ -61,6 +67,9 @@ public class ResourceHttpRequestHandlerTests {
@Before
public void setUp() throws Exception {
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
List<Resource> paths = new ArrayList<>(2);
paths.add(new ClassPathResource("test/", getClass()));
paths.add(new ClassPathResource("testalternatepath/", getClass()));
@@ -127,7 +136,7 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("no-cache", this.response.getHeader("Pragma"));
assertThat(this.response.getHeaderValues("Cache-Control"), Matchers.contains("no-cache", "no-store"));
assertTrue(headerAsLong("Expires") == 1);
assertEquals(this.response.getHeaderValue("Expires"), dateFormat.format(System.currentTimeMillis()));
assertTrue(this.response.containsHeader("Last-Modified"));
assertEquals(headerAsLong("Last-Modified"), resourceLastModified("test/foo.css"));
}
@@ -467,8 +476,8 @@ public class ResourceHttpRequestHandlerTests {
}
private long headerAsLong(String responseHeaderName) {
return Long.valueOf(this.response.getHeader(responseHeaderName));
private long headerAsLong(String responseHeaderName) throws Exception {
return dateFormat.parse(this.response.getHeader(responseHeaderName)).getTime();
}
private long resourceLastModified(String resourceName) throws IOException {