Dot-Net

在 WebException 之後避免 ProtocolViolationException

  • April 11, 2014

我正在解決這個錯誤:

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方法是什麼?

問:為什麼我會觀察到這種行為?

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

引用自:https://stackoverflow.com/questions/22973178