.net core2.2 中间件获取请求和响应内容,并存入Oracle数据库

说明

中间件获取接口请求和响应内容,如果你有尝试过,你肯定发现获取请求内容很容易,然而获取Response.Body却不行,因为它是只读的。这里不会为你分析内部原理,只会提供给你一种取到Response.Body的解决办法。

我尝试了很多网友们提供的方法,均失败,后来在StackOverFlow网站上搜到了一点线索。并在这里做一下记录。

我的目的

我是为了获取接口请求、响应、耗时等数据存入Redis,然后从Redis异步存入Oracle。(仅是我的最初版实现,并不具备生产参考,请酌情考虑采用)

核心代码(中间件)-错误演示

下面这个演示,本来是能解决获取response.body数据的,后来发现一个问题,request.body在具体方法中取不到值了,被释放了。然后又做了修改

        /// <summary>
        /// 执行响应流指向新对象
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        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<string>($"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
    {
        

        /// <summary>
        /// 请求接口名称(中文)
        /// </summary>
        public string RequestName { get; set; }

        /// <summary>
        /// 请求来源IP
        /// </summary>
        public string RequestIP { get; set; }

        /// <summary>
        /// 请求路径
        /// </summary>
        public string RequestUrl { get; set; }

        /// <summary>
        /// 请求类型:GET/POST
        /// </summary>
        public string HttpType { get; set; }

        /// <summary>
        /// 请求参数字符串
        /// </summary>
        public string Query { get; set; }

        /// <summary>
        /// 请求报文,POST专用
        /// </summary>
        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
{
    /// <summary>
    ///  Http 请求中间件
    /// </summary>
    public class HttpContextMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        /// <summary>
        /// 缓存服务(自定义)
        /// </summary>
        private readonly ICacheService _cacheService;
        private Stopwatch _stopwatch;

        /// <summary>
        /// 构造 Http 请求中间件
        /// </summary>
        /// <param name="next"></param>
        /// <param name="loggerFactory"></param>
        /// <param name="cacheService"></param>
        public HttpContextMiddleware(
            RequestDelegate next,
            ILoggerFactory loggerFactory,
            ICacheService cacheService
            )
        {
            _next = next;
            _logger = loggerFactory.CreateLogger<HttpContextMiddleware>();
            _cacheService = cacheService;
        }


        /// <summary>
        /// 执行响应流指向新对象
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        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<string>($"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<HttpContextMiddleware>();
        }
    }

在Startup.cs的Configure方法中加入

app.UseRequestLogger();

如下:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }
		   ...
           app.UseRequestLogger(); //新增中间件
           ...
        }

查看效果实现

请求几次接口

在自己的接口中调用几次
在这里插入图片描述

查看Redies记录请求响应内容

下图,我已经记录到了请求数据
在这里插入图片描述

定时转存服务

(1)编写一个api接口,用于读取Redis请求日志,然后存入Oracle
(2)编写一个Console,实现定时刷新请求(1)
我的运行效果:
在这里插入图片描述

让我们看一下Oracle的记录

在这里插入图片描述

其他

只是一次小尝试,后续还会扩展更多数据记录,以及更好的方式去实现这种情况。我本来是想直接在中间件取到数据存到数据库的,一方面考虑实时存库可能性能不好,另一方面就是我的领域层注入不进来,没办法调用(哈哈哈…)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值