找回密码
 会员注册
查看: 169|回复: 0

ASP.NET Core 如何记录每次请求的Request信息

[复制链接]

1389

主题

5

回帖

496万

积分

管理员

积分
4962992
发表于 2024-2-29 08:19:51 | 显示全部楼层 |阅读模式

在NFX中,我们可以很简单的通过DelegatingHandler来记录每次请求的Request和Response部分信息,但在ASP.NET Core中却行不通了,因为在Core中,我们无法使用Handler,只能通过Middleware中间件来捕获请求。

本篇内容基于ASP.NET Core 2.1版本。

在ASP.NET Core中,一般我们都会在Startup的Configure方法中,注册以下异常处理方法

  1. if (env.IsDevelopment())
  2. {
  3. app.UseDeveloperExceptionPage();
  4. }
  5. else
  6. {
  7. app.UseExceptionHandler("/Error");
  8. }
复制代码

当请求发生异常时,我们会得到一个展示当前请求以及异常的页面,这似乎可以为我们如何获取请求的信息提供一个参考方向。UseDeveloperExceptionPage的代码我们可在https://github.com/aspnet/Diagnostics中进行查看,不过看下来其实没多大用处,因为该部分只是读取了当前的Exception、Query、Cookies以及Headers部分,其中Headers部分还包含了完整的Cookies字符串,而一般我们需要记录的Body部分内容在UseDeveloperExceptionPage中却未进行读取。

通过查看HttpContext.Request,我们可以看到两个属性可以对应Body部分内容:Stream Body以及IFormCollection Form。

对于Form部分,如果在不支持的请求方式(比如Get)下直接调用时,框架会返回一个InvalidOperationException。所以我们必须有个判断过程,我们可以通过context.Request.HasFormContentType来判断当前请求是否可以通过Form来读取,当然实际上,有种更好的读取方式,就是通过context.Request.ReadFormAsync()来读取当前Form部分内容。

但Form明显只支持Content-Type为application/x-www-form-urlencoded类型的请求,如果我们想记录application/json和application/xml之类的请求时,看来我们只能通过Body部分来读取数据。但貌似直接读取并不可行,通过Debug,我们可以看到Body的构成如下图所示:

context.Request.Body

这是一个只读的Stream,如果我们在Middleware中读取了该数据流,因为不能重新设置数据流的初始读取位置,那么到了实际的TextInputFormatter部分,肯定会产生无法读取或读取不到内容的问题,实际测试也如猜想。

通过查看https://github.com/aspnet/Mvc中JsonInputFormatter的相关代码,我们可以看到在ReadRequestBodyAsync的第241行有相关代码,其通过HttpRequestRewindExtensions.EnableBuffering方法来使得Body可以重新设置初始位置,通过BufferingHelper源代码可以发现其实该方法是重新定义了一个Stream来替换当前的Request.Body。

既然知道了如何读取Body并保证内容读取完成之后我们可以重新设置数据流的起始位置,那么我们可以很容易的产生了下面的代码。

  1. private async Task<string> ReadBodyAsync(HttpRequest request)
  2. {
  3. if (request.ContentLength > 0)
  4. {
  5. await EnableRewindAsync(request).ConfigureAwait(false);
  6. var encoding = GetRequestEncoding(request);
  7. return await this.ReadStreamAsync(request.Body, encoding).ConfigureAwait(false);
  8. }
  9. return null;
  10. }
  11. private Encoding GetRequestEncoding(HttpRequest request)
  12. {
  13. var requestContentType = request.ContentType;
  14. var requestMediaType = requestContentType == null ? default(MediaType) : new MediaType(requestContentType);
  15. var requestEncoding = requestMediaType.Encoding;
  16. if (requestEncoding == null)
  17. {
  18. requestEncoding = Encoding.UTF8;
  19. }
  20. return requestEncoding;
  21. }
  22. private async Task EnableRewindAsync(HttpRequest request)
  23. {
  24. if (!request.Body.CanSeek)
  25. {
  26. request.EnableBuffering();
  27. await request.Body.DrainAsync(CancellationToken.None);
  28. request.Body.Seek(0L, SeekOrigin.Begin);
  29. }
  30. }
  31. private async Task<string> ReadStreamAsync(Stream stream, Encoding encoding)
  32. {
  33. using (StreamReader sr = new StreamReader(stream, encoding, true, 1024, true))//这里注意Body部分不能随StreamReader一起释放
  34. {
  35. var str = await sr.ReadToEndAsync();
  36. stream.Seek(0, SeekOrigin.Begin);//内容读取完成后需要将当前位置初始化,否则后面的InputFormatter会无法读取
  37. return str;
  38. }
  39. }
复制代码

其中Encoding的获取参考了TextInputFormatter的SelectCharacterEncoding方法,然后注意通过StreamReader读取完成数据后,不能自动释放掉Body部分,否则后面会产生无法访问已释放数据流的异常,另外数据流读取完后也记得需要将数据流的起始位置设置为0。

一般情况下,为了区分请求,我们还会需要一个唯一性请求Id来使得Request和Response进行对应,所幸这部分微软已经考虑到了,我们可以直接通过context.TraceIdentifier这个属性来获取当前请求的唯一性标志。

现在我们已经可以读取到了Form和Body,但我们知道实际Form也是Body,所以我们实际上完全可以只通过Body来记录当前请求的主体信息,而且我们也可以发现,实际通过Body来读取的Form信息也更方便我们进行日志记录,另外当有文件上传时,通过Body读取也不用担心会读取到文件内容。

包含文件时的Body部分

最后补充下HttpContext的相关属性:https://docs.microsoft.com/zh-cn/aspnet/core/migration/http-modules?view=aspnetcore-2.1#migrating-to-the-new-httpcontext

2018-12-26补充:Asp.Net Core 2.0其实提供了现成的扩展方法来使Request.Body可以修改读取位置,具体为扩展方法Microsoft.AspNetCore.Http.Internal.EnableRewind

2021-05-08补充:现在也可以通过HttpRequest.BodyReader来读取


来源:https://blog.csdn.net/starfd/article/details/82734039
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?会员注册

×
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 01:57 , Processed in 0.361282 second(s), 27 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表