在WebException之后避免ProtocolViolationException

时间:2014-04-09 20:22:01

标签: .net httpwebrequest servicepoint servicepointmanager

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

问题涉及ProtocolViolationException,其中包含以下消息:

  

HTTP / 1.0协议不支持分块编码上传。

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

我能够使用以下hack解决问题(在catch块中)。这段代码"隐藏"来自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();
}

问题:

  1. 为什么我会观察到这种行为?
  2. 解决此问题的非黑客方法是什么?

1 个答案:

答案 0 :(得分:9)

Q值。为什么我会观察到这种行为?

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

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

  1. 通常,单个端点由单个服务提供服务,该服务支持或不支持HTTP / 1.1。

  2. 如果端点实际上是一个负载均衡器,它将请求分派给多个支持HTTP实现(通常跨多个节点),那么这些节点代表同一整体服务安装的多个实例,并且 all 节点支持节点的HTTP / 1.1或 none

  3. 在极少数情况下上述情况不成立,缺乏HTTP / 1.0功能通常不会妨碍服务。部署一个或多个HTTP / 1.0服务器的端点不太可能要求客户端使用HTTP / 1.1功能发送请求。

  4. Q值。是否有一种解决问题的非黑客方式?

    一个。肯定有解决办法,但一个或多个选项可能不适合特定环境。以下列出了其中一些选项。

    1. 更新服务以满足上述条件。如果您提供不符合上述条件的服务,则应考虑更新服务基于这样的理解,即在某些情况下.NET客户端可能无法与您的服务通信。如果您无法控制该服务,显然这不是一个可行的解决方案。

    2. 考虑使用分块编码上传文件的替代方法。如果您知道流的大小,则可能不需要使用分块编码,这样可以避免对HTTP / 1.1的依赖。对于问题中提到的SDK的情况,底层的SimpleRESTServices库实际上需要事先知道流大小,因此分块编码实际上并未用于其预期目的。相反,当预先知道内容长度时,库应该使用缓冲传输,并且当Stream.Size属性抛出NotSupportedException时,仅依赖于分块编码。

    3. 考虑将HttpWebRequest.AllowWriteStreamBuffering设置为true虽然我尚未测试此解决方案,但在浏览参考源时收集的信息表明此属性允许实施fall back to buffering如果不支持分块转移,而不仅仅是throwing the ProtocolViolationException

    4. 强制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();
      }