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.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 {
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
||||||
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 {
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
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><mvc:resources/></code> Spring configuration tag.
|
* through use of the <code><mvc:resources/></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
|
||||||
|
|
||||||
|
|
||||||
public ResourceHttpRequestHandler() {
|
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);
|
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* 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 {
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
* 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();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue