Промежуточное программное обеспечение ASP.NET Core обрабатывает тела запросов / ответов - PullRequest
0 голосов
/ 25 апреля 2019

Я играю с возможностями промежуточного ПО в ASP.NET Core.

Я заменил все свои операторы try / catch на глобальное промежуточное ПО для обработки исключений.

У него есть задачи для обработки всех необработанных исключений (зарегистрировать их, вернуть ответ 500 Внутренняя ошибка сервера), но он также должен обрабатывать все созданные вручную результаты с кодом состояния 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).

Это кажется потенциально неправильным, потому что в случае неперехваченного исключения я не только получаю журналы своего промежуточного программного обеспечения, но и журналы, такие как:

Возникло исключение: System.Exception в Sample2.dll

Исключение типа 'System.Exception' произошло в Sample2.dll, но не было обработано в коде пользователя

тестовое исключение

...