System.Net.HttpWebRequest 在特殊条件下为 SSL 和非 SSL 请求引发不同的 WebExceptionStatus

本文可帮助你解决使用WebExceptionStatus类时引发不同System.Net.HttpWebRequest问题。

原始产品版本: .Net Framework
原始 KB 数: 2007873

症状

使用 System.Net.HttpWebRequest Microsoft .Net Framework 类将超文本传输协议(HTTP)或超文本传输协议安全(HTTPS)请求发送到服务器。 此请求需要一些时间才能从服务器接收响应。 在此等待期间,如果手动增加系统时钟时间,或者系统时钟滞后,然后 Windows 时间服务会调整到实际的本地时间,则会出现以下情况之一:

对于通过纯文本 HTTP 发送的请求,该 System.Net.HttpWebRequest 类将引发以下异常:

请求已中止:操作已超时。

此外, Status 引发 WebException 的属性将指示值 WebExceptionStatus.Timeout

对于通过 HTTPS 发送的请求,该 System.Net.HttpWebRequest 类将引发以下异常之一:

基础连接已关闭:接收时出现意外错误。

此外, Status 引发 WebException 的属性将指示值 WebExceptionStatus.ReceiveFailure

基础连接已关闭:服务器关闭了应保持活动状态的连接。

此外, Status 引发 WebException 的属性将指示值 WebExceptionStatus.KeepAliveFailure

在上述所有情况下,捕获 InnerException 的属性。 如果捕获 WebException 并引用属性 WebException.InnerException.InnerException ,你会注意到,对于上述所有情况,字符串 Message 将指示:

连接尝试因连接的服务器在经过一段时间后未能正确响应而失败,或建立的连接因连接的主机未响应而失败。

此消息是 Winsock 错误代码 10060 = WSAETIMEDOUT 的详细解释。

因此,当系统时间手动增加时,Winsock 会正确引发超时错误 10060,但它被包装为安全套接字层 (SSL) 和非 SSL 请求的不同异常类型。

在系统时间不被篡改的正常超时情况下,SSL 和非 SSL 方案将正确反映 WebExceptionStatus.Timeout 状态并引发常见异常: 操作已超时

原因

通过 SSL 或非 SSL 发出请求时,类 System.Net.ServicePointManager 会将请求分配给内部连接,最终将进行 Winsock 连接。 对于 SSL 请求,此请求或连接通过另一个内部 SSL/TLS 类,负责加密或解密数据。 对于非 SSL 连接,此内部 SSL/TLS 类根本不涉及。

修改时间并在 Winsock 层遇到异常时,此错误现在需要从 Winsock 向上移动到应用程序层。 对于非 SSL 连接,此异常由内部连接类直接捕获,但对于 SSL 请求,此错误由内部 SSL/TLS 类处理。 此类将此非 SSL 错误视为 ReceiveFailureKeepAliveFailure 因此具有不同的异常状态,而对于非 SSL 连接,错误将正确强制转换,因为它由其他类处理。

状态

此行为是设计造成的。

决议

为了解决此特殊条件下引发的异常类型的这种差异,系统时间被篡改,应用程序需要捕获 WebException 并引用 WebException.InnerException.InnerException.Message 该属性。

Message如果字符串等于 10060 = WSAETIMEDOUT 的 winsock 详细错误,则可以考虑或ReceiveFailure视为常规超时,而不将其KeepAliveFailure视为ReceiveFailure或。KeepAliveFailure

应用程序在执行框架英文版时catch()WebException,可以使用以下解决方法。 对于框架的本地化版本,需要根据语言本地化调整以下解决方法。

重要

此示例代码按原样提供,仅用于示例目的。 它提供没有保证,并授予任何权利。

try
{
    ......
}
catch (WebException oWEx)
{
    WebExceptionStatus oStatus = oWEx.Status;
    String strTimeoutErrorMessage = "A connection attempt failed because the connected party did not properly respond "
                                  + "after a period of time, or established connection failed because connected host has failed to respond";
    switch (oStatus)
    {
        case WebExceptionStatus.KeepAliveFailure:
            if ((oWEx.InnerException != null) && (oWEx.InnerException.InnerException != null)
                && oWEx.InnerException.InnerException.Message.ToString().Equals(strTimeoutErrorMessage, StringComparison.CurrentCultureIgnoreCase))
            {   //----------------------------------------------------------------------
                // This is Timeout Error which is wrongly thrown as a ReceiveFailure for
                // SSL requests under this special condition.
                //
                // Handle this as a Timeout Error
                //----------------------------------------------------------------------
            }
            else
            {
                //----------------------------------------------------------------------
                // This is truly a KeepAliveFailure.
                //----------------------------------------------------------------------
            }
            break;
        case WebExceptionStatus.Timeout:
             //----------------------------------------------------------------------
             // This is a Timeout.
             //----------------------------------------------------------------------
             break;
        case WebExceptionStatus.ReceiveFailure:
            if ((oWEx.InnerException != null)
                && (oWEx.InnerException.InnerException != null)
                && oWEx.InnerException.InnerException.Message.ToString ().Equals (strTimeoutErrorMessage, StringComparison.CurrentCultureIgnoreCase))
            {    //----------------------------------------------------------------------
                 // This is Timeout Error which is wrongly thrown as a ReceiveFailure for
                 // SSL requests under this special condition.
                 //
                 // Handle this as a Timeout Error
                 //----------------------------------------------------------------------
            }
            else
            {    //----------------------------------------------------------------------
                 // This is truly a ReceiveFailure.
                 //----------------------------------------------------------------------
            }
            break;
        default:
               //----------------------------------------------------------------------
               //  This is some other Exception
               //----------------------------------------------------------------------
            break;
    }
}