请求处理链

Tomcat 请求处理链 源码分析

一个 HTTP 请求进 Tomcat 要穿过两个世界: coyote(协议层, 面向 socket 字节)和 catalina(容器层, 面向 Servlet 规范)。
整条链是 Http11Processor.service 解析字节 → CoyoteAdapter.service 换乘 → Pipeline/Valve 逐级下沉到 StandardWrapperValveApplicationFilterChain 递归推进 → Servlet.service。每一环只干一件事, 环与环之间用接口隔开。

// 基于本地 Tomcat 仓 (9.0.89), org.apache.coyote.http11.Http11Processor
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper) throws IOException {
    RequestInfo rp = request.getRequestProcessor();
    rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);

    // Setting up the I/O
    setSocketWrapper(socketWrapper);        // 拿到 socket 的包装, 从这里读字节

    // Flags
    keepAlive = true;
    openSocket = false;
    readComplete = true;
    boolean keptAlive = false;
    SendfileState sendfileState = SendfileState.DONE;

    // keep-alive 的实现就是这个 while: 一条 TCP 连接上循环处理多个请求, 不用每次握手
    while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
            sendfileState == SendfileState.DONE && !protocol.isPaused()) {

        // Parsing the request header
        try {
            if (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),
                    protocol.getKeepAliveTimeout())) {      // 解析 "GET /path HTTP/1.1" 这一行
                if (inputBuffer.getParsingRequestLinePhase() == -1) {
                    return SocketState.UPGRADING;
                } else if (handleIncompleteRequestLineRead()) {
                    break;      // NIO 下字节没到齐: 不阻塞干等, 把 socket 还给 Poller, 等下次可读事件再进来
                }
            }

            // ... prepareRequestProtocol(); 判定 HTTP/0.9|1.0|1.1, 略

            if (protocol.isPaused()) {
                // 503 - Service unavailable
                response.setStatus(503);
                setErrorState(ErrorState.CLOSE_CLEAN, null);
            } else {
                keptAlive = true;
                // Set this every time in case limit has been changed via JMX
                request.getMimeHeaders().setLimit(protocol.getMaxHeaderCount());   // 防头部轰炸: 限制 header 个数
                // Don't parse headers for HTTP/0.9
                if (!http09 && !inputBuffer.parseHeaders()) {   // 解析请求头: 只切字节记下标, 不转 String
                    // We've read part of the request, don't recycle it
                    // instead associate it with the socket
                    openSocket = true;
                    readComplete = false;
                    break;      // 头读了一半, 状态挂在 socket 上, 线程先撤
                }
                // ...
            }
        } catch (IOException e) {
            // ... 解析失败: 400 / 关连接, 略
        }

        // ... Connection: upgrade 检查 (WebSocket / h2c), 略

        if (getErrorState().isIoAllowed()) {
            // Setting up filters, and parse some request headers
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            // ...
            prepareRequest();       // 校验 transfer-encoding / content-length, 装配请求体的 InputFilter
            // ...
        }

        // ... maxKeepAliveRequests 扣减, 略

        // Process the request in the adapter
        if (getErrorState().isIoAllowed()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                getAdapter().service(request, response);       // 协议层到此为止, 移交 CoyoteAdapter 进容器
                // ...
            } catch (InterruptedIOException e) {
                setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
            }
            // ... 其余异常统一 500, 略
        }
        // ... endRequest / 统计 / 决定连接去留, 略
    }
    // ...
}
// 基于本地 Tomcat 仓 (9.0.89), org.apache.catalina.connector.CoyoteAdapter
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {

    Request request = (Request) req.getNote(ADAPTER_NOTES);    // 参数是 coyote 的 Request, 局部变量是 catalina 的
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {
        // Create objects
        request = connector.createRequest();        // catalina Request: 实现 HttpServletRequest
        request.setCoyoteRequest(req);              // 只是包一层, 底层数据仍是 coyote 里那份字节
        response = connector.createResponse();
        response.setCoyoteResponse(res);

        // Link objects
        request.setResponse(response);
        response.setRequest(request);

        // Set as notes
        req.setNote(ADAPTER_NOTES, request);        // 挂回 coyote 对象上, 同一连接的下个请求直接复用, 不重复 new
        res.setNote(ADAPTER_NOTES, response);
        // ...
    }
    // ...
    try {
        // Parse and set Catalina and configuration specific
        // request parameters
        postParseSuccess = postParseRequest(req, request, res, response);   // URI 解码/规范化 + Mapper 映射
        if (postParseSuccess) {
            // check valves if we support async
            request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container
            connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);  // 进入 Engine 的 Valve 链
        }
        if (request.isAsync()) {
            async = true;
            // ... Servlet 3.0 异步: 本线程先撤, 请求不结束, 略
        } else {
            request.finishRequest();        // 同步请求: 冲刷输出缓冲, 收尾
            response.finishResponse();
        }

    } catch (IOException e) {
        // Ignore
    } finally {
        // ... access log, 略

        // Recycle the wrapper request and response
        if (!async) {
            updateWrapperErrorCount(request, response);
            request.recycle();      // 对象不丢, 清空状态等下个请求复用, 省 GC
            response.recycle();
        }
    }
}

// —— postParseRequest() 里的关键一步: 把 URL 翻译成容器坐标 ——
    while (mapRequired) {
        // This will map the the latest version by default
        connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());
        // Mapper 按 host名 -> context路径(最长前缀) -> servlet规则 三级查表, 结果填进 MappingData:
        // 之后 request.getHost()/getContext()/getWrapper() 拿到的就是这里定下的三个容器

        // If there is no context at this point, either this is a 404
        // because no ROOT context has been deployed or the URI was invalid
        // so no context could be mapped.
        if (request.getContext() == null) {
            // ...
            return true;    // 不在这抛 404, 放行让 Valve 链去出错误页
        }
        // ... 解析 session id; 若 URL 会话粘在旧版本 webapp 上, 置 mapRequired=true 重新映射(平滑升级), 略
    }
// 基于本地 Tomcat 仓 (9.0.89), org.apache.catalina.core.StandardWrapperValve
// Wrapper 管的 Pipeline 的 basic valve, Valve 链的最后一节
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
    // ...
    StandardWrapper wrapper = (StandardWrapper) getContainer();     // 一个 Wrapper 对应一个 Servlet 定义
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();
    // ... 应用/Servlet 不可用检查(503), 略

    // Allocate a servlet instance to process this request
    try {
        if (!unavailable) {
            servlet = wrapper.allocate();   // 常规 Servlet 返回全局单例(首次触发 init); STM Servlet 才走实例池
        }
    } catch (UnavailableException e) {
        // ...
    }
    // ...
    // Create the filter chain for this request
    ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
    // 每个请求现场组链: 拿 URL 和 servlet 名去匹配 web.xml/注解里的 filter-mapping

    // Call the filter chain for this request
    // NOTE: This also calls the servlet's service() method
    try {
        if ((servlet != null) && (filterChain != null)) {
            // ...
            filterChain.doFilter(request.getRequest(), response.getResponse());
            // getRequest() 给出 RequestFacade: 应用代码从这往上只见 HttpServletRequest, 摸不到容器内部
            // ...
        }
    } catch (IOException e) {
        // ... 各类异常分别记日志、走错误处理, 略
    } finally {
        // Release the filter chain (if any) for this request
        if (filterChain != null) {
            filterChain.release();
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);    // 单例场景只是减计数
            }
        } catch (Throwable e) {
            // ...
        }
        // ...
    }
}

// 基于本地 Tomcat 仓 (9.0.89), org.apache.catalina.core.ApplicationFilterChain
public final class ApplicationFilterChain implements FilterChain {
    // ...
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];    // 数组存链, 不是节点链表

    private int pos = 0;    // 游标: 当前执行到第几个 filter
    private int n = 0;      // 链上 filter 总数
    private Servlet servlet = null;     // 链的终点

    private void internalDoFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];      // 取下一个, 游标前移
            try {
                Filter filter = filterConfig.getFilter();
                // ...
                filter.doFilter(request, response, this);
                // 把链自身(this)传给 Filter; Filter 内部调 chain.doFilter() 又回到本方法, 靠调用栈递归推进。
                // filter.doFilter 返回时, 下游(后续 filter + servlet)已全部执行完 —— 这就是 Filter 后置逻辑的时机
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            }
            // ...
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            // ...
            servlet.service(request, response);     // 链走完, 请求终于交到 Servlet 手上
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        }
        // ...
    }
}

原理串讲

从头走一遍。连接器(NioEndpoint)的 Poller 监听到 socket 可读, 把它扔给工作线程池, 最终落到 Http11Processor.service
它在 keep-alive 循环里先后调 inputBuffer.parseRequestLineparseHeaders, 从 socket 缓冲里切出请求行和请求头。
注意这一步的产物不是 String, 是 MessageBytes: 只记录字节在缓冲区里的起止下标, 谁真正用到(比如后面查表要 host 名)才解码。
大部分 header 应用根本不会读, 延迟解码省掉了成片的 String 分配。prepareRequest 再根据 content-length/transfer-encoding 装好读请求体用的 InputFilter, 然后 getAdapter().service(request, response), 协议层的活到此干完。

为什么中间要隔一个 CoyoteAdapter, 而不是让 Processor 直接调容器? 因为 coyote 的 Request 和 Servlet 规范里的 HttpServletRequest 是两套东西: 前者是协议无关的字节视图, HTTP/1.1、AJP、HTTP/2 各自的 Processor 都往里填同一种结构; 后者是给应用程序的规范接口。
Adapter 是这两层唯一的胶水 —— 换协议不用动容器, 换容器(理论上)不用动协议层。
CoyoteAdapter.service 里把 catalina 的 Request/Response 包在 coyote 对象外面(且挂在 note 上跨请求复用), 再调 postParseRequest 做 URI 解码和安全规范化, 其中 Mapper.map 按 host 名 → context 路径(最长前缀) → servlet 匹配规则(精确 > 前缀 > 扩展名 > 默认)三级查表, 把这个 URL 该由哪个 Host/Context/Wrapper 处理一次性定死, 填进 MappingData。
映射完成后一句 getContainer().getPipeline().getFirst().invoke(request, response), 请求进入容器世界。

为什么容器内部用 Pipeline/Valve 责任链, 而不是 Engine 直接调 Host 的方法? 因为 Engine/Host/Context/Wrapper 每一级都需要可插拔的横切逻辑: 访问日志(AccessLogValve)、错误页(ErrorReportValve)、单点登录, 这些配置在 server.xml 里就能加, 不用改任何容器代码。
每级 Pipeline 的末尾是固定的 basic valve(StandardEngineValve → StandardHostValve → StandardContextValve → StandardWrapperValve), 它负责”落到下一级”: 从 MappingData 里取出映射结果, 调下一级 Pipeline 的第一个 Valve。
所以链的走向早在 Mapper 那一步就决定了, Valve 只是沿着结果下沉。

StandardWrapperValve.invoke 是最后一节: wrapper.allocate() 拿 Servlet 实例(常规 Servlet 是全局单例, 首次分配时才触发 init, 这也是为什么 Servlet 成员变量必须线程安全), 然后 createFilterChain 为本次请求现场组一条过滤器链 —— 链必须每请求一条, 因为哪些 Filter 匹配取决于本次的 URL 和 dispatcher 类型, 而且 pos 游标本身就是请求级状态。
ApplicationFilterChain 的设计很精巧: 它把自己(this)作为 FilterChain 传给每个 Filter, Filter 决定是否调 chain.doFilter 放行, 调了就递归回 internalDoFilter, pos++ 取下一个; 不调就地短路(鉴权失败直接写 401 就是这么做的)。
递归还免费送了后置时机: chain.doFilter 返回后写的代码, 天然在整个下游执行完之后运行, 一个方法同时提供 pre/post 两个切入点。
游标走到 pos == n, 落到 servlet.service(request, response) —— 请求处理链的终点。

Spring MVC 里, 这个被 servlet.service 调到的 Servlet 就是 DispatcherServlet: Tomcat 把请求送到门口, 之后的 HandlerMapping、参数绑定、视图渲染, 全是 DispatcherServlet 在一个 Servlet 的身份内自己组织的另一条链。

设计取舍

  • coyote/catalina 双 Request 不是浪费: 一份面向字节延迟解码, 一份面向规范, Adapter 换乘, 协议和容器互不感知。
  • Request/Response/Processor 全部 recycle 复用, 换来低 GC 压力; 代价是绝不能把这些对象引用存到请求结束之后(异步编程里经典踩坑点)。
  • Valve 是 Tomcat 私有扩展点, Filter 是 Servlet 规范扩展点: 写 Valve 能拿到容器内部对象但绑死 Tomcat, 写 Filter 可移植但只见 facade。
  • 过滤器链每请求新建一条, 别以为它是启动时固定的全局结构; 固定的只是 filter-mapping 规则和 Filter 实例本身。
  • allocate() 默认返回单例, Servlet(包括 DispatcherServlet)天生被并发调用, 无状态设计不是风格问题, 是正确性问题。

延伸阅读