.net - 在 WebException 之后避免 ProtocolViolationException

标签 .net httpwebrequest servicepoint servicepointmanager

我正在努力解决这个错误:
https://github.com/openstacknetsdk/openstack.net/issues/333

问题涉及 ProtocolViolationException带有以下消息:

Chunked encoding upload is not supported on the HTTP/1.0 protocol.



我发现我能够可靠地重现问题,我发出一个产生 502 响应代码的 Web 请求,然后调用使用带有分 block 编码的 POST 请求。我将其追溯到 ServicePoint.HttpBehaviour 值为 HttpBehaviour.HTTP10 的属性在 502 响应之后。

我能够使用以下 hack(在 catch block 中)解决问题。此代码“隐藏”ServicePoint由来自 ServicePointManager 的失败请求创建的实例, 强制它创建一个新的 ServicePoint为下一个请求。
public void TestProtocolViolation()
{
    try
    {
        TestTempUrlWithSpecialCharactersInObjectName();
    }
    catch (WebException ex)
    {
        ServicePoint servicePoint = ServicePointManager.FindServicePoint(ex.Response.ResponseUri);
        FieldInfo table = typeof(ServicePointManager).GetField("s_ServicePointTable", BindingFlags.Static | BindingFlags.NonPublic);
        WeakReference weakReference = (WeakReference)((Hashtable)table.GetValue(null))[servicePoint.Address.GetLeftPart(UriPartial.Authority)];
        if (weakReference != null)
            weakReference.Target = null;
    }

    TestTempUrlExpired();
}

问题:
  • 为什么我要观察这种行为?
  • 解决问题的非hacky方法是什么?
  • 最佳答案

    问:为什么我会观察到这种行为?

    A. .NET 框架对连接到 HTTP 服务器的支持基于 ServicePointManager提供ServicePoint实例。每个ServicePoint实例假设它基于端点地址连接到单个“逻辑”服务。此对象缓存有关另一端服务的某些信息,其中一条信息是该服务是否支持 HTTP/1.1。如果对服务的任何请求表明该服务仅支持 HTTP/1.0,ServicePoint "latches" into that state ,以及 ServicePointManager只会重新创建一个新的 ServicePoint如果/当垃圾收集器清除 WeakReference 时,则不会处于该状态指向实例。

    由于以下原因,此行为可能被认为不是问题:

  • 通常,单个端点由单个服务提供服务,并且该服务支持或不支持 HTTP/1.1。
  • 如果一个端点实际上是一个负载均衡器,它将请求分派(dispatch)到多个支持 HTTP 实现(通常跨多个节点),那么这些节点代表同一整体服务安装的多个实例,并且要么所有节点都支持 HTTP/1.1,要么没有节点支持。
  • 在上述不成立的极少数情况下,缺乏 HTTP/1.0 特性通常不会成为服务的障碍。部署一个或多个 HTTP/1.0 服务器的端点不太可能要求客户端使用 HTTP/1.1 功能发送请求。

  • 问:有没有解决问题的简单方法?

    A. 当然有变通办法,但其中一个或多个选项可能不适合特定环境。下面列出了其中一些选项。
  • 更新服务以满足上述条件。 如果您提供的服务不符合上述条件,您应该考虑更新服务,前提是 .NET 客户端在某些情况下可能无法与您的服务通信。如果您无法控制服务,显然这不是一个可行的解决方案。
  • 考虑使用分 block 编码上传文件的替代方法。 如果您知道流的大小,则可能不需要使用分 block 编码,这样可以避免对 HTTP/1.1 的依赖。对于问题中提到的 SDK 的情况,底层的 SimpleRESTServices 库实际上需要提前知道流大小,因此分 block 编码实际上并未用于其预期目的。相反,当预先知道内容长度时,库应该使用缓冲传输,并且仅在 Stream.Size 时才依赖分 block 编码。属性抛出 NotSupportedException .
  • 考虑设置 HttpWebRequest.AllowWriteStreamBuffering true . 虽然我没有测试过这个解决方案,但是在浏览引用源时收集的信息表明这个属性允许实现 fall back to buffering在不支持分 block 传输的情况下,而不是简单地 throwing the ProtocolViolationException .
  • 强制 ServicePoint让我的设置超时ServicePoint.MaxIdleTime到 0。这仍然是 hacky,但不依赖反射,应该仍然适用于 Mono。修改后的代码如下所示。
    public void TestProtocolViolation()
    {
        try
        {
            TestTempUrlWithSpecialCharactersInObjectName();
        }
        catch (WebException ex)
        {
            ServicePoint servicePoint = ServicePointManager.FindServicePoint(ex.Response.ResponseUri);
            if (servicePoint.ProtocolVersion < HttpVersion.Version11)
            {
                int maxIdleTime = servicePoint.MaxIdleTime;
                servicePoint.MaxIdleTime = 0;
                Thread.Sleep(1);
                servicePoint.MaxIdleTime = maxIdleTime;
            }
        }
    
        TestTempUrlExpired();
    }
    
  • 关于.net - 在 WebException 之后避免 ProtocolViolationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22973178/

    相关文章:

    .net - 我可以将几行代码注入(inject)到其他人的 .NET dll 中吗?

    C# System.Thread.ThreadStateException 异常

    iphone - 使用 MonoTouch 时如何处理/修复 "Error getting response stream (ReadDone2): ReceiveFailure"?

    c# - 为虚拟目录创建 Uri

    c# - HttpWebResponse 不会针对并发出站请求进行扩展

    c# - LINQ 和字典线程安全

    c# - i7 多核处理器上的 .Net 3.5

    asp.net - 重用 CookieContainer 时如何确定 session 超时

    c# - 为什么 System.Net.ServicePoint.ConnectionLimit 在客户端连接到 '7FFFFFFF' 上的服务时使用 'localhost' (Int32.MaxValue/2147483647)?