ASP.NET Core中间件处理请求/响应主体

时间:2019-04-25 12:14:26

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

我正在使用ASP.NET Core中的中间件功能。

我替换了所有try / catch语句,以使用全局异常处理中间件。

它的任务是处理所有未捕获的异常(将它们记录下来,返回500 Internal Server Error响应),但还应处理状态码为500的所有手动创建的结果。

我正在针对此控制器进行测试:

  [Route("api/[controller]")]
  [ApiController]
  public class TestController : ControllerBase
  {

    [HttpGet()]
    public ActionResult<string> Get()
    {
      return "hello";
    }

    [HttpPost("{id:int}")]
    public IActionResult Post(int id)
    {
      throw new Exception("test exception");
    }

    [HttpPut("{id:int}")]
    public IActionResult Put(int id)
    {
      return StatusCode(500, "this information may contains sensitive data");
    }

  }

到目前为止,我的中间件的骨架看起来像:

public class ExceptionMiddleware
{
  private readonly RequestDelegate _next;
  private readonly ILogger<ExceptionMiddleware> _logger;

  private LogLevel LogLevel { get; } = LogLevel.Error;
  private bool EnforceEmptyResult { get; } = true;

  public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
  {
      _logger = logger ?? throw new ArgumentNullException(nameof(logger));
      _next = next ?? throw new ArgumentNullException(nameof(next));
  }

  public async Task InvokeAsync(HttpContext httpContext)
  {

    try
    {
      await _next(httpContext);

      // handles the internal server error responses
      if (httpContext.Response.StatusCode == (int)HttpStatusCode.InternalServerError)
      {
        await LogError(httpContext, LogLevel);
        //if (EnforceEmptyResult) await ClearResponseBody(httpContext.Response);
      }
    }
    catch (Exception ex)
    {
      // handles the uncaught exceptions
      await LogException(httpContext, ex, LogLevel);
      await HandleExceptionAsync(httpContext, (!EnforceEmptyResult) ? ex : null);
    }
  }

  public Task HandleExceptionAsync(HttpContext context, Exception exception)
  {
    if (exception == null)
    {
      // the basic error response (500, no body, no content type)
      context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
      context.Response.ContentType = null;
      return context.Response.WriteAsync(string.Empty);
    }
    else
    {
      // the error response with the exception as plain text
      context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
      context.Response.ContentType = "text/plain";
      return context.Response.WriteAsync(exception.ToString());
    }
  }

  public async Task LogException(HttpContext context, Exception exception, LogLevel logLevel)
  {
    StringBuilder sb = new StringBuilder();
    sb.Append("exception occured");
    sb.Append(Environment.NewLine);

    sb.Append("request body");
    sb.Append(Environment.NewLine);
    sb.Append(await RequestBody(context.Request));
    sb.Append(Environment.NewLine);
    sb.Append("---");

    sb.Append(Environment.NewLine);
    sb.Append("response body");
    sb.Append(Environment.NewLine);
    sb.Append(await ResponseBody(context.Response));
    sb.Append(Environment.NewLine);
    sb.Append("---");

    _logger.Log(logLevel, exception, sb.ToString());
  }

  public async Task LogError(HttpContext context, LogLevel logLevel)
  {

    StringBuilder sb = new StringBuilder();
    sb.Append("error occured");
    sb.Append(Environment.NewLine);

    sb.Append("request body");
    sb.Append(Environment.NewLine);
    sb.Append(await RequestBody(context.Request));
    sb.Append(Environment.NewLine);
    sb.Append("---");

    sb.Append(Environment.NewLine);
    sb.Append("response body");
    sb.Append(Environment.NewLine);
    sb.Append(await ResponseBody(context.Response));
    sb.Append(Environment.NewLine);
    sb.Append("---");

    _logger.Log(logLevel, sb.ToString());
  }

  public async Task<string> RequestBody(HttpRequest request)
  {
    if (!request.ContentLength.HasValue || request.ContentLength.Value <= 0) return null;

    var requestBody = request.Body;

    request.EnableRewind();

    byte[] buffer = new byte[request.ContentLength.Value];

    await request.Body.ReadAsync(buffer, 0, buffer.Length);

    request.Body = requestBody;

    return Encoding.UTF8.GetString(buffer);
  }

  public async Task<string> ResponseBody(HttpResponse response)
  {
    if (!response.ContentLength.HasValue || response.ContentLength.Value <= 0) return null;

    byte[] buffer = new byte[response.ContentLength.Value];

    await response.Body.ReadAsync(buffer, 0, buffer.Length);

    return Encoding.UTF8.GetString(buffer);
  }

  public async Task ClearResponseBody(HttpResponse response)
  {
    response.ContentType = null;
    await response.WriteAsync(string.Empty);
  }
}

我在处理请求/响应的正文方面有一些问题。

public async Task<string> RequestBody(HttpRequest request)中,我正在使用EnableRewind,因为有教程建议这样做。在这种情况下,我不确定是否需要。幸运的是,到目前为止它可以正常工作,并且我在日志中看到了尸体。

我正确使用了吗?

public async Task<string> ResponseBody(HttpResponse response)似乎不起作用,因为我较新地看到了响应正文。我想显示到目前为止响应中包含的内容(可能会有部分结果),或者在返回状态代码500和错误消息的情况下,我要记录此错误消息。

我如何获得回复正文?

使用public async Task ClearResponseBody(HttpResponse response)方法会引发异常:

  

System.InvalidOperationException:无法修改响应标头,因为响应已经开始。

如何操纵响应主体,以免泄漏可能敏感的开发数据?

侧面的一个小细节是,我似乎在管道内的错误位置使用了异常中间件。我将其添加为public void Configure(IApplicationBuilder app, IHostingEnvironment env)方法顶部的第一个条目。

这似乎是错误的,因为在发生未捕获的异常的情况下,我不仅会获取中间件的日志,还会获取以下日志:

  

抛出异常:Sample2.dll中的'System.Exception'

     

Sample2.dll中发生了'System.Exception'类型的异常,但未在用户代码中处理

     

测试例外

0 个答案:

没有答案
相关问题