您的位置:首页 > 运维架构 > Tomcat

分析DispatcherServlet拦截"/"造成静态文件404

2017-08-17 22:52 253 查看
今天在项目中访问静态文件一直404,后来在web.xml里面加入了这样一段代码,就能够访问了。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>

这里以html为例,如果需要多种静态资源类型,就需要配置多个以上。

为什么配置这个就能访问了呢?default是哪个servlet?为什么要使用这个servlet处理静态资源?我的脑海中产生这样一系列的问题。

总的来说,是因为配置了spring MVC的DispatcherServlet拦截“/”,为了实现REST风格,拦截了所有的请求,同时,静态文件也被拦截。 所以激活Tomcat的defaultServlet来处理静态文件。

我们接着刚刚的疑问继续探究。

先在项目里面找了default,没有找到。很好,百度吧!百度说这是他tomcat默认的serlvet,那这个servlet是干什么用的呢?我们已经知道它的一个用处了,就是处理静态资源。从这个网址可以看到官方文档里面对DefaultServlet的解释。http://tomcat.apache.org/tomcat-7.0-doc/default-servlet.html#what

1. What is the DefaultServlet?

DefaultServlet是用来处理静态资源,就像处理目录列表一样。

2. Where is it declared?

DefaultServlet是在$CATALINA_BASE/conf/web.xml里面做全局声明的,默认情况下是这样子的

<servlet>
<servlet-name>default</servlet-name>
<servlet-class>
org.apache.catalina.servlets.DefaultServlet
</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

...

<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
所以通常情况下,DefaultServlet是在web项目启动时加载的,同时debug调试和文件目录都是禁用的。这里的listings是指文件目录,可是文件目录又是什么呢?别着急,我们继续往下看。

3. What can I change?

DefaultServlet允许下面这些初始化参数

debug 这个参数对tomcat的开发者用处比较大,对我们而言用处不大。可选的值有0,1,11,1000.[0]
listings 如果没有设置欢迎页,那么当我们访问项目名时,会显示文件目录吗?这个参数的值是true或者false.[false].(这里我把值设置成了true,并且删除了欢迎文件,访问项目时,显示了文件目录

可以对比我的项目目录


欢迎文件也是servlet api的一部分。警告:包含很多实体的文件目录是非常珍贵的。大量的对大文件列表的请求,十分消耗服务器资源。
readmeFile 如果设置了显示文件列表,readme文件可能也会显示在列表里面。这个文件是插入的,它可能包含html.
globalXsltFile 如果你想定制你的文件目录,你可以使用XSL来转换。这个的值也是一个相对文件的名称($CATALINA_BASE/conf/ or $CATALINA_HOME/conf/),这个将会给所有的文件目录使用。但是这个可以被每一个context或者directory覆盖。具体的请看下面的contextXsltFile和localXsltFile.
contextXsltFile 你可以通过配置contextXsltFile来定制你的文件目录。但是一定要使用上下文的相对路径,并且是一.xsl或者.xslt为扩展名的文件(例如:/path/to/context.xslt)。这个会覆盖globalXsltFile的值. 如果配置的文件路径不存在,仍然会使用globalXsltFile的配置。如果默认的globalXsltFile文件也不存在,那会直接显示文件目录。
localXsltFile 你同样可以通过配置localXsltFile来定制你的文件目录。它一定是以.xsl或.xslt为扩展名的在目录列表里面的文件。并且这个配置能覆盖globalXsltFile和contextXsltFile的配置。如果配置的这个值找不到对应文件,就会使用contextXsltFile的配置,如果contextXsltFile也找不到,就会使用globalXsltFile的配置,如果都找不到,那么就会展示默认的文件目录。
input 当读取资源文件时,服务器写入缓冲区的大小,单位为bytes.[2048]
output 当写入资源文件时,服务器输出缓冲区大小,单位为bytes.[2048]
readonly 上下文是否是只读的,HTTP的命令,像PUT,DELETE这些是不是被拒绝的。[true]
fileEncoding 读取静态资源时的文件编码。[platform default]
sendfileSize 如果使用的连接器支持sendfile,这意味着以KB大小的小文件将会被使用。通常使用负数来禁止sendfile.[48]
useAcceptRanges 如果设置为true,当响应时请求头的Accept-Ranges将会被设置。[true]
showServerInfo 当展示文件列表是,服务器信息是否在响应时一起发送给客户端。[true]
4. How do I customize directory listings?
第三点中的globalXsltFile,localXsltFile,contextXsltFile这几个参数都是用来定制文件目录的,那么具体怎么定制呢?
我们可以重新定义一个Servlet来继承DefaultServlet,重写里面的方法,并在web.xml里面生命。就是说,tomcat开发人员假定我们是可以读到DefaultServlet的代码的,并且可以做适当的调整。如果我们无法读到DefaultServlet的代码,那这种方法就不适用了。
我们也可以使用localXsltFile和globalXsltFile来配置,DefaultServlet将会根据localXsltFile和globalXsltFile的配置找到xsl转换文件,创建并运行一个xml文件。localXsltFile优先级最高,然后是globalXsltFile,最后是默认的行为,格式如下:
<listing>
<entries>
<entry type='file|dir' urlPath='aPath' size='###' date='gmt date'>
fileName1
</entry>
<entry type='file|dir' urlPath='aPath' size='###' date='gmt date'>
fileName2
</entry>
...
</entries>
<readme></readme>
</listing>

如果设置type='dir',就不用设置size
Readme是一个CDATA,不由xml解析器解析
下面这个xsl文件示例,是mimics默认的tomcat行为:
<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">

<xsl:output method="html" html-version="5.0"
encoding="UTF-8" indent="no"
doctype-system="about:legacy-compat"/>

<xsl:template match="listing">
<html>
<head>
<title>
Sample Directory Listing For
<xsl:value-of select="@directory"/>
</title>
<style>
h1 {color : white;background-color : #0086b2;}
h3 {color : white;background-color : #0086b2;}
body {font-family : sans-serif,Arial,Tahoma;
color : black;background-color : white;}
b {color : white;background-color : #0086b2;}
a {color : black;} HR{color : #0086b2;}
table td { padding: 5px; }
</style>
</head>
<body>
<h1>Sample Directory Listing For
<xsl:value-of select="@directory"/>
</h1>
<hr style="height: 1px;" />
<table style="width: 100%;">
<tr>
<th style="text-align: left;">Filename</th>
<th style="text-align: center;">Size</th>
<th style="text-align: right;">Last Modified</th>
</tr>
<xsl:apply-templates select="entries"/>
</table>
<xsl:apply-templates select="readme"/>
<hr style="height: 1px;" />
<h3>Apache Tomcat/8.0</h3>
</body>
</html>
</xsl:template>

<xsl:template match="entries">
<xsl:apply-templates select="entry"/>
</xsl:template>

<xsl:template match="readme">
<hr style="height: 1px;" />
<pre><xsl:apply-templates/></pre>
</xsl:template>

<xsl:template match="entry">
<tr>
<td style="text-align: left;">
<xsl:variable name="urlPath" select="@urlPath"/>
<a href="{$urlPath}">
<pre><xsl:apply-templates/></pre>
</a>
</td>
<td style="text-align: right;">
<pre><xsl:value-of select="@size"/></pre>
</td>
<td style="text-align: right;">
<pre><xsl:value-of select="@date"/></pre>
</td>
</tr>
</xsl:template>

</xsl:stylesheet>


5. How do I secure directory listings?
在每一个web项目里面使用web.xml.你可以去看servlet文档的安全模块。

以上,都是tomcat官网上关于DefaultServelt的说明,我们了解DefaultServelt使用方法,相关参数的配置。那么具体怎么来处理静态资源的呢?还是不知道。
直接来看代码吧。。。
我们可以看到DefaultServlet继承了HttpServlet,刚刚那些参数都是DefaultServlet里面的属性,并且在构造器里面设置了默认值。

/*      */ private static final long serialVersionUID = 1L;
/*      */ private static final DocumentBuilderFactory factory;
/*      */ private static final SecureEntityResolver secureEntityResolver;
/*      */ protected int debug;
/*      */ protected int input;
/*      */ protected boolean listings;
/*      */ protected boolean readOnly;
/*      */ protected int output;
/*      */ protected static final URLEncoder urlEncoder;
/*      */ protected String localXsltFile;
/*      */ protected String contextXsltFile;
/*      */ protected String globalXsltFile;
/*      */ protected String readmeFile;
/*      */ protected transient ProxyDirContext resources;
/*      */ protected String fileEncoding;
/*      */ protected int sendfileSize;
/*      */ protected boolean useAcceptRanges;
/*  227 */ protected static final ArrayList<Range> FULL = new ArrayList();
/*      */ protected boolean showServerInfo;
/*      */ protected static final String mimeSeparation = "CATALINA_MIME_BOUNDARY";
/*      */ protected static final String RESOURCES_JNDI_NAME = "java:/comp/Resources";
/*      */ protected static final StringManager sm;
/*      */ protected static final int BUFFER_SIZE = 4096;

/*      */
/*      */ public DefaultServlet()
/*      */ {
/*  144 */ this.debug = 0;
/*      */
/*  150 */ this.input = 2048;
/*      */
/*  156 */ this.listings = false;
/*      */
/*  162 */ this.readOnly = true;
/*      */
/*  168 */ this.output = 2048;
/*      */
/*  180 */ this.localXsltFile = null;
/*      */
/*  186 */ this.contextXsltFile = null;
/*      */
/*  192 */ this.globalXsltFile = null;
/*      */
/*  198 */ this.readmeFile = null;
/*      */
/*  204 */ this.resources = null;
/*      */
/*  211 */ this.fileEncoding = null;
/*      */
/*  217 */ this.sendfileSize = 49152;
/*      */
/*  222 */ this.useAcceptRanges = true;
/*      */
/*  232 */ this.showServerInfo = true;
/*      */ }
接着就是destroy()和init()方法。init()在服务器启动时执行,destroy()方法在服务器停止时执行。在init()方法里面,我们可以看到它是去读取配置文件的这些属性值,并赋值。
/*      */ public void init()/*      */ throws ServletException
/*      */ {
/*  305 */ if (getServletConfig().getInitParameter("debug") != null) {
/*  306 */ this.debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
/*      */ }
/*  308 */ if (getServletConfig().getInitParameter("input") != null) {
/*  309 */ this.input = Integer.parseInt(getServletConfig().getInitParameter("input"));
/*      */ }
/*  311 */ if (getServletConfig().getInitParameter("output") != null) {
/*  312 */ this.output = Integer.parseInt(getServletConfig().getInitParameter("output"));
/*      */ }
/*  314 */ this.listings = Boolean.parseBoolean(getServletConfig().getInitParameter("listings"));
/*      */
/*  316 */ if (getServletConfig().getInitParameter("readonly") != null) {
/*  317 */ this.readOnly = Boolean.parseBoolean(getServletConfig().getInitParameter("readonly"));
/*      */ }
/*  319 */ if (getServletConfig().getInitParameter("sendfileSize") != null) {
/*  320 */ this.sendfileSize = (Integer.parseInt(getServletConfig().getInitParameter("sendfileSize"))
* 1024);
/*      */ }
/*      */
/*  323 */ this.fileEncoding = getServletConfig().getInitParameter("fileEncoding");
/*      */
/*  325 */ this.globalXsltFile = getServletConfig().getInitParameter("globalXsltFile");
/*  326 */ this.contextXsltFile = getServletConfig().getInitParameter("contextXsltFile");
/*  327 */ this.localXsltFile = getServletConfig().getInitParameter("localXsltFile");
/*  328 */ this.readmeFile = getServletConfig().getInitParameter("readmeFile");
/*      */
/*  330 */ if (getServletConfig().getInitParameter("useAcceptRanges") != null) {
/*  331 */ this.useAcceptRanges = Boolean
.parseBoolean(getServletConfig().getInitParameter("useAcceptRanges"));
/*      */ }
/*      */
/*  334 */ if (this.input < 256)
/*  335 */ this.input = 256;
/*  336 */ if (this.output < 256) {
/*  337 */ this.output = 256;
/*      */ }
/*  339 */ if (this.debug > 0) {
/*  340 */ log(new StringBuilder().append("DefaultServlet.init:  input buffer size=").append(this.input)
.append(", output buffer size=").append(this.output).toString());
/*      */ }
/*      */
/*  345 */ this.resources = ((ProxyDirContext) getServletContext()
.getAttribute("org.apache.catalina.resources"));
/*      */
/*  347 */ if (this.resources == null) {
/*      */ try {
/*  349 */ this.resources = ((ProxyDirContext) new InitialContext().lookup("java:/comp/Resources"));
/*      */ }
/*      */ catch (NamingException e)
/*      */ {
/*  354 */ throw new ServletException("No resources", e);
/*      */ }
/*      */ }
/*      */
/*  358 */ if (this.resources == null) {
/*  359 */ throw new UnavailableException("No resources");
/*      */ }
/*      */
/*  362 */ if (getServletConfig().getInitParameter("showServerInfo") != null)
/*  363 */ this.showServerInfo = Boolean
.parseBoolean(getServletConfig().getInitParameter("showServerInfo"));
/*      */ }
我猜测getServletConfig().getInitParameter("debug")这两个方法就是读取在web.xml里面配置的default的参数的值的。
那么文件拦截后,交到doGet(),doPost方法处理。我们来看下。。在doGet()里面调用了serveResource(request, response, true)方法。我看了一下这个方法,不是所有的代码都能看懂,但是大致就是获取请求地址,然后处理跳转响应页面。和我们普通的servlet差不多。

最后就是要把这个default的servlet配置到springMVC跳转器的前面,这样请求就会走DefaultServlet了。

以上便是我的理解,欢迎指教!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tomcat DefaultServlet
相关文章推荐