中间件获取接口请求和响应内容,如果你有尝试过,你肯定发现获取请求内容很容易,然而获取Response.Body却不行,因为它是只读的。这里不会为你分析内部原理,只会提供给你一种取到Response.Body的解决办法。
我尝试了很多网友们提供的方法,均失败,后来在StackOverFlow网站上搜到了一点线索。并在这里做一下记录。
我是为了获取接口请求、响应、耗时等数据存入Redis,然后从Redis异步存入Oracle。(仅是我的最初版实现,并不具备生产参考,请酌情考虑采用)
下面这个演示,本来是能解决获取response.body数据的,后来发现一个问题,request.body在具体方法中取不到值了,被释放了。然后又做了修改
///
/// 执行响应流指向新对象
///
///
///
public async Task Invoke(HttpContext context)
{
_stopwatch = new Stopwatch();
_stopwatch.Start();
_logger.LogInformation($"Handling request: " + context.Request.Path);
var api = new ApiRequestInputViewModel();
api.HttpType = context.Request.Method;
api.Query = context.Request.QueryString.Value;
api.RequestUrl = context.Request.Path;
api.RequestName = "";
api.RequestIP = context.Request.Host.Value;
using (Stream reader = context.Request.Body)
{
using (StreamReader sr = new StreamReader(reader, Encoding.UTF8))
{
api.Body = sr.ReadToEnd();
}
}
Stream originalBody = context.Response.Body;
try
{
// 通过这种方式,保证能取到Response.Body的数据
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await _next(context);
memStream.Position = 0;
api.ResponseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
}
finally
{
context.Response.Body = originalBody;
}
context.Response.OnCompleted(() =>
{
_stopwatch.Stop();
api.ElapsedTime = _stopwatch.ElapsedMilliseconds;
if (!((bool)api.RequestUrl?.Contains("PushApiLogger")))
_cacheService.Set($"RequestLog:{DateTime.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(1000, 10000)}-{api.ElapsedTime}ms", $"{JsonConvert.SerializeObject(api)}");
return Task.CompletedTask;
});
}
var reqOrigin = context.Request.Body;
var resOrigin = context.Response.Body;
try
{
using (var newReq = new MemoryStream())
{
//替换request流
context.Request.Body = newReq;
using (var newRes = new MemoryStream())
{
//替换response流
context.Response.Body = newRes;
using (var reader = new StreamReader(reqOrigin))
{
//读取原始请求流的内容
api.Body = reader.ReadToEnd();
}
using (var writer = new StreamWriter(newReq))
{
writer.Write(api.Body);
writer.Flush();
newReq.Position = 0;
await _next(context);
}
using (var reader = new StreamReader(newRes))
{
newRes.Position = 0;
api.ResponseBody = reader.ReadToEnd();
}
using (var writer = new StreamWriter(resOrigin))
{
writer.Write(api.ResponseBody);
}
}
}
}
finally
{
context.Request.Body = reqOrigin;
context.Response.Body = resOrigin;
}
请求内容类
public class ApiRequestInputViewModel
{
///
/// 请求接口名称(中文)
///
public string RequestName { get; set; }
///
/// 请求来源IP
///
public string RequestIP { get; set; }
///
/// 请求路径
///
public string RequestUrl { get; set; }
///
/// 请求类型:GET/POST
///
public string HttpType { get; set; }
///
/// 请求参数字符串
///
public string Query { get; set; }
///
/// 请求报文,POST专用
///
public string Body { get; set; }
public string RequestTime { get; set; }
public string ResponseBody { get; set; }
public long ElapsedTime { get; set; }
public ApiRequestInputViewModel()
{
this.RequestName = string.Empty;
this.RequestIP = string.Empty;
this.RequestUrl = string.Empty;
this.HttpType = string.Empty;
this.Query = string.Empty;
this.RequestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
this.Body = string.Empty;
this.ResponseBody = string.Empty;
this.ElapsedTime = -1;
}
}
HttpContextMiddleware中间件
using DLW.CacheRedis;
using DLW.Domain.ViewModels.Api;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace DLW.Open.API.Filters
{
///
/// Http 请求中间件
///
public class HttpContextMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
///
/// 缓存服务(自定义)
///
private readonly ICacheService _cacheService;
private Stopwatch _stopwatch;
///
/// 构造 Http 请求中间件
///
///
///
///
public HttpContextMiddleware(
RequestDelegate next,
ILoggerFactory loggerFactory,
ICacheService cacheService
)
{
_next = next;
_logger = loggerFactory.CreateLogger();
_cacheService = cacheService;
}
///
/// 执行响应流指向新对象
///
///
///
public async Task Invoke(HttpContext context)
{
//context.Request.EnableRewind();
context.Request.EnableBuffering();
_stopwatch = new Stopwatch();
_stopwatch.Start();
_logger.LogInformation($"Handling request: " + context.Request.Path);
var api = new ApiRequestInputViewModel();
api.HttpType = context.Request.Method;
api.Query = context.Request.QueryString.Value;
api.RequestUrl = context.Request.Path;
api.RequestName = "";
api.RequestIP = context.Request.Host.Value;
var reqOrigin = context.Request.Body;
var resOrigin = context.Response.Body;
try
{
using (var newReq = new MemoryStream())
{
//替换request流
context.Request.Body = newReq;
using (var newRes = new MemoryStream())
{
//替换response流
context.Response.Body = newRes;
using (var reader = new StreamReader(reqOrigin))
{
//读取原始请求流的内容
api.Body = reader.ReadToEnd();
}
using (var writer = new StreamWriter(newReq))
{
writer.Write(api.Body);
writer.Flush();
newReq.Position = 0;
await _next(context);
}
using (var reader = new StreamReader(newRes))
{
newRes.Position = 0;
api.ResponseBody = reader.ReadToEnd();
}
using (var writer = new StreamWriter(resOrigin))
{
writer.Write(api.ResponseBody);
}
}
}
}
finally
{
context.Request.Body = reqOrigin;
context.Response.Body = resOrigin;
}
// 响应完成时存入缓存
context.Response.OnCompleted(() =>
{
_stopwatch.Stop();
api.ElapsedTime = _stopwatch.ElapsedMilliseconds;
api.LogType = ApiRequestInputViewModel.LOG_TYPE.BD;
api.LogResult = ApiRequestInputViewModel.LOG_RESULT.S;
if (api.ResponseBody.ToLower().Contains("errmsg"))
api.LogBusiResult = ApiRequestInputViewModel.LOG_BUSI_RESULT.F;
else
api.LogBusiResult = ApiRequestInputViewModel.LOG_BUSI_RESULT.S;
if (!((bool)api.RequestUrl?.Contains("PushApiLogger")))
_cacheService.Set($"RequestLog:{DateTime.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(0, 10000)}-{api.ElapsedTime}ms", $"{JsonConvert.SerializeObject(api)}");
return Task.CompletedTask;
});
_logger.LogInformation($"Finished handling request.{_stopwatch.ElapsedMilliseconds}ms");
}
}
}
新建一个中间件扩展
public static class RequestLoggerMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware();
}
}
在Startup.cs的Configure方法中加入
app.UseRequestLogger();
如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
...
app.UseRequestLogger(); //新增中间件
...
}
(1)编写一个api接口,用于读取Redis请求日志,然后存入Oracle
(2)编写一个Console,实现定时刷新请求(1)
我的运行效果:
只是一次小尝试,后续还会扩展更多数据记录,以及更好的方式去实现这种情况。我本来是想直接在中间件取到数据存到数据库的,一方面考虑实时存库可能性能不好,另一方面就是我的领域层注入不进来,没办法调用(哈哈哈…)