SPR-7116 - Simplified (removed gzip and white-listing) and completed XML namespace config.

This commit is contained in:
Jeremy Grelle 2010-07-31 01:09:40 +00:00
parent 061af2f25f
commit 367048c5d1
14 changed files with 355 additions and 268 deletions

View File

@ -0,0 +1,39 @@
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.w3c.dom.Element;
/**
* Abstract base class for {@link BeanDefinitonParser}s that register an {@link HttpRequestHandler}.
*
* @author Jeremy Grelle
* @since 3.0.4
*/
public abstract class AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser{
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
registerHandlerAdapterIfNecessary(parserContext, source);
doParse(element, parserContext);
return null;
}
public abstract void doParse(Element element, ParserContext parserContext);
private void registerHandlerAdapterIfNecessary(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
}
}

View File

@ -0,0 +1,56 @@
package org.springframework.web.servlet.config;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resources.DefaultServletHttpRequestHandler;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} that parses a {@code default-servlet-handler} element to
* register a {@link DefaultServletHttpRequestHandler}. Will also register a
* {@link SimpleUrlHandlerMapping} for mapping resource requests, and a
* {@link HttpRequestHandlerAdapter} if necessary.
*
* @author Jeremy Grelle
* @since 3.0.4
*/
public class DefaultServletHandlerBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser {
@Override
public void doParse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
String defaultServletName = element.getAttribute("default-servlet-name");
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
defaultServletHandlerDef.setSource(source);
defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (StringUtils.hasText(defaultServletName)) {
defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName);
}
String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));
Map<String, String> urlMap = new ManagedMap<String, String>();
urlMap.put("/**", defaultServletHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));
}
}

View File

@ -23,12 +23,14 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
* {@link NamespaceHandler} for Spring MVC configuration namespace. * {@link NamespaceHandler} for Spring MVC configuration namespace.
* *
* @author Keith Donald * @author Keith Donald
* @author Jeremy Grelle
* @since 3.0 * @since 3.0
*/ */
public class MvcNamespaceHandler extends NamespaceHandlerSupport { public class MvcNamespaceHandler extends NamespaceHandlerSupport {
public void init() { public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser()); registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser()); registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());

View File

@ -10,6 +10,8 @@ import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resources.ResourceHttpRequestHandler; import org.springframework.web.servlet.resources.ResourceHttpRequestHandler;
@ -18,74 +20,66 @@ import org.w3c.dom.Element;
/** /**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a * {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
* {@code resources} element to register a {@link ResourceHttpRequestHandler}. * {@code resources} element to register a {@link ResourceHttpRequestHandler}.
* Will also register a {@link SimpleUrlHandlerMapping} for mapping resource requests, if necessary. * Will also register a {@link SimpleUrlHandlerMapping} for mapping resource requests,
* Will also register a {@link HttpRequestHandlerAdapter} if necessary. * and a {@link HttpRequestHandlerAdapter} if necessary.
* *
* @author Keith Donald * @author Keith Donald
* @author Jeremy Grelle
* @since 3.0.4 * @since 3.0.4
*/ */
public class ResourcesBeanDefinitionParser implements BeanDefinitionParser { public class ResourcesBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser {
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"; @Override
public void doParse(Element element, ParserContext parserContext) {
private static final String HANDLER_MAPPING_BEAN_NAME = "org.springframework.web.servlet.config.resourcesHandlerMapping";
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element); Object source = parserContext.extractSource(element);
registerResourceMappings(parserContext, element, source);
}
registerHandlerAdapterIfNecessary(parserContext, source); private void registerResourceMappings(ParserContext parserContext, Element element, Object source) {
BeanDefinition handlerMappingDef = registerHandlerMappingIfNecessary(parserContext, source); String resourceHandlerName = registerResourceHandler(parserContext, element, source);
if (!StringUtils.hasText(resourceHandlerName)) {
return;
}
List<String> resourcePaths = new ManagedList<String>(); Map<String, String> urlMap = new ManagedMap<String, String>();
resourcePaths.add("/"); String resourceRequestPath = element.getAttribute("mapping");
if (!StringUtils.hasText(resourceRequestPath)) {
parserContext.getReaderContext().error("The 'mapping' attribute is required.", parserContext.extractSource(element));
return;
}
urlMap.put(resourceRequestPath, resourceHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String mappingOrder = element.getAttribute("mapping-order");
handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(mappingOrder) ? mappingOrder : Ordered.LOWEST_PRECEDENCE - 1);
String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
}
private String registerResourceHandler(ParserContext parserContext, Element element, Object source) {
String locationAttr = element.getAttribute("location");
if (!StringUtils.hasText(locationAttr)) {
parserContext.getReaderContext().error("The 'location' attribute is required.", parserContext.extractSource(element));
return "";
}
String[] locationPatterns = locationAttr.split(",\\s*");
List<String> locations = new ManagedList<String>();
for (String location : locationPatterns) {
locations.add(location);
}
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class); RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source); resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, resourcePaths); resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, locations);
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
Map<String, BeanDefinition> urlMap = getUrlMap(handlerMappingDef); parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
String resourceRequestPath = "/resources/**"; parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
urlMap.put(resourceRequestPath, resourceHandlerDef); return beanName;
return null;
} }
private void registerHandlerAdapterIfNecessary(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
}
private BeanDefinition registerHandlerMappingIfNecessary(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_BEAN_NAME)) {
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.getPropertyValues().add("order", "2");
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
return handlerMappingDef;
}
else {
return parserContext.getRegistry().getBeanDefinition(HANDLER_MAPPING_BEAN_NAME);
}
}
@SuppressWarnings("unchecked")
private Map<String, BeanDefinition> getUrlMap(BeanDefinition handlerMappingDef) {
Map<String, BeanDefinition> urlMap;
if (handlerMappingDef.getPropertyValues().contains("urlMap")) {
urlMap = (Map<String, BeanDefinition>) handlerMappingDef.getPropertyValues().getPropertyValue("urlMap").getValue();
}
else {
urlMap = new ManagedMap<String, BeanDefinition>();
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
}
return urlMap;
}
} }

View File

@ -23,10 +23,10 @@ import org.springframework.web.context.ServletContextAware;
* can be matched. * can be matched.
* *
* <p>Requests are handled by forwarding through the {@link RequestDispatcher} obtained via the name specified through the * <p>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 * {@code defaultServletName} property. In most cases, the {@code defaultServletName} 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 * 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 * 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. * {@code defaultServletName} will need to be set explicitly.
* *
* @author Jeremy Grelle * @author Jeremy Grelle
* @since 3.0.4 * @since 3.0.4
@ -55,40 +55,40 @@ public class DefaultServletHttpRequestHandler implements InitializingBean, HttpR
private ServletContext servletContext; private ServletContext servletContext;
private String fileServletName; private String defaultServletName;
/** /**
* If the {@code filedServletName} property has not been explicitly set, attempts to locate the default Servlet using the * If the {@code filedServletName} property has not been explicitly set, attempts to locate the default Servlet using the
* known common container-specific names. * known common container-specific names.
*/ */
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if (!StringUtils.hasText(this.fileServletName)) { if (!StringUtils.hasText(this.defaultServletName)) {
if (this.servletContext.getNamedDispatcher(COMMON_DEFAULT_SERVLET_NAME) != null) { if (this.servletContext.getNamedDispatcher(COMMON_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = COMMON_DEFAULT_SERVLET_NAME; this.defaultServletName = COMMON_DEFAULT_SERVLET_NAME;
} else if (this.servletContext.getNamedDispatcher(RESIN_DEFAULT_SERVLET_NAME) != null) { } else if (this.servletContext.getNamedDispatcher(RESIN_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = RESIN_DEFAULT_SERVLET_NAME; this.defaultServletName = RESIN_DEFAULT_SERVLET_NAME;
} else if (this.servletContext.getNamedDispatcher(WEBLOGIC_DEFAULT_SERVLET_NAME) != null) { } else if (this.servletContext.getNamedDispatcher(WEBLOGIC_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = WEBLOGIC_DEFAULT_SERVLET_NAME; this.defaultServletName = WEBLOGIC_DEFAULT_SERVLET_NAME;
} else if (this.servletContext.getNamedDispatcher(WEBSPHERE_DEFAULT_SERVLET_NAME) != null) { } else if (this.servletContext.getNamedDispatcher(WEBSPHERE_DEFAULT_SERVLET_NAME) != null) {
this.fileServletName = WEBSPHERE_DEFAULT_SERVLET_NAME; this.defaultServletName = WEBSPHERE_DEFAULT_SERVLET_NAME;
} }
Assert.hasText(this.fileServletName, "Unable to locate the default servlet for serving static content. Please set the 'fileServletName' property explicitly."); Assert.hasText(this.defaultServletName, "Unable to locate the default servlet for serving static content. Please set the 'defaultServletName' property explicitly.");
} }
} }
public void handleRequest(HttpServletRequest request, public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException { HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.fileServletName); RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
Assert.notNull(rd, "A RequestDispatcher could not be located for the servlet name '"+this.fileServletName+"'"); Assert.notNull(rd, "A RequestDispatcher could not be located for the servlet name '"+this.defaultServletName+"'");
rd.forward(request, response); rd.forward(request, response);
} }
/** /**
* Set the name of the default Servlet to be forwarded to for static resource requests. * 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. * @param defaultServletName The name of the Servlet to use for static resources.
*/ */
public void setDefaultServletName(String fileServletName) { public void setDefaultServletName(String defaultServletName) {
this.fileServletName = fileServletName; this.defaultServletName = defaultServletName;
} }
/** /**

View File

@ -1,6 +1,5 @@
package org.springframework.web.servlet.resources; package org.springframework.web.servlet.resources;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -8,18 +7,15 @@ import java.io.OutputStream;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.zip.GZIPOutputStream;
import javax.activation.FileTypeMap; import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap; import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -39,8 +35,7 @@ import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
/** /**
* {@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 adding far future cache expiration headers.
* and gzip compressing the resources if supported by the client.
* *
* <p>TODO - expand the docs further * <p>TODO - expand the docs further
* *
@ -54,39 +49,14 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
private final List<Resource> resourcePaths; private final List<Resource> resourcePaths;
private int maxAge = 31556926; private final int maxAge = 31556926;
private static final String defaultMediaTypes = "image/*,text/css,text/javascript,text/html";
private List<MediaType> allowedMediaTypes = new ArrayList<MediaType>();
private FileMediaTypeMap fileMediaTypeMap; private FileMediaTypeMap fileMediaTypeMap;
private boolean gzipEnabled = true;
private int minGzipSize = 150;
private int maxGzipSize = 500000;
public ResourceHttpRequestHandler(List<Resource> resourcePaths) { public ResourceHttpRequestHandler(List<Resource> resourcePaths) {
this(resourcePaths, defaultMediaTypes);
}
public ResourceHttpRequestHandler(List<Resource> resourcePaths, String allowedMediaTypes) {
this(resourcePaths, allowedMediaTypes, false);
}
public ResourceHttpRequestHandler(List<Resource> resourcePaths, String allowedMediaTypes, boolean overrideDefaultMediaTypes) {
Assert.notNull(resourcePaths, "Resource paths must not be null"); Assert.notNull(resourcePaths, "Resource paths must not be null");
validateResourcePaths(resourcePaths); validateResourcePaths(resourcePaths);
this.resourcePaths = resourcePaths; this.resourcePaths = resourcePaths;
if (StringUtils.hasText(allowedMediaTypes)) {
this.allowedMediaTypes.addAll(MediaType.parseMediaTypes(allowedMediaTypes));
}
if (!overrideDefaultMediaTypes) {
this.allowedMediaTypes.addAll(MediaType.parseMediaTypes(defaultMediaTypes));
}
MediaType.sortBySpecificity(this.allowedMediaTypes);
} }
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
@ -95,7 +65,7 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
new String[] {"GET"}, "ResourceHttpRequestHandler only supports GET requests"); new String[] {"GET"}, "ResourceHttpRequestHandler only supports GET requests");
} }
URLResource resource = getResource(request); URLResource resource = getResource(request);
if (resource == null || !isResourceAllowed(resource)) { if (resource == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND); response.sendError(HttpServletResponse.SC_NOT_FOUND);
return; return;
} }
@ -106,18 +76,6 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
writeResponse(resource, request, response); writeResponse(resource, request, response);
} }
public void setGzipEnabled(boolean gzipEnabled) {
this.gzipEnabled = gzipEnabled;
}
public void setMinGzipSize(int minGzipSize) {
this.minGzipSize = minGzipSize;
}
public void setMaxGzipSize(int maxGzipSize) {
this.maxGzipSize = maxGzipSize;
}
public void setServletContext(ServletContext servletContext) { public void setServletContext(ServletContext servletContext) {
this.fileMediaTypeMap = new DefaultFileMediaTypeMap(servletContext); this.fileMediaTypeMap = new DefaultFileMediaTypeMap(servletContext);
} }
@ -168,17 +126,8 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
} }
} }
private boolean isResourceAllowed(URLResource resource) {
for(MediaType allowedType : allowedMediaTypes) {
if (allowedType.includes(resource.getMediaType())) {
return true;
}
}
return false;
}
private void writeResponse(URLResource resource, HttpServletRequest request, HttpServletResponse response) throws IOException { private void writeResponse(URLResource resource, HttpServletRequest request, HttpServletResponse response) throws IOException {
OutputStream out = selectOutputStream(resource, request, response); OutputStream out = response.getOutputStream();
try { try {
InputStream in = resource.getInputStream(); InputStream in = resource.getInputStream();
try { try {
@ -199,19 +148,6 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
} }
} }
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 {
return response.getOutputStream();
}
}
private boolean isValidFile(Resource resource) throws IOException { private boolean isValidFile(Resource resource) throws IOException {
return resource.exists() && StringUtils.hasText(resource.getFilename()); return resource.exists() && StringUtils.hasText(resource.getFilename());
} }
@ -223,13 +159,11 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
} }
} }
// TODO promote to top-level and make reusable private interface FileMediaTypeMap {
public interface FileMediaTypeMap {
MediaType getMediaType(String fileName); MediaType getMediaType(String fileName);
} }
public static class DefaultFileMediaTypeMap implements FileMediaTypeMap { private static class DefaultFileMediaTypeMap implements FileMediaTypeMap {
private static final boolean jafPresent = private static final boolean jafPresent =
ClassUtils.isPresent("javax.activation.FileTypeMap", ContentNegotiatingViewResolver.class.getClassLoader()); ClassUtils.isPresent("javax.activation.FileTypeMap", ContentNegotiatingViewResolver.class.getClassLoader());
@ -314,67 +248,6 @@ public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletCo
} }
} }
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 static class URLResource implements Resource {
private final Resource wrapped; private final Resource wrapped;

View File

@ -53,10 +53,64 @@
<xsd:annotation> <xsd:annotation>
<xsd:documentation <xsd:documentation
source="java:org.springframework.web.servlet.resources.ResourceHttpRequestHandler"><![CDATA[ source="java:org.springframework.web.servlet.resources.ResourceHttpRequestHandler"><![CDATA[
Configures support for efficiently serving static resources such as images, js, and, css files. Configures a handler for serving static resources such as images, js, and, css files with cache headers optimized for efficient
By default, registers a handler mapped to /resources/** capable of serving all resources located in the ${webappRoot}/resources directory. loading in a web browser. Allows resources to be served out of any path that is reachable via Spring's Resource handling.
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:complexType>
<xsd:attribute name="mapping" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The URL mapping pattern, within the current Sevlet context, to use for serving resources from this handler, such as "/resources/**"
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="location" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
The resource location from which to serve static content, specified at a Spring Resource pattern. Each location must point to a valid directory.
Multiple locations may be specified as a comma-seperated list, and the locations will be checked for a given resource in the order specified. For example,
a value of "/, classpath:/META-INF/public-web-resources/" will allow resources to be served both from the web app root and from any JAR on the classpath
that contains a /META-INF/public-web-resources/ directory, with resources in the web app root taking precedence.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapping-order" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Specifies the order of the HandlerMapping for the resource handler. The default order is Ordered.LOWEST_PRECEDENCE - 1.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="default-servlet-handler">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.web.servlet.resources.DefaultServletHttpRequestHandler"><![CDATA[
Configures a handler for serving static resources by forwarding to the Servlet container's default Servlet. Use of this
handler allows using a "/" mapping with the DispatcherServlet while still utilizing the Servlet container to serve static
resources.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="default-servlet-name" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the default Servlet to forward to for static resource requests. The handler will try to auto-detect the container's
default Servlet at startup time using a list of known names. If the default Servlet cannot be detected because of using an unknown
container or because it has been manually configured, the servlet name must be set explicitly.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element> </xsd:element>
<xsd:element name="interceptors"> <xsd:element name="interceptors">

View File

@ -18,6 +18,8 @@ package org.springframework.web.servlet.config;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -28,6 +30,7 @@ import org.junit.Test;
import org.springframework.beans.TypeMismatchException; import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
@ -36,6 +39,7 @@ import org.springframework.format.support.FormattingConversionServiceFactoryBean
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockRequestDispatcher;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
@ -56,12 +60,14 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping; import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
import org.springframework.web.servlet.resources.DefaultServletHttpRequestHandler;
import org.springframework.web.servlet.resources.ResourceHttpRequestHandler; import org.springframework.web.servlet.resources.ResourceHttpRequestHandler;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor; import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
/** /**
* @author Keith Donald * @author Keith Donald
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Jeremy Grelle
*/ */
public class MvcNamespaceTests { public class MvcNamespaceTests {
@ -70,7 +76,7 @@ public class MvcNamespaceTests {
@Before @Before
public void setUp() { public void setUp() {
appContext = new GenericWebApplicationContext(); appContext = new GenericWebApplicationContext();
appContext.setServletContext(new MockServletContext()); appContext.setServletContext(new TestMockServletContext());
LocaleContextHolder.setLocale(Locale.US); LocaleContextHolder.setLocale(Locale.US);
} }
@ -204,14 +210,18 @@ public class MvcNamespaceTests {
public void testResources() throws Exception { public void testResources() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-resources.xml", getClass())); reader.loadBeanDefinitions(new ClassPathResource("mvc-config-resources.xml", getClass()));
assertEquals(2, appContext.getBeanDefinitionCount()); assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh(); appContext.refresh();
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class); HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter); assertNotNull(adapter);
ResourceHttpRequestHandler handler = appContext.getBean(ResourceHttpRequestHandler.class);
assertNotNull(handler);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class); SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, mapping.getOrder());
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/resources/foo.css"); request.setRequestURI("/resources/foo.css");
@ -228,6 +238,76 @@ public class MvcNamespaceTests {
assertNull(mv); assertNull(mv);
} }
@Test
public void testResourcesWithOptionalAttributes() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-resources-optional-attrs.xml", getClass()));
assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh();
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(5, mapping.getOrder());
}
@Test
public void testDefaultServletHandler() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-default-servlet.xml", getClass()));
assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh();
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
DefaultServletHttpRequestHandler handler = appContext.getBean(DefaultServletHttpRequestHandler.class);
assertNotNull(handler);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(Ordered.LOWEST_PRECEDENCE, mapping.getOrder());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/foo.css");
request.setMethod("GET");
HandlerExecutionChain chain = mapping.getHandler(request);
assertTrue(chain.getHandler() instanceof DefaultServletHttpRequestHandler);
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = adapter.handle(request, response, chain.getHandler());
assertNull(mv);
}
@Test
public void testDefaultServletHandlerWithOptionalAtrributes() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
reader.loadBeanDefinitions(new ClassPathResource("mvc-config-default-servlet-optional-attrs.xml", getClass()));
assertEquals(3, appContext.getBeanDefinitionCount());
appContext.refresh();
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
DefaultServletHttpRequestHandler handler = appContext.getBean(DefaultServletHttpRequestHandler.class);
assertNotNull(handler);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
assertEquals(Ordered.LOWEST_PRECEDENCE, mapping.getOrder());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/foo.css");
request.setMethod("GET");
HandlerExecutionChain chain = mapping.getHandler(request);
assertTrue(chain.getHandler() instanceof DefaultServletHttpRequestHandler);
MockHttpServletResponse response = new MockHttpServletResponse();
ModelAndView mv = adapter.handle(request, response, chain.getHandler());
assertNull(mv);
}
@Test @Test
public void testBeanDecoration() throws Exception { public void testBeanDecoration() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
@ -393,14 +473,29 @@ public class MvcNamespaceTests {
@NotNull @NotNull
private String field; private String field;
@SuppressWarnings("unused")
public String getField() { public String getField() {
return field; return field;
} }
@SuppressWarnings("unused")
public void setField(String field) { public void setField(String field) {
this.field = field; this.field = field;
} }
} }
private static class TestMockServletContext extends MockServletContext {
@Override
public RequestDispatcher getNamedDispatcher(String path) {
if (path.equals("default") || path.equals("custom")) {
return new MockRequestDispatcher("/");
} else {
return null;
}
}
}
} }

View File

@ -61,16 +61,6 @@ public class ResourceHttpRequestHandlerTests {
assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.html", getClass()).getFile().lastModified()); assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.html", getClass()).getFile().lastModified());
} }
@Test
public void getResourceWithUnknownMediaType() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/test.unknown");
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals(404, response.getStatus());
}
@Test @Test
public void getResourceFromAlternatePath() throws Exception { public void getResourceFromAlternatePath() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
@ -193,51 +183,6 @@ public class ResourceHttpRequestHandlerTests {
handler = new ResourceHttpRequestHandler(resourcePaths); handler = new ResourceHttpRequestHandler(resourcePaths);
} }
@Test
public void getResourceOfAddedAllowedMimeType() throws Exception{
List<Resource> resourcePaths = new ArrayList<Resource>();
resourcePaths.add(new ClassPathResource("test/", getClass()));
handler = new ResourceHttpRequestHandler(resourcePaths, "text/plain");
handler.setServletContext(new TestServletContext());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.txt");
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals("text/plain", response.getContentType());
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/foo.txt", getClass()).getFile().lastModified());
}
@Test
public void getResourceWithDefaultMimeTypesOverriden() throws Exception{
List<Resource> resourcePaths = new ArrayList<Resource>();
resourcePaths.add(new ClassPathResource("test/", getClass()));
handler = new ResourceHttpRequestHandler(resourcePaths, "text/plain", true);
handler.setServletContext(new TestServletContext());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.txt");
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals("text/plain", response.getContentType());
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/foo.txt", getClass()).getFile().lastModified());
request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.css");
request.setMethod("GET");
response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals(404, response.getStatus());
}
private static class TestServletContext extends MockServletContext { private static class TestServletContext extends MockServletContext {
@Override @Override
public String getMimeType(String filePath) { public String getMimeType(String filePath) {

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:default-servlet-handler default-servlet-name="custom" />
</beans>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:default-servlet-handler />
</beans>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/" mapping-order="5"/>
</beans>

View File

@ -5,6 +5,6 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:resources /> <mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/" />
</beans> </beans>