【问题标题】:ASP.NET handling requests when connection is dropped after switching to IIS8切换到 IIS8 后断开连接时的 ASP.NET 处理请求
【发布时间】:2014-04-05 11:17:01
【问题描述】:

我有一个使用 WebForms 的 ASP.NET 3.5 应用程序,目前它托管在 IIS6 上。一切都很好。

但是,在切换到安装了 IIS8 的 Windows 2012 服务器后,我们会间歇性地收到截断的请求。大多数情况下,这会在我们的事件日志中显示视图状态异常,但是,在没有 ViewState 的表单上,我们会收到不完整的帖子(最后几个字段丢失/部分截断)。

这变得非常有问题,以至于我们升级为 Microsoft 支持,经过数周的调试,他们说这是 II7 及更高版本的“正确”行为。他们的解释是 IIS 管道从 6 到 7 的变化。

IIS6 及以下版本会在将整个请求传递给 Asp.net 之前对其进行缓冲,截断的请求将被忽略。
IIS7 及更高版本将在发送初始标头后将请求发送到 Asp.net,由应用程序处理截断的请求。

当存在连接问题(用户在传输过程中拔掉电缆)或用户在发布过程中按下停止/重新加载页面时,就会出现问题。

在我们的 HTTP 日志中,我们看到与截断请求相关的“connection_dropped”消息。

我很难相信这种行为是有意为之的,但我们已经在几个不同的服务器上进行了测试,并在 IIS7 及更高版本(Windows 2008、2008 R2 和 2012)上得到了相同的结果。

我的问题是:

1) 这种行为是否有意义?

2) 如果这是“正确”行为,您如何保护您的应用免受潜在的处理不完整数据的影响?

3) 为什么应用程序开发人员有责任检测不完整的请求?假设,应用开发者为什么会处理不完整的请求而不是忽略它?

更新

我写了一个小的 asp.net 应用程序和网站来演示这个问题。

服务器

Handler.ashx.cs

public class Handler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        if (context.Request.HttpMethod == "POST")
        {
            var lengthString = context.Request.Form["Length"];
            var data = context.Request.Form["Data"];

            if (lengthString == null)
            {
                throw new Exception("Missing field: Length");
            }

            if (data == null)
            {
                throw new Exception("Missing field: Data");
            }

            var expectedLength = int.Parse(lengthString);

            if (data.Length != expectedLength)
            {
                throw new Exception(string.Format("Length expected: {0}, actual: {1}, difference: {2}", expectedLength, data.Length, expectedLength - data.Length));
            }
        }

        context.Response.ContentType = "text/plain";
        context.Response.Write("Hello World, Request.HttpMethod=" + context.Request.HttpMethod);
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

客户

程序.cs

static void Main(string[] args)
{
    var uri = new Uri("http://localhost/TestSite/Handler.ashx");
    var data = new string('a', 1024*1024); // 1mb

    var payload = Encoding.UTF8.GetBytes(string.Format("Length={0}&Data={1}", data.length, data));

    // send request truncated by 256 bytes
    // my assumption here is that the Handler.ashx should not try and handle such a request
    Post(uri, payload, 256);
}

private static void Post(Uri uri, byte[] payload, int bytesToTruncate)
{
    var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
    {
        // this allows us to disconnect unexpectedly
        LingerState = new LingerOption(true, 0)
    };

    socket.Connect(uri.Host, uri.Port);

    SendRequest(socket, uri, payload, bytesToTruncate);

    socket.Close();
}

private static void SendRequest(Socket socket, Uri uri, byte[] payload, int bytesToTruncate)
{
    var headers = CreateHeaders(uri, payload.Length);

    SendHeaders(socket, headers);

    SendBody(socket, payload, Math.Max(payload.Length - bytesToTruncate, 0));
}

private static string CreateHeaders(Uri uri, int contentLength)
{
    var headers = new StringBuilder();

    headers.AppendLine(string.Format("POST {0} HTTP/1.1", uri.PathAndQuery));
    headers.AppendLine(string.Format("Host: {0}", uri.Host));
    headers.AppendLine("Content-Type: application/x-www-form-urlencoded");
    headers.AppendLine("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/99.0");
    headers.AppendLine("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
    headers.AppendLine("Connection: Close");
    headers.AppendLine(string.Format("Content-Length: {0}", contentLength));

    return headers.ToString();
}

private static void SendHeaders(Socket socket, string headers)
{
    socket.Send(Encoding.ASCII.GetBytes(headers));
    socket.Send(Encoding.ASCII.GetBytes("\n"));
}

private static void SendBody(Socket socket, byte[] payload, int numBytesToSend)
{
    socket.Send(payload, 0, numBytesToSend, SocketFlags.None);
}

【问题讨论】:

    标签: c# asp.net .net iis webforms


    【解决方案1】:

    1) 如果您在集成模式下为您的 3.5 应用程序分配到的应用程序池运行管道,您可能无法处理您的请求due to ISAPI behavior。您可能会生成无法正确理解的请求,然后将它们截断为默认值。您是否尝试过以经典模式运行应用程序池?

    2) 功能测试。大量的功能测试。创建一个测试工具并进行您的应用程序可以进行的所有调用,以确保其正常工作。这不是一个 100% 的解决方案,但实际上没有。有许多计算机科学论文解释了为什么无法测试您的应用可能运行的每一种可能情况based on the Halting Problem

    3) 因为你写了代码。您不应该有不完整的请求,因为该请求可能是针对重要数据的,并且您需要发回一个错误,说明处理请求时出现问题,否则发出方只会将请求视为神秘消失。

    【讨论】:

    • 当用户在浏览器上按下停止时,可能会出现问题。不管我是在集成模式还是经典模式。至于你的第二点,这似乎是框架应该保护你免受的东西,asp.net 应该是 HTTP 之上的抽象。虽然这绝对是一个有漏洞的抽象,但我觉得这是一个我不应该写的问题。反对这个问题是非常困难的。我将使用测试程序更新原始问题以演示该问题。
    • 最好有一个测试程序。同时,是的,我可以理解你对不得不预测像这样的低杠杆错误的沮丧,但正如你所说,这是一个有漏洞的抽象,而有漏洞的抽象只能保护你免受这么多。 FWIW,我从未见过 4.0 或 4.5 会发生这种情况。
    • 澄清一下,当用户在浏览器上单击停止或存在连接问题时,是否会发生此问题,或者您是否还有报告说除了截断的请求之外一切都顺利进行?
    • 该应用程序在移至 IIS8 服务器后无需修改即可运行,并且在 IIS6 中从未有过这种复制。该应用程序在此例外情况下工作。通常,当用户提交具有大视图状态或大量发布数据的页面时,会发生此错误,他们要么在浏览器上按 STOP,要么在页面完成发布之前单击另一个链接。如果用户等待帖子完成,则不会产生错误。这只是一个常规的 WebForms 表单帖子。
    • post数据大概100k
    【解决方案2】:

    IIS 改变其行为的原因是我们(开发人员)需要对请求处理进行更多控制。在请求中断的情况下,我们在调查隐形请求的原因时遇到了问题。我们需要在应用程序级别记录请求以进行调查和记录保存。例如,如果请求涉及信用卡交易等金融交易,我们需要更多控制,并且需要记录每一步以确保合规。

    IIS 是一个 Web 服务器框架,应用程序级别的数据验证不是他们的责任。如果请求被破坏,这意味着输入不完整,应用程序级逻辑将决定做什么。应用程序必须响应正确的错误代码和失败。这就是 ASP.NET mvc 具有模型验证的原因,它允许您在应用程序级别验证完整的输入。

    您可以使用 IsClientConnected 来检查底层套接字是否仍然连接。

    随着网络使用更多 AJAX 和更多移动设备,并且我们有时使用 ping 检查远程服务的运行状况,我们不一定会断定请求中断是错误并且必须丢弃。我们可能仍然希望接受中断的请求。这是应用程序级开发人员可以做出的选择,而不是 IIS。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-14
      • 2014-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-13
      • 1970-01-01
      相关资源
      最近更新 更多