using System.Buffers;
using System.IO.Pipelines;
namespace Dpz.Core.WebApi.Middleware;
/// <summary>
/// 记录 HTTP 请求参数
/// </summary>
/// <param name="next"></param>
/// <param name="diagnosticContext"></param>
public class HttpRequestRecord(RequestDelegate next, IDiagnosticContext diagnosticContext)
{
/// <summary>
///
/// </summary>
/// <param name="httpContext"></param>
public async Task InvokeAsync(HttpContext httpContext)
{
await EnrichFromRequestAsync(httpContext.Request);
await next.Invoke(httpContext);
}
private async Task EnrichFromRequestAsync(HttpRequest request)
{
if (request.Query is { Count: > 0 })
{
diagnosticContext.Set("QueryString", request.Query);
}
var bodyContent = await GetBodyContentAsync(request.BodyReader);
if (bodyContent != string.Empty)
{
diagnosticContext.Set("RequestBody", bodyContent);
}
}
/// <summary>
/// 读取 http body
/// 如果 body 超过 1 << 10 字节,则不记录 body
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
private static async Task<string> GetBodyContentAsync(PipeReader reader)
{
var result = new StringBuilder();
const long maxLength = 1 << 10;
long totalLength = 0;
while (true)
{
var readResult = await reader.ReadAsync();
var buffer = readResult.Buffer;
// 如果 buffer 长度已经大于等于 maxLength,直接截取并返回
if (buffer.Length >= maxLength)
{
var span = buffer.Slice(0, maxLength);
AppendString(result, span);
result.Append("...");
reader.AdvanceTo(buffer.Start, buffer.GetPosition(maxLength));
//await reader.CompleteAsync();
break;
}
totalLength += buffer.Length;
if (readResult.IsCompleted && buffer.Length > 0)
{
AppendString(result, in buffer);
}
reader.AdvanceTo(buffer.Start, buffer.End);
if (totalLength >= maxLength)
{
result.Append("...");
break;
}
if (readResult.IsCompleted)
{
break;
}
}
return result.ToString();
}
private static void AppendString(
StringBuilder result,
in ReadOnlySequence<byte> readOnlySequence
)
{
var span = readOnlySequence.IsSingleSegment
? readOnlySequence.First.Span
: readOnlySequence.ToArray().AsSpan();
result.Append(Encoding.UTF8.GetString(span));
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一个ASP.NET Core中间件类,用于记录HTTP请求的参数信息。让我详细解释一下这个代码:
类的整体功能
HttpRequestRecord 是一个中间件,主要用于:
- 记录HTTP请求的查询字符串参数
- 记录HTTP请求的Body内容
- 将这些信息通过诊断上下文进行记录(通常用于日志记录)
主要组件分析
1. 构造函数
public class HttpRequestRecord(RequestDelegate next, IDiagnosticContext diagnosticContext)
使用了C# 12的主构造函数语法,接收:
next:下一个中间件的委托diagnosticContext:诊断上下文,用于设置日志属性
2. InvokeAsync方法
public async Task InvokeAsync(HttpContext httpContext)
这是中间件的入口点:
- 先调用
EnrichFromRequestAsync提取请求信息 - 然后调用管道中的下一个中间件
3. EnrichFromRequestAsync方法
负责提取请求信息:
- 查询字符串:如果存在查询参数,将其记录到诊断上下文
- 请求体:读取HTTP请求的Body内容并记录
4. GetBodyContentAsync方法(核心逻辑)
这是最复杂的方法,用于安全地读取HTTP请求体:
性能优化特性:
- 使用
PipeReader进行高效的流式读取 - 设置最大读取长度限制(1024字节,即
1 << 10) - 避免内存溢出攻击
读取逻辑:
while (true)
{
var readResult = await reader.ReadAsync();
var buffer = readResult.Buffer;
// 如果单次读取就超过限制,截取前1024字节
if (buffer.Length >= maxLength)
{
var span = buffer.Slice(0, maxLength);
AppendString(result, span);
result.Append("...");
// 标记已处理的位置
reader.AdvanceTo(buffer.Start, buffer.GetPosition(maxLength));
break;
}
// 累计总长度检查
totalLength += buffer.Length;
// 如果读取完成且有内容,追加到结果
if (readResult.IsCompleted && buffer.Length > 0)
{
AppendString(result, in buffer);
}
// 标记缓冲区已处理
reader.AdvanceTo(buffer.Start, buffer.End);
// 总长度超限检查
if (totalLength >= maxLength)
{
result.Append("...");
break;
}
if (readResult.IsCompleted)
{
break;
}
}
5. AppendString方法
高效的字节到字符串转换:
private static void AppendString(StringBuilder result, in ReadOnlySequence<byte> readOnlySequence)
{
// 优化:如果是单段内存,直接使用Span;否则转为数组
var span = readOnlySequence.IsSingleSegment
? readOnlySequence.First.Span
: readOnlySequence.ToArray().AsSpan();
result.Append(Encoding.UTF8.GetString(span));
}
设计亮点
- 内存安全:限制读取长度,防止大文件攻击
- 性能优化:使用PipeReader和ReadOnlySequence,避免不必要的内存拷贝
- 流式处理:支持大文件的分段读取
- 编码处理:正确处理UTF-8编码的请求体
- 优雅降级:超长内容用"..."标记截断
使用场景
这个中间件通常用于:
- API调用日志记录
- 请求参数审计
- 调试和问题排查
- 性能监控
需要注意的是,读取请求体可能会影响后续中间件对Body的访问,在实际使用时需要考虑这个因素。
评论加载中...