Одиночний екземпляр HttpClient з різними заголовками автентифікації


81

Беручи до уваги, що .net HttpClient був розроблений з урахуванням повторного використання і призначений для довгого життя, а про витоки пам'яті повідомлялося в короткочасних випадках. Які напрямні лінії існують, де ви хочете робити спокійні дзвінки до даної кінцевої точки, використовуючи різні маркери носія (або будь-який заголовок авторизації) під час виклику кінцевої точки для кількох користувачів?

private void CallEndpoint(string resourceId, string bearerToken) {
  httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("bearer", bearerToken);
  var response = await httpClient.GetAsync($"resource/{resourceid}");
}

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

Який рекомендований підхід до створення та розпорядження HttpClients для однієї кінцевої точки не викликає суперечок із використанням блокувань та підтримання веб-додатків без громадянства (моя поточна практика полягає у створенні одного клієнта для кінцевої точки)?


Життєвий цикл

Незважаючи на те, що HttpClient побічно реалізує інтерфейс IDisposable, рекомендоване використання HttpClient не полягає в утилізації ним після кожного запиту. Об'єкт HttpClient призначений для життя стільки часу, скільки вашій програмі потрібно робити HTTP-запити. Наявність об’єкта в декількох запитах дозволяє місце для встановлення DefaultRequestHeaders і запобігає необхідності дотримуватись таких речей, як CredentialCache та CookieContainer, на кожному запиті, як це було потрібно з HttpWebRequest.


Ви говорите про відносно невелику кількість різних заголовків або партій авторизації, таких як унікальні для кожного користувача?
Тодд Меньє,

@ToddMenier - Це був би унікальний заголовок для кожного користувача. Можливо, користувачі oauth маркер. Думаю, Скотт Ханнен поставив мене на правильний шлях. Схоже, деякі методи розширення будуть в порядку.
Бронумскі

Привіт @Bronumski, ти можеш поділитися способом вирішення цього? У мене одна і та ж проблема з кількома потоками, що додають однаковий заголовок, але з різним вмістом.
Luis Mejia

@LuisMejia - Я оновив шотландську відповідь із прикладами того, як я зробив GET та POST. Той самий принцип застосовується до будь-якого іншого методу, який ви хочете застосувати. Метод розширення включає дію, яка дозволяє маніпулювати запитом HttpRequest перед його надсиланням.
Бронумскі

@Bronumski Дякую за відповідь ... здається, я піду подібним чином, використовуючи sendasync та передаючи запит як параметр із користувацькими заголовками.
Luis Mejia

Відповіді:


81

Якщо ваші заголовки зазвичай будуть однаковими, тоді ви можете встановити DefaultRequestHeaders . Але вам не потрібно використовувати цю властивість, щоб вказати заголовки. Як ви визначили, це просто не буде працювати, якщо ви збираєтеся мати кілька потоків, використовуючи один і той же клієнт. Зміни заголовків за замовчуванням, зроблені в одному потоці, впливатимуть на запити, надіслані в інші потоки.

Хоча ви можете встановити на клієнті заголовки за замовчуванням і застосовувати їх до кожного запиту, заголовки насправді є властивостями запиту. Отже, коли заголовки специфічні для запиту, ви просто додаєте їх до запиту.

request.Headers.Authorization = new AuthenticationHeaderValue("bearer", bearerToken);

Це означає, що ви не можете використовувати спрощені методи, які не передбачають створення файлу HttpRequest. Вам потрібно буде використовувати

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)

задокументовані тут .


Деякі вважають корисним використовувати методи розширення для ізоляції коду, який оновлює заголовки, від решти методів.

Приклад методів GET та POST, здійснених за допомогою методу розширення, що дозволяє вам маніпулювати заголовком запиту та багатьма іншими, HttpRequestMessageперш ніж він буде надісланий:

public static Task<HttpResponseMessage> GetAsync
    (this HttpClient httpClient, string uri, Action<HttpRequestMessage> preAction)
{
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri);

    preAction(httpRequestMessage);

    return httpClient.SendAsync(httpRequestMessage);
}

public static Task<HttpResponseMessage> PostAsJsonAsync<T>
    (this HttpClient httpClient, string uri, T value, Action<HttpRequestMessage> preAction)
{
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, uri)
    {
        Content = new ObjectContent<T>
            (value, new JsonMediaTypeFormatter(), (MediaTypeHeaderValue)null)
    };
    preAction(httpRequestMessage);

    return httpClient.SendAsync(httpRequestMessage);
}

Потім їх можна використовувати наступним чином:

var response = await httpClient.GetAsync("token",
    x => x.Headers.Authorization = new AuthenticationHeaderValue("basic", clientSecret));

Чудове рішення. Щойно реалізовано. Додано ярлик нижче шаблону під загальнодоступною статичною задачею <HttpResponseMessage> GetAsync (цей HttpClient httpClient, рядок uri, маркер рядка, CancellationToken cancellationToken) {return httpClient.GetAsync (uri, x => SetToken (x, token), cancellationToken); } void SetToken (HttpRequestMessage request, string token, string type = "Bearer")
Osa E

2
А що HttpClientHandler.Proxy, HttpClientHandler.CookieContainerі інші властивості , HttpClientHandlerякі не можуть бути встановлені в HttpRequestMessage? (чи можуть?)
Девід С.

@DavidS. ти знайшов рішення для проксі?
Джей Шах,

1
Для проксі, cookiecontainer тощо, де у вас є конкретні потреби в окремих запитах, я вважаю, що рекомендується використовувати іменовані або введені клієнти, які мають конфігурацію, яку ви бажаєте для цих конкретних запитів, а потім ви можете використовувати неназваний клієнт для всіх інших. docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests
конопля

@hemp - Я згоден, але ця документація стосується Microsoft.Extensions.DependencyInjection( IServiceCollection), або, принаймні, менш зрозуміло, як би ми використовували її з іншим контейнером IoC чи ні.
Скотт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.