Kestrel在FIN及请求结束后出现BadHttpRequestException问题求助
.NET 6 Minimal API 出现 BadHttpRequestException(请求已正常返回200)的解决建议
问题背景
基于.NET 6构建的Minimal API部署在Docker容器并托管于AWS,客户端为Wordpress,通过PHP Curl调用API。上线后日志中频繁出现Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content异常,但请求实际已正常处理并返回200状态码,且该异常无法被自定义异常中间件捕获。
异常日志
1 "Connection id \"{ConnectionId}\" accepted.", 2 "Connection id \"{ConnectionId}\" started.", 3 "{HostingRequestStartingLog:l}", 4 "{CandidateCount} candidate(s) found for the request path '{Path}'", 5 "Request matched endpoint '{EndpointName}'", 6 "The request has an origin header: '{origin}'.", 7 "CORS policy execution successful.", 8 "Successfully validated the token.", 9 "AuthenticationScheme: {AuthenticationScheme} was successfully authenticated.", 10"AuthenticationScheme: {AuthenticationScheme} was successfully authenticated.", 11"Authorization was successful.", 12"Executing endpoint '{EndpointName}'", 13"Writing value of type '{Type}' with status code '200'.", 14"Executed endpoint '{EndpointName}'", 15"HTTP {RequestMethod} {RequestPath} responded 200 in {Elapsed:0.0000} ms", 16"Connection id \"{ConnectionId}\" completed keep alive response.", 17"{HostingRequestFinishedLog:l}", 18"Connection id \"{ConnectionId}\" received FIN.", 19"Connection id \"{ConnectionId}\", Request id \"{TraceIdentifier}\": started reading request body.", 20"Connection id \"{ConnectionId}\" sending FIN because: \"{Reason}\"", 21"Connection id \"{ConnectionId}\" disconnecting.", 22"Connection id \"{ConnectionId}\" bad request data: \"{message}\"", 23"Exception": "Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content.\n at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1ContentLengthMessageBody.TryReadInternal(ReadResult& readResult)\n at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1MessageBody.OnConsumeAsync()" 24"Connection id \"{ConnectionId}\", Request id \"{TraceIdentifier}\": done reading request body.", 25"Connection id \"{ConnectionId}\" stopped.",
正常日志
1 "Connection id \"{ConnectionId}\" accepted.", 2 "Connection id \"{ConnectionId}\" started.", 3 "{HostingRequestStartingLog:l}", 4 "{CandidateCount} candidate(s) found for the request path '{Path}'", 5 "Request matched endpoint '{EndpointName}'", 6 "The request has an origin header: '{origin}'.", 7 "CORS policy execution successful.", 8 "Successfully validated the token.", 9 "AuthenticationScheme: {AuthenticationScheme} was successfully authenticated.", 10"AuthenticationScheme: {AuthenticationScheme} was successfully authenticated.", 11"Authorization was successful.", 12"Executing endpoint '{EndpointName}'", 13"Writing value of type '{Type}' with status code '200'.", 14"Executed endpoint '{EndpointName}'", 15"HTTP {RequestMethod} {RequestPath} responded 200 in {Elapsed:0.0000} ms", 16"Connection id \"{ConnectionId}\" completed keep alive response.", 17"{HostingRequestFinishedLog:l}", 18"Connection id \"{ConnectionId}\", Request id \"{TraceIdentifier}\": started reading request body.", 19"Connection id \"{ConnectionId}\", Request id \"{TraceIdentifier}\": done reading request body.", 20"Connection id \"{ConnectionId}\" received FIN.", 21"Connection id \"{ConnectionId}\" disconnecting.", 22"Connection id \"{ConnectionId}\" sending FIN because: \"{Reason}\"", 23"Connection id \"{ConnectionId}\" stopped.",
核心问题分析
通过日志对比可以明确:
- 异常触发时机:请求已完全处理并返回200后,客户端提前发送FIN关闭连接,而Kestrel仍尝试复用该长连接读取下一个请求的请求体,导致读取失败抛出异常。
- 异常无法捕获原因:该异常属于Kestrel底层连接处理阶段的错误,不在ASP.NET Core请求处理管道范围内,因此自定义异常中间件无法拦截。
解决建议
一、客户端(PHP Curl)侧调整
- 禁用长连接复用:在Curl请求中添加
Connection: close请求头,或设置Curl选项CURLOPT_FORBID_REUSE => true,确保客户端收到响应后立即关闭连接,避免Kestrel等待下一个请求。
示例代码:$curl = curl_init(); curl_setopt($curl, CURLOPT_URL, "你的API地址"); curl_setopt($curl, CURLOPT_HTTPHEADER, array('Connection: close')); // 其他Curl配置项 $response = curl_exec($curl); curl_close($curl); - 确保资源正确释放:避免在
curl_exec后立即销毁Curl资源,等待响应完全接收后再执行curl_close。
二、服务器(.NET 6)侧调整
- 强制关闭长连接:添加全局中间件,对所有响应注入
Connection: close头,主动告知客户端不要复用连接:app.Use(async (context, next) => { context.Response.Headers.Connection = "close"; await next(); }); - 过滤无关异常日志:如果不需要监控这类不影响业务的异常,可在
appsettings.json中调整日志级别,过滤该特定异常:{ "Logging": { "LogLevel": { "Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException": "Warning" } } } - 缩短连接空闲超时:配置Kestrel的连接空闲超时,减少等待下一个请求的时长:
builder.WebHost.ConfigureKestrel(options => { options.Limits.IdleTimeout = TimeSpan.FromSeconds(30); });
内容的提问来源于stack exchange,提问作者Andreas Andersson
相关产品推荐
相关产品推荐

