提交 b4cd5839 编写于 作者: J Juergen Hoeller

revised ResourceHttpRequestHandler (SPR-7116)

上级 bda3d81b
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import org.w3c.dom.Element;
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}.
* Abstract base class for {@link BeanDefinitonParser}s that register an HttpRequestHandler.
*
* @author Jeremy Grelle
* @since 3.0.4
*/
public abstract class AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser{
abstract class AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser{
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
......@@ -36,4 +53,5 @@ public abstract class AbstractHttpRequestHandlerBeanDefinitionParser implements
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
}
}
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.Map;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedMap;
......@@ -11,8 +29,7 @@ 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;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
/**
* {@link BeanDefinitionParser} that parses a {@code default-servlet-handler} element to
......@@ -23,7 +40,7 @@ import org.w3c.dom.Element;
* @author Jeremy Grelle
* @since 3.0.4
*/
public class DefaultServletHandlerBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser {
class DefaultServletHandlerBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser {
@Override
public void doParse(Element element, ParserContext parserContext) {
......
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
......@@ -14,8 +32,7 @@ import org.springframework.core.Ordered;
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.ResourceHttpRequestHandler;
import org.w3c.dom.Element;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
......@@ -27,7 +44,7 @@ import org.w3c.dom.Element;
* @author Jeremy Grelle
* @since 3.0.4
*/
public class ResourcesBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser {
class ResourcesBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser {
@Override
public void doParse(Element element, ParserContext parserContext) {
......@@ -76,7 +93,7 @@ public class ResourcesBeanDefinitionParser extends AbstractHttpRequestHandlerBea
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getConstructorArgumentValues().addIndexedArgumentValue(0, locations);
resourceHandlerDef.getPropertyValues().add("locations", locations);
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
......
package org.springframework.web.servlet.resources;
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
package org.springframework.web.servlet.resource;
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.
*
* <p>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.
*
* <p>Requests are handled by forwarding through the {@link RequestDispatcher} obtained via the name specified through 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
* running in a container where the default Servlet's name is not known, or where it has been customized via configuration, the
* {@code defaultServletName} will need to be set explicitly.
*
*
* <p>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.
*
* <p>Requests are handled by forwarding through the {@link RequestDispatcher} obtained via the
* name specified through the {@link #setDefaultServletName "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 well-known
* containers such as Tomcat, Jetty, Resin, WebLogic and WebSphere. However, when running in a
* container where the default Servlet's name is not known, or where it has been customized
* via server configuration, the {@code defaultServletName} will need to be set explicitly.
*
* @author Jeremy Grelle
* @author Juergen Hoeller
* @since 3.0.4
*/
public class DefaultServletHttpRequestHandler implements InitializingBean, HttpRequestHandler, ServletContextAware {
public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
/**
* Default Servlet name used by Tomcat, Jetty, JBoss, and Glassfish
*/
/** 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
*/
/** Default Servlet name used by Resin */
private static final String RESIN_DEFAULT_SERVLET_NAME = "resin-file";
/**
* Default Servlet name used by WebLogic
*/
/** Default Servlet name used by WebLogic */
private static final String WEBLOGIC_DEFAULT_SERVLET_NAME = "FileServlet";
/**
* Default Servlet name used by WebSphere
*/
/** Default Servlet name used by WebSphere */
private static final String WEBSPHERE_DEFAULT_SERVLET_NAME = "SimpleFileServlet";
private ServletContext servletContext;
private String defaultServletName;
private ServletContext servletContext;
/**
* Set the name of the default Servlet to be forwarded to for static resource requests.
*/
public void setDefaultServletName(String defaultServletName) {
this.defaultServletName = defaultServletName;
}
/**
* If the {@code filedServletName} property has not been explicitly set, attempts to locate the default Servlet using the
* known common container-specific names.
* If the {@code defaultServletName} property has not been explicitly set,
* attempts to locate the default Servlet using the known common
* container-specific names.
*/
public void afterPropertiesSet() throws Exception {
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
if (!StringUtils.hasText(this.defaultServletName)) {
if (this.servletContext.getNamedDispatcher(COMMON_DEFAULT_SERVLET_NAME) != null) {
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.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.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.defaultServletName = WEBSPHERE_DEFAULT_SERVLET_NAME;
}
Assert.hasText(this.defaultServletName, "Unable to locate the default servlet for serving static content. Please set the 'defaultServletName' property explicitly.");
else {
throw new IllegalStateException("Unable to locate the default servlet for serving static content. " +
"Please set the 'defaultServletName' property explicitly.");
}
}
}
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
Assert.notNull(rd, "A RequestDispatcher could not be located for the servlet name '"+this.defaultServletName+"'");
if (rd == null) {
throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
this.defaultServletName +"'");
}
rd.forward(request, response);
}
/**
* Set the name of the default Servlet to be forwarded to for static resource requests.
* @param defaultServletName The name of the Servlet to use for static resources.
*/
public void setDefaultServletName(String defaultServletName) {
this.defaultServletName = defaultServletName;
}
/**
* {@inheritDoc}
*/
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
\ No newline at end of file
}
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.resource;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestHandler;
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.
*
* <p>The constructor takes a list of Spring {@link Resource} locations from which static resources are allowed
* to be served by this handler. For a given request, the list of locations will be consulted in order for the
* presence of the requested resource, and the first found match will be written to the response, with {@code
* Expires} and {@code Cache-Control} headers set for one year in the future. The handler also properly evaluates
* the {@code Last-Modified} header (if present) so that a {@code 304} status code will be returned as appropriate,
* avoiding unnecessary overhead for resources that are already cached by the client. The use of {@code Resource}
* locations allows resource requests to easily be mapped to locations other than the web application root. For
* example, resources could be served from a classpath location such as "classpath:/META-INF/public-web-resources/",
* allowing convenient packaging and serving of resources such as a JavaScript library from within jar files.
*
* <p>To ensure that users with a primed browser cache get the latest changes to application-specific resources
* upon deployment of new versions of the application, it is recommended that a version string is used in the URL
* mapping pattern that selects this handler. Such patterns can be easily parameterized using Spring EL. See the
* reference manual for further examples of this approach.
*
* <p>Rather than being directly configured as a bean, this handler will typically be configured through use of
* the <code>&lt;mvc:resources/&gt;</code> Spring configuration tag.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Juergen Hoeller
* @since 3.0.4
*/
public class ResourceHttpRequestHandler extends WebContentGenerator implements HttpRequestHandler {
private List<Resource> locations;
public ResourceHttpRequestHandler() {
super(METHOD_GET);
}
/**
* Set a {@code List} of {@code Resource} paths to use as sources
* for serving static resources.
*/
public void setLocations(List<Resource> locations) {
Assert.notEmpty(locations, "Location list must not be empty");
this.locations = locations;
}
/**
* Processes a resource request.
* <p>Checks for the existence of the requested resource in the configured list of locations.
* If the resource does not exist, a {@code 404} response will be returned to the client.
* If the resource exists, the request will be checked for the presence of the
* {@code Last-Modified} header, and its value will be compared against the last-modified
* timestamp of the given resource, returning a {@code 304} status code if the
* {@code Last-Modified} value is greater. If the resource is newer than the
* {@code Last-Modified} value, or the header is not present, the content resource
* of the resource will be written to the response with caching headers
* set to expire one year in the future.
*/
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
checkAndPrepare(request, response, true);
Resource resource = getResource(request);
if (resource == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (checkNotModified(resource, request, response)) {
return;
}
writeResponse(resource, response);
}
private Resource 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");
}
if (!StringUtils.hasText(path) || path.contains("WEB-INF") || path.contains("META-INF")) {
return null;
}
for (Resource resourcePath : this.locations) {
try {
Resource resource = resourcePath.createRelative(path);
if (resource.exists() && resource.isReadable()) {
return resource;
}
}
catch (IOException ex) {
// resource not found
return null;
}
}
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 {
MediaType mediaType = getMediaType(resource);
if (mediaType != null) {
response.setContentType(mediaType.toString());
}
response.setContentLength(resource.contentLength());
FileCopyUtils.copy(resource.getInputStream(), response.getOutputStream());
}
protected MediaType getMediaType(Resource resource) {
String mimeType = getServletContext().getMimeType(resource.getFilename());
return (StringUtils.hasText(mimeType) ? MediaType.parseMediaType(mimeType) : null);
}
}
/**
* Support classes for serving static resources.
*/
package org.springframework.web.servlet.config;
package org.springframework.web.servlet.resources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
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 javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
/**
* {@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.
*
* <p>The constructor takes a list of Spring {@link Resource} locations from which static resources are allowed
* to be served by this handler. For a given request, the list of locations will be consulted in order for the
* presence of the requested resource, and the first found match will be written to the response, with {@code
* Expires} and {@code Cache-Control} headers set for one year in the future. The handler also properly evaluates
* the {@code Last-Modified} header (if present) so that a {@code 304} status code will be returned as appropriate,
* avoiding unnecessary overhead for resources that are already cached by the client. The use of {@code Resource}
* locations allows resource requests to easily be mapped to locations other than the web application root. For
* example, resources could be served from a classpath location such as "classpath:/META-INF/public-web-resources/",
* allowing convenient packaging and serving of resources such as a JavaScript library from within jar files.
*
* <p>To ensure that users with a primed browser cache get the latest changes to application-specific resources
* upon deployment of new versions of the application, it is recommended that a version string is used in the URL
* mapping pattern that selects this handler. Such patterns can be easily parameterized using Spring EL. See the
* reference manual for further examples of this approach.
*
* <p>Rather than being directly configured as a bean, this handler will typically be configured through use of
* the <code>&lt;mvc:resources/&gt;</code> Spring configuration tag.
*
* @author Keith Donald
* @author Jeremy Grelle
* @since 3.0.4
*/
public class ResourceHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
private final List<Resource> resourcePaths;
private final int maxAge = 31556926;
private FileMediaTypeMap fileMediaTypeMap;
/**
* Construct a new {@code ResourceHttpRequestHandler} with a {@code List} of {@code Resource} paths to use as
* sources for serving static resources.
* @param resourcePaths the list of paths from which resources can be served
*/
public ResourceHttpRequestHandler(List<Resource> resourcePaths) {
Assert.notNull(resourcePaths, "Resource paths must not be null");
validateResourcePaths(resourcePaths);
this.resourcePaths = resourcePaths;
}
/**
* Processes a resource request.
*
* <p>Checks for the existence of the requested resource in the configured list of locations. If the resource
* does not exist, a {@code 404} response will be returned to the client. If the resource exists, the request will
* be checked for the presence of the {@code Last-Modified} header, and its value will be compared against the last
* modified timestamp of the given resource, returning a {@code 304} status code if the {@code Last-Modified} value
* is greater. If the resource is newer than the {@code Last-Modified} value, or the header is not present, the
* content resource of the resource will be written to the response with caching headers set to expire one year in
* the future.
*/
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");
}
URLResource resource = getResource(request);
if (resource == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (checkNotModified(resource, request, response)) {
return;
}
prepareResponse(resource, response);
writeResponse(resource, request, response);
}
public void setServletContext(ServletContext servletContext) {
this.fileMediaTypeMap = new DefaultFileMediaTypeMap(servletContext);
}
private boolean checkNotModified(Resource resource,HttpServletRequest request, HttpServletResponse response) throws IOException {
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
boolean notModified = ifModifiedSince >= (resource.lastModified() / 1000 * 1000);
if (notModified) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
} else {
response.setDateHeader("Last-Modified", resource.lastModified());
}
return notModified;
}
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");
}
if (path.contains("WEB-INF") || path.contains("META-INF")) {
return null;
}
for (Resource resourcePath : this.resourcePaths) {
Resource resource;
try {
resource = resourcePath.createRelative(path);
if (isValidFile(resource)) {
return new URLResource(resource, fileMediaTypeMap.getMediaType(resource.getFilename()));
}
} catch (IOException e) {
//Resource not found
return null;
}
}
return null;
}
private void prepareResponse(URLResource resource, HttpServletResponse response) throws IOException {
response.setContentType(resource.getMediaType().toString());
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);
// HTTP 1.1 header
response.setHeader("Cache-Control", "max-age=" + this.maxAge);
}
}
private void writeResponse(URLResource resource, HttpServletRequest request, HttpServletResponse response) throws IOException {
OutputStream out = response.getOutputStream();
try {
InputStream in = resource.getInputStream();
try {
byte[] buffer = new byte[1024];
int bytesRead = -1;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} finally {
if (in != null) {
in.close();
}
}
} finally {
if (out != null) {
out.close();
}
}
}
private boolean isValidFile(Resource resource) throws IOException {
return resource.exists() && StringUtils.hasText(resource.getFilename());
}
private void validateResourcePaths(List<Resource> resourcePaths) {
for (Resource path : resourcePaths) {
Assert.isTrue(path.exists(), path.getDescription() + " is not a valid resource location as it does not exist.");
Assert.isTrue(!StringUtils.hasText(path.getFilename()), path.getDescription()+" is not a valid resource location. Resource paths must end with a '/'.");
}
}
private interface FileMediaTypeMap {
MediaType getMediaType(String fileName);
}
private static class DefaultFileMediaTypeMap implements FileMediaTypeMap {
private static final boolean jafPresent =
ClassUtils.isPresent("javax.activation.FileTypeMap", ContentNegotiatingViewResolver.class.getClassLoader());
private ConcurrentMap<String, MediaType> mediaTypes = new ConcurrentHashMap<String, MediaType>();
private final ServletContext servletContext;
public DefaultFileMediaTypeMap(ServletContext servletContext) {
this.servletContext = servletContext;
}
public MediaType getMediaType(String filename) {
String extension = StringUtils.getFilenameExtension(filename);
if (!StringUtils.hasText(extension)) {
return null;
}
extension = extension.toLowerCase(Locale.ENGLISH);
MediaType mediaType = this.mediaTypes.get(extension);
if (mediaType == null) {
String mimeType = servletContext.getMimeType(filename);
if (StringUtils.hasText(mimeType)) {
mediaType = MediaType.parseMediaType(mimeType);
}
}
if (mediaType == null && jafPresent) {
mediaType = ActivationMediaTypeFactory.getMediaType(filename);
if (mediaType != null) {
this.mediaTypes.putIfAbsent(extension, mediaType);
}
}
return mediaType;
}
/**
* Inner class to avoid hard-coded JAF dependency.
*/
private static class ActivationMediaTypeFactory {
private static final FileTypeMap fileTypeMap;
static {
fileTypeMap = loadFileTypeMapFromContextSupportModule();
}
private static FileTypeMap loadFileTypeMapFromContextSupportModule() {
// see if we can find the extended mime.types from the context-support module
Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types");
if (mappingLocation.exists()) {
if (logger.isTraceEnabled()) {
logger.trace("Loading Java Activation Framework FileTypeMap from " + mappingLocation);
}
InputStream inputStream = null;
try {
inputStream = mappingLocation.getInputStream();
return new MimetypesFileTypeMap(inputStream);
}
catch (IOException ex) {
// ignore
}
finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException ex) {
// ignore
}
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Loading default Java Activation Framework FileTypeMap");
}
return FileTypeMap.getDefaultFileTypeMap();
}
public static MediaType getMediaType(String fileName) {
String mediaType = fileTypeMap.getContentType(fileName);
return StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null;
}
}
}
private static class URLResource implements Resource {
private final Resource wrapped;
private final long lastModified;
private final int contentLength;
private final MediaType mediaType;
public URLResource(Resource wrapped, MediaType mediaType) throws IOException {
this.wrapped = wrapped;
URLConnection connection = null;
try {
connection = wrapped.getURL().openConnection();
this.lastModified = connection.getLastModified();
this.contentLength = connection.getContentLength();
this.mediaType = mediaType;
} finally {
if (connection != null) {
connection.getInputStream().close();
}
}
}
public int getContentLength() {
return this.contentLength;
}
public MediaType getMediaType() {
return mediaType;
}
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();
}
}
}
......@@ -103,6 +103,14 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport {
}
}
/**
* Create a new WebContentGenerator.
* @param supportedMethods the supported HTTP methods for this content generator
*/
public WebContentGenerator(String... supportedMethods) {
this.supportedMethods = new HashSet<String>(Arrays.asList(supportedMethods));
}
/**
* Set the HTTP methods that this content generator should support.
......
......@@ -52,7 +52,7 @@
<xsd:element name="resources">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.web.servlet.resources.ResourceHttpRequestHandler"><![CDATA[
source="java:org.springframework.web.servlet.resource.ResourceHttpRequestHandler"><![CDATA[
Configures a handler for serving static resources such as images, js, and, css files with cache headers optimized for efficient
loading in a web browser. Allows resources to be served out of any path that is reachable via Spring's Resource handling.
]]></xsd:documentation>
......@@ -94,7 +94,7 @@
<xsd:element name="default-servlet-handler">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.web.servlet.resources.DefaultServletHttpRequestHandler"><![CDATA[
source="java:org.springframework.web.servlet.resource.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.
......
......@@ -60,8 +60,8 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
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.resource.DefaultServletHttpRequestHandler;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
/**
......
package org.springframework.web.servlet.resources;
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
package org.springframework.web.servlet.resource;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.*;
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;
......@@ -18,6 +32,10 @@ import org.springframework.mock.web.MockServletContext;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.servlet.HandlerMapping;
/**
* @author Keith Donald
* @author Jeremy Grelle
*/
public class ResourceHttpRequestHandlerTests {
private ResourceHttpRequestHandler handler;
......@@ -27,7 +45,9 @@ public class ResourceHttpRequestHandlerTests {
List<Resource> resourcePaths = new ArrayList<Resource>();
resourcePaths.add(new ClassPathResource("test/", getClass()));
resourcePaths.add(new ClassPathResource("testalternatepath/", getClass()));
handler = new ResourceHttpRequestHandler(resourcePaths);
handler = new ResourceHttpRequestHandler();
handler.setLocations(resourcePaths);
handler.setCacheSeconds(3600);
handler.setServletContext(new TestServletContext());
}
......@@ -40,23 +60,23 @@ public class ResourceHttpRequestHandlerTests {
handler.handleRequest(request, response);
assertEquals("text/css", response.getContentType());
assertEquals(17, response.getContentLength());
assertTrue(((Long)response.getHeader("Expires")) > System.currentTimeMillis() + (31556926 * 1000) - 10000);
assertEquals("max-age=31556926", response.getHeader("Cache-Control"));
assertTrue(((Long)response.getHeader("Expires")) >= System.currentTimeMillis() - 1000 + (3600 * 1000));
assertEquals("max-age=3600, must-revalidate", response.getHeader("Cache-Control"));
assertTrue(response.containsHeader("Last-Modified"));
assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.css", getClass()).getFile().lastModified());
assertEquals("h1 { color:red; }", response.getContentAsString());
}
@Test
public void getResourceWithJafProvidedMediaType() throws Exception {
public void getResourceWithHtmlMediaType() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/foo.html");
request.setMethod("GET");
MockHttpServletResponse response = new MockHttpServletResponse();
handler.handleRequest(request, response);
assertEquals("text/html", response.getContentType());
assertTrue(((Long)response.getHeader("Expires")) > System.currentTimeMillis() + (31556926 * 1000) - 10000);
assertEquals("max-age=31556926", response.getHeader("Cache-Control"));
assertTrue(((Long)response.getHeader("Expires")) >= System.currentTimeMillis() - 1000 + (3600 * 1000));
assertEquals("max-age=3600, must-revalidate", response.getHeader("Cache-Control"));
assertTrue(response.containsHeader("Last-Modified"));
assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("test/foo.html", getClass()).getFile().lastModified());
}
......@@ -70,8 +90,8 @@ public class ResourceHttpRequestHandlerTests {
handler.handleRequest(request, response);
assertEquals("text/css", response.getContentType());
assertEquals(17, response.getContentLength());
assertTrue(((Long)response.getHeader("Expires")) > System.currentTimeMillis() + (31556926 * 1000) - 10000);
assertEquals("max-age=31556926", response.getHeader("Cache-Control"));
assertTrue(((Long)response.getHeader("Expires")) >= System.currentTimeMillis() - 1000 + (3600 * 1000));
assertEquals("max-age=3600, must-revalidate", response.getHeader("Cache-Control"));
assertTrue(response.containsHeader("Last-Modified"));
assertEquals(response.getHeader("Last-Modified"), new ClassPathResource("testalternatepath/baz.css", getClass()).getFile().lastModified());
assertEquals("h1 { color:red; }", response.getContentAsString());
......@@ -168,30 +188,22 @@ public class ResourceHttpRequestHandlerTests {
handler.handleRequest(request, response);
assertEquals(404, response.getStatus());
}
@Test(expected=IllegalArgumentException.class)
public void invalidPath() throws Exception {
List<Resource> resourcePaths = new ArrayList<Resource>();
resourcePaths.add(new ClassPathResource("testalternatepath", getClass()));
handler = new ResourceHttpRequestHandler(resourcePaths);
}
@Test(expected=IllegalArgumentException.class)
public void pathDoesNotExist() throws Exception {
List<Resource> resourcePaths = new ArrayList<Resource>();
resourcePaths.add(new ClassPathResource("bogus/"));
handler = new ResourceHttpRequestHandler(resourcePaths);
}
private static class TestServletContext extends MockServletContext {
@Override
public String getMimeType(String filePath) {
if(filePath.endsWith(".css")) {
if (filePath.endsWith(".css")) {
return "text/css";
} else if (filePath.endsWith(".js")) {
}
else if (filePath.endsWith(".js")) {
return "text/javascript";
}
return null;
else {
return super.getMimeType(filePath);
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册