网站首页 网站源码
using System.Buffers;
using System.IO.Pipelines;
using Dpz.Core.Service.V4.Services;
using Wangkanai.Detection.Services;
namespace Dpz.Core.Web.Library.Middleware;
public class HttpRequestRecord(RequestDelegate next, IDiagnosticContext diagnosticContext)
{
public async Task InvokeAsync(
HttpContext httpContext,
IArticleService articleService,
IConfiguration configuration,
IDetectionService detectionService
)
{
if (httpContext.Request.Path.Equals("/sitemap.xml", StringComparison.OrdinalIgnoreCase))
{
var host = configuration.GetSection("upyun")["Host"] ?? "https://localhost:37701";
var baseUri = new Uri(host);
var uri = new Uri(baseUri, "/sitemap/sitemap.xml");
httpContext.Response.Redirect(uri.ToString());
return;
}
SetDetection(detectionService);
await EnrichFromRequestAsync(httpContext.Request);
await next.Invoke(httpContext);
var routeValues = httpContext.Request.RouteValues;
if (
routeValues["controller"] is "Article"
&& routeValues["action"] is "Read"
&& routeValues["id"] is string id
)
{
await articleService.ViewAsync(id);
}
}
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));
}
private void SetDetection(IDetectionService detectionService)
{
diagnosticContext.Set("Device", detectionService.Device.Type.ToString());
diagnosticContext.Set("Browser", detectionService.Browser.Name.ToString());
diagnosticContext.Set("BrowserVersion", detectionService.Browser.Version.ToString());
diagnosticContext.Set("Platform", detectionService.Platform.Name.ToString());
diagnosticContext.Set("Engine", detectionService.Engine.Name.ToString());
diagnosticContext.Set("Crawler", detectionService.Crawler.Name.ToString());
diagnosticContext.Set("UserAgent", detectionService.UserAgent.ToString());
}
}
这段代码定义了一个中间件 HttpRequestRecord
,用于处理 HTTP 请求并记录相关信息。它主要的功能包括:
重定向特定请求:
/sitemap.xml
时,代码会从配置中获取一个主机地址(默认为 https://localhost:37701
),然后构建一个新的 URI,重定向到 /sitemap/sitemap.xml
。设备和浏览器检测:
IDetectionService
来检测请求的设备类型、浏览器名称、浏览器版本、平台名称、引擎名称、爬虫名称和用户代理,并将这些信息存储在 diagnosticContext
中。请求信息的丰富:
EnrichFromRequestAsync
方法,记录请求的查询字符串和请求体(body)。如果查询字符串不为空,则将其存储在 diagnosticContext
中。GetBodyContentAsync
方法读取,最多读取 1024 字节(1 << 10),如果请求体超过这个长度,则只记录前 1024 字节并在后面加上省略号。记录文章阅读:
Article
,动作是 Read
,并且有一个有效的 id
,则调用 articleService.ViewAsync(id)
方法记录文章的阅读。读取 HTTP 请求体:
GetBodyContentAsync
方法使用 PipeReader
读取请求体的内容。它会循环读取数据,直到达到最大长度或读取完成。读取的数据会被转换为字符串并返回。字符串追加:
AppendString
方法将读取的字节序列转换为字符串并追加到 StringBuilder
中。这个中间件的主要目的是在处理 HTTP 请求时,记录请求的相关信息(如查询字符串、请求体、设备和浏览器信息),并在特定条件下执行重定向和记录文章的阅读情况。这对于调试、分析和监控应用程序的请求非常有用。