HttpClient дает разный результат при использование .net и .net core
Предыстория всему этому тут.
Уже какой день пытаюсь решить проблему с подключению к CDN от Microsoft, веду даже переписку с ними, но результата нет. Сейчас попробовал сделать текст подключения не на .NET Framework, а на .NET Core немного удивился.
Есть два консольных приложения, у них одинаковый код:
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
private readonly static HttpClient httpClient = new HttpClient();
static async Task Main(string[] args)
{
var link = "https://appcenter-filemanagement-distrib5ede6f06e.azureedge.net/a35db5eb-bf67-48be-86e9-ae7e90bb87d1/v01.zip?sv=2019-02-02&sr=c&sig=iqORygcUNXADxT8Ev6%2BrWNlPiEn%2BSakQoZM1dkgWl%2FM%3D&se=2020-06-27T09%3A49%3A04Z&sp=r";
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Test [{i}]");
foreach (SecurityProtocolType protocolType in Enum.GetValues(typeof(SecurityProtocolType)))
{
Console.Write($"[{protocolType}] ");
try
{
ServicePointManager.SecurityProtocol = protocolType;
var resp = await httpClient.GetAsync(link, HttpCompletionOption.ResponseHeadersRead);
Console.WriteLine(resp.StatusCode);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
}
await Task.Delay(5000);
Console.WriteLine();
}
Console.WriteLine(true);
}
}
}
Отличия только в namespace, ну и в том, что один проект - .NET Core, а другой .NET Framework.
Запускаю оба проекта и вижу следующую картину:
.NET Framework начинает долго думать после пару соединений
Задаю ему ServicePointManager.DefaultConnectionLimit = 15; и вроде это решает частично проблему, результат такой:
Видно, что соединение рвется и только в редких случаях видны OK статусы.
Далее запускаю аналогичный тест на .NET Core, без всяких установок макс подключений и прочего и вижу следующее:
Видно, что все запросы успешно отработали, сервер успешно отдал данные и все как надо.
Вопрос: Почему такой разный результат и как добиться такой же работы на .NET Framework приложении? Почему он вообще рвет соединение?
Ссылка имеет свой срок жизни, в предыдущем вопросе можете найти ссылку на API, который дает всегда свежую ссылку на CDN.
Только я обрадовался, что все решилось банальным using и проблема не в этом, как стоило мне взять ссылку на мой основной проект и опять, "Сервер закрыл соединение". Залил для теста установщик 7zip, новая ссылка для тестов:
Ответы (2 шт):
Ответ очевиден. Вы ожидаете что ваш, "якобы" один и тот же код будет работать на разных framework одинаково. .net core <> .net framework это разные фреймворки от одного производителя. это как купил картошку у фермера, а она разная (вам кото-то сказал что она одинаковая?)
Если хотите по подробнее то посмотите Подводные камни HttpClient в .NET
Вторая проблема HttpClient — неочевидный лимит одновременных соединений с сервером. Предположим, вы используете привычный вам .NET Framework 4.7, с помощью которого разрабатываете высоконагруженный сервис, где есть обращения к другим сервисам по HTTP. Потенциальная проблема с утечкой соединений учтена, поэтому для всех запросов используется один и тот же экземпляр HttpClient. Что может быть не так?
static void Main(string[] args)
{
var client = new HttpClient();
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
{
tasks.Add(SendRequest(client, "http://slowwly.robertomurray.co.uk/delay/5000/url/https://habr.com"));
}
Task.WaitAll(tasks.ToArray());
}
private static async Task SendRequest(HttpClient client, string url)
{
var response = await client.GetAsync(url);
Console.WriteLine($"Received response {response.StatusCode} from {url}");
}
Как несложно заметить после выполнения приведенного выше кода — через каждые 5 секунд приходит всего по 2 ответа, хотя было создано 10 одновременных запросов.
Поведение в .NET Core в данном случае отличается из-за того, что в нем установка свойства ServicePointManager.SecurityProtocol вообще не оказывает влияния на HttpClient (оно влияет только на HttpWebRequest, а HttpClient использует другие компоненты - в последних версиях это SocketsHttpHandler). К сожалению, это официально не документировано, но вот сообщение от разработчиков NCL на Github: https://github.com/dotnet/runtime/issues/26048
In .NET Core, ServicePointManager affects only HttpWebRequest. It does not affect HttpClient.
В случае установки Ssl3 ваш тест падает с ошибкой еще на этапе проверки допустимых протоколов при установке свойства - см. исходники ServicePointManager.ValidateSecurityProtocol:
private static void ValidateSecurityProtocol(SecurityProtocolType value)
{
const SecurityProtocolType Allowed =
#pragma warning disable CA5364 // Do Not Use Deprecated Security Protocols
SecurityProtocolType.Tls | SecurityProtocolType.Tls11 |
#pragma warning restore CA5364
SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
if ((value & ~Allowed) != 0)
{
throw new NotSupportedException(SR.net_securityprotocolnotsupported);
}
}
На дальнейшую работу HttpClient оно не влияет (используется SystemDefault), что и дает однотипный результат "ОК" на Windows 10 во всех тестах. На Win7 ошибки происходят из-за того, что протокол по умолчанию более старый и может не поддерживаться некоторыми серверами, или вызывать больше времени на согласование параметров подключения, создавая большую нагрузку на сервер. Задать протокол в .NET Core, теоретически, можно как-то так:
SocketsHttpHandler httpHandler = new SocketsHttpHandler();
httpHandler.SslOptions.EnabledSslProtocols = SslProtocols.Tls12;
HttpClient client = new HttpClient(httpHandler);
Но прошивать в коде версию протокола не рекомендуется, так как это создает потенциальную дыру в безопасности на будущее, когда в этом протоколе могут найти уязвимость. Правильное решение этой проблемы я уже описал в другом вопросе: Проблемы при получение доступа к AppCenter distribute - нужно использовать протокол по умолчанию и подправить настройки реестра, чтобы использовался как можно более новый протокол для данной ОС.


