Servlet映射路径匹配解析详解

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

开头

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

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

 servlet01com.monian.study.servlet.Servlet01 servlet01/test/servlet/get servlet02com.monian.study.servlet.Servlet02 servlet02/test/servlet/* servlet03com.monian.study.servlet.Servlet03 servlet03/test/* servlet04com.monian.study.servlet.Servlet04 servlet04/ servlet05com.monian.study.servlet.Servlet05 servlet05*.do

相应各个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 

匹配路径代码

org.apache.catalina.mapper.Mapper#find(org.apache.catalina.mapper.Mapper.MapElement[], org.apache.tomcat.util.buf.CharChunk, int, int)

// 从map找到一个最与路径匹配的 private static final  int find(MapElement[] 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; }>>> 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)  compareTo.charAt(i)) { result = 1; } else if (c[i + start]  (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 > 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

默认匹配

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

以上就是Servlet映射路径匹配解析详解的详细内容,更多关于Servlet映射路径匹配的资料请关注0133技术站其它相关文章!

以上就是Servlet映射路径匹配解析详解的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » Java