ASP.NET Core 6部署IIS时HttpContext.Response.Body为空问题求助
本地IIS部署的ASP.NET Core 6 REST API,约20%请求触发HttpContext.Response.Body为空的异常。Audit.NET日志显示这类请求的ResponseBody值为空,但状态码为200。未使用AWS,但症状类似相关AWS问题。怀疑Audit.NET导致,但分析中间件代码未发现问题,本地压测无法复现。
补充代码
Program.cs
var app = builder.Build(); app.Use((context, next) => { context.Request.EnableBuffering(); return next(); }); app.UseExceptionHandler(GlobalExceptionHandler.Configure); app.UseAuditMiddleware(_ => _ .FilterByRequest(r => r.Path.Value?.Contains("/api/") == true) .WithEventType(context => context.Request.Path) .IncludeRequestBody() .IncludeResponseBody()); app.UseMiddleware<ExceptionHandlingMiddleware>(); app.UseSwagger(); app.MapControllers();
GlobalExceptionHandler.cs
internal class GlobalExceptionHandler { public static void Configure(IApplicationBuilder applicationBuilder) { applicationBuilder.Run(async context => { context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; var status = context.Features.Get<IStatusCodeReExecuteFeature>(); var error = context.Features.Get<IExceptionHandlerFeature>(); if (error != null) { var responseDetails = GetFullExceptionDetails(status, error.Error); await context.Response.WriteAsync(responseDetails, Encoding.UTF8); } }); } }
ExceptionHandlingMiddleware.cs
internal class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; public ExceptionHandlingMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext httpContext) { try { await _next(httpContext); } catch (Exception exception) { var errorResponse = HandleBusinessException(exception as MyException); var statusCode = GetStatusCode(exception as MyException); httpContext.Response.StatusCode = statusCode; httpContext.Response.ContentType = "application/json"; await httpContext.Response.WriteAsync(errorResponse.Serialize()); } } private object HandleBusinessException(MyException ex) => new { Message = ex?.Message }; private int GetStatusCode(MyException ex) => ex != null ? 400 : 500; }
1. 中间件顺序错误
当前UseAuditMiddleware注册在ExceptionHandlingMiddleware之前,会导致Audit.NET先捕获响应流,但后续异常处理中间件执行时,可能因响应流未正确重置或被Audit.NET提前释放,引发Response.Body为空的问题。
修复方案:调整中间件顺序,让异常处理逻辑先执行,再进入Audit记录流程:
var app = builder.Build(); app.Use((context, next) => { context.Request.EnableBuffering(); return next(); }); app.UseExceptionHandler(GlobalExceptionHandler.Configure); // 先注册异常处理中间件 app.UseMiddleware<ExceptionHandlingMiddleware>(); // 再注册Audit中间件 app.UseAuditMiddleware(_ => _ .FilterByRequest(r => r.Path.Value?.Contains("/api/") == true) .WithEventType(context => context.Request.Path) .IncludeRequestBody() .IncludeResponseBody()); app.UseSwagger(); app.MapControllers();
2. Audit.NET响应流捕获的并发问题
高并发场景下,Audit.NET默认的响应流读取逻辑可能出现流未初始化或提前释放的情况。可以通过启用内置缓冲或手动包装响应流解决:
方案A:启用Audit.NET内置响应缓冲
app.UseAuditMiddleware(_ => _ .FilterByRequest(r => r.Path.Value?.Contains("/api/") == true) .WithEventType(context => context.Request.Path) .IncludeRequestBody() .IncludeResponseBody() .UseResponseBuffering()); // 启用响应缓冲
方案B:手动包装响应流
在请求管道最前端添加流缓冲逻辑,确保响应流始终可用:
app.Use(async (context, next) => { var originalBody = context.Response.Body; using var memoryStream = new MemoryStream(); context.Response.Body = memoryStream; try { await next(); memoryStream.Position = 0; await memoryStream.CopyToAsync(originalBody); } finally { context.Response.Body = originalBody; } });
3. IIS环境配置优化
IIS在高并发场景下可能出现响应流初始化异常,检查以下配置:
- 将应用池的.NET CLR版本设置为
No Managed Code(ASP.NET Core为自托管模式,无需IIS托管CLR) - 调整应用池队列长度和最大工作进程数,避免请求排队引发资源竞争
- 启用IIS的
Failed Request Tracing,捕获异常请求的完整处理流程,定位流为空的具体触发时机
4. 异常处理中间件代码修复
原代码存在变量名错误(未定义context,应使用httpContext)和构造函数缺失的问题,这些错误可能导致中间件执行异常,间接引发响应流问题,修正后的代码已在补充代码部分给出。
5. 本地压测复现建议
生产环境的并发量、请求模式(如长连接、突发流量)与本地差异较大,建议使用工具模拟接近生产的流量模式(如混合长短请求、匹配生产并发数),同时在生产环境添加日志,记录Response.Body为空时的请求路径、请求头、上下文信息,辅助定位问题。
内容的提问来源于stack exchange,提问作者Pawel

