在ASP.NET Core中禁用分块传输编码

26

我正在使用ASP.NET Core Azure Web App为客户端提供RESTful API服务,但客户端无法正确处理分块传输。

是否可能完全关闭Transfer-Encoding: chunked,无论是在控制器层面还是在文件web.config中?

我返回的JsonResult类似于以下示例:

[HttpPost]
[Produces("application/json")]
public IActionResult Post([FromBody] AuthRequest RequestData)
{
    AuthResult AuthResultData = new AuthResult();

    return Json(AuthResultData);
}

1
你的响应中是否有“Content-Length”头? - Dmitry S.
我已经添加了一个我的操作的示例。我不添加该标头,因为我不知道生成的Json有多长。以前,ASP已经添加了该标头,但在最近的服务器上它不再这样做(可能是自从移动到RC2以来)。 - Iain Brown
1
你可以使用响应缓冲来实现这个功能:https://github.com/aspnet/BasicMiddleware/blob/dev/samples/ResponseBufferingSample/Startup.cs#L17 - Tratcher
1
有趣的是这在本地服务器上可以运行,但在Azure上却不行。我猜想这是Server: KestrelServer: Microsoft-IIS/8.0之间的差异? - Iain Brown
2
你找到解决方法了吗?我遇到了同样的问题,本地 VS 上可以运行,但是部署到 Azure 应用服务上就无法正常工作。 - Ehsan
很遗憾,我没有这样做。相反,我不得不修复客户端。 - Iain Brown
4个回答

15

如何在.NET Core 2.2中消除块状数据传输:

诀窍是将响应体读入自己的MemoryStream中,以便获取长度。这样做后,您可以设置内容长度标头,IIS就不会将其分块。我认为这对Azure也适用,但我还没有测试过。

这是中间件:

public class DeChunkerMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var originalBodyStream = context.Response.Body;
        using (var responseBody = new MemoryStream())
        {
            context.Response.Body = responseBody;
            long length = 0;
            context.Response.OnStarting(() =>
            {
                context.Response.Headers.ContentLength = length;
                return Task.CompletedTask;
            });
            await _next(context);

            // If you want to read the body, uncomment these lines.
            //context.Response.Body.Seek(0, SeekOrigin.Begin);
            //var body = await new StreamReader(context.Response.Body).ReadToEndAsync();

            length = context.Response.Body.Length;
            context.Response.Body.Seek(0, SeekOrigin.Begin);
            await responseBody.CopyToAsync(originalBodyStream);
        }
    }
}

然后在启动时添加这个:

app.UseMiddleware<DeChunkerMiddleware>();

它需要在app.UseMvC()之前。


Mono droid 1.2的一个bug得到了很好的修复。 https://github.com/mono/mono/issues/9511 & https://dev59.com/6q7la4cB1Zd3GeqPd3-c - turkinator
虽然我认为在读取和复制整个响应时,肯定会有一些内存和处理方面的开销,但它运行良好。只是好奇你是否尝试过对此进行基准测试?我已经实现了这个功能,但添加了逻辑,只有在传入请求中存在“nochunk”头时才执行此操作,因此客户端可以请求一个“未分块”的响应,所以我不会强制所有客户端都使用未分块的响应。 - GPW
1
不要在OnStarting中设置Content-Length,在CopyToAsync之前设置它。 - Tratcher

4
在ASP.NET Core中,这似乎可以跨主机工作:
response.Headers["Content-Encoding"] = "identity";
response.Headers["Transfer-Encoding"] = "identity";

表示身份函数(即不压缩,也不修改)。除非明确指定,否则该令牌始终被视为可接受。

当您明确禁用响应缓冲时,这也可以起作用:

var bufferingFeature = httpContext.Features.Get<IHttpBufferingFeature>();
bufferingFeature?.DisableResponseBuffering();

1
提醒一下,对我来说,在ASP.Net Core 2.2中这些解决方案都不起作用。添加这些头信息会导致客户端的响应被冻结,并且响应体为空。 - billy jean

3
在.NET Core 2.0中可以使用它。在将结果写入响应主体流之前,只需设置ContentLength即可。
在启动类中:
app.Use(async (ctx, next) =>
{
    var stream = new xxxResultTranslatorStream(ctx.Response.Body);
    ctx.Response.Body = stream;

    await Run(ctx, next);

    stream.Translate(ctx);
    ctx.Response.Body = stream.Stream;
});

在 xxxResultTranslatorStream 中:

ctx.Response.Headers.ContentLength = 40;
stream.Write(writeTargetByte, 0, writeTargetByte.Length);

4
这很好,但是Run方法是什么? - m3z

0
我发现如果我只是从Get()返回一个FileStream并让ASP.NET处理剩下的部分,所有我的分块问题都会消失。
微软的软件往往在你放弃控制并信任它们时效果最好。如果你试图控制这个过程,它往往效果最差。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接