我正在使用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'类型的异常,但未在用户代码中处理
测试例外