Dot-Net
在 WebException 之後避免 ProtocolViolationException
我正在解決這個錯誤:
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(); }問題:
- 為什麼我要觀察這種行為?
- 解決問題的非hacky方法是什麼?
問:為什麼我會觀察到這種行為?
A. .NET 框架對與 HTTP 伺服器的連接的支持基於
ServicePointManager提供ServicePoint實例。每個ServicePoint實例都假定它基於端點地址連接到單個“邏輯”服務。此對象記憶體有關另一端服務的某些資訊,其中一條資訊是該服務是否支持 HTTP/1.1。如果對服務的任何請求表明該服務僅支持 HTTP/1.0,則ServicePoint“鎖定”到該狀態,並且僅當垃圾收集器清除指向該實例的指向時,ServicePointManager才會重新創建一個ServicePoint不在該狀態的新鮮事物。WeakReference由於以下原因,此行為可能被認為不是問題:
- 通常,單個端點由單個服務提供服務,並且該服務支持或不支持 HTTP/1.1。
- 如果一個端點實際上是一個負載均衡器,它將請求分派到多個支持 HTTP 實現(通常跨多個節點),那麼這些節點代表同一整體服務安裝的多個實例,並且要麼所有節點都支持 HTTP/1.1,要麼沒有節點支持。
- 在上述不成立的極少數情況下,缺乏 HTTP/1.0 特性通常不會成為服務的障礙。部署一個或多個 HTTP/1.0 伺服器的端點不太可能要求客戶端使用 HTTP/1.1 功能發送請求。
問:有沒有解決問題的簡單方法?
A. 當然有變通辦法,但其中一個或多個選項可能不適合特定環境。下面列出了其中一些選項。
- **更新服務以滿足上述條件。**如果您提供的服務不符合上述條件,您應該考慮更新服務,前提是 .NET 客戶端在某些情況下可能無法與您的服務通信。如果您無法控制服務,顯然這不是一個可行的解決方案。
- **考慮使用分塊編碼上傳文件的替代方法。**如果您知道流的大小,則可能不需要使用分塊編碼,這樣可以避免對 HTTP/1.1 的依賴。對於問題中提到的 SDK 的情況,底層的 SimpleRESTServices 庫實際上需要提前知道流大小,因此分塊編碼實際上並未用於其預期目的。相反,當預先知道內容長度時,庫應該使用緩衝傳輸,並且僅在
Stream.Size屬性拋出NotSupportedException.- **考慮設置
HttpWebRequest.AllowWriteStreamBuffering為true。**雖然我沒有測試過這個解決方案,但在瀏覽參考源時收集的資訊表明,這個屬性允許實現在不支持分塊傳輸的情況下回退到緩衝,而不是簡單地拋出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(); }