Как избежать исключения ProtocolViolationException после WebException
Я работаю над устранением этой ошибки:
https://github.com/openstacknetsdk/openstack.net/issues/333
Проблема включает в себя ProtocolViolationException
со следующим сообщением:
Загрузка фрагментированного кодирования не поддерживается протоколом HTTP/1.0.
Я обнаружил, что могу надежно воспроизвести проблему, делая свой веб-запрос, который выдает код ответа 502, после чего следует вызов POST-запроса с кусочным кодированием. Я проследил это до ServicePoint.HttpBehaviour
имущество, имеющее ценность HttpBehaviour.HTTP10
после ответа 502.
Я смог решить проблему, используя следующий хак (в 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 ответ
В. Почему я наблюдаю это поведение?
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.
В. Есть ли нехакерский способ решить проблему?
О. Конечно, есть обходные пути, но один или несколько вариантов могут быть неподходящими для конкретной среды. Ниже перечислены некоторые из этих опций.
Обновите услугу, чтобы соответствовать условиям, перечисленным выше. Если вы предоставляете услугу, которая не соответствует указанным выше условиям, вам следует рассмотреть возможность обновления службы на основе понимания того, что клиенты.NET могут не иметь возможности взаимодействовать с вашей службой в некоторых сценариях. Если у вас нет контроля над службой, очевидно, что это не жизнеспособное решение.
Рассмотрим альтернативы использованию chunked-кодировки для загрузки файлов. Если вы знаете размер вашего потока, вам может не потребоваться использовать чанкованное кодирование, что позволяет избежать зависимости от HTTP/1.1. Для случая SDK, упомянутого в этом вопросе, лежащая в основе библиотека SimpleRESTServices фактически требует, чтобы размер потока был известен заранее, поэтому кодирование по частям фактически не используется по назначению. Вместо этого библиотека должна использовать буферизованные передачи, когда длина контента известна заранее, и полагаться только на фрагментированное кодирование, когда
Stream.Size
собственность бросаетNotSupportedException
,Рассмотрим настройки
HttpWebRequest.AllowWriteStreamBuffering
вtrue
, Хотя я не тестировал это решение, информация, собранная при просмотре справочного источника, предполагает, что это свойство позволяет реализации возвращаться к буферизации в случае, когда фрагментированные передачи не поддерживаются, а не просто выбрасыватьProtocolViolationException
,Заставить
ServicePoint
тайм-аут мои настройкиServicePoint.MaxIdleTime
до 0. Это все еще хакерский, но не полагается на рефлексию и все равно должен работать на моно. Измененный код будет выглядеть следующим образом.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(); }