Почему HttpClient BaseAddress не работает?

299

Рассмотрим следующий код, где BaseAddressопределяется частичный путь URI.

using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
    client.BaseAddress = new Uri("http://something.com/api");
    var response = await client.GetAsync("/resource/7");
}

Я ожидаю, что это выполнит GET запрос к http://something.com/api/resource/7. Но это не так.

После некоторых поисков я нахожу этот вопрос и ответ: HttpClient с BaseAddress . Предложение заключается в том, чтобы поставить /в концеBaseAddress .

using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
    client.BaseAddress = new Uri("http://something.com/api/");
    var response = await client.GetAsync("/resource/7");
}

Это все еще не работает. Вот документация: HttpClient.BaseAddress Что здесь происходит?

Тимоти Шилдс
источник
Возможный дубликат HttpClient с BaseAddress
Джордж Ланец
@ ГеоргийЛанец Обратный дубликат уже предлагался. Я написал этот вопрос специально, потому что этот другой вопрос не был написан так, чтобы его могли легко обнаружить люди с такой же проблемой, и я написал ответ здесь, потому что ответ там не затронул важный момент.
Тимоти Шилдс
но этот вопрос задают позже
Джордж Ланец
2
@ ГеоргийЛанец Это не так. Обычно самый «канонический» вопрос - это тот, который заставляет дубликаты указывать на него. Другой вопрос касался одной проблемы, с которой сталкивался пользователь, а не читал как FAQ.
Тимоти Шилдс
2
@ ГеоргийЛанец Также обратите внимание, что я ссылаюсь на этот другой вопрос в этом вопросе, и объясняю, почему другого вопроса и ответа недостаточно для решения проблемы.
Тимоти Шилдс

Ответы:

720

Оказывается, из четырех возможных перестановок, включающих или исключающих конечные или ведущие косые черты на BaseAddressи относительный URI, передаваемый вGetAsync методу - или любому другому методу HttpClient- работает только одна перестановка. Вы должны поместить косую черту в конце BaseAddress, и вы не должны помещать косую черту в начало вашего относительного URI, как в следующем примере.

using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
    client.BaseAddress = new Uri("http://something.com/api/");
    var response = await client.GetAsync("resource/7");
}

Несмотря на то, что я ответил на свой собственный вопрос, я решил внести здесь свое решение, поскольку, опять же, это недружественное поведение недокументировано. Мы с коллегой провели большую часть дня, пытаясь решить проблему, которая в конечном итоге была вызвана этой странностью HttpClient.

Тимоти Шилдс
источник
4
Спасибо. Это решило проблему, с которой я боролся в течение большей части двух дней, между переключением на Azure, обратно на IIS и обратно на IIS Express, который наиболее грубо игнорирует неуместные или дополнительные косые черты. Однажды установленный в моем базовом классе RestClient, он был почти невидим и не привлек к себе никакого внимания, и я никогда не видел полный URL на моих контрольных точках и т. Д.
ProfK
43
Я могу подтвердить, что эта странность (и это исправление) все еще актуальны в .NET Core. Спасибо за то, что уменьшила мою прическу, Тимоти.
Нейт Барбеттини,
8
Это потому, что без косой черты, когда он создает запросы, он отбрасывает последнюю часть. Таким образом, это поражает что- то.com/ resource/7 . Если вы устанавливаете базовый адрес как что-то / com (не имеет значения, с косой чертой или без нее), также не имеет значения, ставите ли вы косую черту в начале api / resource / 7. Без косой черты последняя часть базового адреса обрабатывается как файл и удаляется при составлении запроса.
Петр Перак
12
Это не касается непосредственно исходного вопроса, а связано. Согласно Mircosoft, экземпляр HttpClient () должен быть назначен статической переменной и использоваться повторно ( docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/… - Creating a new HttpClient instance per request can exhaust the available sockets). Поэтому вы должны рассмотреть возможность удаления с помощью ().
sanmcp
6
Просто ужасная реализация. Почему они не исправят это?
timmkrause
56

Ссылочное разрешение описывается в RFC 3986 Унифицированный идентификатор ресурса (URI): общий синтаксис . И это именно так, как это должно работать. Чтобы сохранить базовый путь URI, необходимо добавить косую черту в конце базового URI и удалить косую черту в начале относительного URI.

Если базовый URI содержит непустой путь, процедура слияния отбрасывает свою последнюю часть (после последней /). Соответствующий раздел :

5.2.3. Пути слияния

Вышеуказанный псевдокод относится к процедуре «слияния» для объединения ссылки относительного пути с путем базового URI. Это достигается следующим образом:

  • Если базовый URI имеет определенный компонент полномочий и пустой путь, тогда вернуть строку, состоящую из "/", соединенную с путем ссылки; в противном случае

  • вернуть строку, состоящую из компонента пути ссылки, добавленного ко всем, кроме последнего сегмента пути базового URI (т. е. исключая любые символы после крайнего правого "/" в пути базового URI, или исключая весь путь базового URI, если он не содержит символов "/").

Если относительный URI начинается с косой черты, он называется относительным URI абсолютного пути. В этом случае процедура слияния игнорирует все базовые пути URI. Для получения дополнительной информации проверьте 5.2.2. Раздел « Преобразование ссылок ».

Леонид Васильев
источник
4
Хорошо, но клиентские библиотеки, такие как HttpClient, должны защищать нас от эзотерических деталей реализации, подобных этой.
Джейми Ид
Замечательно, когда ответы охватывают причины ПОЧЕМУ что-то работает таким образом, с соответствующими ссылками и цитатами. Даже если это не поможет решить вопрос напрямую, это очень поможет.
Раст
-1

Столкнулся с проблемой с HTTPClient, даже с предложениями все еще не мог заставить его аутентифицироваться. Оказывается, мне нужен конечный «/» в моем относительном пути.

т.е.

var result = await _client.GetStringAsync(_awxUrl + "api/v2/inventories/?name=" + inventoryName);
var result = await _client.PostAsJsonAsync(_awxUrl + "api/v2/job_templates/" + templateId+"/launch/" , new {
                inventory = inventoryId
            });
Тони
источник
-6

В качестве альтернативы - не использовать BaseAddressвообще. Поместите весь URL в GetAsync()

userSteve
источник
33
Не отвечает на вопрос вообще.
Арчибальд
7
BaseAddress уменьшает шум. В любом случае, на мой взгляд. :)
MetalMikester
2
Я должен не согласиться с отрицательными комментариями. Я потратил 2 дня, пытаясь выяснить, почему мои вызовы HttpClient работают на моем компьютере разработчика, но не работают на сервере. Как ни странно, Powershell работает, а .net - нет. Я использовал .SendAsyc. Тогда я обнаружил, что .GetAsyc работал. Это привело меня по другой дороге и в конечном итоге сюда. Добавление или удаление / между базовым адресом и относительным URL ничего не сделало. По-прежнему получено 404 ошибки ... однако, когда я не установил базовый адрес и не поместил весь путь в относительный ... это сработало! Опять же, это с .SendAsync, но было 2 дня, которые я никогда не вернусь!
da_jokker