tomcat源码分析06:请求处理流程(二)

550 阅读10分钟

注:本文源码分析基于 tomcat 9.0.43,源码的gitee仓库仓库地址:gitee.com/funcy/tomca….

本文是tomcat源码分析的第六篇,上一篇文章中我们提前,在Poller线程中,tomcat的会把连接请求会包装为SocketProcessorBase,然后丢到线程池中运行。这其中的运行过程是怎么样的呢,最终又是怎么执行到servlet的?本文将为你一一揭晓。

需要注意的是,从把请求丢到线程池到servlet的执行,其中包含的链路非常多,通过调试的方式得到的调用链路如下:

这其中包含了http协议的解析、servlet规范的实现,对于这些我们就简单略过了,仅分析关键步骤。

1. 解析http协议:Http11Processor#service

tomcat解析http协议的方法为Http11Processor#service,方法如下:

public SocketState service(SocketWrapperBase<?> socketWrapper)
    ...
    while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
            sendfileState == SendfileState.DONE && !protocol.isPaused()) {
        try {

            // inputBuffer.parseRequestLine():解析处理请求行
            if (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),
                    protocol.getKeepAliveTimeout())) {
                ...
            }

            // prepareRequestProtocol():处理 http 协议版本
            prepareRequestProtocol();
            if (protocol.isPaused()) {
                ...
            } else {
                keptAlive = true;
                ...
                // inputBuffer.parseHeaders():解析请求头
                if (!http09 && !inputBuffer.parseHeaders()) {
                    ...
                }

            }
        } catch (...) {
            ...
        }
        ...
        if (getErrorState().isIoAllowed()) {
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            try {
                // 准备request请求
                prepareRequest();
            } catch (Throwable t) {
                ...
            }
        }
        ...
        if (getErrorState().isIoAllowed()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                // 处理读事件
                getAdapter().service(request, response);
                ...
            } catch (...) {
                ...
            }
        }
        ...
    }
    ...
}

这个方法非常长,不过我已经精简了许多,仅留下了关键方法,列举如下:

  • 解析http请求行:inputBuffer.parseRequestLine()
  • 处理http协议版本:prepareRequestProtocol()
  • 解析http请求头:inputBuffer.parseHeaders()
  • 准备http请求数据:prepareRequest()
  • 继续处理请求:CoyoteAdapter#service(request, response)

上面4个方法看得让我十分惆怅,如果不是对http的每个细节有深入了解,不建议研究那几个方法,真的让人头大。

解析完http请求后,继续调用getAdapter().service(request, response);处理之后的逻辑,也就是 CoyoteAdapter#service(request, response) 方法.

2. 生成httpServletRequest/httpServletResponseCoyoteAdapter#service(request, response)

httpServletRequest/httpServletResponse是在CoyoteAdapter#service(request, response)中生成的,代码如下:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {

    // 获取 httpServletRequest,得到的值为null
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    // 获取 httpServletResponse,得到的值为null
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {
        // 创建 request 对象
        request = connector.createRequest();
        request.setCoyoteRequest(req);

        // 创建 response 对象
        response = connector.createResponse();
        response.setCoyoteResponse(res);
        ...
    }

    ...

    try {
        // 处理参数,指 servlet 规范的一些参数,如果请求方法,sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // 调用 container 的 valve 处理
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
        }
        ...

    } catch (IOException e) {
        // Ignore
    } finally {
        ...
    }
}

tomcat中,RequestResponse有两种类型:

  • org.apache.coyote.Requestorg.apache.coyote.Response,tomcat 提供的,用来存放的http连接数据的
  • org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response,也是tomcat提供的,不过它分分别实现了HttpServletRequest/HttpServletResponse

这里我们来看看org.apache.catalina.connector.Request 的创建过程:

request = connector.createRequest();

再进入Connector#createRequest方法:

public Request createRequest() {
    return new Request(this);
}

调用的是构造方法,继续跟进去:

/**
 * 实现了 HttpServletRequest 
 */
public class Request implements HttpServletRequest {

    /** 连接器 */
    protected final Connector connector;

    /** 这是 `org.apache.coyote.Request`, 用来存放 http 请求连接的数据 */
    protected org.apache.coyote.Request coyoteRequest;

    ...

    public Request(Connector connector) {
        this.connector = connector;

        formats = new SimpleDateFormat[formatsTemplate.length];
        for(int i = 0; i < formats.length; i++) {
            formats[i] = (SimpleDateFormat) formatsTemplate[i].clone();
        }
    }

    /**
     * 部分 httpServletRequest 的方法如下:
     * 他们最终调用的是 coyoteRequest
     */

    @Override
    public String getMethod() {
        return coyoteRequest.method().toString();
    }

    @Override
    public String getRequestURI() {
        return coyoteRequest.requestURI().toString();
    }
    ...
}

到这里,Request 只是创建了出来,并没有做什么实质性的工作,我们继续看下去。

3. 解析reqeust:postParseRequest(...)

让我们回到CoyoteAdapter#service()方法,创建完Request/Response,接下来就是把这个里面装东西了:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {
    ...
    try {
        // 处理参数,指 servlet 规范的一些参数,如果请求方法,sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        ...
    } 
    ...
}

处理参数解析的方法为CoyoteAdapter#postParseRequest,里面处理了servlet规范的一些参数,其实就是给httpServletRequest类型的Request进行赋值,它的代码如下:

protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
        org.apache.coyote.Response res, Response response) throws IOException, ServletException {

    // 解析 scheme
    if (req.scheme().isNull()) {
        // 解析 scheme,设置 httServletRequest 类型的 request
        req.scheme().setString(connector.getScheme());
        request.setSecure(connector.getSecure());
    } else {
        request.setSecure(req.scheme().equals("https"));
    }

    // 处理代理
    String proxyName = connector.getProxyName();
    int proxyPort = connector.getProxyPort();
    if (proxyPort != 0) {
        req.setServerPort(proxyPort);
    } else if (req.getServerPort() == -1) {
        if (req.scheme().equals("https")) {
            req.setServerPort(443);
        } else {
            req.setServerPort(80);
        }
    }
    if (proxyName != null) {
        req.serverName().setString(proxyName);
    }

    MessageBytes undecodedURI = req.requestURI();
    // 处理请求方法
    if (undecodedURI.equals("*")) {
        if (req.method().equalsIgnoreCase("OPTIONS")) {
            StringBuilder allow = new StringBuilder();
            allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS");
            // Trace if allowed
            if (connector.getAllowTrace()) {
                allow.append(", TRACE");
            }
            res.setHeader("Allow", allow.toString());
            connector.getService().getContainer().logAccess(request, response, 0, true);
            return false;
        } else {
            response.sendError(400, "Invalid URI");
        }
    }

    // 其他的一些解析操作就不看了
    ...

    while (mapRequired) {
        // 解析servlet相关内容,如 Host, Context,Wrapper 等
        connector.getService().getMapper().map(serverName, decodedURI,
                version, request.getMappingData());

        ...
    }

    ...

    return true;
}

这个方法依旧十分长,所做的工作为解析http请求的数据,将其转换为HttpServletRequest所需要的参数,对于里面的一些细节就不分析了。

这个方法中调用了这样一段代码:

// 解析servlet相关内容,如 Host, Context,Wrapper 等
connector.getService().getMapper().map(serverName, decodedURI,
        version, request.getMappingData());

这段代码最终调用的是Mapper#map(),内容如下:

public void map(MessageBytes host, MessageBytes uri, String version,
                MappingData mappingData) throws IOException {
    // 拿到了 host 与 uri
    if (host.isNull()) {
        String defaultHostName = this.defaultHostName;
        if (defaultHostName == null) {
            return;
        }
        host.getCharChunk().append(defaultHostName);
    }
    host.toChars();
    uri.toChars();
    // 处理映射
    internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}

这个方法拿到hosturi后,继续请求Mapper#internalMap方法,在这个方法里会处理http的请求路径,也就是根据请求路径找到最终执行的servlet,我们继续。

4. 处理映射路径:Mapper#internalMap

继续跟进Mapper#internalMap方法:

private final void internalMap(CharChunk host, CharChunk uri,
        String version, MappingData mappingData) throws IOException {
    // 这一步相当于获取到项目中所有的 Host
    MappedHost[] hosts = this.hosts;
    // 查找 host
    MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
    ...
    mappingData.host = mappedHost.object;

    if (uri.isNull()) {
        return;
    }

    uri.setLimit(-1);

    // 上面已经查找到了host,接下来就从该host下的所有context中查找
    ContextList contextList = mappedHost.contextList;
    MappedContext[] contexts = contextList.contexts;
    // 查找 context
    int pos = find(contexts, uri);
    if (pos == -1) {
        return;
    }
    ...

    mappingData.contextPath.setString(context.name);

    ContextVersion contextVersion = null;

    // 前面已经找到了 context,接下来就是从该context下所有的contextVersions中查找
    ContextVersion[] contextVersions = context.versions;
    final 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;
        if (version != null) {
            // 根据版本号查找contextVersions
            contextVersion = exactFind(contextVersions, version);
        }
    }
    if (contextVersion == null) {
        contextVersion = contextVersions[versionCount - 1];
    }
    mappingData.context = contextVersion.object;
    mappingData.contextSlashCount = contextVersion.slashCount;

    if (!contextVersion.isPaused()) {
        // 处理 wrapper
        internalMapWrapper(contextVersion, uri, mappingData);
    }

}

需要注意的是,这里的HostContext并不是前面提到的StandardHostStandardContext,而是MappedHostMappedContextMappedHost中存放的是一个个的MappedContextMappedContext中存放的是一个个ContextVersion

MappedHost内容如下::

protected static final class MappedHost extends MapElement<Host> {

    /** 存放一个个MappedContext的结构,继续看下去 */
    public volatile ContextList contextList;

    ...
}

protected static final class ContextList {
    /** 用数组存放 MappedContext */
    public final MappedContext[] contexts;

    ...
}

MappedContext内容如下:

protected static final class MappedContext extends MapElement<Void> {
    /** 存放多个ContextVersion,使用数组存放 */
    public volatile ContextVersion[] versions;
    ...
}

4.1 查找 host

那么他们是如何查找host的呢?在tomcat创建host时,默认为设置一个hostname:

public class Tomcat {

    protected String hostname = "localhost";

    public Host getHost() {
        // 获取 engine,不存在时会创建
        Engine engine = getEngine();
        if (engine.findChildren().length > 0) {
            return (Host) engine.findChildren()[0];
        }
        // 创建 Host
        Host host = new StandardHost();
        host.setName(hostname);
        // 添加到 engine
        getEngine().addChild(host);
        return host;
    }

    ...
}

在http请求时,tomcat会解析传入的host,当这两者匹配时,就表示找到了对应的host,看下Mapper#exactFindIgnoreCase就明白了:

private static final <T, E extends MapElement<T>> E exactFindIgnoreCase(
        E[] map, CharChunk name) {
    // 查找map数组中与name相等或最接近的元素,返回下标
    int pos = findIgnoreCase(map, name);

    if (pos >= 0) {
        // 再一次判断,因为 findIgnoreCase(...) 返回的是与给定最接近或相等的下标
        E result = map[pos];
        if (name.equalsIgnoreCase(result.name)) {
            return result;
        }
    }
    return null;
}

4.2 查找 Context

Context又是如何查找的呢?在添加Context时,我们是这么做的:

// 创建 context
String docBase = System.getProperty("java.io.tmpdir");
Context context = tomcat.addContext("", docBase);

tomcat.addContext(...)方法中,我们可以给Context指定一个请求路径,像这样:

Context context = tomcat.addContext("/api", docBase);

这样就表示以api为前缀的请求使用该Context处理,Mapper#internalMap方法在查找Context时,就是根据uri查找对匹配的Context的。

4.3 处理wrapper匹配:Mapper#internalMapWrapper

查找到Context后,接着就是处理Wrapper了,方法为Mapper#internalMapWrapper

private final void internalMapWrapper(ContextVersion contextVersion, CharChunk path,
        MappingData mappingData) throws IOException {

    // 获取所有的 exactWrappers
    MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
    // 进行查找操作
    internalMapExactWrapper(exactWrappers, path, mappingData);
    // 省略了很多的内容
    ...
}

这个方法也是非常长,处理了各种匹配规则,这里我们仅看其中一个,了解下吧匹配流程吧,进入Mapper#internalMapExactWrapper方法:

private final void internalMapExactWrapper(MappedWrapper[] wrappers, 
        CharChunk path, MappingData mappingData) {
    // 路径匹配,请求的uri是否匹配servlet path
    MappedWrapper wrapper = exactFind(wrappers, path);
    if (wrapper != null) {
        mappingData.requestPath.setString(wrapper.name);
        // 找到后赋值
        mappingData.wrapper = wrapper.object;
        if (path.equals("/")) {
            mappingData.pathInfo.setString("/");
            mappingData.wrapperPath.setString("");
            mappingData.contextPath.setString("");
            mappingData.matchType = MappingMatch.CONTEXT_ROOT;
        } else {
            mappingData.wrapperPath.setString(wrapper.name);
            mappingData.matchType = MappingMatch.EXACT;
        }
    }
}

这里的path就是uri的路径,这一步是根据路径来判断是否匹配的,即判断传入的uriservlet path是否切尔西,匹配成功后,就赋值给mappingDatawrapper属性了。

至些,请求对应的hostcontextwrapepr就都找到了。

5. 调用XxxValve

让我们回到CoyoteAdapter#service方法:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {
    ...

    try {
        // 处理参数,指 servlet 规范的一些参数,如果请求方法,sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // 调用 container 的 valve 处理
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
        }
        ...

    } catch (IOException e) {
        // Ignore
    } finally {
        ...
    }
}

在解析完http参数、请求对应的servlet后,接着就开始调用containerPipeline 处理了:

// 调用 container 的 valve 处理
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

整个pipeline结构如下:

执行时就是这样一个接一个的Pipeline调用。Pipeline里存放的是什么呢?这就是所谓有ValveStandardEngineStandardHostStandardContextStandardWrapper等都着对应的Valve

5.1 StandardEngineValve#invoke

我们跟进connector.getService().getContainer().getPipeline().getFirst().invoke(request, response),由于connector.getService().getContainer()得到的是StandardEngine,因此运行的是StandardEngineValve#invoke方法:

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // 使用对应的host来处理
    Host host = request.getHost();
    ...
    // 调用 host 的 valve 处理
    host.getPipeline().getFirst().invoke(request, response);
}

该方法关键代码有两个:

  • request.getHost():获取该请求对应的host,这个就是前面Mapper#internalMap千辛万苦找到的与该请求对应的host
  • host.getPipeline().getFirst().invoke(request, response):继续调用StandardHostValve#invoke方法

5.2 StandardHostValve#invoke

我们再进入StandardHostValve#invoke方法:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {
    // 从 request 中得到使用的 context
    Context context = request.getContext();
    ...

    try {
        
        ...

        try {
            if (!response.isErrorReportRequired()) {
                // 调用 context 的 valve 处理
                context.getPipeline().getFirst().invoke(request, response);
            }
        } catch (Throwable t) {
            ...
        }

    } finally {
        ...
    }

这个方法的调用套路与StandardEngineValve#invoke是一样的:

  • request.getContext():从request中得到该请求对应的context,这个也是在Mapper#internalMap方法找到的
  • context.getPipeline().getFirst().invoke(request, response)继续调用StandardContextValve#invoke处理

5.3 StandardContextValve#invoke

StandardContextValve#invoke方法如下:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    ...

    // 从 request 中拿到 wrapper
    Wrapper wrapper = request.getWrapper();
    ...
    // 调用 wrapper 的 valve 处理
    wrapper.getPipeline().getFirst().invoke(request, response);
}

嗯,还是与StandardEngineValve#invoke方法一样的套路,我们继续看StandardWrapperValve#invoke方法。

6. StandardWrapperValve#invoke

我们再来看看Pipeline调用图:

从调用上来看,StandardWrapperValve已经调用到底了,servlet的调用就是在这里发生的,该方法代码如下:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    ...

    try {
        if (!unavailable) {
            // 获取 servlet,如果实例存在,直接返回,否则就创建实例并调用 Servlet#init 方法
            servlet = wrapper.allocate();
        }
    } catch (...) {
        ...
    }

    ...

    // 创建过滤器链
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    Container container = this.container;
    try {
        if ((servlet != null) && (filterChain != null)) {
            // 在这里调用 filterChain.doFilter
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        // 调用 filter
                        filterChain.doFilter(request.getRequest(),
                                response.getResponse());
                    }
                } finally {
                    String log = SystemLogHandler.stopCapture();
                    if (log != null && log.length() > 0) {
                        context.getLogger().info(log);
                    }
                }
            } else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else {
                    // 调用 filter
                    filterChain.doFilter
                        (request.getRequest(), response.getResponse());
                }
            }

        }
    } catch (...) {
        ...
    } finally {
        ...
    }
}

这个方法依旧比较长,不过非关键点我都已经取消了,只留下了三个关键操作:

  • servlet = wrapper.allocate():获取servlet,如果servletloadOnStart小于0,那么在这里才会实例化并调用Servlet#init方法
  • ApplicationFilterFactory.createFilterChain(...):创建过滤器链
  • filterChain.doFilter(...):调用过滤器操作

接下来我们就来分析这几个方法。

6.1 StandardWrapper#allocate

在前面分析servlet的加载时,对于loadOnStartup小于0的servlet并没有处理(没有实例化,没有执行servlet#init方法),这个方法里会实例化loadOnStartup小于0的servlet的,代码如下:

public Servlet allocate() throws ServletException {

    ...

    boolean newInstance = false;

    // If not SingleThreadedModel, return the same instance every time
    if (!singleThreadModel) {
        // 实例不存在且未初始化
        if (instance == null || !instanceInitialized) {
            synchronized (this) {
                if (instance == null) {
                    try {
                        ...
                        // 调用 StandardWrapper#loadServlet 方法
                        instance = loadServlet();
                        newInstance = true;
                        ...
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                    }
                }
                if (!instanceInitialized) {
                    initServlet(instance);
                }
            }
        }

        ...
    }
    ...
}

这个先判断实例是否存在,是否进行进行过初始化操作,如果满足条件则调用StandardWrapper#loadServlet方法,关于这个方法前面的文章中已经分析过了,它所做的主要工作就两个:

  • 实例化servlet
  • 调用Servlet#init(...)方法

6.2 ApplicationFilterFactory#createFilterChain

ApplicationFilterFactory#createFilterChain方法所做的是创建过滤器链,代码如下:

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {

    if (servlet == null)
        return null;

    ApplicationFilterChain filterChain = null;
    // 创建 filterChain 对象
    if (request instanceof Request) {
        Request req = (Request) request;
        if (Globals.IS_SECURITY_ENABLED) {
            filterChain = new ApplicationFilterChain();
        } else {
            filterChain = (ApplicationFilterChain) req.getFilterChain();
            if (filterChain == null) {
                filterChain = new ApplicationFilterChain();
                req.setFilterChain(filterChain);
            }
        }
    } else {
        filterChain = new ApplicationFilterChain();
    }

    filterChain.setServlet(servlet);
    filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

    // 拿到 warpper 对应的 Context,然后从 context中拿到 filterMaps
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();

    ...

    // 添加满足servlet路径的filter到过滤器链
    for (FilterMap filterMap : filterMaps) {
        if (!matchDispatcher(filterMap, dispatcher)) {
            continue;
        }
        // 判断哪些filter满足servlet的路径
        if (!matchFiltersURL(filterMap, requestPath))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
    }

    // 添加满足servlet名称的filter到过滤器链
    for (FilterMap filterMap : filterMaps) {
        if (!matchDispatcher(filterMap, dispatcher)) {
            continue;
        }
        if (!matchFiltersServlet(filterMap, servletName))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
    }
    return filterChain;
}

ApplicationFilterFactory#createFilterChain方法所略长,不过逻辑相当清晰,它主要是用来创建过滤器链的,流程如下:

  1. 创建ApplicationFilterChain对象
  2. 获取当前wrapper对应的Context,然后从Context拿到filterMaps,在Context中有一个结构专门用来存放filter的,关于filter的加载,前面的文章也分析了,这里就不再分析了
  3. 查找满足条件的filter,然后添加到filterChain中,注意,这里满足条件的filter是指满足当前servletfilter,处理匹配条件时,是通过servlet路径与servlet名称进行匹配的,两者满足其一即可。

6.3 ApplicationFilterChain#doFilter

获取到过滤器链后,接下来就是执行了,方法为ApplicationFilterChain#doFilter

public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    if( Globals.IS_SECURITY_ENABLED ) {
        final ServletRequest req = request;
        final ServletResponse res = response;
        try {
            java.security.AccessController.doPrivileged(
                    (java.security.PrivilegedExceptionAction<Void>) () -> {
                        internalDoFilter(req,res);
                        return null;
                    }
            );
        } catch( PrivilegedActionException pe) {
            ...
        }
    } else {
        // 执行过滤器
        internalDoFilter(request,response);
    }
}

调用了internalDoFilter(...)执行,继续跟进去:

/** 当前正在使用的过滤器的下标索引 */
private int pos = 0;

/** 当前过滤器链中过滤器的数量 */
private int n = 0;

/** 存放过滤器的地方 */
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

/**
 * 执行过滤器
 */
private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
    
    // 判断是否执行完了
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            // 获取要执行的 filter
            Filter filter = filterConfig.getFilter();

            ...

            // 执行 filter的doFilter(...) 方法
            if( Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal = ((HttpServletRequest) req).getUserPrincipal();

                Object[] args = new Object[]{req, res, this};
                SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
            } else {
                filter.doFilter(request, response, this);
            }
        } catch (...) {
            ...
        }
        return;
    }

    try {
        ...
        // 调用 servlet#service 方法
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) 
                && Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();
            Object[] args = new Object[]{req, res};
            SecurityUtil.doAsPrivilege("service", servlet, 
                    classTypeUsedInService, args, principal);
        } else {
            // 执行 servlet#service 方法
            servlet.service(request, response);
        }
    } catch (...) {
        ...
    }
}

internalDoFilter(...)分为两个部分:

  1. 执行filter.doFilter(...)方法
  2. 执行servlet.service(...)方法

ApplicationFilterChain中有两个成员变量:

  • n:当前过滤器链中过滤器的数量
  • post:当前使用的过滤器的下标索引

在过滤器的调用时,会根据这两个参数的大小判断要不要进入过滤器的调用:

 // 判断是否执行完了
if (pos < n) {
    ApplicationFilterConfig filterConfig = filters[pos++];
    try {
        // 获取要执行的 filter
        Filter filter = filterConfig.getFilter();

        ...
        // 执行 filter的doFilter(...) 方法
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();

            Object[] args = new Object[]{req, res, this};
            SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
        } else {
            filter.doFilter(request, response, this);
        }
    } catch (...) {
        ...
    }
    return;
}

这个filter.doFilter(request, response, this)就是我们真正调用Filter的代码了。

执行完当前的filter后,是怎么执行到下一个的呢?我们在实现Filter时,都会有这么一句代码:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
    // 你的业务逻辑
    ...
    // 调用 FilterChain#doFilter 方法
    chain.doFilter(request, response)

    // 你的业务逻辑
    ...
}

tomcat中,FilterChain#doFilter调用的就是ApplicationFilterChain#doFilter方法,接着又到了ApplicationFilterChain#internalDoFilter方法,这次调用时,pos的值会加1,然后继续调用Filter#doFilter方法,之后又调回ApplicationFilterChain#doFilter方法...如此循环往复的方法,在设计模式中,有一个专业的名字:责任链模式

每一次调用ApplicationFilterChain#doFilter方法,pos的值就会加1,直到pos >= n后,filter就全部执行完了,接着就开始执行servlet了:

private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
    
    // 判断是否执行完了
    if (pos < n) {
        // 执行 filter
        ...
    }

    try {
        ...
        // 调用 servlet#service 方法
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) 
                && Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();
            Object[] args = new Object[]{req, res};
            // 执行 servlet#service 方法
            SecurityUtil.doAsPrivilege("service", servlet, 
                    classTypeUsedInService, args, principal);
        } else {
            // 执行 servlet#service 方法
            servlet.service(request, response);
        }
    } catch (...) {
        ...
    }
}

关于servlet的执行,其实非常简单:前面已经获取到了servlet的实例,这里就直接调用其service()方法即可。

7. 总结

本文主要分析了tomcat线程池处理请求的整个过程,包括http协议的解析、httpServletRequest/httpServletResponse的创建及准备、解析映射路径(查找对应的hostcontextwrapper)、执行filterservlet,不过对于http的解析、servlet规范的实现,本文只是点出了处理的方法,并没有深入到这些方法的细节。


限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

本文首发于微信公众号 Java技术探秘,链接:mp.weixin.qq.com/s/o6ZKNqdcd…. 如果您喜欢本文,欢迎关注该公众号,让我们一起在技术的世界里探秘吧!