.NET Core异常处理器响应头丢失及响应日志中间件问题排查
问题:IExceptionHandler添加响应头被Kestrel移除,且ResponseLoggingMiddleware导致OnStarting添加头失效
问题描述
- 在
IExceptionHandler实现中添加自定义响应头(X-Client-IP、trace-id、interaction-id),Kestrel会将这些头全部移除。 - 改用
context.Response.OnStarting回调添加头后,引入自定义响应日志中间件ResponseLoggingMiddleware1,该添加头的方法也失效。
自定义异常处理器代码
internal class CustomExceptionHandler : IExceptionHandler { public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken) { var traceId = Guid.NewGuid().ToString(); var interactionId = Guid.NewGuid().ToString(); var clientIp = context.Connection.RemoteIpAddress?.ToString() ?? "Unknown"; context.Response.Clear(); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ContentType = "application/json"; context.Response.Headers["X-Client-IP"] = clientIp; context.Response.Headers["trace-id"] = traceId; context.Response.Headers["interaction-id"] = interactionId; var errorResponse = new { StatusCode = context.Response.StatusCode, Message = "An unexpected error occurred.", TraceId = traceId, InteractionId = interactionId }; await context.Response.WriteAsync(JsonSerializer.Serialize(errorResponse), cancellationToken); return false; } }
失效的OnStarting添加头代码
context.Response.OnStarting(() => { context.Response.Headers.Append("X-Client-IP", clientIp); context.Response.Headers.Append("trace-id", traceId); context.Response.Headers.Append("interaction-id", interactionId); return Task.CompletedTask; });
原响应日志中间件代码
public class ResponseLoggingMiddleware1 { private readonly RequestDelegate _next; private readonly ILogger<ResponseLoggingMiddleware1> _logger; public ResponseLoggingMiddleware1(RequestDelegate next, ILogger<ResponseLoggingMiddleware1> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { // Create a memory stream to capture the response body var originalBodyStream = context.Response.Body; using (var memoryStream = new MemoryStream()) { context.Response.Body = memoryStream; // Continue processing the request await _next(context); // Log the response information context.Response.Body.Seek(0, SeekOrigin.Begin); var responseBody = await new StreamReader(context.Response.Body).ReadToEndAsync(); _logger.LogInformation($"Response Status: {context.Response.StatusCode}"); _logger.LogInformation($"Response Body: {responseBody}"); // Reset the body stream position and copy it to the original body stream context.Response.Body.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalBodyStream); } } }
问题根源
问题出在原响应日志中间件ResponseLoggingMiddleware1:
- 用
using包裹MemoryStream,块结束后流会自动关闭。 OnStarting回调执行时,尝试操作已关闭的响应流,导致添加头失效;同时IExceptionHandler中直接添加的头,也会因为流提前关闭,被Kestrel判定为无效而移除。
修正方案
修改响应日志中间件,确保操作完成后恢复原始响应流,避免流提前关闭:
修正后的响应日志中间件代码
public class ResponseLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ResponseLoggingMiddleware> _logger; public ResponseLoggingMiddleware(RequestDelegate next, ILogger<ResponseLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { // Preserve the original response body stream var originalBodyStream = context.Response.Body; try { // Use a memory stream to capture the response using (var memoryStream = new MemoryStream()) { context.Response.Body = memoryStream; // Call the next middleware in the pipeline await _next(context); // Read the response stream and log it memoryStream.Seek(0, SeekOrigin.Begin); var responseBody = await new StreamReader(memoryStream).ReadToEndAsync(); // Log response details _logger.LogInformation($"Response Status: {context.Response.StatusCode}"); _logger.LogInformation($"Response Body: {responseBody}"); // Copy the response body back to the original stream memoryStream.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalBodyStream); } } finally { // Restore the original response body stream context.Response.Body = originalBodyStream; } } }
关键修正点
- 新增
try-finally块,确保无论是否异常,都能恢复原始响应流。 - 直接操作
memoryStream读取和复制内容,避免流状态不一致的问题。
内容的提问来源于stack exchange,提问作者A.kadir olmez
相关产品推荐
相关产品推荐

