目录

搞懂tomcat中web.xml配置servlet的url-pattern为"/"和"/*"的区别

原文地址

前言


我在写原生javaWEB项目时,想通过注册一个servlet实现拦截所有请求由HandOutServlet统一分发,于是我就想到把urlPatterns设置为"/"

https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121923036.png 但是这个会出现一个问题就是这个也会把 静态资源(html,css,js等) 的请求也拦截了,导致前端无法获取到正确的页面资源(解决方法在下面会提到),也正好我在配置过滤器时使用到另一个形式的url就是"/*",所以我想弄清楚这两个的区别是什么?我于是开始了源码的调试和研究。。。。。。

结论


<url-pattern>/</url-pattern>:

url-pattern为"/“时会覆盖tomcat中的default servlet(这个给是用于提供静态资源的),表示匹配所有没有注册的url-pattern的地址,就是web.xml里的其他的url-pattern都没有匹配时就会走这个地址。(比如index.html也会被匹配到因为没有注册html类型的mapping,而index.jsp就不会匹配因为jsp有注册了对应的servlet-mapping)

注意: 在tomcat的安装路径下也有一个web.xml(比如:“apache-tomcat-x.x.xx\conf\web.xml”),这个web.xml会和我们项目里的web.xml合并,该web.xml配置了默认的servlet和处理jsp的servlet

tomcat安装路径下的web.xml文件部分截图如下:

https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121924669.png


<url-pattern>/*</url-pattern>:

url-pattern为”/*“时,其实和<url-pattern>/</url-pattern>都表示匹配所有资源,只不过<url-pattern>/*</url-pattern>先于<url-pattern>/</url-pattern>匹配,意思就是/*的优先级比/高。在项目的servlet中如果配置了<url-pattern>/*</url-pattern>,在没有配置处理对应静态资源的servlet时,什么index.jsp,index.html,css,js啥的文件统统匹配到/*对应的servlet下,不过如果注册有更加清晰的路径比如<url-pattern>/test/*</url-pattern>,请求路径如果是\test\123之类的则是匹配到<url-pattern>/test/*</url-pattern>这个,而不是<url-pattern>/*</url-pattern>,具体原因可以看下面的源码和解读


Servlet匹配规则(tomcat源码)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
 private final void internalMapWrapper(ContextVersion contextVersion,
                                          CharChunk path,
                                          MappingData mappingData) throws IOException {

        int pathOffset = path.getOffset();
        int pathEnd = path.getEnd();
        boolean noServletPath = false;

        int length = contextVersion.path.length();
        if (length == (pathEnd - pathOffset)) {
            noServletPath = true;
        }
        int servletPath = pathOffset + length;
        path.setOffset(servletPath);

        // Rule 1 -- Exact Match
        MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
        internalMapExactWrapper(exactWrappers, path, mappingData);

        // Rule 2 -- Prefix Match
        boolean checkJspWelcomeFiles = false;
        MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
        if (mappingData.wrapper == null) {
            internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,
                                       path, mappingData);
            if (mappingData.wrapper != null && mappingData.jspWildCard) {
                char[] buf = path.getBuffer();
                if (buf[pathEnd - 1] == '/') {
                    /*
                     * Path ending in '/' was mapped to JSP servlet based on
                     * wildcard match (e.g., as specified in url-pattern of a
                     * jsp-property-group.
                     * Force the context's welcome files, which are interpreted
                     * as JSP files (since they match the url-pattern), to be
                     * considered. See Bugzilla 27664.
                     */
                    mappingData.wrapper = null;
                    checkJspWelcomeFiles = true;
                } else {
                    // See Bugzilla 27704
                    mappingData.wrapperPath.setChars(buf, path.getStart(),
                                                     path.getLength());
                    mappingData.pathInfo.recycle();
                }
            }
        }

        if(mappingData.wrapper == null && noServletPath &&
                contextVersion.object.getMapperContextRootRedirectEnabled()) {
            // The path is empty, redirect to "/"
            path.append('/');
            pathEnd = path.getEnd();
            mappingData.redirectPath.setChars
                (path.getBuffer(), pathOffset, pathEnd - pathOffset);
            path.setEnd(pathEnd - 1);
            return;
        }

        // Rule 3 -- Extension Match
        MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;
        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
            internalMapExtensionWrapper(extensionWrappers, path, mappingData,
                    true);
        }

        // Rule 4 -- Welcome resources processing for servlets
        if (mappingData.wrapper == null) {
            boolean checkWelcomeFiles = checkJspWelcomeFiles;
            if (!checkWelcomeFiles) {
                char[] buf = path.getBuffer();
                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
            }
            if (checkWelcomeFiles) {
                for (int i = 0; (i < contextVersion.welcomeResources.length)
                         && (mappingData.wrapper == null); i++) {
                    path.setOffset(pathOffset);
                    path.setEnd(pathEnd);
                    path.append(contextVersion.welcomeResources[i], 0,
                            contextVersion.welcomeResources[i].length());
                    path.setOffset(servletPath);

                    // Rule 4a -- Welcome resources processing for exact macth
                    internalMapExactWrapper(exactWrappers, path, mappingData);

                    // Rule 4b -- Welcome resources processing for prefix match
                    if (mappingData.wrapper == null) {
                        internalMapWildcardWrapper
                            (wildcardWrappers, contextVersion.nesting,
                             path, mappingData);
                    }

                    // Rule 4c -- Welcome resources processing
                    //            for physical folder
                    if (mappingData.wrapper == null
                        && contextVersion.resources != null) {
                        String pathStr = path.toString();
                        WebResource file =
                                contextVersion.resources.getResource(pathStr);
                        if (file != null && file.isFile()) {
                            internalMapExtensionWrapper(extensionWrappers, path,
                                                        mappingData, true);
                            if (mappingData.wrapper == null
                                && contextVersion.defaultWrapper != null) {
                                mappingData.wrapper =
                                    contextVersion.defaultWrapper.object;
                                mappingData.requestPath.setChars
                                    (path.getBuffer(), path.getStart(),
                                     path.getLength());
                                mappingData.wrapperPath.setChars
                                    (path.getBuffer(), path.getStart(),
                                     path.getLength());
                                mappingData.requestPath.setString(pathStr);
                                mappingData.wrapperPath.setString(pathStr);
                            }
                        }
                    }
                }

                path.setOffset(servletPath);
                path.setEnd(pathEnd);
            }

        }

        /* welcome file processing - take 2
         * Now that we have looked for welcome files with a physical
         * backing, now look for an extension mapping listed
         * but may not have a physical backing to it. This is for
         * the case of index.jsf, index.do, etc.
         * A watered down version of rule 4
         */
        if (mappingData.wrapper == null) {
            boolean checkWelcomeFiles = checkJspWelcomeFiles;
            if (!checkWelcomeFiles) {
                char[] buf = path.getBuffer();
                checkWelcomeFiles = (buf[pathEnd - 1] == '/');
            }
            if (checkWelcomeFiles) {
                for (int i = 0; (i < contextVersion.welcomeResources.length)
                         && (mappingData.wrapper == null); i++) {
                    path.setOffset(pathOffset);
                    path.setEnd(pathEnd);
                    path.append(contextVersion.welcomeResources[i], 0,
                                contextVersion.welcomeResources[i].length());
                    path.setOffset(servletPath);
                    internalMapExtensionWrapper(extensionWrappers, path,
                                                mappingData, false);
                }

                path.setOffset(servletPath);
                path.setEnd(pathEnd);
            }
        }


        // Rule 7 -- Default servlet
        if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
            if (contextVersion.defaultWrapper != null) {
                mappingData.wrapper = contextVersion.defaultWrapper.object;
                mappingData.requestPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
                mappingData.wrapperPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
                mappingData.matchType = ApplicationMappingMatch.DEFAULT;
            }
            // Redirection to a folder
            char[] buf = path.getBuffer();
            if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
                String pathStr = path.toString();
                // Note: Check redirect first to save unnecessary getResource()
                //       call. See BZ 62968.
                if (contextVersion.object.getMapperDirectoryRedirectEnabled()) {
                    WebResource file;
                    // Handle context root
                    if (pathStr.length() == 0) {
                        file = contextVersion.resources.getResource("/");
                    } else {
                        file = contextVersion.resources.getResource(pathStr);
                    }
                    if (file != null && file.isDirectory()) {
                        // Note: this mutates the path: do not do any processing
                        // after this (since we set the redirectPath, there
                        // shouldn't be any)
                        path.setOffset(pathOffset);
                        path.append('/');
                        mappingData.redirectPath.setChars
                            (path.getBuffer(), path.getStart(), path.getLength());
                    } else {
                        mappingData.requestPath.setString(pathStr);
                        mappingData.wrapperPath.setString(pathStr);
                    }
                } else {
                    mappingData.requestPath.setString(pathStr);
                    mappingData.wrapperPath.setString(pathStr);
                }
            }
        }

        path.setOffset(pathOffset);
        path.setEnd(pathEnd);
    }

分析

从源码结合注释可以看出servlet的匹配规则顺序是:

  1. 精确匹配:查找一个与请求路径匹配的 Servlet(比如:请求路径是/test/login,如果配置有<url-pattern>/test/login</url-pattern>的servlet映射则会匹配该servlet,没有接着下一个规则即前缀路径匹配,下面的规则也是如此,没有匹配到就到下一个规则,直到默认的处理)
  2. 前缀路径匹配:通配符匹配,匹配前缀最长的(比如<url-pattern>/test1/test2/*</url-pattern><url-pattern>/test1/*</url-pattern>,请求路径假设是/test/login/123则匹配的是<url-pattern>/test1/test2/*</url-pattern>,而不是<url-pattern>/test1/*</url-pattern>
  3. 扩展名匹配:如果请求路径最后一部分包含扩展名,就像 .html之类的,则尝试匹配注册有处理此扩展名请求的 Servlet(比如:<url-pattern>*.html</url-pattern>
  4. 欢迎资源处理:查找是否有匹配的默认首页文件(在请求路径后加上欢迎文件列表比如请求的是/test,则添加后就是/test/index.html,欢迎列表默认有[index.html,index.htm,index.jsp],这些都会依次尝试匹配) 下图就是tomcat默认的欢迎列表: https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121925981.png
  5. 默认匹配:前几个规则没有匹配成功,交由默认 Servlet处理,如果在自己项目的web.xml配置有<url-pattern>/</url-pattern>则这个就是默认的servlet,即它取代了tomcat默认的servlet

举个例子

假设项目结构如下(故意加了一个index.jsp) https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121926398.png 项目的上下文是/test https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121926309.png 在浏览器访问localhost:8080/test https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121927573.png

之后会被重定向到localhost:8080/test/ https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121927486.png

假设没有在项目的web.xml中配置任何servlet(但是不要忘记了还有tomcat默认的servlet) https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121927295.png

http://localhost:8080/test/这个请求进来,经过处理实际进行匹配的路径是/,然后开始第一个匹配规则精确匹配,这时候因为在web.xml中没有配置有明确路径的servlet,所以第一个匹配失败,接着尝试前缀匹配,和第一个规则一样,没有配置这类的servlet所以也失败了,然后是拓展名匹配,默认tomcat的web.xml配置有处理拓展名为jspjspx的servlet,但是这个也不符合/这个路径;接着下一个规则就是欢迎资源处理,这里会给路径加上欢迎列表中指定的文件。

默认的欢迎列表如下 https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121928123.png 即给路径/加上index.html变成/index.html,之后欢迎列表会按顺序依此在路径上添加后进行尝试匹配,首先开始尝试/index.html

  1. 一开始先进行精确匹配,和一开始一样,因为在web.xml中没有配置有明确路径的servlet,所以匹配失败
  2. 之后开始前缀匹配,这个也和之前一样,没有配置这类的servlet所以也失败了
  3. 在尝试过加上欢迎列表后的精确匹配和前缀匹配后,如果还没找到对应的处理的serlvet,会判断项目物理上是否存在欢迎列表中指定的文件即index.html如果物理上存在在且注册有对应的拓展名servlet映射,就会交给该servlet处理,如果没有对应的拓展名映射的servlet就会交给默认的servlet处理。显然index.html物理上是存在的,但是我们没有配置处理html拓展名的servlet,所以会交给默认的servlet处理,之后就不会继续尝试/index.htm/index.jsp了。默认的servlet是处理静态资源的所以可以正确响应index.html文件。

响应后浏览器显示 https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121928242.png

路径配置为/导致拦截静态资源问题的解决方案

这个是我的项目结构
https://jsdelivr.codeqihan.com/gh/MysticalDream/images/assets/202311121929191.png

static包下放了css,js,image等资源

我的 web.xml 的部分配置

1
2
3
4
5
6
7
8
9
   <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/static/*</url-pattern>
    </servlet-mapping>

这样配置映射可以将html文件和static包下的其他的静态资源的请求交给tomcat默认的servlet处理

总结

以上就是我对servlet路径匹配的一些见解

喜欢这篇文章就点个赞呗!!!