10

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

该问题涉及ProtocolViolationException以下消息:

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

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

我能够使用以下技巧(在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. 解决问题的非hacky方法是什么?
4

1 回答 1

9

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

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

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

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

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

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

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

A. 当然有变通办法,但其中一个或多个选项可能不适合特定环境。下面列出了其中一些选项。

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

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

  3. 考虑设置HttpWebRequest.AllowWriteStreamBufferingtrue虽然我没有测试过这个解决方案,但在浏览参考源时收集的信息表明,这个属性允许实现在不支持分块传输的情况下回退到缓冲,而不是简单地抛出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();
    }
    
于 2014-04-10T01:04:58.400 回答