Dlaczego HttpClient BaseAddress nie działa?

299

Rozważ następujący kod, w którym BaseAddressdefiniuje częściową ścieżkę 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");
}

Oczekuję, że spełni to GETżądanie http://something.com/api/resource/7. Ale tak nie jest.

Po kilku poszukiwaniach znajduję to pytanie i odpowiedź: HttpClient z BaseAddress . Sugeruje się, aby umieścić /na końcu 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");
}

To wciąż nie działa. Oto dokumentacja: HttpClient.BaseAddress Co się tutaj dzieje?

Tarcze Tymoteusza
źródło
Możliwy duplikat HttpClient z BaseAddress
George Lanetz
@ ГеоргийЛанец Odwrotny duplikat został już zaproponowany. Napisałem to pytanie konkretnie, ponieważ to drugie pytanie nie zostało napisane w sposób, który był bardzo łatwy do odkrycia przez osoby z tym samym problemem, i napisałem tutaj odpowiedź, ponieważ tamta odpowiedź pomijała ważny punkt.
Timothy Shields,
ale pytanie to zadaje się później
George Lanetz,
2
@ ГеоргийЛанец To nie tak działa. Zwykle najbardziej „kanonicznym” pytaniem jest to, które zwraca duplikaty wskazujące na to pytanie. To drugie pytanie dotyczyło jednego problemu, który miał użytkownik zamiast czytać jak FAQ.
Timothy Shields,
2
@ ГеоргийЛанец Również zauważam, że odnoszę się do tego drugiego pytania w tym pytaniu i wyjaśniam, dlaczego drugie pytanie i odpowiedź są niewystarczające do rozwiązania problemu.
Timothy Shields,

Odpowiedzi:

719

Okazuje się, że z czterech możliwych kombinacji włączenia lub wyłączenia ukośników końcowych lub wiodących do przodu na BaseAddresswzględnym URI przekazanym do GetAsyncmetody - lub innej metody HttpClient- działa tylko jedna permutacja. Państwo musi umieścić ukośnik na końcu BaseAddress, a nie musi umieścić ukośnik na początku swojej względnej URI, jak w poniższym przykładzie.

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

Mimo że odpowiedziałem na własne pytanie, pomyślałem, że przyczynię się do rozwiązania tego problemu, ponieważ to nieprzyjazne zachowanie jest nieudokumentowane. Mój kolega i ja spędziliśmy większość dnia, próbując rozwiązać problem, który został ostatecznie spowodowany tą dziwnością HttpClient.

Tarcze Tymoteusza
źródło
4
Dziękuję Ci. To rozwiązało problem, z którym zmagałem się przez większość dwóch dni, między przejściem na platformę Azure, powrót do usług IIS i powrót do usług IIS Express, który w najbardziej nieuprzejmy sposób ignoruje źle umieszczone lub dodatkowe ukośniki. Raz ustawiony w mojej klasie bazowej RestClient, był prawie niewidoczny i nie zwrócił na mnie uwagi, i nigdy nie widziałem pełnego
adresu
43
Mogę potwierdzić, że ta osobliwość (i ta poprawka) jest nadal istotna w .NET Core. Dzięki, że zmniejszyłeś moje pociągające włosy Tymoteusz.
Nate Barbettini
8
Dzieje się tak, ponieważ bez końcowego ukośnika, gdy buduje żądania, upuszcza ostatnią część. Więc uderza coś.com/ resource/7 . Jeśli ustawisz adres bazowy jako coś / com (nie ma znaczenia, czy z ukośnikiem końcowym, czy bez), nie ma również znaczenia, czy umieścisz ukośnik na początku api / resource / 7. Bez końcowego ukośnika ostatnia część adresu podstawowego jest traktowana jak plik i upuszczana przy żądaniu wypełniania.
Piotr Perak
12
Nie dotyczy to bezpośrednio pierwotnego pytania, ale jest powiązane. Według Mircosoft wystąpienie HttpClient () powinno być przypisane do zmiennej statycznej i ponownie użyte ( docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/… - Creating a new HttpClient instance per request can exhaust the available sockets). Więc powinieneś rozważyć usunięcie Using ().
sanmcp,
6
Po prostu okropne wdrożenie. Dlaczego tego nie naprawią?
timmkrause
55

Rozdzielczość odniesienia opisano w dokumencie RFC 3986 Uniform Resource Identifier (URI): Ogólna składnia . I tak właśnie powinno działać. Aby zachować podstawową ścieżkę URI, musisz dodać ukośnik na końcu podstawowego identyfikatora URI i usunąć ukośnik na początku względnego identyfikatora URI.

Jeśli podstawowy identyfikator URI zawiera niepustą ścieżkę, procedura scalania odrzuca jej ostatnią część (po ostatniej /). Odpowiednia sekcja :

5.2.3 Scal ścieżki

Powyższy pseudokod odnosi się do procedury „scalania” służącej do łączenia odniesienia ścieżki względnej ze ścieżką podstawowego URI. Odbywa się to w następujący sposób:

  • Jeśli podstawowy identyfikator URI ma zdefiniowany komponent uprawnień i pustą ścieżkę, zwróć ciąg składający się z „/” połączonego ze ścieżką odwołania; Inaczej

  • zwraca ciąg składający się ze składnika ścieżki odwołania dołączonego do wszystkich oprócz podstawowego segmentu ścieżki podstawowego identyfikatora URI (tj. wykluczając znaki po skrajnym prawym „/” w podstawowej ścieżce URI lub wykluczając całą podstawową ścieżkę URI, jeśli nie zawiera żadnych znaków „/”).

Jeśli względny identyfikator URI zaczyna się od ukośnika, nazywany jest względnym identyfikatorem URI ścieżki bezwzględnej. W takim przypadku procedura scalania zignoruje całą podstawową ścieżkę URI. Aby uzyskać więcej informacji, sprawdź 5.2.2. Przekształć sekcję Odniesienia .

Leonid Wasilew
źródło
3
Świetne, ale biblioteki klienta, takie jak HttpClient, powinny chronić nas przed ezoterycznymi szczegółami implementacyjnymi, takimi jak ta.
Jamie Ide
-1

Wystąpił problem z klientem HTTPClient, nawet jeśli sugestie wciąż nie mogły go uwierzytelnić. Okazuje się, że potrzebowałem trailing '/' na mojej względnej ścieżce.

to znaczy

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
            });
Tony
źródło
-6

Alternatywnie - nie używaj BaseAddressw ogóle. Umieść cały adres URL w GetAsync()

userSteve
źródło
33
W ogóle nie odpowiada na pytanie.
Archibald
7
BaseAddress redukuje hałas. W każdym razie na moje oczy. :)
MetalMikester
2
Będę musiał się nie zgodzić z negatywnymi komentarzami. Spędziłem 2 dni próbując dowiedzieć się, dlaczego moje połączenia HttpClient działają na moim komputerze deweloperskim, ale psują się na serwerze. Dziwnie Powershell działa, ale nie działa .net. Korzystałem z .SendAsyc. Potem odkryłem, że działa. GetAsyc. To doprowadziło mnie inną drogą i ostatecznie tutaj. Dodanie lub usunięcie / pomiędzy adresem podstawowym a względnym adresem URL nic nie dało. Nadal dostaję błędy 404 .... ale kiedy nie ustawiłem adresu bazowego i nie umieściłem całej ścieżki w Względnym ... zadziałało! Ponownie, to jest z .SendAsync, ale były 2 dni, których nigdy nie odzyskam!
da_jokker