提交 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;
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 <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.
* <p>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 <code>null</code> 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;
}
}
......
......@@ -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).
*
* <p>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
* <p>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.
*
* <p>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.
* <p>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.
*
* <p>Rather than being directly configured as a bean, this handler will typically be configured through use of
* the <code>&lt;mvc:resources/&gt;</code> Spring configuration tag.
* <p>Rather than being directly configured as a bean, this handler will typically be configured
* through use of the <code>&lt;mvc:resources/&gt;</code> 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());
}
}
......@@ -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 {
......
/*
* 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 <code>null</code> if none.
* <p>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 <code>null</code> if none.
* <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)
*/
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<String> getHeaderNames();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册