revised ResourceHttpRequestHandler (SPR-7116)
This commit is contained in:
@@ -1,20 +1,37 @@
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
/*
|
||||
* 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) {
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
/*
|
||||
* 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));
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
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 {@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 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 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 defaultServletName} property has not been explicitly set,
|
||||
* attempts to locate the default Servlet using the known common
|
||||
* container-specific names.
|
||||
*/
|
||||
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) {
|
||||
this.defaultServletName = RESIN_DEFAULT_SERVLET_NAME;
|
||||
}
|
||||
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) {
|
||||
this.defaultServletName = WEBSPHERE_DEFAULT_SERVLET_NAME;
|
||||
}
|
||||
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 {
|
||||
|
||||
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
|
||||
if (rd == null) {
|
||||
throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
|
||||
this.defaultServletName +"'");
|
||||
}
|
||||
rd.forward(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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><mvc:resources/></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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
/**
|
||||
* Support classes for serving static resources.
|
||||
*/
|
||||
package org.springframework.web.servlet.config;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
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.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* @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 defaultServletName;
|
||||
|
||||
/**
|
||||
* 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.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) {
|
||||
this.defaultServletName = RESIN_DEFAULT_SERVLET_NAME;
|
||||
} 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) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
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+"'");
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
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><mvc:resources/></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.
|
||||
|
||||
Reference in New Issue
Block a user