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 начинает долго думать после пару соединений

.NET Framework Test

Задаю ему ServicePointManager.DefaultConnectionLimit = 15; и вроде это решает частично проблему, результат такой:

.NET Framework Test (fix)

Видно, что соединение рвется и только в редких случаях видны OK статусы.

Далее запускаю аналогичный тест на .NET Core, без всяких установок макс подключений и прочего и вижу следующее:

.NET Core Test

Видно, что все запросы успешно отработали, сервер успешно отдал данные и все как надо.

Вопрос: Почему такой разный результат и как добиться такой же работы на .NET Framework приложении? Почему он вообще рвет соединение?

Ссылка имеет свой срок жизни, в предыдущем вопросе можете найти ссылку на API, который дает всегда свежую ссылку на CDN.


Только я обрадовался, что все решилось банальным using и проблема не в этом, как стоило мне взять ссылку на мой основной проект и опять, "Сервер закрыл соединение". Залил для теста установщик 7zip, новая ссылка для тестов:

https://appcenter-filemanagement-distrib5ede6f06e.azureedge.net/c8be8012-cafe-48d4-b5e6-aab361374707/7z2000-x64.zip?sv=2019-02-02&sr=c&sig=LEGHVRXghiFi8yt4Xx%2BixNd%2BmKSbrWfcTgFc2M8fLzo%3D&se=2020-06-27T18%3A31%3A08Z&sp=r


Ответы (2 шт):

Автор решения: Aziz Umarov

Ответ очевиден. Вы ожидаете что ваш, "якобы" один и тот же код будет работать на разных 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 одновременных запросов.

→ Ссылка
Автор решения: MSDN.WhiteKnight

Поведение в .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 - нужно использовать протокол по умолчанию и подправить настройки реестра, чтобы использовался как можно более новый протокол для данной ОС.

→ Ссылка