【问题标题】:Response pipeline响应管道
【发布时间】:2016-07-06 08:13:53
【问题描述】:

我在使用 Asp.net core 1.0 RTM 时遇到了困难。例如,在下面的情况下,我们将看到输出结果为“-Message_1--Message_5-”:

 public class MessageMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IApplicationBuilder _app;

    public MessageMiddleware(RequestDelegate next, IApplicationBuilder app)
    {
        _next = next;
        _app = app;
    }

    public async Task Invoke(HttpContext context)
    {
        var started1 = context.Response.HasStarted;//false
        await context.Response.WriteAsync("-Message_1-");
        var test = true; // will hit this line
        var started2 = context.Response.HasStarted;//true
        await context.Response.WriteAsync("-Message_5-");

        await _next.Invoke(context);
    }
}

但在这种情况下(添加了标题“Content-Type”)结果将只有“-Message_1-”并且执行真的停止了:

  public class MessageMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IApplicationBuilder _app;

    public MessageMiddleware(RequestDelegate next, IApplicationBuilder app)
    {
        _next = next;
        _app = app;
    }

    public async Task Invoke(HttpContext context)
    {
        var started1 = context.Response.HasStarted;//false
        await context.Response.WriteAsync("-Message_1-");
        var started2 = context.Response.HasStarted;//true
        context.Response.ContentType = "text/html";
        var test = true; // will NOT hit this line
        var started3 = context.Response.HasStarted;//will NOT hit this line
        await context.Response.WriteAsync("-Message_5-"); //will NOT hit this line

        await _next.Invoke(context);
    }
}

我在官方文档中只找到了这句话:

避免在调用下一个之后修改 HttpResponse,管道中的下一个组件之一可能已写入响应,导致它被发送到客户端。

这个问题在 SO:Why can't the HttpResponse be changed after 'next' call?

但是仅仅了解在中间件管道期间与 HttpContext.Response 的 props 的交互以及这种交互如何影响最终结果 - HttpResponse 的标头和正文内容是不够的。

有人可以解释 ASP.NET 核心处理响应的一般行为吗?例如,当响应标头发送到客户端时,设置 HttpContext.Response 属性(标头,正文内容)对此有何影响? 当管道内(外)中间件终止时?

谢谢!

【问题讨论】:

  • 在向响应流写入内容后不要设置标头。您只能在数据发送到客户端之前设置标头。一旦您向客户端发送任何数据,您就不能再更改或设置标头,因为需要在发送内容之前设置标头(它们在内容之前提交)
  • 曾,向客户端发送数据时发出事件?你是什​​么意思“发送数据”?是否只发送标头?观察者在哪里以及它是如何工作的?
  • await context.Response.WriteAsync("-Message_1-");,取决于您是否拥有(或没有)任何缓冲的中间件。即使那样,您也无法控制它何时被刷新。之后的一行 context.Response.ContentType = "text/html"; 正在设置标题内容已写入

标签: asp.net-core response middleware pipeline


【解决方案1】:

作为一般规则,当客户端向服务器发出请求时,它会返回响应。该响应包含标题和正文。标头包含许多有关响应的信息,例如内容类型、使用的编码/压缩、cookie 等。以下是在 chrome 开发人员工具中看到的 live.asp.net 站点发回的标头示例:

响应的另一部分是正文。它通常包含 html 或 json。这是同一响应的正文截图:

考虑它的最简单方法是考虑将这两个一起发送到客户端,首先是标头,然后是正文。因此,作为开发人员,您在响应对象上设置影响标头的任何值的唯一机会是直到您开始发送正文为止。当您开始发送响应正文时,您将无法再更改标头,因为它们是在正文开始发送之前作为响应的第一部分发送的。

这就是为什么@tseng 说“在向响应流中写入内容后不要设置标头”。

如果开发人员不熟悉 http 标头,他们可能不会意识到 context.Response.ContentType = "text/html" 正在更改标头,但在幕后,这正是它正在做的事情。同样,设置 cookie 会在后台更改响应标头。通常,如果您要更改响应对象的某些属性,您应该问自己“这会更改 http 标头吗?”如果答案是“是”,那么您需要在致电 Response.WriteAsync 之前进行操作。

【讨论】:

  • 大鹏,感谢您的详细回答。如果我理解正确,响应标头和响应正文可能会分开发送:在开始时,标头会发送,然后是正文。这样对吗?如果是,则当 Asp.net 核心开始发送标头时。它在看什么事件?写入响应流?在设置标头根本不执行后,我比代码感到惊讶。
  • 将其视为发送到客户端的数据流。该流的开头是标头,然后在发送标头之后,流中发送的下一个内容是正文。 WriteAsync(或将正文文本发送到客户端的任何其他调用)强制发送标头,因为它们在发送的流中位于正文之前。
  • Ron,所以如果在某些中间件中我开始写入响应流,ASP.core 开始向客户端发送响应。在第二个中间件中,如果我不使用一些缓冲中间件,我就无法替换正文流内容?
  • 没错,您无法替换已经通过网络流式传输到客户端的内容。
  • @malonowa:在这种情况下,您必须创建一些 funky 缓冲中间件(早期注册),它会创建一个内存流并将响应流替换为它。然后所有其他中间件将写入此内存流,在_next(context) 调用之后,您可以读取此内存流并将其写入实际响应流。然而,这会对性能和响应时间产生一些影响,因为将使用更多内存并且用户在所有中间件完成之前不会收到任何数据
猜你喜欢
  • 2014-09-21
  • 2019-07-25
  • 1970-01-01
  • 2016-11-23
  • 2019-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多