开头

servlet是javaweb用来处理请求和响应的重要对象,本文将从源码的角度分析tomcat内部是如何根据请求路径匹配得到处理请求的servlet的

假设有一个request请求路径为/text/servlet/get,并且在web.xml中配置了4个servlet,代码如下,那么该请求调用的是哪一个servlet呢?

<servlet>
<servlet-name>servlet01</servlet-name>
<servlet-class>com.monian.study.servlet.Servlet01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet01</servlet-name>
<url-pattern>/test/servlet/get</url-pattern>
</servlet-mapping> <servlet>
<servlet-name>servlet02</servlet-name>
<servlet-class>com.monian.study.servlet.Servlet02</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet02</servlet-name>
<url-pattern>/test/servlet/*</url-pattern>
</servlet-mapping> <servlet>
<servlet-name>servlet03</servlet-name>
<servlet-class>com.monian.study.servlet.Servlet03</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet03</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping> <servlet>
<servlet-name>servlet04</servlet-name>
<servlet-class>com.monian.study.servlet.Servlet04</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet04</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping> <servlet>
<servlet-name>servlet05</servlet-name>
<servlet-class>com.monian.study.servlet.Servlet05</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet05</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

相应各个servlet的代码,代码很简单,调用哪一个servlet就输出哪个servlet的名称:

servlet代码
public class Servlet01 extends HttpServlet {

  @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Servlet01");
}
} public class Servlet02 extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Servlet02");
}
} public class Servlet03 extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Servlet03");
}
} public class Servlet04 extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Servlet04");
}
} public class Servlet05 extends HttpServlet { @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Servlet05");
} }

源码

org.apache.catalina.mapper.Mapper#internalMapWrapper
// 在本例子中 path = '/zxq/test/servlet/get',用offset和end来控制路径部分长度
// contextPath = '/zxq'
private final void internalMapWrapper(ContextVersion contextVersion,
CharChunk path,
MappingData mappingData) throws IOException { int pathOffset = path.getOffset();
int pathEnd = path.getEnd();
boolean noServletPath = false; // contextVersion.path = '/zxq'
int length = contextVersion.path.length();
if (length == (pathEnd - pathOffset)) {
noServletPath = true;
}
int servletPath = pathOffset + length;
// path = '/text/servlet/get'
path.setOffset(servletPath); // 规则1:先开始精确匹配
MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
internalMapExactWrapper(exactWrappers, path, mappingData); // 规则2:前缀匹配,也就是路径匹配
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 = MappingMatch.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);
}

匹配路径代码

org.apache.catalina.mapper.Mapper#find(org.apache.catalina.mapper.Mapper.MapElement[], org.apache.tomcat.util.buf.CharChunk, int, int)
// 从map找到一个最与路径匹配的
private static final <T> int find(MapElement<T>[] map, CharChunk name,
int start, int end) { int a = 0;
int b = map.length - 1; // Special cases: -1 and 0
if (b == -1) {
return -1;
} // -1表示完全不匹配,直接返回
if (compare(name, start, end, map[0].name) < 0 ) {
return -1;
}
// 完全匹配或部分匹配,且只有一个待匹配的servlet直接返回
if (b == 0) {
return 0;
} // 类似于二分查找,找到一个最长路径匹配
int i = 0;
while (true) {
i = (b + a) >>> 1;
int result = compare(name, start, end, map[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, start, end, map[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
} } private static final int compare(CharChunk name, int start, int end,
String compareTo) {
int result = 0;
char[] c = name.getBuffer();
int len = compareTo.length();
if ((end - start) < len) {
len = end - start;
}
// 比较url-pattern与 请求路径path,若有一个字符不相等退出循环
for (int i = 0; (i < len) && (result == 0); i++) {
if (c[i + start] > compareTo.charAt(i)) {
result = 1;
} else if (c[i + start] < compareTo.charAt(i)) {
result = -1;
}
} // 都相等的话再比较长度,请求路径长度比待匹配部分长
if (result == 0) {
if (compareTo.length() > (end - start)) {
result = -1;
} else if (compareTo.length() < (end - start)) {
result = 1;
}
}
// result=0代表完全匹配, result=-1代表不匹配,result=1代表开头部分匹配
return result;
}

针对上述的匹配举个例子,假设有两个servlet都是通配符匹配的,url-pattern为 /test/one/* 和/test/* ,tomcat解析的时候会去掉通配符再排序['/test', 'test/one'],之后再去匹配数据中的元素也就是map[i].name,匹配路径 '/test/one/two'会返回url-parttern=/test/one/* 的这个servlet,这就是最长路径匹配

精确匹配

可以看到符合精确匹配的只有servlet01,且name就是它配置的url-pattern值,然后与requestPath进行匹配

private final void internalMapExactWrapper
(MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) {
// 找到一个与path精确匹配的wrapper
MappedWrapper wrapper = exactFind(wrappers, path);
if (wrapper != null) {
mappingData.requestPath.setString(wrapper.name);
mappingData.wrapper = wrapper.object;
if (path.equals("/")) {
// Special handling for Context Root mapped servlet
mappingData.pathInfo.setString("/");
mappingData.wrapperPath.setString("");
// This seems wrong but it is what the spec says...
mappingData.contextPath.setString("");
mappingData.matchType = MappingMatch.CONTEXT_ROOT;
} else {
mappingData.wrapperPath.setString(wrapper.name);
mappingData.matchType = MappingMatch.EXACT;
}
}
} private static final <T, E extends MapElement<T>> E exactFind(E[] map,
CharChunk name) {
// find方法会返回部分匹配或完全匹配的map
int pos = find(map, name);
if (pos >= 0) {
E result = map[pos];
// 完全匹配
if (name.equals(result.name)) {
return result;
}
}
return null;
}

显而易见的开头那个request与servlet01的url-pattern是精确匹配的

通配符匹配 (路径匹配)

接下来web.xml去掉servlet01的配置,只剩下4个servlet,从前面来看,精确匹配肯定是失败的因为现在去掉servlet01已经没有符合要求的servlet去精确匹配了,只能进行路径匹配了,而路径匹配符合要求的有两个servlet

/**
* Wildcard mapping.
*/
private final void internalMapWildcardWrapper
(MappedWrapper[] wrappers, int nesting, CharChunk path,
MappingData mappingData) { int pathEnd = path.getEnd(); int lastSlash = -1;
int length = -1;
// 找一个最匹配path路径的,根据上面的匹配代码可以得到servlet02
int pos = find(wrappers, path);
if (pos != -1) {
boolean found = false;
while (pos >= 0) {
if (path.startsWith(wrappers[pos].name)) {
length = wrappers[pos].name.length();
if (path.getLength() == length) {
found = true;
break;
// path不以/开头,则重新找
} else if (path.startsWithIgnoreCase("/", length)) {
found = true;
break;
}
}
// 获取path最后一个/ 所在的位置
if (lastSlash == -1) {
lastSlash = nthSlash(path, nesting + 1);
} else {
lastSlash = lastSlash(path);
}
path.setEnd(lastSlash);
pos = find(wrappers, path);
}
path.setEnd(pathEnd);
if (found) {
mappingData.wrapperPath.setString(wrappers[pos].name);
if (path.getLength() > length) {
mappingData.pathInfo.setChars
(path.getBuffer(),
path.getOffset() + length,
path.getLength() - length);
}
mappingData.requestPath.setChars
(path.getBuffer(), path.getOffset(), path.getLength());
mappingData.wrapper = wrappers[pos].object;
mappingData.jspWildCard = wrappers[pos].jspWildCard;
mappingData.matchType = MappingMatch.PATH;
}
}
}

因此servlet02是匹配的,输出

若再web.xml去掉servlet02,那么匹配的就是servlet03了

另外我们可以从上面的代码得到若请求路径path = '/test/servlet/get', 则 '/*' 、 '/test/*' 、 '/test/servlet/*' 、 '/test/servlet/get/*' 与之匹配,'/test/serv/*' 这种不匹配

路径匹配是能匹配请求路径以 .jsp 、.html结尾的request的

扩展名匹配(后缀匹配)

web.xml中注释servlet02和servlet03后,再次访问.jsp后缀结尾的请求就会直接报404了,可以看后续的匹配逻辑虽然能匹配到处理.jsp的servlet但我们并没有在相应路径下配置jsp文件,那么自然报404错误了

下图可以看到后缀匹配的servlet有三个,一个我们自定义的后缀为do,另外两个jsp和jspx是tomcat内置的默认处理jsp的servlet

/**
* Extension mappings.
*
* @param wrappers Set of wrappers to check for matches
* @param path Path to map
* @param mappingData Mapping data for result
* @param resourceExpected Is this mapping expecting to find a resource
*/
private final void internalMapExtensionWrapper(MappedWrapper[] wrappers,
CharChunk path, MappingData mappingData, boolean resourceExpected) {
char[] buf = path.getBuffer();
int pathEnd = path.getEnd();
int servletPath = path.getOffset();
int slash = -1;
for (int i = pathEnd - 1; i >= servletPath; i--) {
if (buf[i] == '/') {
slash = i;
break;
}
}
if (slash >= 0) {
int period = -1;
for (int i = pathEnd - 1; i > slash; i--) {
if (buf[i] == '.') {
period = i;
break;
}
}
if (period >= 0) {
// 截取到后缀的字符位置 匹配
path.setOffset(period + 1);
path.setEnd(pathEnd);
MappedWrapper wrapper = exactFind(wrappers, path);
if (wrapper != null
&& (resourceExpected || !wrapper.resourceOnly)) {
mappingData.wrapperPath.setChars(buf, servletPath, pathEnd
- servletPath);
mappingData.requestPath.setChars(buf, servletPath, pathEnd
- servletPath);
mappingData.wrapper = wrapper.object;
mappingData.matchType = MappingMatch.EXTENSION;
}
path.setOffset(servletPath);
path.setEnd(pathEnd);
}
}
}

根据find的匹配逻辑可以匹配到我们自定义的servlet05,输出

首页welcome资源匹配

若上述匹配都失败了则尝试寻找默认的资源文件,默认有三个,也可以自定义配置

假设请求路径为http://localhost:8082/zxq/ 以'/'结尾,那么会尝试将文件名加到path后面,以index.jsp为例,加完后路径为'/zxq/index.jsp',之后会以此新路径再去尝试精确匹配、路径匹配、物理文件查找再进行扩展名匹配顺序查找,直到找到能处理此path的servlet

在webapp目录下加一个index.jsp文件之后能成功访问到,执行此请求的就是tomcat默认的jsp servlet

默认匹配

<url-pattern>/</url-pattern> '/'就是默认匹配,当上述匹配都失败的时候,则启用这个servlet,也就是本文中的servlet04

servlet映射路径匹配解析的更多相关文章

  1. servlet映射路径

    1 访问映射过程 问题:访问URL:http://localhost:8080/day10/first  ,服务器如何相应的? 前提: tomcat服务器启动时,首先加载webapps中的每个web应 ...

  2. Servlet虚拟路径匹配规则

    当 Servlet 容器接收到请求后,容器会将请求的 URL 减去当前应用的上下文路径,使用剩余的字符串作为映射 URL 与 Servelt 虚拟路径进行匹配,匹配成功后将请求交给相应的 Servle ...

  3. servlet路径映射中的完全路径匹配、目录匹配、扩展名匹配

    在servlet路径映射中,关于url-pattern的配置有三种,分别是完全路径匹配.目录匹配.扩展名匹配 其优先级分别为:完全路径匹配>目录匹配>扩展名匹配: 一.三种路径印射的区别 ...

  4. 001_JavaWeb之Servlet的路径映射问题

    001_JavaWeb之Servlet的路径映射问题 在web.xml中写入: <servlet> <servlet-name>DeleteStudent</servle ...

  5. servlet虚拟路径映射

    在web.xml文件中,一个<servlet-mapping>元素用于映射一个Servlet的对外访问路径,该路径也称为虚拟路径.例如<url-pattern>/TestSer ...

  6. Spring MVC的路径匹配

    Spring MVC中的路径匹配比起标准web.xml的servlet映射要灵活得多.路径匹配的默认策略是由org.springframework.util.AntPathMatcher实现的.顾名思 ...

  7. Java开发学习(二十四)----SpringMVC设置请求映射路径

    一.环境准备 创建一个Web的Maven项目 参考Java开发学习(二十三)----SpringMVC入门案例.工作流程解析及设置bean加载控制中环境准备 pom.xml添加Spring依赖 < ...

  8. 转:Servlet的url匹配以及url-pattern详解

    Servlet是J2EE开发中常用的技术,使用方便,配置简单,老少皆宜.估计大多数朋友都是直接配置用,也没有关心过具体的细节,今天遇到一个问题,上网查了servlet的规范才发现,servlet中的u ...

  9. servlet的url-pattern匹配规则详细描述

    一.概述 在利用servlet或Filter进行url请求的匹配时,很关键的一点就是匹配规则,但servlet容器中的匹配规则既不是简单的通配,也不是正则表达式,而是由自己的规则,比较容易混淆.本文来 ...

随机推荐

  1. Python实现将excel文件转化为html文件

    需要转化的excel文件(nsrxx.xlsx): 源代码: import pandas as pdimport codecspd.set_option('display.width', 1000)p ...

  2. C#实现找二维数组中的鞍点

    鞍点定义:该位置上的元素值在行中最大,在该列上最小 代码示例: using System; using System.Collections.Generic; using System.Linq; u ...

  3. 万字+28张图带你探秘小而美的规则引擎框架LiteFlow

    大家好,今天给大家介绍一款轻量.快速.稳定可编排的组件式规则引擎框架LiteFlow. 一.LiteFlow的介绍 LiteFlow官方网站和代码仓库地址 官方网站:https://yomahub.c ...

  4. Kafka到底有多高可靠?(RNG NB)

    在聊Kafka高可靠之前,先在评论区来波RNG NB好不好! 什么叫可靠性? 大家都知道,系统架构有三高:「高性能.高并发和高可用」,三者的重要性不言而喻. 对于任意系统,想要同时满足三高都是一件非常 ...

  5. Asp.Net Core Identity 多数据库支持

    Asp.Net Core Identity 是.Net自带的身份认证系统,支持用户界面 (UI) 登录功能,并且管理用户.密码.配置文件数据.角色.声明.令牌.电子邮件确认等等.使用Visual St ...

  6. mac M1 php扩展 xlswriter 编译安装爬坑记录

    电脑配置 MacBook Pro(14英寸,2021年) 系统版本 macOS Monterey 12.4 芯片 Apple M1 Pro PHP环境 MAMP Pro Version 6.6.1 ( ...

  7. Eclipse历史版本下载和选择对应的java版本

    下载Eclipse 官网: https://www.eclipse.org/ 直达 直接进入连接:https://www.eclipse.org/downloads/packages/installe ...

  8. 关于Vue Element组件el-checkbox与el-select默认选中值的几点注意事项

    el-select 示例: 代码: <el-select v-model="doc.zhic" placeholder="请选择"> <el- ...

  9. MySQL-5-TCL,视图,变量,存储过程和函数,流程控制

    TCL:Transaction Control Language事务控制语言 TCL 事务的特点 acid: 原子性(Atomicity),一致性(Consistency),隔离性(isolation ...

  10. SAP setting and releasing locks

    REPORT demo_transaction_enqueue MESSAGE-ID sabapdocu. TABLES sflight. DATA text(8) TYPE c. DATA ok_c ...