revised DispatcherServlet's last-modified handling to properly work with scoped controllers; added HEAD support to ResourceHttpRequestHandler
This commit is contained in:
parent
29b12adbaa
commit
f6c07b371f
|
|
@ -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><mvc:resources/></code> Spring configuration tag.
|
||||
* <p>Rather than being directly configured as a bean, this handler will typically be configured
|
||||
* through use of the <code><mvc:resources/></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 {
|
||||
|
|
|
|||
|
|
@ -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 <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();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue