LiXiang98

导航

 

常见错误处理示例

  直接读取原始响应流

// 错误写法!
var originalStream = context.Response.Body;
await _next(context); // 此时数据可能已写入原始流

if (condition)
{
    // 这里已经无法修改响应,因为部分数据可能已发送给客户端
    await originalStream.WriteAsync(...); 
}

正确写法  

  默认情况下,响应体是一个只写一次的流,一旦开始写入,就不能直接修改。如果在中间件中直接读取或修改响应体,可能会导致数据丢失或异常。因此,常见的做法是用一个临时流来捕获后续中间件的输出,处理完后再决定如何修改响应内容,再写回原始流。在修改响应内容时,需要确保在修改响应内容时,重置响应流,并设置正确的Content-Type,同时处理可能已经部分写入的响应内容。例如,在写入自定义JSON之前,可能需要清除之前的响应内容,并设置正确的状态码和头部信息。总结来说,用户的中间件需要调整结构,正确捕获异常,并在处理管道中的适当位置执行,同时确保能够获取到终结点元数据,以便判断是否属于特定的控制器和Action。此外,需要处理响应流的重置和内容的正确写入,避免冲突或错误。

  

// 关键代码 及 解释说明
public async Task InvokeAsync(HttpContext context)
{
    // 阶段1:准备分拣台
    var originalBodyStream = context.Response.Body; // 记录原始传送带位置
    using var memoryStream = new MemoryStream(); // 准备临时分拣台
    context.Response.Body = memoryStream; // 暂时重定向到分拣台

    try
    {
        // 阶段2:传递处理
        await _next(context); // 让控制器等后续流程处理包裹

        // 阶段3:质量检查
        memoryStream.Seek(0, SeekOrigin.Begin); // 将分拣台倒带到起点
        if (ShouldHandle(context))
        {
            // 特殊处理流程
            context.Response.Body = originalBodyStream; // 恢复原始传送带
            await CreateCustomResponse(memoryStream, context); // 定制新包装
        }
        else
        {
            // 常规放行
            memoryStream.Seek(0, SeekOrigin.Begin); // 再次倒带
            await memoryStream.CopyToAsync(originalBodyStream); // 放回原始传送带
        }
    }
    finally
    {
        context.Response.Body = originalBodyStream; // 确保传送带归位
    }
}

 为什么需要使用一个临时流?

  防止数据丢失

    如果直接操作原始流,一旦开始读取,框架可能已经部分发送数据给客户端。

  灵活决策

    需要先看到完整处理结果(如状态码),再决定如何修改响应。

  避免管道污染

    临时流相当于沙箱环境,所有操作不会直接影响最终输出,直到确认需要修改。

 

posted on 2025-04-11 13:20  LiXiang98  阅读(29)  评论(0)    收藏  举报