¿Por qué no funciona HttpClient BaseAddress?

299

Considere el siguiente código, donde BaseAddressdefine una ruta de URI parcial.

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

Espero que esto realice una GETsolicitud a http://something.com/api/resource/7. Pero no lo hace.

Después de buscar un poco, encuentro esta pregunta y respuesta: HttpClient con BaseAddress . La sugerencia es colocar /al final de la 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");
}

Aún no funciona. Aquí está la documentación: HttpClient.BaseAddress ¿Qué está pasando aquí?

Timothy Shields
fuente
Posible duplicado de HttpClient con BaseAddress
George Lanetz
@ ГеоргийЛанец El duplicado inverso ya se ha propuesto. Escribí esta pregunta específicamente porque esa otra pregunta no fue escrita de una manera que fuera muy reconocible por personas con el mismo problema, y ​​escribí la respuesta aquí porque la respuesta allá dejó un punto importante.
Timothy Shields
pero esta pregunta se hace más tarde
George Lanetz
2
@ ГеоргийЛанец Así no es como funciona. Por lo general, la pregunta más "canónica" es la que hace que los duplicados la apunten. Esa otra pregunta era sobre un solo problema que el usuario tenía en lugar de leer como un FAQ.
Timothy Shields
2
@ ГеоргийЛанец Observe también que hago referencia a esa otra pregunta en esta pregunta, y explico por qué la otra pregunta y respuesta son insuficientes para resolver el problema.
Timothy Shields

Respuestas:

719

Resulta que, de las cuatro posibles permutaciones de incluir o excluir las barras inclinadas hacia adelante o hacia atrás en el BaseAddressURI relativo pasado al GetAsyncmétodo, o cualquier otro método de HttpClient, solo funciona una permutación. Usted debe colocar una barra al final de la BaseAddress, y usted no debe colocar una barra al comienzo de su URI relativo, como en el siguiente ejemplo.

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

Aunque respondí mi propia pregunta, pensé que contribuiría con la solución aquí ya que, nuevamente, este comportamiento hostil no está documentado. Mi colega y yo pasamos la mayor parte del día tratando de solucionar un problema que finalmente fue causado por esta rareza de HttpClient.

Timothy Shields
fuente
44
Gracias. Eso resolvió un problema con el que he estado luchando durante la mayoría de los dos días, entre cambiar a Azure, volver a IIS y volver a IIS Express, que ignora groseramente las barras inclinadas extra o extraviadas. Una vez establecido en la clase base de mi RestClient, era casi invisible y no recibió ninguna atención, y nunca vi la url completa en mis puntos de interrupción, etc.
ProfK
43
Puedo confirmar que esta rareza (y esta corrección) sigue siendo relevante en .NET Core. Gracias por reducir mi cabello Timothy.
Nate Barbettini
8
Esto se debe a que sin la barra inclinada final cuando genera solicitudes, deja caer la última parte. Entonces golpea algo.com/resource/7 . Si configura la dirección base como algo / com (no importa si con o sin barra inclinada), tampoco importa si coloca la barra diagonal al comienzo de api / resource / 7. Sin la barra inclinada final, la última parte de la dirección base se trata como un archivo y se descarta cuando se solicita un bulding.
Piotr Perak
12
Esto no aborda directamente la pregunta original sino que está relacionada. Según Mircosoft, una instancia de HttpClient () debe asignarse a una variable estática y reutilizarse ( docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/… - Creating a new HttpClient instance per request can exhaust the available sockets). Por lo tanto, debe considerar eliminar Using ().
sanmcp
66
Solo una implementación horrible. ¿Por qué no arreglan esto?
timmkrause
55

La resolución de referencia se describe mediante el identificador uniforme de recursos (URI) RFC 3986: sintaxis genérica . Y así es exactamente como se supone que funciona. Para preservar la ruta del URI base, debe agregar una barra diagonal al final del URI base y eliminar la barra diagonal al comienzo del URI relativo.

Si el URI base contiene una ruta no vacía, el procedimiento de fusión descarta su última parte (después de la última /). Sección relevante :

5.2.3. Combinar caminos

El pseudocódigo anterior se refiere a una rutina de "fusión" para fusionar una referencia de ruta relativa con la ruta del URI base. Esto se logra de la siguiente manera:

  • Si el URI base tiene un componente de autoridad definido y una ruta vacía, entonces devuelve una cadena que consiste en "/" concatenado con la ruta de la referencia; de otra manera

  • devuelve una cadena que consiste en el componente de ruta de la referencia adjunta a todos menos el último segmento de la ruta del URI base (es decir, excluye cualquier carácter después del "/" más a la derecha en la ruta del URI base, o excluye la ruta del URI base completa si no contiene ningún carácter "/").

Si el URI relativo comienza con una barra oblicua, se llama URI relativo de ruta absoluta. En este caso, el procedimiento de fusión ignora toda la ruta base de URI. Para más información verifique 5.2.2. Transformar la sección de referencias .

Leonid Vasilev
fuente
3
Bien, pero se supone que las bibliotecas de clientes como HttpClient nos protegen de detalles de implementación esotéricos como este.
Jamie Ide
-1

Se encontró con un problema con el HTTPClient, incluso con las sugerencias todavía no se pudo autenticar. Resulta que necesitaba un '/' final en mi ruta relativa.

es decir

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
fuente
-6

Alternativamente, no lo use BaseAddressen absoluto. Ponga la URL completa en GetAsync()

userSteve
fuente
33
No responde la pregunta en absoluto.
Archibald
77
BaseAddress reduce el ruido. A mis ojos de todos modos. :)
MetalMikester
2
Tendré que estar en desacuerdo con los comentarios negativos. He pasado 2 días tratando de descubrir por qué mis llamadas HttpClient funcionan en mi PC Dev pero se rompen en el servidor. Curiosamente Powershell funciona pero .net no. Estaba usando .SendAsyc. Entonces descubrí que .GetAsyc funcionó. Esto me llevó por un camino diferente y, finalmente, aquí. Agregar o quitar / entre la Dirección base y la URL relativa no hizo nada. Todavía recibí errores 404 ... sin embargo, cuando no configuré la Dirección base y puse toda la ruta en Relativo ... ¡funcionó! De nuevo, esto es con .SendAsync pero hubo 2 días que nunca volveré.
da_jokker