原文:ASP.NET Core 如何记录每次请求的Request信息 - sky 胡萝卜星星 - CSDN博客

版权声明:本文为starfd原创文章,转载请标明出处。 https://blog.csdn.net/starfd/article/details/82734039

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

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

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

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

当请求发生异常时,我们会得到一个展示当前请求以及异常的页面,这似乎可以为我们如何获取请求的信息提供一个参考方向。UseDeveloperExceptionPage的代码我们可在https://github.com/aspnet/Diagnostics中进行查看,不过看下来其实没多大用处,因为该部分只是读取了当前的ExceptionQueryCookies以及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-Typeapplication/x-www-form-urlencoded类型的请求,如果我们想记录application/jsonapplication/xml之类的请求时,看来我们只能通过Body部分来读取数据。但貌似直接读取并不可行,通过Debug,我们可以看到Body的构成如下图所示:



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

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

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

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

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

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

现在我们已经可以读取到了FormBody,但我们知道实际Form也是Body,所以我们实际上完全可以只通过Body来记录当前请求的主体信息,而且我们也可以发现,实际通过Body来读取的Form信息也更方便我们进行日志记录,另外当有文件上传时,通过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

ASP.NET Core 如何记录每次请求的Request信息 - sky 胡萝卜星星 - CSDN博客的更多相关文章

  1. ASP.NET Core 如何记录每次响应的Response信息 - sky 胡萝卜星星 - CSDN博客

    原文:ASP.NET Core 如何记录每次响应的Response信息 - sky 胡萝卜星星 - CSDN博客 上一篇文章中我们已经成功的记录了Request部分的信息,现在我们来看下如何记录Res ...

  2. ASP.NET Core默认注入方式下如何注入多个实现(多种方式) - sky 胡萝卜星星 - CSDN博客

    原文:ASP.NET Core默认注入方式下如何注入多个实现(多种方式) - sky 胡萝卜星星 - CSDN博客 版权声明:本文为starfd原创文章,转载请标明出处. https://blog.c ...

  3. asp.net core2.0 依赖注入 AddTransient与AddScoped的区别 - 晓剑 - CSDN博客

    原文:asp.net core2.0 依赖注入 AddTransient与AddScoped的区别 - 晓剑 - CSDN博客 原文地址:http://www.tnblog.net/aojiancc2 ...

  4. ASP.NET Core 入门教程 10、ASP.NET Core 日志记录(NLog)入门

    一.前言 1.本教程主要内容 ASP.NET Core + 内置日志组件记录控制台日志 ASP.NET Core + NLog 按天记录本地日志 ASP.NET Core + NLog 将日志按自定义 ...

  5. ASP.NET Core 防止跨站请求伪造(XSRF/CSRF)攻击 (转载)

    什么是反伪造攻击? 跨站点请求伪造(也称为XSRF或CSRF,发音为see-surf)是对Web托管应用程序的攻击,因为恶意网站可能会影响客户端浏览器和浏览器信任网站之间的交互.这种攻击是完全有可能的 ...

  6. asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密。

    原文:asp.net core 使用中间件拦截请求和返回数据,并对数据进行加密解密. GitHub demo https://github.com/zhanglilong23/Asp.NetCore. ...

  7. Asp.Net MVC是否针对每次请求都重新创建一个控制器实例

    一.Asp.Net MVC是否针对每次请求都重新创建一个控制器实例 默认情况下,答案是确定的. ControllerBuilder类 ControllerBuilder.Current用户获取默认的控 ...

  8. Asp.net Core 入门实战 2.请求流程

    Asp.Net Core 是开源,跨平台,模块化,快速而简单的Web框架. Asp.net Core官网的一个源码合集,方便一次性Clone,喜欢的(Star),本系列持续更新,也可以通过我的网站访问 ...

  9. ASP.NET Core 实战:使用 NLog 将日志信息记录到 MongoDB

    一.前言 在项目开发中,日志系统是系统的一个重要组成模块,通过在程序中记录运行日志.错误日志,可以让我们对于系统的运行情况做到很好的掌控.同时,收集日志不仅仅可以用于诊断排查错误,由于日志同样也是大量 ...

随机推荐

  1. shell脚本中使用echo显示带颜色的内容

    shell脚本中使用echo显示带颜色的内容,需要使用参数-e 格式如下: echo -e "\033[字背景颜色;文字颜色m字符串\033[0m" 例如: echo -e &qu ...

  2. jQuery实现Ajax

    jQuery.ajax([settings]) type:类型,“POST”或“GET”,默认为GET url:发送地址 data:连同请求发送到服务器的数据 dataType:预期服务器返回的数据类 ...

  3. 《零基础入门学习Python》【第一版】视频课后答案第005讲

    测试题答案: 0.Python中: int:整型 bool:布尔类型 float:浮点型 str:字符串类型 1.为什么布尔类型(bool)的TRUE和FALSE分别用0和1表示? 计算机只认识二进制 ...

  4. GIL 线程/进程池 同步异步

    GIL 什么是GIL 全局解释器锁,本质是一把互斥锁,是加在cpython解释器上的一把锁, 同一个进程内的所有线程需要先抢到GIL锁,才能执行python代码 为什么要有GIL cpython解释器 ...

  5. Verilog学习笔记基本语法篇(二)·········运算符

    Verilog HDL的语言的运算符的范围很广,按照其功能大概可以分为以下几类: (1)算术运算符 +,-,*,/,% 优先顺序 !~ *  /   % +    - <<    > ...

  6. Python基础之字符编码,文件操作流与函数

    一.字符编码 1.字符编码的发展史 阶段一:现代计算机起源于美国,最早诞生也是基于英文考虑的ASCII ASCII:一个Bytes代表一个字符(英文字符/键盘上的所有其他字符),1Bytes=8bit ...

  7. [android 代码search地址]

    http://www.androidcodesearch.com/source/packages/apps/Settings/src/com/android/settings/SecuritySett ...

  8. 九度oj 题目1025:最大报销额

    题目描述:     现有一笔经费可以报销一定额度的发票.允许报销的发票类型包括买图书(A类).文具(B类).差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600 ...

  9. 【Luogu】P2331最大子矩阵(DP)

    题目链接 这题的状态转移方程真是粗鄙. f[i][j][k]表示前i行用了j个矩阵状态为k的时候的最大值. k=0:两列都不选. k=1:取左弃右. k=2:选右弃左. k=3:左右都选,但分属于两个 ...

  10. python 写excal

           workbook.save(filename)