先记住这个答案
clientError 处理客户端连接或 HTTP 解析阶段的错误,例如非法报文或请求头溢出。回调拿到错误和底层 socket,没有正常路由处理时的 req、res 参数,因此不能直接调用 res.status。Node 默认会尝试返回 400,头部溢出对应 431;接入自定义监听器后,应用要负责关闭或销毁连接,不能只记日志。需要检查连接是否仍可写、避免重复回应,并控制诊断信息量。业务逻辑抛错和监听端口失败属于其他错误边界,不应全塞进 clientError。
- 协议解析可能在业务处理器之前失败
- 自定义监听意味着接管底层连接的收尾
- 原始报文不是适合无条件保存的日志字段
为什么业务路由捕获不到这些错误
浏览器或代理发送的字节必须先被 HTTP 解析器理解,才能构造正常请求。若请求行或头部已经不合法,业务路由可能根本不会执行,包裹路由的 try/catch 自然观察不到这类故障。
排查时可在受控本地端口用 net.Socket 写入有限长度的非法请求行,记录 clientError 的错误码及业务处理器调用次数。测试完成要关闭客户端和服务端,不能留下持续发送数据的连接。
监听器必须有明确终止路径
如果只是想统计错误,应在记录必要字段后销毁连接,或者在确认适合回应时写入完整 HTTP 错误响应并结束连接。原始 socket 不会替应用自动补齐状态行、头部终止符和响应边界。
错误可能在同一连接上重复出现,连接也可能已经重置。需要避免重复写状态行,对不可写或已销毁的连接直接清理,并为仍未完成关闭的连接设置有界兜底;一次 socket.end 不代表对端一定收到响应。
保留能定位问题而不过量的诊断信息
错误码、解析到的位置、时间窗口与受控关联标识通常足以区分协议问题和代理配置问题。rawPacket 是原始字节,可能包含认证头或用户内容,不能因为调试方便就完整长期记录。
如果必须留样本,应限制长度、采样并移除敏感字段,且区分外部异常流量与发布后出现的协议兼容故障。高频错误下同步输出大量日志也会拖慢事件循环,所以指标聚合比逐包打印更合适。
容易答错的地方
- 注册监听器后只调用 console.error
- 自定义监听替换了原有处理责任,如果没有关闭或销毁路径,坏连接可能持续占资源。应把记录和收尾放在同一明确流程,并验证重复错误、不可写连接和客户端提前断开。
- 把所有失败都转成普通 JSON 业务响应
- 协议解析可能尚未建立可靠的请求响应上下文,不能假定有 res 或可以复用连接。应遵守原始 HTTP 报文格式,并在边界不清楚或连接已坏时结束连接,避免继续解析后续数据。
面试官还会怎么问?
clientError 与 server 的 error 有什么区别?
前者关联客户端连接或 HTTP 解析故障,后者可报告监听失败等服务器自身错误。两者回调参数和清理对象不同,应该分别监听并分类统计,不能指望某一个监听器自动兜住所有异常。
为何有的错误适合返回 431?
请求头超过解析器限制时,431 能表达头字段过大,而普通格式错误常用 400。自定义策略应保留错误分类,同时考虑连接是否仍适合写入响应;状态码选择不能代替资源回收。
使用 Express 后还需要理解这个事件吗?
框架主要处理已经进入业务请求层的对象,底层 HTTP 服务仍有协议和连接生命周期。是否需要自定义监听取决于部署和观测需求,但排查路由完全没执行的错误时必须知道这个层次存在。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。