net::ERR_CONNECTION_RESET导致Angular API请求重复?技术求助
解决长流程API的重复执行与浏览器超时问题
看起来你碰到的是典型的长同步请求导致的代理超时+自动重试问题,结合你的场景(.NET中间层+Angular,服务器流程正常但控制器重复执行,前端无重试请求),我来梳理下原因和解决方案:
核心原因分析
你的问题关键点在于:控制器方法重复执行但前端没发对应请求,这说明重试不是浏览器发起的,而是中间的代理服务器(比如IIS、Nginx、云负载均衡)或者.NET自身的配置导致的:
- 大多数服务器默认的请求超时窗口是5分钟左右,当你的API请求超过这个时间还没返回响应,代理会主动断开连接,并且如果开启了自动重试(很多默认配置会加这个),就会重新发起请求到你的控制器,导致重复执行。
- IE的连接超时阈值比Chrome更短,所以更早触发
net::ERR_CONNECTION_RESET;Chrome默认超时约10分钟,所以10分钟后才提示失败,但其实代理早就开始重试了。 - 服务器端流程正常是因为第一次请求已经启动了业务逻辑,后续的重试又重复触发了一次,但业务流程本身没有做幂等校验,所以会并行/重复执行。
针对性解决方案
1. 调整服务器/代理的超时与重试配置
首先要把代理层的超时时间拉长到比你的长流程预期时间更长,同时关闭自动重试:
- 如果用IIS托管.NET应用:
- 对于.NET Framework:在
web.config里修改httpRuntime的executionTimeout(注意要搭配compilation debug="false"才会生效):<system.web> <httpRuntime executionTimeout="3600" /> <!-- 1小时,根据你的流程调整 --> </system.web> - 对于ASP.NET Core:在Program.cs里配置Kestrel和IIS的超时:
builder.WebHost.ConfigureKestrel(options => { options.Limits.KeepAliveTimeout = TimeSpan.FromHours(1); }) .UseIIS() .ConfigureServices(services => { services.Configure<IISServerOptions>(options => { options.MaxRequestBodySize = int.MaxValue; options.AllowSynchronousIO = true; // 如果你的代码是同步的话 }); });
- 对于.NET Framework:在
- 如果用反向代理(比如Nginx):修改
proxy_read_timeout和关闭重试:location /api { proxy_pass http://your-backend; proxy_read_timeout 3600s; # 1小时 proxy_next_upstream off; # 关闭自动重试 } - 云服务场景:比如Azure App Service、AWS EC2负载均衡,要在控制台调整对应的请求超时时间,同时关闭重试策略。
2. 改用异步长流程模式(推荐)
同步挂着等待长流程完成本身就不是最佳实践,更可靠的方式是用“触发-查询”模式:
- 第一步:触发流程并返回任务ID:
你的API控制器方法不再等待流程完成,而是立即创建一个后台任务(可以用BackgroundService、Hangfire、Quartz.NET等),然后返回一个唯一的taskId给前端:[HttpPost("trigger-long-process")] public IActionResult TriggerLongProcess([FromBody] ProcessRequest request) { var taskId = Guid.NewGuid().ToString(); // 将任务加入后台队列执行 _backgroundQueue.QueueBackgroundWorkItem(async token => { await _processService.RunLongProcess(request, taskId, token); }); return Ok(new { TaskId = taskId }); } - 第二步:前端轮询或实时查询状态:
Angular前端拿到taskId后,通过定时轮询(比如每30秒一次)或者SignalR实时推送,查询任务的执行状态:triggerProcess() { this.apiService.triggerLongProcess(request).subscribe(res => { this.taskId = res.taskId; // 开始轮询状态 this.statusPolling = interval(30000).subscribe(() => { this.apiService.getTaskStatus(this.taskId).subscribe(status => { this.processStatus = status; if (status === 'Completed' || status === 'Failed') { this.statusPolling.unsubscribe(); } }); }); }); } - 第三步:后台任务处理:后台执行长流程时,把状态更新到数据库或缓存,供查询接口读取。
3. 给长流程接口加幂等校验
如果暂时没法改成异步模式,至少要给控制器方法加幂等校验,防止重复执行:
- 让前端在请求时带上唯一的
requestId,控制器先检查这个requestId是否已经处理过,没处理过再执行流程:[HttpPost("long-process")] public async Task<IActionResult> LongProcess([FromBody] ProcessRequest request) { // 检查requestId是否已处理(比如从Redis或数据库查) if (_idempotencyService.IsRequestProcessed(request.RequestId)) { return Ok(new { Status = "AlreadyProcessed" }); } // 标记为已处理 _idempotencyService.MarkRequestAsProcessing(request.RequestId); try { await _processService.RunLongProcess(request); _idempotencyService.MarkRequestAsCompleted(request.RequestId); return Ok(new { Status = "Completed" }); } catch (Exception ex) { _idempotencyService.MarkRequestAsFailed(request.RequestId, ex.Message); return StatusCode(500, new { Status = "Failed", Message = ex.Message }); } }
4. 排查.NET中间层的重试配置
检查你的.NET项目是否用了Polly之类的重试库,不小心给这个长流程接口加了重试策略。如果有的话,要排除这个接口,或者调整重试条件(比如只在特定错误时重试,而不是超时就重试)。
总结
最推荐的是异步长流程模式,从根本上避免长时间挂起请求带来的超时、重试问题;如果暂时要维持同步模式,一定要调整代理/服务器的超时时间,关闭重试,同时加幂等校验防止重复执行。
内容的提问来源于stack exchange,提问作者CBC_NS
相关产品推荐
相关产品推荐

