在ASP.NET 5响应中设置Content-Length头信息

6

TL; DR 我有一个ASP.NET 5(MVC 6)应用程序,想要设置HTTP Content-Length头以避免分块响应。

令我惊讶的是,在ASP.NET 5中实现这个任务相当棘手。一切都在运行Kestrel上,从ASP.NET 5 Beta7 自动支持写入分块响应,如果没有为响应指定内容长度

有类似的问题这里,但不同之处在于OP只想计算响应大小,而我需要确保Content-Length头在响应中发送。到目前为止尝试了很多方法,唯一有效的方法是编写一个自定义中间件:

public class WebApiMiddleware {
    RequestDelegate _next;

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

    public async Task Invoke(HttpContext context) {
         using (var buffer = new MemoryStream()) {
            var response = context.Response;

            var bodyStream = response.Body;
            response.Body = buffer;

            await _next(context);

            response.Headers.Add("Content-Length", new[] { buffer.Length.ToString()});
            buffer.Position = 0;
            await buffer.CopyToAsync(bodyStream);
        }
     }
}

然而,这种方法效率极低,因为我们在处理每个请求时都会使用额外的内存。使用一个自定义流来包装 context.Response.Body 流并额外计算字节数并不起作用,因为微软团队指出 一旦他们愿意缓冲的数据量被写入,它就会被发送到网络上,他们不再存储它, 所以我最多只能在最后一个块中添加 Content-Length,这不是期望的行为 - 我想完全避免分块。

非常感谢任何帮助!

p.s. 我知道分块是 HTTP/1.1 的常规功能,但在我的情况下,它会降低性能,所以我想避免它,同时又不强制客户端发送 HTTP/1.0 请求。


你知道如何在不缓冲内容的情况下获取其长度吗?如果不知道,这个问题是无法解决的。 - usr
@usr 我可以在每个方法的结尾计算内容的长度。然而,我的控制器有很多方法,我想避免在每个方法的响应上调用toString(),然后设置Response.Headers.Append("Content-Length", retVal.Length.ToString())。其中一些方法返回IEnumerable(即,一些集合),其他方法返回IActionResult等。MVC在某个地方已经对响应执行了序列化,然后返回它,如果可能的话,我想抓取那个序列化的响应,并设置Content-Length头。 - Miljen Mikic
我曾经在请求头中添加了错误的内容长度,导致请求在自定义中间件中被卡住(似乎丢失),并且从未到达控制器。后来删除了该头部,它就正常工作了。这可能与问题无关,但是我将其评论,因为它可能有助于其他人。 - kaarthick raman
1个回答

3
在发送响应前,您需要发送标头,因此您需要在发送之前存储整个响应以确定长度。
正如您所说的那样,context.Response.Body 流在发送之前不会存储太多内容,因此开销并不大。
有一个类似的中间件:https://github.com/aspnet/BasicMiddleware/blob/master/src/Microsoft.AspNetCore.Buffering/ResponseBufferingMiddleware.cs,它与您编写的内容非常相似,但支持其他特性,例如 IHttpBufferingFeature,以允许其他中间件控制缓冲。
检查 bufferStream.CanSeek 是为了确保没有人已经将响应流包装成缓冲流,并避免重复缓冲。
对于您的第二个问题:这个地方是中间件。

谢谢您的回复Pavel。有两个问题: 1)您提出的使用中间件的解决方案与“MemoryStream”中提到的解决方案有何不同?我可以看到您正在检查“bufferStream.CanSeek”,这是否意味着在写入响应时可以关闭缓冲? 2)在MVC管道中有没有一个地方可以读取要发送的内容并添加适当的标头/进行压缩/等等?那实际上是我最初问题的核心,坦白说,我无法相信这样简单的事情是不可能的。 - Miljen Mikic
感谢你的努力,我给你点了个赞。我的第二个问题应该是:“在MVC管道中是否有一个地方可以读取内容而不缓冲它”。如果不可能,请给我一些支持这一点的参考资料,否则我会等待看看是否有人能给出更好的答案(尽管很难期望有人能比ASP.NET团队的成员给出更好的答案:))。 - Miljen Mikic
你可以包装响应流并在那里读取,但如果你需要在发送给客户端之前读取所有内容,唯一的方法是缓冲。 - Pavel Krymets

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