避免在WebException之后出现ProtocolViolationException

10

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

问题涉及到一个 ProtocolViolationException,错误信息如下:

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

我发现可以通过发送一个产生502响应代码的网络请求,再使用带分块编码的POST请求来可靠地复现该问题。我将此追溯到在502响应后,ServicePoint.HttpBehaviour 属性具有值 HttpBehaviour.HTTP10

我使用了以下 hack(在 catch 块中),成功解决了该问题。这段代码“隐藏”了由失败请求创建的 ServicePoint 实例,使其强制为下一个请求创建新的 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. 有没有一种非hack的方法来解决这个问题?

问题:

  1. 为什么我会观察到这种行为?
  2. 有没有一种非hack的方法来解决这个问题?
1个回答

9

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

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

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

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

  2. 如果终端点实际上是一个负载均衡器,将请求分配到多个后备HTTP实现(通常跨多个节点),则这些节点表示同一整个服务安装的多个实例,而且所有节点都支持HTTP/1.1或所有节点都不支持。

  3. 在极少数情况下,上述内容不适用时,缺少HTTP/1.0功能通常不会影响服务。部署一个或多个HTTP/1.0服务器的终端点不太可能要求客户端使用HTTP/1.1功能发送请求。

Q. 是否有一种非hacky的方法来解决这个问题?

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

  1. Update the service to meet the conditions listed above. If you are providing the service that does not meet the above conditions, you should consider updating the service based on the understanding that .NET clients may not be able to communicate with your service under some scenarios. If you do not have control over the service, the obviously this is not a viable solution.

  2. Consider alternatives to using chunked encoding for uploading files. If you know the size of your stream, you may not need to use chunked encoding, which avoids the dependency on HTTP/1.1. For the case of the SDK mentioned in the question, the underlying SimpleRESTServices library actually requires the stream size to be known in advance, so chunked encoding is not actually being used for its intended purpose. Instead, the library should use buffered transfers when the content length is known in advance, and only rely on chunked encoding when the Stream.Size property throws a NotSupportedException.

  3. Consider setting HttpWebRequest.AllowWriteStreamBuffering to true. While I have not tested this solution, information gathered while browsing the reference source suggests that this property allows the implementation to fall back to buffering in the case where chunked transfers are not supported, rather than simply throwing the ProtocolViolationException.

  4. Force the ServicePoint to time out my setting ServicePoint.MaxIdleTime to 0. This is still hacky, but doesn't rely on reflection and should still work on Mono. The modified code would look like the following.

    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();
    }
    

你的修复拉取请求已经被 Openstack 接受了吗? - Rebecca
我的应用程序也遇到了同样的问题,但它引起的问题略有不同。我们发现,一旦像你所说的那样“锁定”到Http 1.0,它会禁用keep-alive,因为它不再是默认值,它还会禁用管线化。由于我们通过https与AWS云中的一组服务器通信,在那里我们有HAProxies终止SSL,我们正在压倒HAProxies,导致超时和响应缓慢。 - markdemich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接