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

今天在项目中访问静态文件一直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了。

以上便是我的理解,欢迎指教!!


  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zerlinda_Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值