提交 f6c07b37 编写于 作者: J Juergen Hoeller

revised DispatcherServlet's last-modified handling to properly work with...

revised DispatcherServlet's last-modified handling to properly work with scoped controllers; added HEAD support to ResourceHttpRequestHandler
上级 29b12adb
...@@ -46,6 +46,7 @@ import org.springframework.core.io.support.PropertiesLoaderUtils; ...@@ -46,6 +46,7 @@ import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.ui.context.ThemeSource; import org.springframework.ui.context.ThemeSource;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest; import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.MultipartResolver;
...@@ -166,9 +167,6 @@ public class DispatcherServlet extends FrameworkServlet { ...@@ -166,9 +167,6 @@ public class DispatcherServlet extends FrameworkServlet {
*/ */
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; 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 * Request attribute to hold the current web application context. Otherwise only the global web app context is
* obtainable by tags etc. * obtainable by tags etc.
...@@ -750,12 +748,29 @@ public class DispatcherServlet extends FrameworkServlet { ...@@ -750,12 +748,29 @@ public class DispatcherServlet extends FrameworkServlet {
processedRequest = checkMultipart(request); processedRequest = checkMultipart(request);
// Determine handler for the current request. // Determine handler for the current request.
mappedHandler = getHandler(processedRequest, false); mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) { if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response); noHandlerFound(processedRequest, response);
return; 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. // Apply preHandle methods of registered interceptors.
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) { if (interceptors != null) {
...@@ -770,7 +785,6 @@ public class DispatcherServlet extends FrameworkServlet { ...@@ -770,7 +785,6 @@ public class DispatcherServlet extends FrameworkServlet {
} }
// Actually invoke the handler. // Actually invoke the handler.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation? // Do we need view name translation?
...@@ -834,41 +848,6 @@ public class DispatcherServlet extends FrameworkServlet { ...@@ -834,41 +848,6 @@ public class DispatcherServlet extends FrameworkServlet {
} }
} }
/**
* Override HttpServlet's <code>getLastModified</code> 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. * Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
* <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale, * <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
...@@ -882,7 +861,6 @@ public class DispatcherServlet extends FrameworkServlet { ...@@ -882,7 +861,6 @@ public class DispatcherServlet extends FrameworkServlet {
public Locale getLocale() { public Locale getLocale() {
return localeResolver.resolveLocale(request); return localeResolver.resolveLocale(request);
} }
@Override @Override
public String toString() { public String toString() {
return getLocale().toString(); return getLocale().toString();
...@@ -925,28 +903,16 @@ public class DispatcherServlet extends FrameworkServlet { ...@@ -925,28 +903,16 @@ public class DispatcherServlet extends FrameworkServlet {
/** /**
* Return the HandlerExecutionChain for this request. Try all handler mappings in order. * Return the HandlerExecutionChain for this request. Try all handler mappings in order.
* @param request current HTTP request * @param request current HTTP request
* @param cache whether to cache the HandlerExecutionChain in a request attribute
* @return the HandlerExceutionChain, or <code>null</code> if no handler could be found * @return the HandlerExceutionChain, or <code>null</code> if no handler could be found
*/ */
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception { protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
HandlerExecutionChain handler = (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
if (handler != null) {
if (!cache) {
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
}
return handler;
}
for (HandlerMapping hm : this.handlerMappings) { for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace( logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
} }
handler = hm.getHandler(request); HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) { if (handler != null) {
if (cache) {
request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
}
return handler; return handler;
} }
} }
......
...@@ -28,30 +28,33 @@ import org.springframework.util.Assert; ...@@ -28,30 +28,33 @@ import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestHandler; import org.springframework.web.HttpRequestHandler;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.support.WebContentGenerator; import org.springframework.web.servlet.support.WebContentGenerator;
/** /**
* {@link HttpRequestHandler} that serves static resources optimized for superior browser performance * {@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).
* *
* <p>The constructor takes a list of Spring {@link Resource} locations from which static resources are allowed * <p>The {@link #setLocations "locations" property takes a list of Spring {@link Resource} locations
* to be served by this handler. For a given request, the list of locations will be consulted in order for the * from which static resources are allowed to be served by this handler. For a given request, the
* presence of the requested resource, and the first found match will be written to the response, with {@code * list of locations will be consulted in order for the presence of the requested resource, and the
* Expires} and {@code Cache-Control} headers set for one year in the future. The handler also properly evaluates * first found match will be written to the response, with {@code Expires} and {@code Cache-Control}
* the {@code Last-Modified} header (if present) so that a {@code 304} status code will be returned as appropriate, * headers set as configured. The handler also properly evaluates the {@code Last-Modified} header
* avoiding unnecessary overhead for resources that are already cached by the client. The use of {@code Resource} * (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary
* locations allows resource requests to easily be mapped to locations other than the web application root. For * 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/", * 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. * allowing convenient packaging and serving of resources such as a JavaScript library from within jar files.
* *
* <p>To ensure that users with a primed browser cache get the latest changes to application-specific resources * <p>To ensure that users with a primed browser cache get the latest changes to application-specific
* upon deployment of new versions of the application, it is recommended that a version string is used in the URL * resources upon deployment of new versions of the application, it is recommended that a version string
* mapping pattern that selects this handler. Such patterns can be easily parameterized using Spring EL. See the * is used in the URL mapping pattern that selects this handler. Such patterns can be easily parameterized
* reference manual for further examples of this approach. * using Spring EL. See the reference manual for further examples of this approach.
* *
* <p>Rather than being directly configured as a bean, this handler will typically be configured through use of * <p>Rather than being directly configured as a bean, this handler will typically be configured
* the <code>&lt;mvc:resources/&gt;</code> Spring configuration tag. * through use of the <code>&lt;mvc:resources/&gt;</code> XML configuration element.
* *
* @author Keith Donald * @author Keith Donald
* @author Jeremy Grelle * @author Jeremy Grelle
...@@ -64,7 +67,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H ...@@ -64,7 +67,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
public ResourceHttpRequestHandler() { public ResourceHttpRequestHandler() {
super(METHOD_GET); super(METHOD_GET, METHOD_HEAD);
} }
/** /**
...@@ -98,13 +101,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H ...@@ -98,13 +101,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
response.sendError(HttpServletResponse.SC_NOT_FOUND); response.sendError(HttpServletResponse.SC_NOT_FOUND);
return; return;
} }
if (checkNotModified(resource, request, response)) { setHeaders(resource, response);
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified()) ||
METHOD_HEAD.equals(request.getMethod())) {
return; 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); String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (path == null) { if (path == null) {
throw new IllegalStateException("Required request attribute '" + throw new IllegalStateException("Required request attribute '" +
...@@ -128,22 +133,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H ...@@ -128,22 +133,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
return null; return null;
} }
private boolean checkNotModified(Resource resource,HttpServletRequest request, HttpServletResponse response) protected void setHeaders(Resource resource, HttpServletResponse response) throws IOException {
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 {
MediaType mediaType = getMediaType(resource); MediaType mediaType = getMediaType(resource);
if (mediaType != null) { if (mediaType != null) {
response.setContentType(mediaType.toString()); response.setContentType(mediaType.toString());
...@@ -153,7 +143,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H ...@@ -153,7 +143,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource); throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
} }
response.setContentLength((int) length); response.setContentLength((int) length);
FileCopyUtils.copy(resource.getInputStream(), response.getOutputStream());
} }
protected MediaType getMediaType(Resource resource) { protected MediaType getMediaType(Resource resource) {
...@@ -161,4 +150,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H ...@@ -161,4 +150,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
return (StringUtils.hasText(mimeType) ? MediaType.parseMediaType(mimeType) : null); return (StringUtils.hasText(mimeType) ? MediaType.parseMediaType(mimeType) : null);
} }
protected void writeContent(Resource resource, HttpServletResponse response) throws IOException {
FileCopyUtils.copy(resource.getInputStream(), response.getOutputStream());
}
} }
...@@ -186,7 +186,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ ...@@ -186,7 +186,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ
long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE); long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE);
this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000)); this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));
if (this.response != null) { if (this.response != null) {
if (this.notModified) { if (this.notModified && "GET".equals(getRequest().getMethod())) {
this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
} }
else { else {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -35,6 +35,7 @@ public interface WebRequest extends RequestAttributes { ...@@ -35,6 +35,7 @@ public interface WebRequest extends RequestAttributes {
/** /**
* Return the request header of the given name, or <code>null</code> if none. * Return the request header of the given name, or <code>null</code> if none.
* <p>Retrieves the first header value in case of a multi-value header. * <p>Retrieves the first header value in case of a multi-value header.
* @since 3.0
* @see javax.servlet.http.HttpServletRequest#getHeader(String) * @see javax.servlet.http.HttpServletRequest#getHeader(String)
*/ */
String getHeader(String headerName); String getHeader(String headerName);
...@@ -43,14 +44,15 @@ public interface WebRequest extends RequestAttributes { ...@@ -43,14 +44,15 @@ public interface WebRequest extends RequestAttributes {
* Return the request header values for the given header name, * Return the request header values for the given header name,
* or <code>null</code> if none. * or <code>null</code> if none.
* <p>A single-value header will be exposed as an array with a single element. * <p>A single-value header will be exposed as an array with a single element.
* @since 3.0
* @see javax.servlet.http.HttpServletRequest#getHeaders(String) * @see javax.servlet.http.HttpServletRequest#getHeaders(String)
*/ */
String[] getHeaderValues(String headerName); String[] getHeaderValues(String headerName);
/** /**
* Return a Iterator over request header names. * Return a Iterator over request header names.
* @see javax.servlet.http.HttpServletRequest#getHeaderNames()
* @since 3.0 * @since 3.0
* @see javax.servlet.http.HttpServletRequest#getHeaderNames()
*/ */
Iterator<String> getHeaderNames(); Iterator<String> getHeaderNames();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册