Я играю с возможностями промежуточного ПО в 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, но не было обработано в коде пользователя
тестовое исключение