diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
index 67d38c2750f..79728d51d55 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
@@ -46,6 +46,7 @@ import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.ui.context.ThemeSource;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
+import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
@@ -166,9 +167,6 @@ public class DispatcherServlet extends FrameworkServlet {
*/
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";
- /** Request attribute to hold the currently chosen HandlerExecutionChain. Only used for internal optimizations. */
- public static final String HANDLER_EXECUTION_CHAIN_ATTRIBUTE = DispatcherServlet.class.getName() + ".HANDLER";
-
/**
* Request attribute to hold the current web application context. Otherwise only the global web app context is
* obtainable by tags etc.
@@ -750,12 +748,29 @@ public class DispatcherServlet extends FrameworkServlet {
processedRequest = checkMultipart(request);
// Determine handler for the current request.
- mappedHandler = getHandler(processedRequest, false);
+ mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
+ // Determine handler adapter for the current request.
+ HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
+
+ // Process last-modified header, if supported by the handler.
+ String method = request.getMethod();
+ boolean isGet = "GET".equals(method);
+ if (isGet || "HEAD".equals(method)) {
+ long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
+ if (logger.isDebugEnabled()) {
+ String requestUri = urlPathHelper.getRequestUri(request);
+ logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
+ }
+ if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
+ return;
+ }
+ }
+
// Apply preHandle methods of registered interceptors.
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
@@ -770,7 +785,6 @@ public class DispatcherServlet extends FrameworkServlet {
}
// Actually invoke the handler.
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
@@ -834,41 +848,6 @@ public class DispatcherServlet extends FrameworkServlet {
}
}
- /**
- * Override HttpServlet's getLastModified method to evaluate the Last-Modified value
- * of the mapped handler.
- */
- @Override
- protected long getLastModified(HttpServletRequest request) {
- if (logger.isDebugEnabled()) {
- String requestUri = urlPathHelper.getRequestUri(request);
- logger.debug(
- "DispatcherServlet with name '" + getServletName() + "' determining Last-Modified value for [" +
- requestUri + "]");
- }
- try {
- HandlerExecutionChain mappedHandler = getHandler(request, true);
- if (mappedHandler == null || mappedHandler.getHandler() == null) {
- // Ignore -> will reappear on doService.
- logger.debug("No handler found in getLastModified");
- return -1;
- }
-
- HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
- if (logger.isDebugEnabled()) {
- String requestUri = urlPathHelper.getRequestUri(request);
- logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
- }
- return lastModified;
- }
- catch (Exception ex) {
- // Ignore -> will reappear on doService.
- logger.debug("Exception thrown in getLastModified", ex);
- return -1;
- }
- }
-
/**
* Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
*
The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
@@ -882,7 +861,6 @@ public class DispatcherServlet extends FrameworkServlet {
public Locale getLocale() {
return localeResolver.resolveLocale(request);
}
-
@Override
public String toString() {
return getLocale().toString();
@@ -925,28 +903,16 @@ public class DispatcherServlet extends FrameworkServlet {
/**
* Return the HandlerExecutionChain for this request. Try all handler mappings in order.
* @param request current HTTP request
- * @param cache whether to cache the HandlerExecutionChain in a request attribute
* @return the HandlerExceutionChain, or null if no handler could be found
*/
- protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
- HandlerExecutionChain handler = (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
- if (handler != null) {
- if (!cache) {
- request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
- }
- return handler;
- }
-
+ protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
- handler = hm.getHandler(request);
+ HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
- if (cache) {
- request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
- }
return handler;
}
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
index fdf47d0a058..adb4809fb72 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java
@@ -28,30 +28,33 @@ import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestHandler;
+import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.support.WebContentGenerator;
/**
* {@link HttpRequestHandler} that serves static resources optimized for superior browser performance
- * (according to the guidelines of Page Speed, YSlow, etc.) by adding far future cache expiration headers.
+ * (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings
+ * ({@link #setCacheSeconds "cacheSeconds" property}, last-modified support).
*
- *
The constructor takes a list of Spring {@link Resource} locations from which static resources are allowed - * to be served by this handler. For a given request, the list of locations will be consulted in order for the - * presence of the requested resource, and the first found match will be written to the response, with {@code - * Expires} and {@code Cache-Control} headers set for one year in the future. The handler also properly evaluates - * the {@code Last-Modified} header (if present) so that a {@code 304} status code will be returned as appropriate, - * avoiding unnecessary overhead for resources that are already cached by the client. The use of {@code Resource} - * locations allows resource requests to easily be mapped to locations other than the web application root. For + *
The {@link #setLocations "locations" property takes a list of Spring {@link Resource} locations + * from which static resources are allowed to be served by this handler. For a given request, the + * list of locations will be consulted in order for the presence of the requested resource, and the + * first found match will be written to the response, with {@code Expires} and {@code Cache-Control} + * headers set as configured. The handler also properly evaluates the {@code Last-Modified} header + * (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary + * overhead for resources that are already cached by the client. The use of {@code Resource} locations + * allows resource requests to easily be mapped to locations other than the web application root. For * example, resources could be served from a classpath location such as "classpath:/META-INF/public-web-resources/", * allowing convenient packaging and serving of resources such as a JavaScript library from within jar files. * - *
To ensure that users with a primed browser cache get the latest changes to application-specific resources - * upon deployment of new versions of the application, it is recommended that a version string is used in the URL - * mapping pattern that selects this handler. Such patterns can be easily parameterized using Spring EL. See the - * reference manual for further examples of this approach. + *
To ensure that users with a primed browser cache get the latest changes to application-specific + * resources upon deployment of new versions of the application, it is recommended that a version string + * is used in the URL mapping pattern that selects this handler. Such patterns can be easily parameterized + * using Spring EL. See the reference manual for further examples of this approach. * - *
Rather than being directly configured as a bean, this handler will typically be configured through use of
- * the <mvc:resources/> Spring configuration tag.
+ *
Rather than being directly configured as a bean, this handler will typically be configured
+ * through use of the <mvc:resources/> XML configuration element.
*
* @author Keith Donald
* @author Jeremy Grelle
@@ -64,7 +67,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
public ResourceHttpRequestHandler() {
- super(METHOD_GET);
+ super(METHOD_GET, METHOD_HEAD);
}
/**
@@ -98,13 +101,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
- if (checkNotModified(resource, request, response)) {
+ setHeaders(resource, response);
+ if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified()) ||
+ METHOD_HEAD.equals(request.getMethod())) {
return;
}
- writeResponse(resource, response);
+ writeContent(resource, response);
}
- private Resource getResource(HttpServletRequest request) {
+ protected Resource getResource(HttpServletRequest request) {
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (path == null) {
throw new IllegalStateException("Required request attribute '" +
@@ -128,22 +133,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
return null;
}
- private boolean checkNotModified(Resource resource,HttpServletRequest request, HttpServletResponse response)
- throws IOException {
-
- long ifModifiedSince = request.getDateHeader("If-Modified-Since");
- long lastModified = resource.lastModified();
- boolean notModified = ifModifiedSince >= (lastModified / 1000 * 1000);
- if (notModified) {
- response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
- }
- else {
- response.setDateHeader("Last-Modified", lastModified);
- }
- return notModified;
- }
-
- private void writeResponse(Resource resource, HttpServletResponse response) throws IOException {
+ protected void setHeaders(Resource resource, HttpServletResponse response) throws IOException {
MediaType mediaType = getMediaType(resource);
if (mediaType != null) {
response.setContentType(mediaType.toString());
@@ -153,7 +143,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
}
response.setContentLength((int) length);
- FileCopyUtils.copy(resource.getInputStream(), response.getOutputStream());
}
protected MediaType getMediaType(Resource resource) {
@@ -161,4 +150,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
return (StringUtils.hasText(mimeType) ? MediaType.parseMediaType(mimeType) : null);
}
+ protected void writeContent(Resource resource, HttpServletResponse response) throws IOException {
+ FileCopyUtils.copy(resource.getInputStream(), response.getOutputStream());
+ }
+
}
diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
index 7ccb5d70963..b59909052b0 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java
@@ -186,7 +186,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
if (this.response != null) {
- if (this.notModified) {
+ if (this.notModified && "GET".equals(getRequest().getMethod())) {
this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
else {
diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java
index 4690b850094..61219935944 100644
--- a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java
+++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * Copyright 2002-2010 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.
@@ -35,6 +35,7 @@ public interface WebRequest extends RequestAttributes {
/**
* Return the request header of the given name, or null if none.
*
Retrieves the first header value in case of a multi-value header.
+ * @since 3.0
* @see javax.servlet.http.HttpServletRequest#getHeader(String)
*/
String getHeader(String headerName);
@@ -43,14 +44,15 @@ public interface WebRequest extends RequestAttributes {
* Return the request header values for the given header name,
* or null if none.
*
A single-value header will be exposed as an array with a single element.
+ * @since 3.0
* @see javax.servlet.http.HttpServletRequest#getHeaders(String)
*/
String[] getHeaderValues(String headerName);
/**
* Return a Iterator over request header names.
- * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
* @since 3.0
+ * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
*/
Iterator