问题描述
我们有一个 .NET 3.1 应用程序,它使用 Microsoft 的 YARP ReverseProxy,使用版本 Preview 8。我们的应用程序是一个后端换前端 (BFF),它托管一个 ReactJS SPA,与 IdentityServer (IS5) 相关联,并且使用反向代理来访问我们的各种 API。 BFF 在 IIS 服务器上运行,并通过防火墙和负载平衡器连接到 IS5 和其他 API。
当我们将应用程序升级到 .NET 5 时,我们注意到所有 API 请求都失败了,响应错误代码为 400,BadRequest。我们尝试将反向代理升级到 Preview 10,但错误继续发生。我们尝试做的其他一些事情是:
- 将 YARP 配置为仅使用 HTTP/1.1 进行了此更改,因为负载均衡器 (LB) 是一个 TLS 终止端点,并且所有发送到 API 的请求都将通过 HTTP 而不是 HTTPS。 HTTP/2 需要 TLS,我们不确定 LB 如何处理转换。后来在日志中看到有 HTTP 调用的降级请求,所以相信 LB 确实将 HTTP/2 降级到 HTTP/1.1
- 在从 HTTP/2 到 HTTP/1.1 的转换过程中添加了一个转换来处理响应标头 这样做是基于其存储库中的问题 583
这是生成的代理配置。
{
"ReverseProxy": {
"Routes": [
{
"RouteId": "route_api","ClusterId": "cluster_api","Match": {
"Path": "/api/{*remainder}"
},"Transforms": [
{
"PathRemovePrefix": "/api"
},{
"ResponseHeader": "Connection","Set": "","When": "Always"
},{
"RequestHeadersCopy": "true"
}
]
},{
"RouteId": "route_odata","ClusterId": "cluster_odata","Match": {
"Path": "/odata/{*remainder}"
},"Transforms": [
{
"PathRemovePrefix": "/odata"
},{
"RequestHeadersCopy": "true"
}
]
}
],"Clusters": {
"cluster_api": {
"Destinations": {
"route_api/destination1": {
"Address": "https://api.domain.com/api/v1/","Version": "1.1"
}
}
},"cluster_odata": {
"Destinations": {
"cluster_odata/destination1": {
"Address": "https://api.domain.com/odata/v1/","Version": "1.1"
}
}
}
}
}
}
鉴于我们告诉 YARP 目标将是 HTTP/1.1,我们可能不需要设置 Connection
响应标头的额外转换。
我希望我们可以从防火墙/LB 获取一些日志,因为我猜测 400 错误来自那里。在查看 API 的 IIS 和应用程序日志时,我们没有看到请求命中 API。有没有其他人遇到过这个问题并找到了解决方法?
编辑 2021 年 4 月 1 日 13:25
发现 .NET 3.1 版本只调用了 API 控制器工作。任何对 OData 端点的调用都会失败,并显示 HTTP 状态代码 400。在 .NET 5 中,所有控制器、API 或 OData 都同样失败。
编辑 2021 年 4 月 8 日 08:29
尝试使用 Ocelot 反向代理并看到相同的问题,因此现在相信这不是代理/网关库的问题。决定配置我们部署的服务以允许来自开发环境的请求并产生了一个有趣的结果。通过 Visual Studio 2019 从 IIS Express 运行站点,一切正常。当站点部署到我的本地 IIS 实例并且我从那里运行它时,OData 查询再次失败。现在查看 IIS 配置和模块以查看可能导致问题的原因。
解决方法
问题最终出在 YARP 客户端和后端 API 之间的负载均衡器上。由于直接针对 API 的请求正在运行,而通过代理的请求失败,我们决定记录代理发送到 API 的所有标头。总共有 27 个标头,其中最大的两个是从前端请求转发的 Cookie 和 Authorization 标头。以下是我们为调试问题所采取的步骤。
- 将这些标头复制到 Postman 请求中,并尝试将它们发送到不需要任何身份验证的端点。请求失败。
- 尝试删除 Cookie 标头,因为只需要后端换前端即可处理身份管理。请求有效。
- 尝试删除 Authorization 标头,但重新添加了 Cookie 标头,请求再次生效。
- 然后我们尝试添加一个伪造的标题,我们不断增加它的大小,看看它是否最终会停止工作。超过 1000 个字符后,我们的 API 调用再次停止工作。
- 下一步是从随机标头中删除一个字符并将请求发送到 api。那行得通。
通过这些测试,我们确定负载均衡器在 HTTP 请求达到某个总标头大小时将其丢弃。由于 API 对 Cookie 标头没有任何用处,我们删除了它和另一个无用的标头。这解决了问题。
正如原始帖子标题所述,问题似乎在于从 .NET 3.1 到 5.0,我认为情况并非如此。我们看到的另一个花絮是 Authorization 标头和 Cookie 标头值的大小范围很大,具体取决于您尝试使用该服务的人员和时间。我认为我们看到的是,在某些情况下,这些标头的大小小到足以通过负载均衡器,但是当我们刷新会话时,大小会增加。
TLDR;标头中发送的数据量导致负载均衡器拒绝 HTTP 请求。