Проблемы с использованием WebRequest в Mono
У меня действительно странная проблема с WebRequest в веб-приложении ServiceStack (размещается в XSP на Mono). Кажется, что регистрация модулей запросов работает очень странным образом; Я использую WebRequest для создания HTTP-запроса, и он терпит неудачу, потому что он не смог найти создателя для этого "префикса" (HTTP).
Исключение, которое я вижу, NotSupportedException
и я смог отследить тот факт, что ни один создатель не зарегистрирован для префикса HTTP (я нажимаю https://github.com/mono/mono/blob/master/mcs/class/System/System.Net/WebRequest.cs, строка 479)
РЕДАКТИРОВАТЬ: более подробно: NotSupportedException
брошен WebRequest.GetCreator
, который использует префикс URL в качестве ключа, чтобы выбрать, какой создатель должен вернуться; в моем случае HttpRequestCreator
, Исключение вызвано тем, что для префикса "HTTP" не зарегистрировано создатель (на самом деле создателей нет вообще).
Поэтому я немного поискал, копался в источниках Mono и обнаружил, что модули (или должны быть) добавлены в раздел webRequestModules system.web в одном из различных файлов *.config.
Я посмотрел на мой файл machine.config, и вот он:System.Net.HttpRequestCreator, System, Version=4.0.0.0
Глядя на источники WebRequest Mono, кажется, что префиксы действительно добавляются из конфигурации (конфигураций) внутри статического конструктора класса (не очень хороший выбор, IMHO, но все же.. должен работать).
Чтобы проверить это, я попытался добавить HttpRequestCreator
в system.net/webRequestModules
в моем web.config
; это загружается XSP/Mono и приводит к исключению дублирующегося ключа (что ожидается, так как HttpRequestCreator должен быть уже загружен, так как он уже присутствует в machine.config).
Даже незнакомец: если я добавлю обработчик макета для Http, вот так:
bool res = System.Net.WebRequest.RegisterPrefix ("http", new MyHttpRequestCreator ());
Debug.Assert (res == false);
утверждение иногда проходит... иногда нет! (RegisterPrefix возвращает "false", если создатель с таким же префиксом уже зарегистрирован; я ожидаю, что он всегда будет возвращать false, но это не так! Опять же, это совершенно случайно)
Когда регистрация "не удается" (т. Е. Возвращает false, поскольку префикс "HTTP" уже зарегистрирован), WebRequest может создавать запросы для HTTP. Как будто вызов RegisterPrefix "пробуждает" статический конструктор и позволяет ему работать.
Я озадачен: это похоже на состояние гонки при выполнении статического конструктора WebRequest, но это не имеет смысла (среда выполнения защищает статические конструкторы с помощью блокировки, IIRC)
Что мне не хватает? Как я мог решить или обойти эту проблему? Это моя ошибка (недоразумение или отсутствие чего-то) или это похоже на ошибку Mono, и поэтому я должен отправить ее?
Подробности:
mono --version Mono JIT-компилятор версии 3.0.6 (Debian 3.0.6 + dfsg-1 ~ exp1 ~ pre1)
(Возможно связанный, оставшийся без ответа вопрос: протокол HTTP не поддерживается в WebRequest под mono)
2 ответа
Попробуйте этот хакерский обходной путь для этой проблемы:
private static HttpWebRequest CreateWebRequest(Uri uri)
{
var type = Type.GetType("System.Net.HttpRequestCreator, System, Version=4.0.0.0,Culture=neutral, PublicKeyToken=b77a5c561934e089");
var creator = Activator.CreateInstance(type,nonPublic:true) as IWebRequestCreate;
return creator.Create(uri) as HttpWebRequest;
}
Похоже, вы испытываете состояние гонки при выполнении статического конструктора класса WebRequest. Это может быть вызвано множеством факторов, включая неправильную синхронизацию доступа к общим ресурсам или использование неинициализированных переменных.
Одним из возможных решений этой проблемы может быть обеспечение инициализации класса WebRequest перед попыткой его использования. Это можно сделать, обратившись к статическому полю или методу класса WebRequest перед выполнением каких-либо запросов. Например:
int dummy = WebRequest.DefaultMaximumErrorResponseLength;
Это заставит запустить статический конструктор, гарантируя, что класс WebRequest будет правильно инициализирован, прежде чем вы попытаетесь сделать какие-либо запросы.
В качестве альтернативы вы можете попробовать использовать класс Lazy для инициализации класса WebRequest потокобезопасным способом. Это можно сделать следующим образом:
private static readonly Lazy<int> DefaultMaximumErrorResponseLength =
new Lazy<int>(() => WebRequest.DefaultMaximumErrorResponseLength);
// ...
int dummy = DefaultMaximumErrorResponseLength.Value;
Это гарантирует, что класс WebRequest инициализируется потокобезопасным способом без риска возникновения условий гонки.