请求处理链
Tomcat 请求处理链 源码分析
一个 HTTP 请求进 Tomcat 要穿过两个世界: coyote(协议层, 面向 socket 字节)和 catalina(容器层, 面向 Servlet 规范)。
整条链是 Http11Processor.service 解析字节 → CoyoteAdapter.service 换乘 → Pipeline/Valve 逐级下沉到 StandardWrapperValve → ApplicationFilterChain 递归推进 → 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.parseRequestLine 和 parseHeaders, 从 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)天生被并发调用, 无状态设计不是风格问题, 是正确性问题。