Какова цель HttpClient.BaseAddress и почему я не могу изменить его после первого запроса

Таким образом, большинство из нас, вероятно, читали, что мы должны повторно использовать экземпляры HttpClient Вместо того, чтобы использовать using и создание новых. Это означает, что я могу просто создать один экземпляр HttpClient в моей программе и звоните GetAsync используя полную строку URI для запроса. Это приводит меня к BaseAddress собственностью HttpClient, Рассмотрим следующий код:

HttpClient microsoftClient = new HttpClient() { BaseAddress = new Uri("https://www.microsoft.com/") };
HttpClient stackruClient = new HttpClient() { BaseAddress = new Uri("https://stackru.com/") };

var response = microsoftClient.GetAsync("about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the microsoft client");

response = microsoftClient.GetAsync("trademarks").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/trademarks from the microsoft client");

response = stackruClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackru.com/company/about from the stackru client");

response = stackruClient.GetAsync("https://www.microsoft.com/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the stackru client");

microsoftClient.BaseAddress = new Uri("https://stackru.com");
response = microsoftClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackru.com/company/about from the microsoft client, after changing the BaseAddress");

Вплоть до последнего блока этот код работает нормально, даже при использовании клиента со стековым потоком BaseAddress чтобы получить доступ к Microsoft. Однако этот код бросает InvalidOperationException в начале последнего блока, при переназначении BaseAddressзаявляя

'This instance has already started one or more requests. Properties can only be modified before sending the first request.'

Это приводит меня к следующим вопросам:

  1. В чем выгода использования BaseAddress совсем? Я мог бы всегда использовать полный адрес в моем GetAsync вызов. Это просто для удобства / производительности, когда нет необходимости создавать полную строку запроса? Я предполагал, что это создаст только один ServicePoint внутренне как описано в первом параграфе этого сообщения в блоге (или что-то подобное, поскольку сообщение довольно старое).
  2. Что происходит внутри, что мы не можем изменить свойство HttpClient, особенно BaseAddressпосле отправки первого запроса? Это кажется довольно неудобным, если использование этого свойства действительно приносит выгоды.

2 ответа

Решение

Для (1) распространенным вариантом использования будет клиент, который взаимодействует только с одним сервером. Может быть, это внутренний API, который этот клиент был создан для использования. Точные данные будут сохранены в файле конфигурации, который клиент читает во время запуска.

Мы могли бы засорять наш код прямым доступом к конфигурации или вводить строку, считанную из конфигурации, в каждое место, где требуется создать полный URL-адрес. Или мы могли бы просто настроить BaseAddress из HttpClient мы помещаем в наш контейнер Dependency Injection и просто позволяем потребителям вводить этот объект. Это для меня несколько ожидаемый вариант использования.

Для (2), я не думаю, что есть техническое ограничение. Я думаю, что это больше, чтобы спасти людей от самих себя. С настройкой BaseAddress и вызывая фактический запрос, чтобы выйти через, например, GetAsync это отдельные действия, было бы небезопасно, если бы две отдельные части кода делали это одновременно - вы могли бы легко получить гонки. Поэтому проще рассуждать о многопоточных программах, которые могут совместно использовать один экземпляр HttpClient если такие гонки не разрешены в первую очередь.

2 цели:

  1. Удобство. Если вы вызываете множество конечных точек с одного хоста и управляете сегментами базового адреса и конечной точки как отдельными строками (очень часто), это поможет вам избежать уродливой конкатенации строк при каждом вызове.

  2. Поощрение лучших практик. Хотя звонить через GetAsyncи т. д. является потокобезопасным, HttpClient имеет несколько свойств в дополнение к BaseAddress, такие как DefaultRequestHeadersЭто не так. Как правило, вы хотите, чтобы они были одинаковыми для вызовов на один и тот же хост, но не для вызовов на разные. По этой причине HttpClientэкземпляр для каждого вызываемого хоста на самом деле очень хорошая практика. Если вы не вызываете тысячи разных хостов, вам не нужно беспокоиться о печально известной проблеме исчерпания сокетов. (И даже если вы используете одноэлементный пакет, базовый сетевой стек в любом случае должен будет открыть другой сокет для каждого хоста.)

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

Другие вопросы по тегам