在 .net core 2 Web API 中添加日志记录中间件会导致响应中出现 http 307 和 400 错误



我想使用 aspnet core 示例提供的日志记录中间件。在我添加日志记录中间件之前,一切正常。添加日志记录中间件后,我收到错误消息。

添加中间件之前:

info: Microsoft.AspNetCore.Server.Kestrel[32]
      Connection id "0HLJA6SH4AF9Q", Request id "0HLJA6SH4AF9Q:00000001": the application completed without reading the entire request body.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 POST https://localhost:5001/api/operations/deposit application/json 282
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Route matched with {action = "Post", controller = "Operations"}. Executing action API.Controllers.OperationsController.Post (API)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method API.Controllers.OperationsController.Post (API) with arguments (API.Req.Deposit) - Validation state: Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action method API.Controllers.OperationsController.Post (API), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 4782.8378ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
      Executing ObjectResult, writing value of type 'API.Res.Deposit'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action API.Controllers.OperationsController.Post (API) in 4901.4518ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 4930.5295ms 200 application/json; charset=utf-8

在启动中添加中间件后.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    app.UseMiddleware<RequestResponseLoggingMiddleware>();
    app.UseHttpsRedirection();
    app.UseMvc();
}

将输出整个日志

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 POST http://localhost:5000/api/operations/deposit application/json 282
info: API.RequestResponseLoggingMiddleware[0]
      http localhost:5000/api/operations/deposit  
      {
        "merchant": "981001",
        ....,
        "userName": "susan"
      }
info: API.RequestResponseLoggingMiddleware[0]
      Response
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 50.7335ms 307
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 POST https://localhost:5001/api/operations/deposit application/json 282
info: API.RequestResponseLoggingMiddleware[0]
      https localhost:5001/api/operations/deposit  
      {
           "merchant": "981001",
           ...,
           "userName": "susan"
      }
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Route matched with {action = "Post", controller = "Operations"}. Executing action API.Controllers.OperationsController.Post (API)
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
      Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.SerializableError'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action API.Controllers.OperationsController.Post (API) in 179.1267ms
info: API.RequestResponseLoggingMiddleware[0]
      Response {"":["A non-empty request body is required."]}
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 213.3557ms 400 application/json; charset=utf-8

它发现了一个序列化问题,然后我尝试按如下方式设置SuppressModelStateInvalidFilter

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    services.Configure<ApiBehaviorOptions>(
        options => {options.SuppressModelStateInvalidFilter = true; });
}

日志显示更多信息,如下所示:

info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Route matched with {action = "Post", controller = "Operations"}. Executing action API.Controllers.OperationsController.Post (API)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method API.Controllers.OperationsController.Post (API) with arguments () - Validation state: Invalid
      Object reference not set to an instance of an object.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action method API.Controllers.OperationsController.Post (API), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 454.3309ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
      Executing ObjectResult, writing value of type 'API.Res.Deposit'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action API.Controllers.OperationsController.Post (API) in 640.913ms
info: API.RequestResponseLoggingMiddleware[0]
      Response 
      {
         "branch": null,
         "holder": null,
         "number": null,
         "expire": "0001-01-01T00:00:00",
         "error": 0,
         "verifyText": null,
         "transactionId": null
      }
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 671.6427ms 200 application/json; charset=utf-8

一切都变为空。为什么请求上下文正文在中间件中丢失?

正如@PeterBons指出的那样,问题已解决。

我们需要设置request.EnableRewind();,并在阅读内容后将正文位置设置为 0 request.Body.Position = 0; .以下是我使用的代码:

private async Task<string> FormatRequest(HttpRequest request)
{
    request.EnableRewind();
    var buffer = new byte[Convert.ToInt32(request.ContentLength)];
    await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
    var bodyAsText = Encoding.UTF8.GetString(buffer);
    request.Body.Position = 0;
    return $"{bodyAsText}";
}

祝您编码愉快!

创建中间件类以截获所有请求和响应,而不会影响响应流。在启动时启用中间件类关联.cs

app.UseMiddleware<HttpRequestResponseLogger>();

实现一个中间件类来拦截请求和响应。 (可选)可以将这些日志存储在数据库中。我忽略了机密和不必要的标头值

 public class HttpRequestResponseLogger
{
    RequestDelegate next;
    public HttpRequestResponseLogger(RequestDelegate next)
    {
        this.next = next;
    }
    //can not inject as a constructor parameter in Middleware because only Singleton services can be resolved
    //by constructor injection in Middleware. Moved the dependency to the Invoke method
    public async Task InvokeAsync(HttpContext context, IHttpLogRepository repoLogs)
    {
        HttpLog logEntry = new HttpLog();
        await RequestLogger(context, logEntry);
        
        await next.Invoke(context);
        await ResponseLogger(context, logEntry);
        //store log to database repository
        repoLogs.SaveLog(logEntry);
    }
    // Handle web request values
    public async Task RequestLogger(HttpContext context, HttpLog log)
    {
        string requestHeaders = string.Empty;
        log.RequestedOn = DateTime.Now;
        log.Method = context.Request.Method;
        log.Path = context.Request.Path;
        log.QueryString = context.Request.QueryString.ToString();
        log.ContentType = context.Request.ContentType;
        foreach (var headerDictionary in context.Request.Headers)
        {
            //ignore secrets and unnecessary header values
            if (headerDictionary.Key != "Authorization" && headerDictionary.Key != "Connection" &&
                headerDictionary.Key != "User-Agent" && headerDictionary.Key != "Postman-Token" &&
                headerDictionary.Key != "Accept-Encoding")
            {
                requestHeaders += headerDictionary.Key + "=" + headerDictionary.Value + ", ";
            }
        }
        if (requestHeaders != string.Empty)
            log.Headers = requestHeaders;
        //Request handling. Check if the Request is a POST call 
        if (context.Request.Method == "POST")
        {
            context.Request.EnableBuffering();
            var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
            context.Request.Body.Position = 0;
            log.Payload = body;
        }
    }
    //handle response values
    public async Task ResponseLogger(HttpContext context, HttpLog log)
    {
        using (Stream originalRequest = context.Response.Body)
        {
            try
            {
                using (var memStream = new MemoryStream())
                {
                    context.Response.Body = memStream;
                    // All the Request processing as described above 
                    // happens from here.
                    // Response handling starts from here
                    // set the pointer to the beginning of the 
                    // memory stream to read
                    memStream.Position = 0;
                    // read the memory stream till the end
                    var response = await new StreamReader(memStream)
                        .ReadToEndAsync();
                    // write the response to the log object
                    log.Response = response;
                    log.ResponseCode = context.Response.StatusCode.ToString();
                    log.IsSuccessStatusCode = (
                        context.Response.StatusCode == 200 ||
                        context.Response.StatusCode == 201);
                    log.RespondedOn = DateTime.Now;
                    // since we have read till the end of the stream, 
                    // reset it onto the first position
                    memStream.Position = 0;
                    // now copy the content of the temporary memory 
                    // stream we have passed to the actual response body 
                    // which will carry the response out.
                    await memStream.CopyToAsync(originalRequest);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            finally
            {
                // assign the response body to the actual context
                context.Response.Body = originalRequest;
            }
        }
    }

相关内容

  • 没有找到相关文章

最新更新