Tomcat源码阅读之Mapper分析

Mapper对于Tomcat或者jetty这种应用服务器来说应该算是非常重要的一个东西了。。

首先来说它是干嘛用的,听名字就基本上能猜出来,对于请求,对这个请求进行路由,交给应该负责处理这个请求的最终代码就是Mapp而应该干的或。。

对于servlet来说,对应的就是一次http请求该交给哪一个servlet来处理。

其实以前在看jetty的代码的时候就相当于看过了一种实现的方式,jetty采用的是一种类似于tries(字典树)的查询来进行请求的路由。。。感觉还算是蛮不错的吧。。毕竟字典树在做基于字符串的查询效率还是很高的。。

那么接下来来大体的来说一下tomcat是怎么实现的吧。。。嗯,最关键的就是:二分搜索,字符串也是可以排序的嘛,也就是字典序,那么也就可以做二分搜索咯。。。


在开始具体的代码之前,先来看一个简略的结构图吧:

Tomcat源码阅读之Mapper分析_第1张图片


这个是整个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如何对请求进行路由,就算是比较清楚了。。。用基于字典序的二分搜索,应该效率也不差吧。。。

你可能感兴趣的:(Tomcat源码阅读之Mapper分析)