【问题标题】:How to log the HTTP Response Body in ASP.NET Core 1.0如何在 ASP.NET Core 1.0 中记录 HTTP 响应正文
【发布时间】:2016-10-17 17:54:26
【问题描述】:

我正在使用 ASP.NET Core 1.0 RC2 创建一个公共 REST Api,并且喜欢记录传入请求和传出响应。

我创建了一个中间件类,它在调用 app.UseMvc();之前添加到管道中;

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{            
        app.UseIOMiddleware();
        app.UseMvc();            
}

我的中间件类如下所示:

public class IOMiddleware
{
    private readonly RequestDelegate _next;

    public IOMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        LogRequest(context.Request);

        await _next.Invoke(context);            
    }

    private async void LogRequest(HttpRequest request)
    {
        using (var bodyReader = new StreamReader(request.Body))
        {
            string body = await bodyReader.ReadToEndAsync();

            request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));

            System.Diagnostics.Debug.Print(body);
        }
    }
}

我可以使用以下示例读取请求正文流并将其倒回:Rewind request body stream,但由于流不可读,我不确定如何读取响应正文。

在 Web API 2.0 中,我可以使用 HttpResponseMessage.Content.ReadAsByteArrayAsync() 方法,但如何在 ASP.Net Core 1.0 RC2 中完成同样的事情?

【问题讨论】:

标签: c# asp.net-core-1.0


【解决方案1】:

问题是request.Body 不可读,只能写 - 通常流会定期通过网络刷新到客户端。

您可以通过替换流并缓冲内容直到管道的其余部分完成来解决此问题。

public class IOMiddleware
{
    private readonly RequestDelegate _next;

    public IOMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        await LogRequest(context.Request);

        await LogResponseAndInvokeNext(context);
    }

    private async Task LogRequest(HttpRequest request)
    {
        using (var bodyReader = new StreamReader(request.Body))
        {
            string body = await bodyReader.ReadToEndAsync();

            request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
            System.Diagnostics.Debug.Print(body);
        }
    }

    private async Task LogResponseAndInvokeNext(HttpContext context)
    {
        using (var buffer = new MemoryStream())
        {
            //replace the context response with our buffer
            var stream = context.Response.Body;
            context.Response.Body = buffer;

            //invoke the rest of the pipeline
            await _next.Invoke(context);

            //reset the buffer and read out the contents
            buffer.Seek(0, SeekOrigin.Begin);
            var reader = new StreamReader(buffer);
            using (var bufferReader = new StreamReader(buffer))
            {
                string body = await bufferReader.ReadToEndAsync();

                //reset to start of stream
                buffer.Seek(0, SeekOrigin.Begin);

                //copy our content to the original stream and put it back
                await buffer.CopyToAsync(stream);
                context.Response.Body = stream;

                System.Diagnostics.Debug.Print($"Response: {body}");

            }
        }
    }
}

【讨论】:

  • 为了读取请求正文,您可以执行以下操作来获取一个缓冲流,该流允许您多次查找和读取它; context.Request.EnableRewind()(在Microsoft.AspNetCore.Http.Internal.BufferingHelper中找到的HttpRequest扩展方法)。
  • 我不确定这样做的副作用是什么。由于缓冲区是通过套接字流式传输的,因此可能会产生意想不到的结果。由于某种原因,正文流的读取未实现。
  • 将其包装成一个用于请求和响应日志记录的类,这也避免了缓存文件的异常,并在github 上正确处理。我想使用它,因为任何错误都会从中摆脱出来。
  • @SergueiFedorov 我看到了这种行为的一个副作用。直到现在可以安全地流式传输到客户端的数据现在必须完全保存在内存中。本来可以成功逐个流式传输并且现在太大而无法存储在内存中的东西会抛出 OutOfMemoryException 或类似的东西。
【解决方案2】:

很遗憾,如果您将 Request 替换为 MemoryStream,则相同的流将用于以后的调用。 这是错误: https://github.com/aspnet/KestrelHttpServer/issues/940

解决方法是将 Request.Body 流复制到局部变量,最后将 Body 设置回原始流。

像这样:

  public async Task Invoke(HttpContext context)
    {
        //Workaround - copy original Stream
        var initalBody = context.Request.Body;

        using (var bodyReader = new StreamReader(request.Body))
        {
            string body = await bodyReader.ReadToEndAsync();
            //Do something with body
            //Replace write only request body with read/write memorystream so you can read from it later

               request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));

        //handle other middlewares
        await _next.Invoke(context);

        //Workaround - return back to original Stream
        context.Request.Body = initalBody;
    }

【讨论】:

  • 暂时修复 (1.0.1+) :)
  • 问题请求响应正文,而不是请求正文。
【解决方案3】:

在到处搜索之后,这是我最终得到的课程。它对我来说工作正常并处理出现异常的情况[它过去不返回任何响应但成功记录它!!]。这是网上这么多帖子的集体产物。

using Microsoft.AspNetCore.Http;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNetCore.Http.Internal;



public class LoggerMiddleware
{
    private readonly RequestDelegate _next;

    public LoggerMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        using (MemoryStream requestBodyStream = new MemoryStream())
        using (MemoryStream responseBodyStream = new MemoryStream())
        {
            Stream originalRequestBody = context.Request.Body;
            context.Request.EnableRewind();
            Stream originalResponseBody = context.Response.Body;

            try
            {
                await context.Request.Body.CopyToAsync(requestBodyStream);
                requestBodyStream.Seek(0, SeekOrigin.Begin);

                string requestBodyText = new StreamReader(requestBodyStream).ReadToEnd();

                requestBodyStream.Seek(0, SeekOrigin.Begin);
                context.Request.Body = requestBodyStream;

                string responseBody = "";

                context.Response.Body = responseBodyStream;

                Stopwatch watch = Stopwatch.StartNew();
                    await _next(context);
                    watch.Stop();

                responseBodyStream.Seek(0, SeekOrigin.Begin);
                responseBody = new StreamReader(responseBodyStream).ReadToEnd();
                AuditLogger.LogToAudit(context.Request.Host.Host,
                    context.Request.Path, context.Request.QueryString.ToString(), context.Connection.RemoteIpAddress.MapToIPv4().ToString(),
                        string.Join(",", context.Request.Headers.Select(he => he.Key + ":[" + he.Value + "]").ToList()),
                        requestBodyText, responseBody, DateTime.Now, watch.ElapsedMilliseconds);

                responseBodyStream.Seek(0, SeekOrigin.Begin);

                await responseBodyStream.CopyToAsync(originalResponseBody);
            }
            catch (Exception ex)
            {
                ExceptionLogger.LogToDatabse(ex);
                byte[] data = System.Text.Encoding.UTF8.GetBytes("Unhandled Error occured. Please, try again in a while.");
                originalResponseBody.Write(data, 0, data.Length);
            }
            finally
            {
                context.Request.Body = originalRequestBody;
                context.Response.Body = originalResponseBody;
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 2016-11-02
    • 1970-01-01
    • 1970-01-01
    • 2019-03-14
    • 1970-01-01
    • 2018-09-06
    相关资源
    最近更新 更多