首页
/ Nacos 请求过滤与运行时上下文:HTTP/gRPC 过滤器链、RequestContext 与命名空间校验的设计解析

Nacos 请求过滤与运行时上下文:HTTP/gRPC 过滤器链、RequestContext 与命名空间校验的设计解析

2026-09-09 17:56:15作者:乔或婵

本文基于 Nacos 仓库中的设计规范 foundation-request-context-spec.md,系统讲解 Nacos 中 HTTP Servlet 过滤器与 gRPC 请求过滤器作为"handler 前置执行层"的完整设计:包括进程级请求上下文(RequestContextHolder/RequestContext)的初始化与清理、HTTP/gRPC 两条过滤器链的职责划分、参数抽取与校验机制、命名空间存在性校验,以及鉴权、流量控制等横切关注点如何被统一收敛到过滤器层。读完本文,你可以在排查 Nacos 请求被"未进入 Controller 即被拒绝"类问题时准确定位拦截点,并理解 v3 API 设计为何要求把鉴权、限流、参数校验从控制器中抽离。

1. 定位:过滤器是 handler 前置执行层,不拥有领域语义

规范开宗明义:请求过滤(Request filtering)是 Nacos HTTP 与 gRPC 请求的前置执行层(pre-handler execution layer)。它的能力边界是四类:

  • 丰富(enrich)运行时上下文;
  • 拒绝(reject)非法请求;
  • 执行横切检查(cross-cutting checks),如鉴权、限流、参数校验;
  • 为下游 handler 适配(adapt)请求元数据。

同时规范划出一条硬边界:请求过滤层不得拥有领域资源语义。Config、Naming、AI、Core、Auth 等域继续在自己的规范中定义资源身份、生命周期、授权含义与操作结果。这意味着过滤器只能做"通用结构性检查",而不能替某个域决定"这个配置能不能发布"。这条边界直接对应到仓库的模块划分:过滤器实现集中在 core 模块的 com.alibaba.nacos.core.contextcom.alibaba.nacos.core.remotecom.alibaba.nacos.core.authcom.alibaba.nacos.core.paramcheck 等包中,而不是散落在各业务域的 Controller 里。

本规范与 HTTP API 规范gRPC API 规范鉴权与权限规范流量控制插件规范远程连接生命周期规范 互为补充:那些规范定义"接口长什么样、连接怎么管理",而请求上下文规范定义"请求在进入业务 handler 之前经过哪些关卡"。

2. 运行时请求上下文:RequestContextHolder 与 RequestContext

Nacos 采用 RequestContextHolder + RequestContext 作为进程内(process-local)的请求上下文模型,源码位于 RequestContext.javaRequestContextHolder.java

2.1 ThreadLocal 持有与清理义务

RequestContextHolder 的核心实现非常直接:

public class RequestContextHolder {

    private static final Supplier<RequestContext> REQUEST_CONTEXT_FACTORY = () -> {
        long requestTimestamp = System.currentTimeMillis();
        return new RequestContext(requestTimestamp);
    };

    private static final ThreadLocal<RequestContext> CONTEXT_HOLDER =
        ThreadLocal.withInitial(REQUEST_CONTEXT_FACTORY);

    public static RequestContext getContext() {
        return CONTEXT_HOLDER.get();
    }

    public static void removeContext() {
        CONTEXT_HOLDER.remove();
    }
}

从源码结构看,上下文是懒创建的:任何线程第一次调用 getContext() 时才通过工厂生成 RequestContextrequestTimestamp 取创建时刻的系统时间。规范由此给出关键规则:当工作线程可被复用时,请求入口必须在请求处理结束后清理 ThreadLocal——removeContext() 就是为此提供的清理入口。Servlet 容器线程池中的线程会被反复复用,若不清理,上一个请求的上下文会"泄漏"到下一个请求,这在鉴权上下文字段中属于严重的数据串流风险。

2.2 RequestContext 的字段构成

规范要求 RequestContext 包含:请求 ID、请求时间戳、BasicContextEngineContextAuthContext 以及具名扩展上下文。对照 RequestContext.java 的源码,这些字段与规范一一对应:

public class RequestContext {
    // HTTP 请求不显式使用,自动生成;gRPC 请求与真实请求 id 相同
    private String requestId;
    private final long requestTimestamp;
    private final BasicContext basicContext;
    private final EngineContext engineContext;
    private final AuthContext authContext;
    private final Map<String, Object> extensionContexts;
}

其中 requestId 的语义按传输协议区分(源码注释明确):HTTP 请求不显式设置 id,由构造函数生成 UUID;gRPC 请求的 id 与协议层真实请求 id 相同。这解释了第 4 节中 gRPC 接收器为什么要调用 setRequestId 而 HTTP 入口过滤器不需要。

规范对三类上下文各自的职责做了约束,源码中的对应类为:

  • BasicContext.java:记录协议(protocol)、请求目标(target)、编码(encoding)、应用名(app)、User-Agent、远端/源地址信息;
  • EngineContext.java:引擎层运行时元数据;
  • AuthContext.java:当鉴权过滤器已执行时,记录 API 类型、解析出的身份(parsed identity)、资源(resource)与鉴权结果。

规范还强调一个易被忽视的细节:解析出的身份(parsed identity)记录的是从请求中实际提取到的身份参数名,且要与"传输层派生"和"插件丰富(plugin-enriched)"的元数据分开记录;HTTP 身份参数名按大小写不敏感方式匹配。这为后续排查"某个身份头为什么没被识别"提供了明确的规范依据。

2.3 两条初始化链路:HTTP 与 gRPC

HTTP 侧HttpRequestContextFilter.java 初始化,它运行在最早的 Servlet 过滤器顺序上。规范描述的行为在源码中完整体现:

public class HttpRequestContextFilter implements Filter {
    private static final String PATTERN_REQUEST_TARGET = "%s %s";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
        FilterChain filterChain) throws IOException, ServletException {
        RequestContext requestContext = RequestContextHolder.getContext();
        try {
            requestContext.getBasicContext().setRequestProtocol(BasicContext.HTTP_PROTOCOL);
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            setRequestTarget(request, requestContext);
            setEncoding(request, requestContext);
            setAddressContext(request, requestContext);
            setOtherBasicContext(request, requestContext);
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            RequestContextHolder.removeContext();
        }
    }
}

几个实现要点值得注意:

  1. target 的格式是 "{METHOD} {URI}"PATTERN_REQUEST_TARGET = "%s %s"),即以 HTTP 方法与 URI 拼出请求目标,供日志、指标、追踪统一引用;
  2. 编码仅在 request.getCharacterEncoding() 非空时写入;
  3. 地址信息包含四元组:远端 IP(getRemoteAddr)、远端端口、源 IP(通过 WebUtils.getRemoteIp(request) 解析,以处理代理转发场景)、Host 头;
  4. app 名支持双头回退:先读 Nacos 的 app 头,为空时回退读 CLIENT_APPNAME_HEADER,体现了对多版本客户端的兼容;
  5. 清理在 finally 中执行,即使 doFilter 抛出异常也保证 ThreadLocal 被移除——这正是规范第 2 节"请求入口必须在请求处理后清理"规则的落地方式。其测试见 HttpRequestContextFilterTest.java

gRPC 侧GrpcRequestAcceptor.java连接校验通过且 payload 解析完成后初始化上下文。与 HTTP 的差异点:使用 Request 对象中的请求 id(因此 gRPC 的请求 id 与协议层一致)、协议标记为 gRPC、以请求类名作为 target、以客户端版本作为 user agent、从注册连接(registered connection)解析 app 元数据与远端/源地址。这个差异来自 gRPC 长连接模型:连接建立时即登记了来源信息,请求级上下文直接复用连接级元数据,规范将连接元数据归口到 远程连接生命周期规范,此处不重复。

2.4 上下文是纯运行时的

规范最后对上下文的性质做了三条否定式定义,这是理解其作用域的关键:

  • 不持久化(not persisted);
  • 不是集群复制负载(not a cluster replication payload);
  • 不会自动传播到异步任务——组件如需在异步链路中携带请求元数据,必须显式拷贝所需字段。

最后一点对排查问题有直接意义:如果在异步线程(如线程池任务)中拿到"空"或"错误的"上下文,不是 bug,而是设计预期;正确做法是在提交任务时显式传递 requestId 等字段。

3. HTTP 过滤器模型:职责、顺序与拒绝行为

HTTP 过滤器是标准 Servlet Filter,由 Nacos Web 配置与各域模块注册。规范列出了核心过滤器及其职责,仓库中的实现均可逐一找到:

过滤器 实现位置 职责
FormSizeFilter FormSizeFilter.java 在常规 Controller 处理之前拒绝超大 form 请求,避免恶意大报文打爆内存
HttpRequestContextFilter HttpRequestContextFilter.java 初始化并清理 RequestContext(见第 2 节)
AuthFilter / AuthAdminFilter / Console 鉴权过滤器 AuthFilter.java 处理 @Secured 标注的 API,在鉴权评估后写入 AuthContext
NacosHttpTpsFilter NacosHttpTpsFilter.java 通过流量控制插件管理器检查 HTTP v1/v2 Config 与 Naming 路径上的 @TpsControl 控制点
ParamCheckerFilter ParamCheckerFilter.java 通过 ExtractorManager 抽取结构化参数,并交由当前生效的 ParamChecker 校验
域级过滤器 各域模块 可适配遗留请求参数、流量元数据或模块级兼容行为

其中域级过滤器有一条纪律:可以做遗留兼容适配,但不得绕过新 API 的通用响应、鉴权与校验规则

3.1 过滤器顺序规则

规范给出四条顺序与拒绝行为规则:

  1. 上下文初始化必须最先执行——因为后续依赖请求、鉴权、追踪、流量控制元数据的过滤器都建立在 RequestContext 之上;
  2. 尺寸检查、鉴权、流量控制、参数校验四类过滤器都可以在 Controller 调用前拒绝请求
  3. 过滤器拒绝 HTTP 请求时,若目标 API 族期望包装式响应,必须返回 Nacos 标准结果格式(即统一的 code/message/data 结构,错误码语义见 响应与错误规范);
  4. 过滤器自身抛出的异常,在过滤器"拥有"该拒绝权时应转换为统一异常/结果模型;而意料之外的基础设施故障可以重新抛出,交由全局异常处理兜底。

第 3、4 条区分了"业务性拒绝"与"故障性异常",前者要给出可预期的标准错误响应,后者保留给全局 handler,这是过滤器链可维护性的基础。

3.2 Controller 方法解析规则:鉴权与派发必须选到同一个方法

这是规范中最具实战价值的部分之一。它规定:任何需要在 Spring MVC 派发之前解析 Controller 方法的组件(典型如鉴权过滤器,它要在请求进 Controller 前判断权限),必须复用当前生效的 Spring MVC RequestMappingHandlerMapping。鉴权与派发必须基于同一个 Servlet 请求、同一套 context-path 与路径匹配规则选出同一个 Controller 方法。

规范接着列举了不得交给"仅鉴权用"的独立归一化算法处理的边角输入,清单本身就是一份攻击面/兼容性检查表:

  • 字面量路径参数(literal path parameters);
  • 单次或多次百分号编码(percent encoding);
  • 重复空段(duplicate empty segments)、点段(dot segments);
  • 非法编码、非法 UTF-8、控制字符;
  • Unicode 分隔符仿形字符(separator lookalikes);
  • 绝对形式请求目标(absolute-form request targets);
  • 被编码的路径分隔符(encoded path separators)。

此外:查询参数不参与 Controller 路径匹配

关于遗留回退开关,规范给出了完整的生命周期标注:

  • nacos.core.auth.controller-method-cache.legacy-enabled=true 可以临时将方法解析降级到遗留注解缓存;
  • 该遗留解析器自 3.3.0 起废弃(deprecated),计划在 3.4.0 移除,且它与 Spring MVC 路径匹配可能不一致,因此必须默认保持关闭
  • 开启期间,遗留解析器必须在移除 context path 之前,对请求 URI 与 context path 做一致的解析——包括两者都含百分号编码字符的情况。

从工程视角看,这一段规则本质是在修复一类真实风险:如果鉴权用自研的路径解析、Spring MVC 用另一套,两者对同一 URL 的解析结果可能不同,就会出现"鉴权认为命中了 A 接口、实际派发到了 B 接口"的错配。规范的解法是"单一事实来源":谁做派发,鉴权就复用谁的映射表。

4. gRPC 请求过滤器模型:串行过滤器链与"返回 null 即放行"

gRPC 业务请求的处理链路是:GrpcRequestAcceptor 接收 → 解析为 Request 对象 → 匹配 RequestHandler → 穿过已注册的 AbstractRequestFilter 实例链 → 调用 handler 的 handle 方法。相关实现分布在 AbstractRequestFilter.javaRequestFilters.javaGrpcRequestAcceptor.java

4.1 过滤器链的执行语义

规范定义的链语义与 Servlet 过滤器链有显著区别,要点如下:

  • AbstractRequestFilter 实例在初始化时注册进 RequestFilters
  • 过滤器在 RequestHandler.handleRequest 内部串行执行
  • 返回 null 表示继续链;返回非成功(non-success)响应则立即停止链并把该响应返回给调用方;
  • 过滤器抛出的异常只被请求 handler 记录日志,本身不会中止 handler 链——这与 HTTP 侧"异常可能被转换为拒绝响应"的语义不同,读者在跨协议排查时不要混用两套心智模型;
  • 拒绝请求的过滤器应当创建该 handler 声明的响应类型,并设置合适的错误码与消息,而不是返回一个通用错误对象。

4.2 四个标准 gRPC 过滤器

过滤器 实现位置 职责
RemoteRequestAuthFilter RemoteRequestAuthFilter.java 评估 @Secured、服务端身份、身份有效性与权限,写入 AuthContext
RemoteParamCheckFilter RemoteParamCheckFilter.java 使用 ExtractorManager 与当前生效的 ParamChecker 校验请求参数
TpsControlRequestFilter TpsControlRequestFilter.java 通过流量控制插件管理器检查 @TpsControl 控制点,受限时返回 OVER_THRESHOLD
NamespaceValidationRequestFilter NamespaceValidationRequestFilter.java 当 handler 通过 @NamespaceValidation 显式加入时,校验命名空间存在性

可以看到,gRPC 侧的四个过滤器与 HTTP 侧的鉴权、参数校验、流量控制、命名空间校验一一对应,两条协议共享同一套注解(@Secured@TpsControl@NamespaceValidation)与同一套底层管理器,只是挂载点不同(Servlet Filter vs gRPC Filter)。

4.3 进入过滤器链之前的快速拒绝

规范还明确:gRPC 接收器在进入 handler 过滤器链之前就拒绝五类请求:

  1. 服务尚在启动中(starting)期间到达的请求;
  2. 未知请求类型(unknown request types);
  3. 非法连接(invalid connections);
  4. 非法 payload;
  5. Request 类型的 payload。

这个前置拒绝层保证过滤器链只处理"形态合法"的请求,把资源消耗最低的路径放在最前面。

5. 参数抽取与校验:ExtractorManager 的统一抽象

规范第 5 节定义了跨协议的参数抽取机制。ExtractorManager.Extractor把 Controller 方法或请求 handler 映射到 HTTP/RPC 参数抽取器的通用注解,实现见 ExtractorManager.java

抽取规则逐条对应到源码约定:

  1. 抽取器只产出 ParamInfo 记录供共享校验器使用,不得修改领域状态或执行持久化写——抽取器是纯读函数;
  2. 注解可声明在方法声明类上,方法级注解优先;
  3. HTTP 抽取器读 Servlet 请求,RPC 抽取器读 Request 对象;
  4. 抽取器通过 Nacos SPI 加载,且对同一请求输入必须是**确定性(deterministic)**的;
  5. 校验行为由服务端参数检查配置与当前生效的 ParamChecker 共同决定;
  6. 领域级校验仍属于表单、请求对象、服务或域 handler——参数过滤器只强制通用结构性规则。

第 6 条再次呼应第 1 节的边界原则:ParamCheckerFilter(HTTP)与 RemoteParamCheckFilter(gRPC)管"参数结构合不合法","业务上合不合理"永远归域内处理。

6. 命名空间校验:显式加入的横切守卫

命名空间校验(Namespace validation)是一个只对显式加入(opt-in)的 API 生效的横切守卫,注解 NamespaceValidation.java 与 gRPC 过滤器 NamespaceValidationRequestFilter.java 共同实现。规范给出的规则:

  • 全局命名空间校验开关与 handler 级 @NamespaceValidation 注解双重控制——全局开关可整体关闭,注解决定单个 API 是否参与;
  • 空白命名空间值不按"缺失命名空间"拒绝,而是交给各域默认值处理(因为很多 API 允许省略 namespace 以使用 default);
  • 非空 namespace id 必须在命名空间操作服务中真实存在,请求才继续;
  • 校验失败必须使用当前传输协议的标准错误码与响应模型(HTTP 返回标准结果结构,gRPC 返回 handler 声明的响应类型加错误码);
  • 命名空间校验不得创建命名空间、不得推断租户归属、不得覆盖域级授权规则——它只是存在性守卫,不是资源管理入口。

7. 横切边界:过滤器层与领域规范的分工

规范第 7 节用五条边界声明把过滤器层与其他规范解耦,这些边界决定了"看到某类行为应该去读哪份规范":

  • 鉴权过滤器评估身份与权限,但鉴权资源语义鉴权与权限规范
  • 流量控制过滤器执行限流,但控制点定义与插件行为流量控制插件规范
  • 请求上下文可以为指标与追踪提供字段,但可观测行为可观测钩子规范
  • 远程连接元数据归 远程连接生命周期规范
  • 域 handler 不得假设过滤器已做过领域特定校验,除非 API 契约明确要求该过滤器;
  • 新 API 应优先复用共享过滤器与注解,而不是在 Controller 里复制鉴权、参数、命名空间或限流逻辑。

最后两条尤其重要:前者防止"隐式依赖过滤器"的脆弱设计,后者是 v3 API 的设计纲领——横切能力收敛到注解 + 过滤器,Controller 只管业务。

8. 遗留问题与演进方向

规范诚实地列出两个未决问题(Pending Issues),可作为跟进上游演进的观察点:

  1. 模块级遗留过滤器/控制器仍混杂兼容适配与校验、业务行为。方向是新 v3 API 不把这类行为写进正式 API 契约,并把通用检查逐步迁移到共享过滤器或域服务;
  2. gRPC 连接心跳与半开(half-open)检测目前隐藏在 Naming 等域之下。规范主张把详细的传输层心跳语义放到未来的远程连接或 gRPC 客户端规范中展开,而不是在各域规范中重复。

9. 小结与延伸阅读

Nacos 的请求上下文规范本质上回答了一个问题:横切关注点(上下文、鉴权、限流、参数校验、命名空间守卫)应该在哪里落地。答案是统一的过滤器/过滤器链层,通过 @Secured@TpsControl@NamespaceValidation 等注解驱动,HTTP 与 gRPC 双协议共享同一套管理器与规则,并严格不越界到领域语义。配套的规范入口:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527