如何读取中间件中的分块请求?

How can I read a chunked request in a middleware?

我有一个非常基本的 Asp.net 核心 mvc api。这些请求来自我无法控制的客户。我使用中间件来记录请求的主体。它适用于常规请求,但如果它是分块请求,我将无法读取正文。但是,框架随后可以很好地处理它,并且使用正确解码的有效负载调用我的控制器的操作。如果我在我的中间件中放置一个断点并检查 HttpRequest,我可以到达这个对象,这似乎是我想要阅读的内容:

((Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestPipeReader)((Microsoft.AspNetCore.Http.DefaultHttpRequest)httpContext.Request).BodyReader)._body

类型为Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1ChunkedEncodingMessageBody

如果我从我的操作中检查它,我可以看到它已完成并读取了我发送的字节数。 None 虽然可以访问,但我该如何阅读?

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
   //other stuff ommited
    app.UseRouting();
    app.UseMiddleware<RequestLoggingMiddleware>();
    app.UseEndpoints(e => e.MapControllers());
}

RequestLoggingMiddleware.cs

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        //httpContext.Request.Body is null
        //httpContext.Request.ContentLength is 0
        //here in debug I can see that the Http1ChunkedEncodingMessageBody _body hasn't received anything yet
        await _next(httpContext);
    }
}

MyController.cs

[Route("rest/[action]")]
[ApiController]
public class MyController : Controller
{
    [HttpPost]
    public ActionResult<string> CreateWidget([FromBody] WidgetModel widgetData)
    {
        //widgetData is populated correctly
        //if I inspect this.Request here, _body is marked as completed and having read 108 bytes
        return Ok();
    }
}

使用 fiddler 捕获的示例请求

POST http://mywidgetapi.mycompany.com/rest/CreateWidget HTTP/1.1
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8

61
{"Name":"logitech","Type":"mouse","id":"A1B2"}
0

据我所知,如果数据以一系列块的形式发送。

在这种情况下省略了Content-Length header并且在每个块的开头你需要添加当前块的十六进制格式的长度,然后是'\r\n ' 然后是块本身,然后是另一个 '\r\n'。

终止块是一个常规块,但它的长度为零。紧随其后的是预告片,它由实体 header 字段的(可能为空)序列组成。

所以如果你想在中间件内部获取请求body,你可以参考下面的代码:

        app.Use(async (context, next) => {
            context.Request.EnableBuffering();
            // Leave the body open so the next middleware can read it.
            using (var reader = new StreamReader(
                context.Request.Body,
                encoding: Encoding.UTF8,
                detectEncodingFromByteOrderMarks: false,
                bufferSize: 1000000,
                leaveOpen: true))
            {
                var body = await reader.ReadToEndAsync();

                // Do some processing with body…

                // Reset the request body stream position so the next middleware can read it
                context.Request.Body.Position = 0;
            }
            //You could find the contentlength is null
            var re2 = context.Request.ContentLength;
            await next();
        });

结果: