【tomcat路径匹配源码分析】搞懂tomcat中web.xml配置servlet的url

编程入门 行业动态 更新时间:2024-10-26 06:28:53

【tomcat<a href=https://www.elefans.com/category/jswz/34/1771438.html style=路径匹配源码分析】搞懂tomcat中web.xml配置servlet的url"/>

【tomcat路径匹配源码分析】搞懂tomcat中web.xml配置servlet的url

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

  • 前言
    • 结论
    • Servlet匹配规则(tomcat源码)
    • 分析
    • 举个例子
    • 路径配置为`/`导致拦截静态资源问题的解决方案
  • 总结


原文地址

前言


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

但是这个会出现一个问题就是这个也会把 静态资源(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文件部分截图如下:


<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源码)

 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 MatchMappedWrapper[] exactWrappers = contextVersion.exactWrappers;internalMapExactWrapper(exactWrappers, path, mappingData);// Rule 2 -- Prefix Matchboolean 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 27704mappingData.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 MatchMappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;if (mappingData.wrapper == null && !checkJspWelcomeFiles) {internalMapExtensionWrapper(extensionWrappers, path, mappingData,true);}// Rule 4 -- Welcome resources processing for servletsif (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 macthinternalMapExactWrapper(exactWrappers, path, mappingData);// Rule 4b -- Welcome resources processing for prefix matchif (mappingData.wrapper == null) {internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,path, mappingData);}// Rule 4c -- Welcome resources processing//            for physical folderif (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 servletif (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 folderchar[] 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 rootif (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默认的欢迎列表:
  5. 默认匹配:前几个规则没有匹配成功,交由默认 Servlet处理,如果在自己项目的web.xml配置有<url-pattern>/</url-pattern>则这个就是默认的servlet,即它取代了tomcat默认的servlet

举个例子

假设项目结构如下(故意加了一个index.jsp)

项目的上下文是/test

在浏览器访问localhost:8080/test

之后会被重定向到localhost:8080/test/

假设没有在项目的web.xml中配置任何servlet(但是不要忘记了还有tomcat默认的servlet)

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

默认的欢迎列表如下

即给路径/加上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文件。

响应后浏览器显示

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

这个是我的项目结构

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

我的 web.xml 的部分配置

   <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路径匹配的一些见解

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


更多推荐

【tomcat路径匹配源码分析】搞懂tomcat中web.xml配置servlet的url

本文发布于:2024-03-04 10:04:10,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1708967.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:路径   源码   tomcat   web   url

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!