Tomcat + SpringMVC 普通HTTP _ WebSocket 完整链路技术总结文档
约 1875 字大约 6 分钟
2026-08-07
文档说明
本文档统一梳理 SpringBoot + 内嵌Tomcat 架构下两套核心请求链路:
普通 HTTP/HTTPS 接口完整 MVC 执行链路(从TCP连接到Controller响应)
WebSocket 握手升级链路(HTTP短连接阶段)
WebSocket 长连接帧传输链路(握手完成后,独立IO模型)
同时明确 Tomcat Filter / Spring Interceptor / WebSocket专属拦截器 的层级差异、线程模型切换规则,解决绝大多数WebSocket链路认知误区。
一、前置:Tomcat NIO 核心线程模型(全局通用)
Tomcat NIO 三大核心线程,贯穿所有请求场景:
Acceptor 线程:死循环监听端口,接收TCP三次握手连接,将新Socket交给Poller
Poller 线程(多路复用Selector):监听Socket读写就绪事件,只分发任务,不执行业务,避免阻塞IO监听
http-nio-worker 线程池:Tomcat标准HTTP工作线程池,处理所有普通HTTP请求、WebSocket握手请求
核心规则:握手阶段走worker线程池,握手完成后WebSocket帧不再占用worker线程。
二、普通HTTP请求完整链路(API接口)
2.1 整体时序总览
全流程全程阻塞占用 Tomcat http-nio-worker 线程,请求结束线程归还池子。
TCP连接建立 → Acceptor → Poller → http-nio-worker线程
→ Servlet Filter链(全部放行)
→ DispatcherServlet.doService()
→ doDispatch() 核心调度
├─ HandlerMapping匹配URL → 生成处理器执行链
├─ HandlerInterceptor 正序preHandle
├─ HandlerAdapter调用Controller
│ ├─ 参数解析(ArgumentResolver)
│ ├─ 反射执行Controller业务
│ └─ 返回值序列化(ReturnValueHandler/MessageConverter)
├─ HandlerInterceptor 逆序postHandle
├─ 视图解析与渲染(@ResponseBody场景跳过)
└─ HandlerInterceptor 逆序afterCompletion(必执行)
→ 响应写回Socket → 线程归还线程池2.2 分层详细拆解
1)Tomcat容器层
Poller检测Socket读就绪,将任务交付worker线程,构建标准HttpServletRequest/HttpServletResponse对象,执行Servlet Filter责任链。
关键Filter说明:Tomcat内置WsFilter,普通HTTP请求无Upgrade请求头,直接放行,不截断链路。
2)SpringMVC 核心调度层(DispatcherServlet)
Filter全部放行后,请求进入SpringMVC核心入口 DispatcherServlet.doDispatch(),执行7大核心步骤:
获取处理器执行链:通过
RequestMappingHandlerMapping匹配URL、请求方式,定位到目标HandlerMethod(Controller方法元数据),绑定匹配的所有HandlerInterceptor,封装为HandlerExecutionChain。前置拦截:正序执行所有拦截器
preHandle(),返回false则直接短路请求。获取适配器:匹配
RequestMappingHandlerAdapter,用于反射调用Controller方法。执行业务逻辑:
ArgumentResolver:解析路径参数、请求参数、JSON请求体等所有入参
反射调用目标Controller方法,执行业务逻辑
ReturnValueHandler:处理返回值,
@ResponseBody场景通过Jackson序列化为JSON写入响应流
后置拦截:逆序执行
postHandle()(异常场景不执行)。视图渲染:非REST接口执行视图解析、页面渲染,REST接口跳过。
最终收尾:逆序执行
afterCompletion(),资源清理、日志记录,无论是否异常必执行。
3)请求收尾
DispatcherServlet执行完毕,链路回流Filter后置逻辑,Tomcat将响应数据写入Socket,worker线程释放回线程池,请求彻底结束。
三、WebSocket 握手升级链路(HTTP短连接阶段)
3.1 核心结论
WebSocket 握手是一次标准HTTP请求,全程走http-nio-worker线程;握手阶段就截断MVC链路,不会进入DispatcherServlet、不会执行SpringMVC拦截器。
3.2 完整时序
客户端 uni.connectSocket(wss://xxx/ws/chat?token=xxx)
→ TCP连接就绪 → Poller → http-nio-worker线程
→ 构建HttpServletRequest,执行Servlet Filter链
├─ 自定义Filter、跨域Filter正常执行
└─ 到达Tomcat内置 WsFilter
├─ 识别请求头 Upgrade:websocket、Connection:upgrade
├─ 调用 WsServerContainer.doUpgrade() 开始协议升级
│ ├─ 执行 Spring HandshakeInterceptor.beforeHandshake() 握手鉴权
│ ├─ 校验token、存入userId到session属性
│ ├─ 服务端返回 101 Switching Protocols
│ └─ 解绑Servlet HTTP生命周期,Socket脱离普通HTTP管控
└─ 【关键】不执行chain.doFilter(),截断整个Filter链
→ 不进入DispatcherServlet、不执行MVC拦截器、不进入Controller
→ worker线程执行完毕,归还线程池(握手阶段结束)3.3 关键组件区分(高频考点)
Servlet Filter(容器层):握手请求可以经过,WsFilter是核心截断点
SpringMVC HandlerInterceptor:完全不执行,请求未进入DispatcherServlet
Spring HandshakeInterceptor:WebSocket专属握手拦截器,嵌入升级流程,用于握手鉴权、参数存储
四、WebSocket 长连接帧传输链路(握手完成后)
4.1 核心线程切换(最重要)
握手协议升级完成后,彻底脱离Servlet、HTTP、MVC体系,线程模型完全切换:
不再占用 http-nio-worker 普通HTTP线程池
后续所有消息帧、心跳、断开事件,由 Tomcat Poller多路复用 + container-ws-ex专属WebSocket线程池 处理
4.2 消息帧完整处理时序
客户端发送WebSocket文本/二进制帧/心跳ping
→ TCP报文到达服务端Socket缓冲区
→ Poller线程检测到Socket读就绪(OP_READ事件)
→ 投递任务到 container-ws-ex 专属WebSocket线程池
├─ 解析WebSocket协议帧(掩码、Opcode、Payload)
├─ 回调 ChatWebSocketHandler 生命周期方法
│ ├─ handleTextMessage() 处理业务消息/心跳续期Redis TTL
│ └─ sendMessage() 推送响应消息
→ 写回Socket,完成一次帧交互
→ ws线程归还专属线程池,等待下一次IO事件4.3 连接关闭链路
正常关闭:客户端/服务端主动关闭,Poller感知关闭事件,触发
afterConnectionClosed(),清理本地Session、删除Redis在线状态静默断开(运营商切网/休眠):无TCP FIN包,Tomcat无法感知,依赖客户端心跳+Redis TTL超时判定离线
五、Nginx前置代理对链路的影响(无变更核心结论)
前置Nginx反向代理(wss443 -> http8080),Tomcat+Spring内部整套链路、线程模型、执行时序完全不变。
唯一变化点:
新增Nginx TCP隧道转发、SSL解密,Nginx仅透传字节流,不解析WebSocket协议帧
Tomcat拿到的是Nginx内网代理Socket,需配置
X-Real-IP透传真实客户端IP新增Nginx层超时切断(
proxy_read_timeout),需配合业务心跳保活
所有Java层组件(Filter、WsFilter、HandshakeInterceptor、WebSocketHandler、线程池)行为完全一致。
六、核心链路对比总表
| 场景 | 执行线程池 | 是否经过Filter | 是否经过MVC拦截器 | 是否进入DispatcherServlet | 核心处理器 |
|---|---|---|---|---|---|
| 普通HTTP接口 | http-nio-worker | ✅ 全部经过 | ✅ 正常执行 | ✅ 进入 | Controller + MVC整套流程 |
| WebSocket握手 | http-nio-worker | ✅ 部分经过(WsFilter截断) | ❌ 不执行 | ❌ 不进入 | WsFilter + HandshakeInterceptor |
| WebSocket消息帧 | container-ws-ex | ❌ 不经过 | ❌ 不执行 | ❌ 不进入 | WebSocketHandler |
七、高频踩坑总结
MVC拦截器拦截不到WebSocket握手:握手请求被WsFilter在Filter链截断,未进入DispatcherServlet,必须用
HandshakeInterceptor做握手鉴权握手后无法获取HttpServletRequest:协议升级后Servlet请求生命周期销毁,只能在握手阶段存储用户信息到session属性
长连接不占用HTTP线程池:大量在线用户不会打满Tomcat worker线程,只会消耗ws专属线程与内存连接数
公网WebSocket必须wss:微信小程序禁止明文ws协议,必须Nginx前置443 SSL代理
URL传Token有安全风险:握手URL的Token会被Nginx/Tomcat日志记录,建议通过
Sec-WebSocket-Protocol自定义协议头传递
(注:部分内容可能由 AI 生成)