Skip to content

Commit f6c07b3

Browse files
committed
revised DispatcherServlet's last-modified handling to properly work with scoped controllers; added HEAD support to ResourceHttpRequestHandler
1 parent 29b12ad commit f6c07b3

File tree

4 files changed

+54
-93
lines changed

4 files changed

+54
-93
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java

Lines changed: 21 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.ui.context.ThemeSource;
4747
import org.springframework.util.ClassUtils;
4848
import org.springframework.util.StringUtils;
49+
import org.springframework.web.context.request.ServletWebRequest;
4950
import org.springframework.web.multipart.MultipartException;
5051
import org.springframework.web.multipart.MultipartHttpServletRequest;
5152
import org.springframework.web.multipart.MultipartResolver;
@@ -166,9 +167,6 @@ public class DispatcherServlet extends FrameworkServlet {
166167
*/
167168
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
168169

169-
/** Request attribute to hold the currently chosen HandlerExecutionChain. Only used for internal optimizations. */
170-
public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class.getName() + ".HANDLER";
171-
172170
/**
173171
* Request attribute to hold the current web application context. Otherwise only the global web app context is
174172
* obtainable by tags etc.
@@ -750,12 +748,29 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
750748
processedRequest = checkMultipart(request);
751749

752750
// Determine handler for the current request.
753-
mappedHandler = getHandler(processedRequest, false);
751+
mappedHandler = getHandler(processedRequest);
754752
if (mappedHandler == null || mappedHandler.getHandler() == null) {
755753
noHandlerFound(processedRequest, response);
756754
return;
757755
}
758756

757+
// Determine handler adapter for the current request.
758+
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
759+
760+
// Process last-modified header, if supported by the handler.
761+
String method = request.getMethod();
762+
boolean isGet = "GET".equals(method);
763+
if (isGet || "HEAD".equals(method)) {
764+
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
765+
if (logger.isDebugEnabled()) {
766+
String requestUri = urlPathHelper.getRequestUri(request);
767+
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
768+
}
769+
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
770+
return;
771+
}
772+
}
773+
759774
// Apply preHandle methods of registered interceptors.
760775
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
761776
if (interceptors != null) {
@@ -770,7 +785,6 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
770785
}
771786

772787
// Actually invoke the handler.
773-
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
774788
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
775789

776790
// Do we need view name translation?
@@ -834,41 +848,6 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
834848
}
835849
}
836850

837-
/**
838-
* Override HttpServlet's <code>getLastModified</code> method to evaluate the Last-Modified value
839-
* of the mapped handler.
840-
*/
841-
@Override
842-
protected long getLastModified(HttpServletRequest request) {
843-
if (logger.isDebugEnabled()) {
844-
String requestUri = urlPathHelper.getRequestUri(request);
845-
logger.debug(
846-
"DispatcherServlet with name '" + getServletName() + "' determining Last-Modified value for [" +
847-
requestUri + "]");
848-
}
849-
try {
850-
HandlerExecutionChain mappedHandler = getHandler(request, true);
851-
if (mappedHandler == null || mappedHandler.getHandler() == null) {
852-
// Ignore -> will reappear on doService.
853-
logger.debug("No handler found in getLastModified");
854-
return -1;
855-
}
856-
857-
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
858-
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
859-
if (logger.isDebugEnabled()) {
860-
String requestUri = urlPathHelper.getRequestUri(request);
861-
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
862-
}
863-
return lastModified;
864-
}
865-
catch (Exception ex) {
866-
// Ignore -> will reappear on doService.
867-
logger.debug("Exception thrown in getLastModified", ex);
868-
return -1;
869-
}
870-
}
871-
872851
/**
873852
* Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
874853
* <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
@@ -882,7 +861,6 @@ protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
882861
public Locale getLocale() {
883862
return localeResolver.resolveLocale(request);
884863
}
885-
886864
@Override
887865
public String toString() {
888866
return getLocale().toString();
@@ -925,28 +903,16 @@ protected void cleanupMultipart(HttpServletRequest request) {
925903
/**
926904
* Return the HandlerExecutionChain for this request. Try all handler mappings in order.
927905
* @param request current HTTP request
928-
* @param cache whether to cache the HandlerExecutionChain in a request attribute
929906
* @return the HandlerExceutionChain, or <code>null</code> if no handler could be found
930907
*/
931-
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
932-
HandlerExecutionChain handler = (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
933-
if (handler != null) {
934-
if (!cache) {
935-
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
936-
}
937-
return handler;
938-
}
939-
908+
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
940909
for (HandlerMapping hm : this.handlerMappings) {
941910
if (logger.isTraceEnabled()) {
942911
logger.trace(
943912
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
944913
}
945-
handler = hm.getHandler(request);
914+
HandlerExecutionChain handler = hm.getHandler(request);
946915
if (handler != null) {
947-
if (cache) {
948-
request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
949-
}
950916
return handler;
951917
}
952918
}

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,30 +28,33 @@
2828
import org.springframework.util.FileCopyUtils;
2929
import org.springframework.util.StringUtils;
3030
import org.springframework.web.HttpRequestHandler;
31+
import org.springframework.web.context.request.ServletWebRequest;
3132
import org.springframework.web.servlet.HandlerMapping;
3233
import org.springframework.web.servlet.support.WebContentGenerator;
3334

3435
/**
3536
* {@link HttpRequestHandler} that serves static resources optimized for superior browser performance
36-
* (according to the guidelines of Page Speed, YSlow, etc.) by adding far future cache expiration headers.
37+
* (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings
38+
* ({@link #setCacheSeconds "cacheSeconds" property}, last-modified support).
3739
*
38-
* <p>The constructor takes a list of Spring {@link Resource} locations from which static resources are allowed
39-
* to be served by this handler. For a given request, the list of locations will be consulted in order for the
40-
* presence of the requested resource, and the first found match will be written to the response, with {@code
41-
* Expires} and {@code Cache-Control} headers set for one year in the future. The handler also properly evaluates
42-
* the {@code Last-Modified} header (if present) so that a {@code 304} status code will be returned as appropriate,
43-
* avoiding unnecessary overhead for resources that are already cached by the client. The use of {@code Resource}
44-
* locations allows resource requests to easily be mapped to locations other than the web application root. For
40+
* <p>The {@link #setLocations "locations" property takes a list of Spring {@link Resource} locations
41+
* from which static resources are allowed to be served by this handler. For a given request, the
42+
* list of locations will be consulted in order for the presence of the requested resource, and the
43+
* first found match will be written to the response, with {@code Expires} and {@code Cache-Control}
44+
* headers set as configured. The handler also properly evaluates the {@code Last-Modified} header
45+
* (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary
46+
* overhead for resources that are already cached by the client. The use of {@code Resource} locations
47+
* allows resource requests to easily be mapped to locations other than the web application root. For
4548
* example, resources could be served from a classpath location such as "classpath:/META-INF/public-web-resources/",
4649
* allowing convenient packaging and serving of resources such as a JavaScript library from within jar files.
4750
*
48-
* <p>To ensure that users with a primed browser cache get the latest changes to application-specific resources
49-
* upon deployment of new versions of the application, it is recommended that a version string is used in the URL
50-
* mapping pattern that selects this handler. Such patterns can be easily parameterized using Spring EL. See the
51-
* reference manual for further examples of this approach.
51+
* <p>To ensure that users with a primed browser cache get the latest changes to application-specific
52+
* resources upon deployment of new versions of the application, it is recommended that a version string
53+
* is used in the URL mapping pattern that selects this handler. Such patterns can be easily parameterized
54+
* using Spring EL. See the reference manual for further examples of this approach.
5255
*
53-
* <p>Rather than being directly configured as a bean, this handler will typically be configured through use of
54-
* the <code>&lt;mvc:resources/&gt;</code> Spring configuration tag.
56+
* <p>Rather than being directly configured as a bean, this handler will typically be configured
57+
* through use of the <code>&lt;mvc:resources/&gt;</code> XML configuration element.
5558
*
5659
* @author Keith Donald
5760
* @author Jeremy Grelle
@@ -64,7 +67,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
6467

6568

6669
public ResourceHttpRequestHandler() {
67-
super(METHOD_GET);
70+
super(METHOD_GET, METHOD_HEAD);
6871
}
6972

7073
/**
@@ -98,13 +101,15 @@ public void handleRequest(HttpServletRequest request, HttpServletResponse respon
98101
response.sendError(HttpServletResponse.SC_NOT_FOUND);
99102
return;
100103
}
101-
if (checkNotModified(resource, request, response)) {
104+
setHeaders(resource, response);
105+
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified()) ||
106+
METHOD_HEAD.equals(request.getMethod())) {
102107
return;
103108
}
104-
writeResponse(resource, response);
109+
writeContent(resource, response);
105110
}
106111

107-
private Resource getResource(HttpServletRequest request) {
112+
protected Resource getResource(HttpServletRequest request) {
108113
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
109114
if (path == null) {
110115
throw new IllegalStateException("Required request attribute '" +
@@ -128,22 +133,7 @@ private Resource getResource(HttpServletRequest request) {
128133
return null;
129134
}
130135

131-
private boolean checkNotModified(Resource resource,HttpServletRequest request, HttpServletResponse response)
132-
throws IOException {
133-
134-
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
135-
long lastModified = resource.lastModified();
136-
boolean notModified = ifModifiedSince >= (lastModified / 1000 * 1000);
137-
if (notModified) {
138-
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
139-
}
140-
else {
141-
response.setDateHeader("Last-Modified", lastModified);
142-
}
143-
return notModified;
144-
}
145-
146-
private void writeResponse(Resource resource, HttpServletResponse response) throws IOException {
136+
protected void setHeaders(Resource resource, HttpServletResponse response) throws IOException {
147137
MediaType mediaType = getMediaType(resource);
148138
if (mediaType != null) {
149139
response.setContentType(mediaType.toString());
@@ -153,12 +143,15 @@ private void writeResponse(Resource resource, HttpServletResponse response) thro
153143
throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
154144
}
155145
response.setContentLength((int) length);
156-
FileCopyUtils.copy(resource.getInputStream(), response.getOutputStream());
157146
}
158147

159148
protected MediaType getMediaType(Resource resource) {
160149
String mimeType = getServletContext().getMimeType(resource.getFilename());
161150
return (StringUtils.hasText(mimeType) ? MediaType.parseMediaType(mimeType) : null);
162151
}
163152

153+
protected void writeContent(Resource resource, HttpServletResponse response) throws IOException {
154+
FileCopyUtils.copy(resource.getInputStream(), response.getOutputStream());
155+
}
156+
164157
}

org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public boolean checkNotModified(long lastModifiedTimestamp) {
186186
long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
187187
this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
188188
if (this.response != null) {
189-
if (this.notModified) {
189+
if (this.notModified && "GET".equals(getRequest().getMethod())) {
190190
this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
191191
}
192192
else {

org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ public interface WebRequest extends RequestAttributes {
3535
/**
3636
* Return the request header of the given name, or <code>null</code> if none.
3737
* <p>Retrieves the first header value in case of a multi-value header.
38+
* @since 3.0
3839
* @see javax.servlet.http.HttpServletRequest#getHeader(String)
3940
*/
4041
String getHeader(String headerName);
@@ -43,14 +44,15 @@ public interface WebRequest extends RequestAttributes {
4344
* Return the request header values for the given header name,
4445
* or <code>null</code> if none.
4546
* <p>A single-value header will be exposed as an array with a single element.
47+
* @since 3.0
4648
* @see javax.servlet.http.HttpServletRequest#getHeaders(String)
4749
*/
4850
String[] getHeaderValues(String headerName);
4951

5052
/**
5153
* Return a Iterator over request header names.
52-
* @see javax.servlet.http.HttpServletRequest#getHeaderNames()
5354
* @since 3.0
55+
* @see javax.servlet.http.HttpServletRequest#getHeaderNames()
5456
*/
5557
Iterator<String> getHeaderNames();
5658

0 commit comments

Comments
 (0)