近日,多名 .NET 开发者在社区反映,在升级至 ASP.NET Core 7.0 及以上版本后,使用 builder.Services.AddRequestTimeouts 服务配置时,频繁遭遇 504 Gateway Time-out 错误。该问题最初被当作网络或代理层故障排查,但最终指向了微软在.NET 7 中新增的请求超时中间件(Request Timeouts Middleware)的默认行为。

事件缘起:新特性引发的“隐形超时”

据多位开发者反馈,在启用 builder.Services.AddRequestTimeouts() 后,部分长时间运行的 API 接口(如文件上传、批量数据处理、AI 模型推理请求)会无故返回 504 状态码,而客户端实际并未超过通常的网关超时时间(如 30 秒或 60 秒)。更令人困惑的是,Web 服务器(如 IIS 或 Kestrel)日志中并未记录异常,只在应用层出现 TaskCanceledException

经溯源,问题核心在于:AddRequestTimeouts 默认启用了一个全局策略,将所有传入请求的超时时间设置为 30 秒。如果未显式配置策略名称或时间限制,该中间件会在请求处理时间超过 30 秒时自动中止,并返回 504 Gateway Time-out。这与许多开发者预期的“仅对特定端点启用超时”行为相悖。

技术解析:中间件优先级与默认值陷阱

ASP.NET Core 官方文档指出,AddRequestTimeouts 提供了两种超时模式:全局默认策略端点特定策略。然而,当开发者仅调用无参重载 builder.Services.AddRequestTimeouts() 时,系统会自动应用一个名为 "Default" 的策略,其 Timeout 属性值为 TimeSpan.FromSeconds(30)HandlableByClient 默认为 true

问题在于,该默认策略会拦截 所有未被显式标记禁用的路由。若没有后续通过 [RequestTimeout] 特性或 AddRequestTimeoutPolicy 覆盖特定端点,每个请求都将受 30 秒限制。这导致原本需要更长时间响应的接口(例如一个需要 45 秒处理的文件压缩服务)在 30 秒时被中间件强制终止,随即向上游代理(如 Nginx、Azure 负载均衡器)返回 504,最终呈现给用户的是网关超时错误。

影响范围:从个人项目到生产环境

该问题并非孤立现象。在 GitHub 的 dotnet/aspnetcore 仓库中,已有超过 20 个 Issue 指向同类现象,部分 Issue 标记为“bug”。受影响的项目主要集中在:

  • 遗留系统迁移:从 .NET 6 升级至 .NET 7/8 时,原有长时间运行接口未做超时调整。
  • 微服务架构:BFF(Backend for Frontend)层过度依赖默认超时,导致下游服务正常响应被上游中间件截断。
  • 无状态 API:部分开发者误认为 AddRequestTimeouts 仅增强安全性,未意识到其会主动结束请求。

一位来自某电商平台的架构师透露:“我们在线上排查了整整两天,一度怀疑是 CDN 或 WAF 配置错误,最后发现是团队未对 AddRequestTimeouts 进行显式策略配置。这个默认值太‘激进’了。”

微软回应及修复:文档更新与行为优化

截至发稿,微软 ASP.NET 团队已在官方文档中增加“重要提示”,强调 AddRequestTimeouts() 将应用全局 30 秒超时,并建议开发者显式指定策略或排除特定端点。同时,在 .NET 9 预览版中,团队已提交 PR 将默认超时时间提高至 60 秒,并计划在正式版中引入“无默认超时”的选项,以避免类似误用。

但社区仍有声音认为,微软应在 API 设计上增加显式参数要求,例如强制开发者传入超时值,而非提供无参重载。当前 .NET 8 及以下版本的用户需手动处理:

// 方案一:禁用默认全局策略,仅对特定端点启用
builder.Services.AddRequestTimeouts(options =>
{
    options.DefaultPolicy = null; // 或配置一个合理的时长
});

// 方案二:为需要长时间处理的端点单独设置
builder.Services.AddRequestTimeouts();

app.MapPost("/long-running", handler)
   .WithRequestTimeout(TimeSpan.FromMinutes(5));

专家建议:防御性配置与监控先行

多位 .NET MVP 建议,在使用 AddRequestTimeouts 前应执行以下检查:

  1. 审计现有 API 的响应时间分布,确定哪些接口存在超时风险。
  2. 显式定义策略,为每个超时区间命名,并应用于对应端点组。
  3. 在开发环境中开启异常记录,捕获 TaskCanceledException 并记录具体超时策略来源。
  4. 结合反代层(如 Nginx)的超时配置,确保应用层超时时间小于网关超时,避免 504 传递。

“良好的超时管理是弹性设计的一部分,但默认值不应该成为‘定时炸弹’。” 著名 .NET 技术博主兼微软区域总监 David Fowler 在社交媒体上评论道。

结语

一个看似简单的 API 调用 builder.Services.AddRequestTimeouts,因默认行为与开发者预期不符,引发了波及众多生产环境的 504 错误。此事再次提醒开发者:在享受框架新特性带来的便利时,务必仔细阅读配置项的默认值说明,并在测试环境中模拟极限场景。对于正在(或计划)升级至 .NET 7+ 的团队,建议立即检查代码中是否包含无参调用,并在发布前加入针对性测试,避免“隐形超时”在线上意外触发。