Mapper对于Tomcat或者jetty这种应用服务器来说应该算是非常重要的一个东西了。。
首先来说它是干嘛用的,听名字就基本上能猜出来,对于请求,对这个请求进行路由,交给应该负责处理这个请求的最终代码就是Mapp而应该干的或。。
对于servlet来说,对应的就是一次http请求该交给哪一个servlet来处理。
其实以前在看jetty的代码的时候就相当于看过了一种实现的方式,jetty采用的是一种类似于tries(字典树)的查询来进行请求的路由。。。感觉还算是蛮不错的吧。。毕竟字典树在做基于字符串的查询效率还是很高的。。
那么接下来来大体的来说一下tomcat是怎么实现的吧。。。嗯,最关键的就是:二分搜索,字符串也是可以排序的嘛,也就是字典序,那么也就可以做二分搜索咯。。。
在开始具体的代码之前,先来看一个简略的结构图吧:
这个是整个Mapper的比较简单的总体的结构层次图吧,通过以前的分析我们知道,在tomcat服务器中,可以有多个service,每个service只能有一个engine,而一个engine可以部署多个host,一个host又可以部署多个context,而mapper对象是属于service所拥有的。。。也符合上面的层次图。。。
先来看看上面提到一些对象的定义吧,他们都是定义在Mapper里面的嵌套类:
protected abstract static class MapElement<T> { public String name = null; //名字 public T object = null; //对应的对象,例如是host对象,context对象或者wrapper对象啥的 } // ------------------------------------------------------- Host Inner Class protected static final class MappedHost //对host的map信息 extends MapElement<Host> { public ContextList contextList = null; //有一个contextlist } // ------------------------------------------------ ContextList Inner Class protected static final class ContextList { //在mappedhost里面将会用其来存拥有的context的信息 public MappedContext[] contexts = new MappedContext[0]; //mappedcontext对象的数组 public int nesting = 0; //所有的context的path中,最多的斜线数目 } // ---------------------------------------------------- Context Inner Class protected static final class MappedContext extends MapElement<Context> { //对context的map的信息 public ContextVersion[] versions = new ContextVersion[0]; //版本的数组 } protected static final class ContextVersion extends MapElement<Context> { //某个context的某个版本的具体信息 public String path = null; //path public String[] welcomeResources = new String[0]; //welcome的数据 public WebResourceRoot resources = null; //操作当前web应用程序的资源 public MappedWrapper defaultWrapper = null; //默认的wrapper public MappedWrapper[] exactWrappers = new MappedWrapper[0]; //对wrapper的精确的map public MappedWrapper[] wildcardWrappers = new MappedWrapper[0]; //基于通配符的map public MappedWrapper[] extensionWrappers = new MappedWrapper[0]; //基于扩展名的map public int nesting = 0; // 属于这个context的所有servlet的path里面最大斜线数目 } // ---------------------------------------------------- Wrapper Inner Class protected static class MappedWrapper //对wrapper对象的map信息 extends MapElement<Wrapper> { public boolean jspWildCard = false; public boolean resourceOnly = false; }
这里最基本的类型是MapElement,它是一个泛型吧,属性首先是名字,对于host来说,那么就是host的名字了,对于context来说那就是context的path了。。。。以此类推。。
然后是MappedHost的定义,这里其实也就稍微扩展了一下MapElement类型吧,加入了一个ContextList,看名字就知道它用于保存当前host所拥有的所有的host对象。。。ContextList的定义我们可以看到其实也是用数组来保存MappedContext对象的。。。
然后是MappedContext的定义,这里扩展了 一个ContextVersion的数组,因对于context来说,可能在时间段上同一个context可能会重新部署啥的,就涉及到不同的版本了。。当然一般情况下这个数组的长度都是为1的,也就是只有一个版本。。。
接下来就是ContextVersion定义了,可以将它理解为具体的一个context的map的信息,有path,defaultWrapper,以及wrapper的匹配什么的。。分为精确匹配,通配符匹配,和扩展名匹配。。
最后就是MappedWrapper了,扩展的东西不多吧。。主要是一些标记。。。
好啦,接下来来具体的看看Mapper的定义,先来看看它的重要的属性吧:
protected MappedHost[] hosts = new MappedHost[0]; // 对host的map信息 protected String defaultHostName = null; // engine使用的默认的host名字 protected Map<Context, ContextVersion> contextObjectToContextVersionMap = //对context的map信息,key是context对象,value是contextVersion对象 new ConcurrentHashMap<>();
hosts数组,用于保存所有的host的map信息,然后又一个defaultHostName,在engine对象的定义中有一个defaultHostName属性,就是对应的这里。。。
然后就是一个map,用于将context对象与具体的contextVersion匹配起来。。。
好啦,接下来来看看如何在mapper中添加对一个host。。
//添加host的map信息,第一个参数是hsot的名字,第二个是别名,第三个是host对象 public synchronized void addHost(String name, String[] aliases, Host host) { MappedHost[] newHosts = new MappedHost[hosts.length + 1]; //创建MappedHost对象的数组,这里长度需要加1 MappedHost newHost = new MappedHost(); //创建一个MappedHost对象,用于保存host的map信息 ContextList contextList = new ContextList(); //ContextList对象,用于保存context newHost.name = name; //设置名字 newHost.contextList = contextList; //设置 newHost.object = host; //设置对应的host对象 if (insertMap(hosts, newHosts, newHost)) { //这里需要复制以前的host的map信息,并维护好排序 hosts = newHosts; //指向新新的数组 } for (int i = 0; i < aliases.length; i++) { //遍历这个host的所有的别名,为每一个别名都创建一次map信息,感觉这里做了挺多重复的事情 newHosts = new MappedHost[hosts.length + 1]; newHost = new MappedHost(); newHost.name = aliases[i]; newHost.contextList = contextList; //这里指向同一个contextList newHost.object = host; if (insertMap(hosts, newHosts, newHost)) { hosts = newHosts; } } }
代码应该很简单的吧,这里说白了扩展hosts数组,创建新的MappedHost对象,将其保存起来。。。然后对于最终保存的数组,mappedHost对象是按照name的排序好的。。这里就需要来具体的看一下insertMap方法了。。
private static final <T> boolean insertMap //将oldmap里面的信息保存到newMap,并且还要加入newElement,这里还要基于name进行排序 (MapElement<T>[] oldMap, MapElement<T>[] newMap, MapElement<T> newElement) { int pos = find(oldMap, newElement.name); //在old里面,最近接新的元素的name的位置,这里返回的pos要么name相当,要么最左侧 if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) { //这里表示有名字相同的,那么失败 return false; } //分段拷贝,这样拷贝完了之后也是排好序的 System.arraycopy(oldMap, 0, newMap, 0, pos + 1); //对数组拷贝,这里相当于先拷贝小的 newMap[pos + 1] = newElement; System.arraycopy (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1); return true; }
这里首先调用find方法,通过名字在old里面去寻找是否有相同的,如果有的话,那么返回相同的下标,如果没有的话就返回小于当前name的最大的一个,也就是最左侧的那个。。。然后再进行分段的拷贝,这样也就可以保证按照name的排序进行保存了。。。来看看find方法吧:
//在排序的element里面找与name最接近的,相等的或者小于name的最大的,也就是最左侧的 //这里其实就是一个二分查找 private static final <T> int find(MapElement<T>[] map, String name) { int a = 0; int b = map.length - 1; // Special cases: -1 and 0 if (b == -1) { return -1; } if (name.compareTo(map[0].name) < 0) { return -1; } if (b == 0) { return 0; } int i = 0; while (true) { i = (b + a) / 2; int result = name.compareTo(map[i].name); if (result > 0) { a = i; } else if (result == 0) { return i; } else { b = i; } if ((b - a) == 1) { int result2 = name.compareTo(map[b].name); if (result2 < 0) { return a; } else { return b; } } } }
就是一个基于字典序排序的二分搜索嘛。。。。。
好啦,接下来来看看如何添加一个context吧:
//为host添加context,其实也就是添加context的map信息,第一个参数是所属的host的名字,第二个是这个context的path(/examples啥的),第三个是版本,第三个是对应的context对象,接着是首页?,最后是root的资源引用 public void addContextVersion(String hostName, Host host, String path, String version, Context context, String[] welcomeResources, WebResourceRoot resources) { MappedHost[] hosts = this.hosts; int pos = find(hosts, hostName); //根据host的名字来查找相应的mapedHost对象的下标 if( pos <0 ) { //如果没有,那么添加这个host addHost(hostName, new String[0], host); // hosts = this.hosts; pos = find(hosts, hostName); //重新获取下标 } if (pos < 0) { log.error("No host found: " + hostName); } MappedHost mappedHost = hosts[pos]; //获取mappedHost对象 if (mappedHost.name.equals(hostName)) { //名字肯定要相等了 int slashCount = slashCount(path); //获取path里面的斜线的数量 synchronized (mappedHost) { MappedContext[] contexts = mappedHost.contextList.contexts; //获取相应的host的mappedContext的数组 // Update nesting if (slashCount > mappedHost.contextList.nesting) { mappedHost.contextList.nesting = slashCount; //记录最大的context的path里面的斜线的数目 } int pos2 = find(contexts, path); //在context数组里面获取一个位置,这里要么返回相当的path,要么返回左侧的 if (pos2 < 0 || !path.equals(contexts[pos2].name)) { //如果有path相等的,那么不幸 MappedContext newContext = new MappedContext(); //创建context的map信息 newContext.name = path; MappedContext[] newContexts = new MappedContext[contexts.length + 1]; //将数组变大 if (insertMap(contexts, newContexts, newContext)) { //更新mappedContext对象的数组 mappedHost.contextList.contexts = newContexts; //指向新的数组 } pos2 = find(newContexts, path); //获取新的下标 } MappedContext mappedContext = mappedHost.contextList.contexts[pos2]; //获取刚刚加入的MappedContext对象 ContextVersion[] contextVersions = mappedContext.versions; //获取版本信息 ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1]; //这里会初始化将contextVersion的数组为1,初始化的时候contextVersion的数组长度为0 ContextVersion newContextVersion = new ContextVersion(); //创建一个ContextVersion对象 newContextVersion.path = path; //设置path newContextVersion.name = version; // 设置version的名字 newContextVersion.object = context; //保存context对象 newContextVersion.welcomeResources = welcomeResources; //保存welcomeResources newContextVersion.resources = resources; //保存WebResourceRoot对象 if (insertMap(contextVersions, newContextVersions, newContextVersion)) { //更新mappedContext的contextVersion的数组 mappedContext.versions = newContextVersions; //指向新的数组 contextObjectToContextVersionMap.put( //key是context,value是ContextVersion context, newContextVersion); } } } }
这个代码其实跟前面添加host差不多太对,只不过多了一个层次,首先更具hostName来查找要添加到的mappedHost对象,然后我们知道在mappedHost中有一个contextList,其实也就是一个MappedContext对象数组,然后接着就根据当年context的名字,创建一个新的MappedContext对象根据context的path的排序加入到contextList数组里面就好了,这里也就完成了将某个context加入到某个host的map过程。。。
那么最后就是如何加入wrapper对象了。。。
//添加warpper,首先是所属的host的名字,接着是contextpath,接着是context版本,接着是warpper的path,然后是warpper对象 public void addWrapper(String hostName, String contextPath, String version, String path, Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) { MappedHost[] hosts = this.hosts; int pos = find(hosts, hostName); //根据host的名字,查找mappedHost对象的下标 if (pos < 0) { return; } MappedHost host = hosts[pos]; //获取mapedHost对象 if (host.name.equals(hostName)) { //这里名字必须是相等的才行了 MappedContext[] contexts = host.contextList.contexts; //获取mappedContext的数组 int pos2 = find(contexts, contextPath); //找到相应的mappedContext的下标 if (pos2 < 0) { //找不到 log.error("No context found: " + contextPath ); return; } MappedContext context = contexts[pos2]; //获取所属的mappedContext对象 if (context.name.equals(contextPath)) { //这里必须相等才行了 ContextVersion[] contextVersions = context.versions; //获取context的版本数组 int pos3 = find(contextVersions, version); //寻找对一个的version的位置 if( pos3<0 ) { log.error("No context version found: " + contextPath + " " + version); return; } ContextVersion contextVersion = contextVersions[pos3]; //获取对应的version的map信息 if (contextVersion.name.equals(version)) { //这里也要相等才行 addWrapper(contextVersion, path, wrapper, jspWildCard, //在这个里面添加warrper对象 resourceOnly); } } } } /** * Adds a wrapper to the given context. * * @param context The context to which to add the wrapper * @param path Wrapper mapping * @param wrapper The Wrapper object * @param jspWildCard true if the wrapper corresponds to the JspServlet * @param resourceOnly true if this wrapper always expects a physical * resource to be present (such as a JSP) * and the mapping path contains a wildcard; false otherwise */ //将某个wrapper放到某个对应的context,第二个参数是servlet要map的path,后面是wrapper对象,第一个参数是应该加到的contextVersion对象 protected void addWrapper(ContextVersion context, String path, Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) { synchronized (context) { MappedWrapper newWrapper = new MappedWrapper(); // 创建一个mappedWrapper对象 newWrapper.object = wrapper; newWrapper.jspWildCard = jspWildCard; newWrapper.resourceOnly = resourceOnly; if (path.endsWith("/*")) { // 如果path的map是通配符类型的 // Wildcard wrapper newWrapper.name = path.substring(0, path.length() - 2); //将/*去掉 MappedWrapper[] oldWrappers = context.wildcardWrappers; // 获取通配符匹配的mappedWrapper数组 MappedWrapper[] newWrappers = //将长度加1 new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.wildcardWrappers = newWrappers; int slashCount = slashCount(newWrapper.name); if (slashCount > context.nesting) { context.nesting = slashCount; //更新当前context拥有的servlet的path里面最多的斜线数目 } } } else if (path.startsWith("*.")) { //表示是扩展名的mapper // Extension wrapper newWrapper.name = path.substring(2); MappedWrapper[] oldWrappers = context.extensionWrappers; //获取扩展名匹配的mappedWrapper的数组 MappedWrapper[] newWrappers = new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.extensionWrappers = newWrappers; } } else if (path.equals("/")) { // 表示是默认的wrapper // Default wrapper newWrapper.name = ""; context.defaultWrapper = newWrapper; } else { //最后就是精确的map了 // Exact wrapper if (path.length() == 0) { // Special case for the Context Root mapping which is // treated as an exact match newWrapper.name = "/"; } else { newWrapper.name = path; } MappedWrapper[] oldWrappers = context.exactWrappers; //获取精确的map MappedWrapper[] newWrappers = //更新map数组 new MappedWrapper[oldWrappers.length + 1]; if (insertMap(oldWrappers, newWrappers, newWrapper)) { context.exactWrappers = newWrappers; } } } }
这里可能就别context更多了一个层次,首先找到相应的mappedHost对象,然后找到相应的mappedContext对象,然后再擦入。。
然后这里在擦入的时候还有一点不一样的地方,因为servlet的map可能有多重,例如通配符匹配,精确匹配,扩展名匹配啥的。。这个都要区分进行。。上面的注释应该算是蛮清楚的吧。。。
好啦,如何添加map信息算是有一定的了解了吧。。那么接下来来看看如何来检索吧,也就是给一个http请求路径,然后检索出对应的host,context以及wrapper对象。。。
在httpprocessor中,如果生成了一个http请求,会将请求交给adapter对象来处理。。。而在adapter里面就会调当前servcie对象的mapper对象来对请求进行路由。。。其实也就是调用如下的方法:
//第一个是host的名字,第二个是请求路径,例如/examples/servlets/servlet/HelloWorldExample,第三个是版本,默认使用最后的版本,最后是保存map信息的地方 public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws Exception { if (host.isNull()) { host.getCharChunk().append(defaultHostName); } host.toChars(); uri.toChars(); internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData); }
对于最后map出来的信息,也就是哪一个host,context,wrapper会保存在mappingData参数中。。。
// 第一个参数是host的名字,第二个参数访问的path,第三个参数是version,最后保存map信息 private final void internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData) throws Exception { uri.setLimit(-1); MappedContext[] contexts = null; MappedContext context = null; ContextVersion contextVersion = null; int nesting = 0; // Virtual host mapping if (mappingData.host == null) { MappedHost[] hosts = this.hosts; //获取当前所有的host int pos = findIgnoreCase(hosts, host); //这里找到所属的host的下标 if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) { //如果能够找到的话 mappingData.host = hosts[pos].object; //保存map到的host contexts = hosts[pos].contextList.contexts; //所有的context nesting = hosts[pos].contextList.nesting; } else { //如果不能找到,那么就用默认的host if (defaultHostName == null) { return; } pos = find(hosts, defaultHostName); if ((pos != -1) && (defaultHostName.equals(hosts[pos].name))) { mappingData.host = hosts[pos].object; contexts = hosts[pos].contextList.contexts; nesting = hosts[pos].contextList.nesting; } else { return; } } } // Context mapping if (mappingData.context == null && contexts != null) { int pos = find(contexts, uri); //通过访问的path来找相应的context if (pos == -1) { return; // 找不到 } int lastSlash = -1; int uriEnd = uri.getEnd(); //刚开始end就是字符串的最后了 int length = -1; boolean found = false; while (pos >= 0) { //这里表示找到了可能可用的context if (uri.startsWith(contexts[pos].name)) { // 如果path的开头与context的名字相等 length = contexts[pos].name.length(); //获取context的name的长度 if (uri.getLength() == length) { //如果path的长度就等等与context的名字,那么表示path与context的名字相等 found = true; //找到了context break; } else if (uri.startsWithIgnoreCase("/", length)) { // 如果path在context的名字后面就是“/”了,那么也行 found = true; break; } } //到这里表示刚开始找到的context的不满足 if (lastSlash == -1) { lastSlash = nthSlash(uri, nesting + 1); } else { lastSlash = lastSlash(uri); } uri.setEnd(lastSlash); pos = find(contexts, uri); } uri.setEnd(uriEnd); //恢复end if (!found) { //找不到相应的context if (contexts[0].name.equals("")) { //如果有根context,那么就用它,因为有可能就直接访问类似于 www.baidu.com/aa.do?aa=1这种地址,就是根context context = contexts[0]; } } else { context = contexts[pos]; } if (context != null) { //设置context mappingData.contextPath.setString(context.name); } } if (context != null) { //这里主要是处理context的版本 ContextVersion[] contextVersions = context.versions; int versionCount = contextVersions.length; if (versionCount > 1) { Context[] contextObjects = new Context[contextVersions.length]; //默认是用最新的了 for (int i = 0; i < contextObjects.length; i++) { contextObjects[i] = contextVersions[i].object; } mappingData.contexts = contextObjects; //设置map出来的context } if (version == null) { // Return the latest version contextVersion = contextVersions[versionCount - 1]; } else { int pos = find(contextVersions, version); if (pos < 0 || !contextVersions[pos].name.equals(version)) { // Return the latest version contextVersion = contextVersions[versionCount - 1]; } else { contextVersion = contextVersions[pos]; } } mappingData.context = contextVersion.object; } // Wrapper mapping 找到相应的wrapper if ((contextVersion != null) && (mappingData.wrapper == null)) { internalMapWrapper(contextVersion, uri, mappingData); } } /** * Wrapper mapping. */ //wrapper的mapping信息,这里的path还带有前面的context的名字 private final void internalMapWrapper(ContextVersion contextVersion, CharChunk path, MappingData mappingData) throws Exception { int pathOffset = path.getOffset(); //当前path的游标 int pathEnd = path.getEnd(); //获取end int servletPath = pathOffset; //servlet的path的游标,这里刚开始设置为path的一样 boolean noServletPath = false; //这里主要是为了将前面的context的略过 int length = contextVersion.path.length(); //获取context的名字的长度 if (length != (pathEnd - pathOffset)) { //表示在context的名字后面还有servlet的名字啥的 servletPath = pathOffset + length; //将servlet的匹配游标向前加上context的名字长度 } else { noServletPath = true; //表示在url里面没有指定特定的servlet path.append('/'); //在path后面加上/ pathOffset = path.getOffset(); pathEnd = path.getEnd(); servletPath = pathOffset+length; //还是将servletpath的游标加上context的长度 } path.setOffset(servletPath); //设置path的游标 // 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) { // The path is empty, redirect to "/" 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()); } // Redirection to a folder char[] buf = path.getBuffer(); if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') { String pathStr = path.toString(); WebResource 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); } } } path.setOffset(pathOffset); path.setEnd(pathEnd); }
代码是在太多了吧,其实这里最关键的也就是一个二分搜索的过程。。。具体的细节就不交代了。。有兴趣自己看看就好了。。。
那么到这里位置tomcat如何对请求进行路由,就算是比较清楚了。。。用基于字典序的二分搜索,应该效率也不差吧。。。