From 60a69bd6536656af560ab71df4a14e50acc92320 Mon Sep 17 00:00:00 2001 From: Jeremy Grelle Date: Tue, 27 Jul 2010 04:45:43 +0000 Subject: [PATCH] SPR-7116 work in progress. ResourceHttpRequestHandler is now functionally equivalent to the Spring JS ResourceServlet, with the exception of the resource concatenation feature (which will be deferred for reconsideration in 3.1). --- .../config/ResourcesBeanDefinitionParser.java | 7 +- .../DefaultServletHttpRequestHandler.java | 100 ++++++ .../resources/ResourceHttpRequestHandler.java | 324 +++++++++++++----- .../ResourceHttpRequestHandlerTests.java | 58 ++-- .../resources/testalternatepath/bar.css | 1 + .../resources/testalternatepath/baz.css | 1 + .../resources/testalternatepath/foo.css | 1 + .../resources/testalternatepath/js/bar.js | 1 + .../resources/testalternatepath/js/baz.js | 1 + .../resources/testalternatepath/js/foo.js | 1 + 10 files changed, 366 insertions(+), 129 deletions(-) create mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java create mode 100644 org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/bar.css create mode 100644 org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/baz.css create mode 100644 org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/foo.css create mode 100644 org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/bar.js create mode 100644 org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/baz.js create mode 100644 org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/foo.js diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java index 70e0fa8190..8ebed5f239 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java @@ -1,9 +1,11 @@ package org.springframework.web.servlet.config; +import java.util.List; import java.util.Map; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; @@ -34,11 +36,12 @@ public class ResourcesBeanDefinitionParser implements BeanDefinitionParser { registerHandlerAdapterIfNecessary(parserContext, source); BeanDefinition handlerMappingDef = registerHandlerMappingIfNecessary(parserContext, source); - String resourceDirectory = "/resources/"; + List resourcePaths = new ManagedList(); + resourcePaths.add("/"); RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class); resourceHandlerDef.setSource(source); resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, resourceDirectory); + resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, resourcePaths); Map urlMap = getUrlMap(handlerMappingDef); String resourceRequestPath = "/resources/**"; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java new file mode 100644 index 0000000000..6e753f36f6 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/DefaultServletHttpRequestHandler.java @@ -0,0 +1,100 @@ +package org.springframework.web.servlet.resources; + +import java.io.IOException; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.HttpRequestHandler; +import org.springframework.web.context.ServletContextAware; + +/** + * An {@link HttpRequestHandler} for serving static files using the Servlet container's "default" Servlet. + * + *

This handler is intended to be used with a "/*" mapping when the {@link DispatcherServlet} is mapped to "/", thus + * overriding the Servlet container's default handling of static resources. The mapping to this handler should generally + * be ordered as the last in the chain so that it will only execute when no other more specific mappings (i.e., to controllers) + * can be matched. + * + *

Requests are handled by forwarding through the {@link RequestDispatcher} obtained via the name specified through the + * {@code fileServletName} property. In most cases, the {@code fileServletName} does not need to be set explicitly, as the + * handler checks at initialization time for the presence of the default Servlet of one of the known containers. However, if + * running in a container where the default Servlet's name is not known, or where it has been customized via configuration, the + * {@code fileServletName} will need to be set explicitly. + * + * @author Jeremy Grelle + * @since 3.0.4 + */ +public class DefaultServletHttpRequestHandler implements InitializingBean, HttpRequestHandler, ServletContextAware { + + /** + * Default Servlet name used by Tomcat, Jetty, JBoss, and Glassfish + */ + private static final String COMMON_DEFAULT_SERVLET_NAME = "default"; + + /** + * Default Servlet name used by Resin + */ + private static final String RESIN_DEFAULT_SERVLET_NAME = "resin-file"; + + /** + * Default Servlet name used by WebLogic + */ + private static final String WEBLOGIC_DEFAULT_SERVLET_NAME = "FileServlet"; + + /** + * Default Servlet name used by WebSphere + */ + private static final String WEBSPHERE_DEFAULT_SERVLET_NAME = "SimpleFileServlet"; + + private ServletContext servletContext; + + private String fileServletName; + + /** + * If the {@code filedServletName} property has not been explicitly set, attempts to locate the default Servlet using the + * known common container-specific names. + */ + public void afterPropertiesSet() throws Exception { + if (!StringUtils.hasText(this.fileServletName)) { + if (this.servletContext.getNamedDispatcher(COMMON_DEFAULT_SERVLET_NAME) != null) { + this.fileServletName = COMMON_DEFAULT_SERVLET_NAME; + } else if (this.servletContext.getNamedDispatcher(RESIN_DEFAULT_SERVLET_NAME) != null) { + this.fileServletName = RESIN_DEFAULT_SERVLET_NAME; + } else if (this.servletContext.getNamedDispatcher(WEBLOGIC_DEFAULT_SERVLET_NAME) != null) { + this.fileServletName = WEBLOGIC_DEFAULT_SERVLET_NAME; + } else if (this.servletContext.getNamedDispatcher(WEBSPHERE_DEFAULT_SERVLET_NAME) != null) { + this.fileServletName = WEBSPHERE_DEFAULT_SERVLET_NAME; + } + Assert.hasText(this.fileServletName, "Unable to locate the default servlet for serving static content. Please set the 'fileServletName' property explicitly."); + } + } + + public void handleRequest(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.fileServletName); + Assert.notNull(rd, "A RequestDispatcher could not be located for the servlet name '"+this.fileServletName+"'"); + rd.forward(request, response); + } + + /** + * Set the name of the default Servlet to be forwarded to for static resource requests. + * @param fileServletName The name of the Servlet to use for static resources. + */ + public void setDefaultServletName(String fileServletName) { + this.fileServletName = fileServletName; + } + + /** + * {@inheritDoc} + */ + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java index f966b41b0d..4383b9b73a 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandler.java @@ -1,17 +1,23 @@ package org.springframework.web.servlet.resources; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; import java.util.List; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.zip.GZIPOutputStream; import javax.activation.FileTypeMap; import javax.activation.MimetypesFileTypeMap; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -26,101 +32,112 @@ import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestHandler; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; +import org.springframework.web.servlet.mvc.LastModified; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; -public class ResourceHttpRequestHandler implements HttpRequestHandler { +/** + * {@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 + * and gzip compressing the resources if supported by the client. + * + *

TODO - expand the docs further + * + * @author Keith Donald + * @author Jeremy Grelle + * @since 3.0.4 + */ +public class ResourceHttpRequestHandler implements HttpRequestHandler, LastModified { private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class); - private Resource resourceDirectory; + private final List resourcePaths; private int maxAge = 31556926; private FileMediaTypeMap fileMediaTypeMap = new DefaultFileMediaTypeMap(); + + private boolean gzipEnabled = true; + + private int minGzipSize = 150; + + private int maxGzipSize = 500000; - public ResourceHttpRequestHandler(Resource resourceDirectory) { - Assert.notNull(resourceDirectory, "The resource directory may not be null"); - this.resourceDirectory = resourceDirectory; + public ResourceHttpRequestHandler(List resourcePaths) { + Assert.notNull(resourcePaths, "Resource paths must not be null"); + this.resourcePaths = resourcePaths; } - public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (!"GET".equals(request.getMethod())) { throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] {"GET"}, "ResourceHttpRequestHandler only supports GET requests"); } - List resources = getResources(request); - if (resources.size() == 0) { + URLResource resource = getResource(request); + if (resource == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } - boolean notModified = checkNotModified(resources, request, response); - if (notModified) { - return; + prepareResponse(resource, response); + writeResponse(resource, request, response); + } + + public long getLastModified(HttpServletRequest request) { + try { + Resource resource = getResource(request); + if (resource == null) { + return -1; + } + return resource.lastModified(); + } catch (Exception e) { + return -1; } - prepareResponse(resources, response); - writeResponse(resources, response); + } + + public void setGzipEnabled(boolean gzipEnabled) { + this.gzipEnabled = gzipEnabled; } - private List getResources(HttpServletRequest request) throws ServletException, IOException { + public void setMinGzipSize(int minGzipSize) { + this.minGzipSize = minGzipSize; + } + + public void setMaxGzipSize(int maxGzipSize) { + this.maxGzipSize = maxGzipSize; + } + + private URLResource getResource(HttpServletRequest request) { String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); if (path == null) { throw new IllegalStateException("Required request attribute '" + HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set"); } - String[] resourceElements = path.split(","); - if (resourceElements.length == 1 && resourceElements[0].length() == 0) { - throw new NoSuchRequestHandlingMethodException(request); - } - List resources = new ArrayList(); - String[] dirAndFilename = splitDirectoryAndFilename(resourceElements[0]); - String dir = dirAndFilename[0]; - String filename = dirAndFilename[1]; - Resource parent = dir != null ? this.resourceDirectory.createRelative(dir) : this.resourceDirectory; - addResource(parent, filename, resources); - if (resourceElements.length > 1) { - for (int i = 1; i < resourceElements.length; i++) { - addResource(parent, resourceElements[i], resources); - } - } - return resources; - } - - private boolean checkNotModified(List resources,HttpServletRequest request, HttpServletResponse response) throws IOException { - long lastModifiedTimestamp = -1; - long ifModifiedSince = request.getDateHeader("If-Modified-Since"); - for (Resource resource : resources) { - long resourceLastModified = resource.lastModified(); - if (resourceLastModified > lastModifiedTimestamp) { - lastModifiedTimestamp = resourceLastModified; - } + if (path.contains("WEB-INF") || path.contains("META-INF")) { + return null; } - boolean notModified = ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000); - if (notModified) { - response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - } else { - response.setDateHeader("Last-Modified", lastModifiedTimestamp); - } - return notModified; - } - - private void prepareResponse(List resources, HttpServletResponse response) { - MediaType mediaType = null; - int contentLength = 0; - for (Resource resource : resources) { + for (Resource resourcePath : this.resourcePaths) { + Resource resource; try { - File file = resource.getFile(); - if (mediaType == null) { - mediaType = fileMediaTypeMap.getMediaType(file.getName()); + resource = resourcePath.createRelative(path); + if (isValidFile(resource)) { + return new URLResource(resource); } - contentLength += file.length(); } catch (IOException e) { - + //Resource not found + return null; } } + return null; + } + + private void prepareResponse(URLResource resource, HttpServletResponse response) throws IOException { + MediaType mediaType = null; + if (mediaType == null) { + mediaType = fileMediaTypeMap.getMediaType(resource.getFilename()); + } if (mediaType != null) { response.setContentType(mediaType.toString()); } - response.setContentLength(contentLength); + response.setContentLength(resource.getContentLength()); + response.setDateHeader("Last-Modified", resource.lastModified()); if (this.maxAge > 0) { // HTTP 1.0 header response.setDateHeader("Expires", System.currentTimeMillis() + this.maxAge * 1000L); @@ -129,53 +146,43 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler { } } - private void writeResponse(List resources, HttpServletResponse response) throws IOException { - for (Resource resource : resources) { - InputStream in = null; + private void writeResponse(URLResource resource, HttpServletRequest request, HttpServletResponse response) throws IOException { + OutputStream out = selectOutputStream(resource, request, response); + try { + InputStream in = resource.getInputStream(); try { - in = resource.getInputStream(); + byte[] buffer = new byte[1024]; int bytesRead = -1; - byte[] buffer = new byte[4096]; while ((bytesRead = in.read(buffer)) != -1) { - response.getOutputStream().write(buffer, 0, bytesRead); + out.write(buffer, 0, bytesRead); } - } catch (IOException e) { - } finally { if (in != null) { - try { - in.close(); - } catch (IOException e) { - - } + in.close(); } } + } finally { + if (out != null) { + out.close(); + } } } - private String[] splitDirectoryAndFilename(String firstResourceElement) { - int index = firstResourceElement.lastIndexOf("/"); - String dir; - if (index == -1) { - dir = null; + private OutputStream selectOutputStream(URLResource resource, HttpServletRequest request, + HttpServletResponse response) throws IOException { + String acceptEncoding = request.getHeader("Accept-Encoding"); + boolean isGzipEligible = resource.getContentLength() >= this.minGzipSize && resource.getContentLength() <= this.maxGzipSize; + if (this.gzipEnabled && isGzipEligible && StringUtils.hasText(acceptEncoding) + && acceptEncoding.indexOf("gzip") > -1 + && response.getContentType().startsWith("text/")){ + return new GZIPResponseStream(response); } else { - dir = firstResourceElement.substring(0, index + 1); - } - String filename = firstResourceElement.substring(index + 1, firstResourceElement.length()); - return new String[] { dir, filename }; - } - - private void addResource(Resource parent, String name, List resources) throws IOException { - if (name.length() > 0) { - Resource resource = parent.createRelative(name); - if (isAllowed(resource)) { - resources.add(resource); - } + return response.getOutputStream(); } } - private boolean isAllowed(Resource resource) throws IOException { - return resource.exists() && resource.getFile().isFile(); + private boolean isValidFile(Resource resource) throws IOException { + return resource.exists() && StringUtils.hasText(resource.getFilename()); } // TODO promote to top-level and make reusable @@ -259,5 +266,136 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler { } } } + + private class GZIPResponseStream extends ServletOutputStream { + + private ByteArrayOutputStream byteStream = null; + + private GZIPOutputStream gzipStream = null; + + private boolean closed = false; + + private HttpServletResponse response = null; + + private ServletOutputStream servletStream = null; + + public GZIPResponseStream(HttpServletResponse response) throws IOException { + super(); + closed = false; + this.response = response; + this.servletStream = response.getOutputStream(); + byteStream = new ByteArrayOutputStream(); + gzipStream = new GZIPOutputStream(byteStream); + } + + public void close() throws IOException { + if (closed) { + throw new IOException("This output stream has already been closed"); + } + gzipStream.finish(); + byte[] bytes = byteStream.toByteArray(); + response.setContentLength(bytes.length); + response.addHeader("Content-Encoding", "gzip"); + servletStream.write(bytes); + servletStream.flush(); + servletStream.close(); + closed = true; + } + + public void flush() throws IOException { + if (closed) { + throw new IOException("Cannot flush a closed output stream"); + } + gzipStream.flush(); + } + + public void write(int b) throws IOException { + if (closed) { + throw new IOException("Cannot write to a closed output stream"); + } + gzipStream.write((byte) b); + } + + public void write(byte b[]) throws IOException { + write(b, 0, b.length); + } + + public void write(byte b[], int off, int len) throws IOException { + if (closed) { + throw new IOException("Cannot write to a closed output stream"); + } + gzipStream.write(b, off, len); + } + } + + private static class URLResource implements Resource { + + private final Resource wrapped; + + private final long lastModified; + + private final int contentLength; + + public URLResource(Resource wrapped) throws IOException { + this.wrapped = wrapped; + URLConnection connection = null; + try { + connection = wrapped.getURL().openConnection(); + this.lastModified = connection.getLastModified(); + this.contentLength = connection.getContentLength(); + } finally { + if (connection != null) { + connection.getInputStream().close(); + } + } + } + + public int getContentLength() { + return this.contentLength; + } + + public long lastModified() throws IOException { + return this.lastModified; + } + + public Resource createRelative(String relativePath) throws IOException { + return wrapped.createRelative(relativePath); + } + + public boolean exists() { + return wrapped.exists(); + } + + public String getDescription() { + return wrapped.getDescription(); + } + + public File getFile() throws IOException { + return wrapped.getFile(); + } + public String getFilename() { + return wrapped.getFilename(); + } + + public URI getURI() throws IOException { + return wrapped.getURI(); + } + + public URL getURL() throws IOException { + return wrapped.getURL(); + } + + public boolean isOpen() { + return wrapped.isOpen(); + } + + public boolean isReadable() { + return wrapped.isReadable(); + } + + public InputStream getInputStream() throws IOException { + return wrapped.getInputStream(); + } + } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java index bee26b8856..bda8cfc0c9 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/resources/ResourceHttpRequestHandlerTests.java @@ -3,18 +3,19 @@ package org.springframework.web.servlet.resources; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; + import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; -import org.springframework.web.servlet.resources.ResourceHttpRequestHandler; - public class ResourceHttpRequestHandlerTests { @@ -22,7 +23,10 @@ public class ResourceHttpRequestHandlerTests { @Before public void setUp() { - handler = new ResourceHttpRequestHandler(new ClassPathResource("test/", getClass())); + List resourcePaths = new ArrayList(); + resourcePaths.add(new ClassPathResource("test/", getClass())); + resourcePaths.add(new ClassPathResource("testalternatepath/", getClass())); + handler = new ResourceHttpRequestHandler(resourcePaths); } @Test @@ -40,32 +44,21 @@ public class ResourceHttpRequestHandlerTests { assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.css", getClass()).getFile().lastModified()); assertEquals("h1 { color:red; }", response.getContentAsString()); } - + @Test - public void getResourceBundle() throws Exception { + public void getResourceFromAlternatePath() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.css,bar.css"); + request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/baz.css"); request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); handler.handleRequest(request, response); assertEquals("text/css", response.getContentType()); - assertEquals(36, response.getContentLength()); + assertEquals(17, response.getContentLength()); assertTrue(((Long)response.getHeader("Expires")) > System.currentTimeMillis() + (31556926 * 1000) - 10000); assertEquals("max-age=31556926", response.getHeader("Cache-Control")); assertTrue(response.containsHeader("Last-Modified")); - assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/bar.css", getClass()).getFile().lastModified()); - assertEquals("h1 { color:red; }h2 { color:white; }", response.getContentAsString()); - } - - @Test - public void getResourceBundleDifferentTypes() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.css,/js/bar.js"); - request.setMethod("GET"); - MockHttpServletResponse response = new MockHttpServletResponse(); - handler.handleRequest(request, response); - assertEquals("text/css", response.getContentType()); - assertEquals("h1 { color:red; }function foo() { console.log(\"hello bar\"); }", response.getContentAsString()); + assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("testalternatepath/baz.css", getClass()).getFile().lastModified()); + assertEquals("h1 { color:red; }", response.getContentAsString()); } @Test @@ -75,31 +68,28 @@ public class ResourceHttpRequestHandlerTests { request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); handler.handleRequest(request, response); - System.out.println(response.getContentAsString()); assertEquals("text/javascript", response.getContentType()); assertEquals("function foo() { console.log(\"hello world\"); }", response.getContentAsString()); } @Test - public void getResourceBundleDifferentTypesIncludingDirectory() throws Exception { + public void getResourceFromSubDirectoryOfAlternatePath() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.css,/js,/js/foo.js"); + request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/js/baz.js"); request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); handler.handleRequest(request, response); - assertEquals("text/css", response.getContentType()); - assertEquals("h1 { color:red; }function foo() { console.log(\"hello world\"); }", response.getContentAsString()); + assertEquals("text/javascript", response.getContentType()); + assertEquals("function foo() { console.log(\"hello world\"); }", response.getContentAsString()); } @Test - public void notModified() throws Exception { + public void lastModified() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.css"); - request.addHeader("If-Modified-Since", new ClassPathResource("test/foo.css", getClass()).getFile().lastModified()); - request.setMethod("GET"); - MockHttpServletResponse response = new MockHttpServletResponse(); - handler.handleRequest(request, response); - assertEquals(HttpServletResponse.SC_NOT_MODIFIED, response.getStatus()); + long expected = new ClassPathResource("test/foo.css", getClass()).lastModified(); + long lastModified = handler.getLastModified(request); + assertEquals(expected, lastModified); } @Test @@ -124,13 +114,14 @@ public class ResourceHttpRequestHandlerTests { assertEquals(404, response.getStatus()); } - @Test(expected=NoSuchRequestHandlingMethodException.class) + @Test public void missingResourcePath() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, ""); request.setMethod("GET"); MockHttpServletResponse response = new MockHttpServletResponse(); handler.handleRequest(request, response); + assertEquals(404, response.getStatus()); } @Test(expected=IllegalStateException.class) @@ -159,5 +150,4 @@ public class ResourceHttpRequestHandlerTests { handler.handleRequest(request, response); assertEquals(404, response.getStatus()); } - } diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/bar.css b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/bar.css new file mode 100644 index 0000000000..f45d4b694d --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/bar.css @@ -0,0 +1 @@ +fail \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/baz.css b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/baz.css new file mode 100644 index 0000000000..e2f0b1c742 --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/baz.css @@ -0,0 +1 @@ +h1 { color:red; } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/foo.css b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/foo.css new file mode 100644 index 0000000000..f45d4b694d --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/foo.css @@ -0,0 +1 @@ +fail \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/bar.js b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/bar.js new file mode 100644 index 0000000000..f45d4b694d --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/bar.js @@ -0,0 +1 @@ +fail \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/baz.js b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/baz.js new file mode 100644 index 0000000000..0a694588c5 --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/baz.js @@ -0,0 +1 @@ +function foo() { console.log("hello world"); } \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/foo.js b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/foo.js new file mode 100644 index 0000000000..f45d4b694d --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/resources/testalternatepath/js/foo.js @@ -0,0 +1 @@ +fail \ No newline at end of file