From f6c07b371f20ff8dc38660cbbd48c874681a2c67 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 12 Aug 2010 22:54:24 +0000 Subject: [PATCH] revised DispatcherServlet's last-modified handling to properly work with scoped controllers; added HEAD support to ResourceHttpRequestHandler --- .../web/servlet/DispatcherServlet.java | 76 +++++-------------- .../resource/ResourceHttpRequestHandler.java | 63 +++++++-------- .../context/request/ServletWebRequest.java | 2 +- .../web/context/request/WebRequest.java | 6 +- 4 files changed, 54 insertions(+), 93 deletions(-) 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 67d38c2750..79728d51d5 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 fdf47d0a05..adb4809fb7 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 7ccb5d7096..b59909052b 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 4690b85009..6121993594 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 getHeaderNames(); -- GitLab