Posted on 13. December 2022

.NET 7 Покращення мережевих можливостей

Read this article in your language IT | EN | DE | ES


.NET 7 Покращення мережевих можливостей

Оскільки нещодавно вийшов .NET 7, хотілося б познайомити вас з деякими цікавими змінами та доповненнями, зробленими в мережевому просторі. У цій статті йтиметься про зміни .NET 7 у просторі HTTP, нові API-інтерфейси QUIC, мережеву безпеку та WebSockets.

HTTP

Удосконалена обробка невдалих спроб з'єднання

У версіях до .NET 6, якщо в пулі з'єднань не було доступного з'єднання, новий HTTP-запит завжди створював нову спробу з'єднання і чекав на неї (якщо це дозволяли налаштування обробника, наприклад, MaxConnectionsPerServer для HTTP/1.1 або EnableMultipleHttp2Connections для HTTP/2). Недоліком цього сценарію є те, що якщо встановлення цього з'єднання займе деякий час, а тим часом стане доступним інше з'єднання, цей запит продовжить очікувати на з'єднання, яке він створив, що призведе до збільшення затримки. У .NET 6.0 було замінено це на обробку запитів на тому з'єднанні, яке стане доступним першим, незалежно від того, чи це щойно створене з'єднання, чи те, яке стало готовим до обробки запиту в цей час. Нове з'єднання все одно створюється (з урахуванням обмежень), і якщо воно не було використане ініціатором запиту, воно об'єднується в пул, щоб наступні запити могли його використати.

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

У .NET 7.0 було впроваджено наступні зміни для усунення цих проблем:

1. Невдала спроба з'єднання може призвести до невдачі лише того запиту, який її ініціював, але ніколи не може призвести до невдачі запиту, який не пов'язаний з нею. Якщо початковий запит було оброблено до того моменту, коли з'єднання було розірвано, то помилка з'єднання ігнорується (dotnet/runtime#62935).

 

2. Якщо запит ініціює нове з'єднання, але потім обробляється іншим з'єднанням з пулу, нова спроба з'єднання буде автоматично завершена через короткий проміжок часу, незалежно від ConnectTimeout. Завдяки цій зміні, зупинені з'єднання не будуть зупиняти непов'язані запити (dotnet/runtime#71785). Зауважте, що невдачі таких "відкинутих" спроб з'єднання відбуватимуться у фоновому режимі й ніколи не стануть відомі користувачеві, єдиний спосіб їх побачити - увімкнути телеметрію.

HttpHeaders зчитують безпеку потоку

Колекції HttpHeaders ніколи не були безпечними для потоків. Доступ до заголовка може призвести до лінивого розбору його значення, що може спричинити модифікацію базових структур даних.

До .NET 6 одночасне читання з колекції у більшості випадків було безпечним для потоків.

Починаючи з .NET 6, розбір заголовків став менше блокуватися, оскільки в ньому зникла внутрішня потреба. Через цю зміну з'явилося багато прикладів помилкового одночасного доступу користувачів до заголовків, наприклад, у gRPC (dotnet/runtime#55898),, NewRelic (newrelic/newrelic-dotnet-agent#803) або навіть у самому HttpClient (dotnet/runtime#65379). Порушення безпеки потоків у .NET 6 може призвести до дублювання/спотворення значень заголовків або генерування різних винятків під час зчитування/доступу до заголовків.

.NET 7 робить поведінку заголовків більш інтуїтивно зрозумілою. Колекція HttpHeaders тепер відповідає гарантіям потокової безпеки Словника:

Колекція може підтримувати декілька читачів одночасно, якщо її не модифіковано. У рідкісних випадках, коли перерахування конкурує з доступом на запис, колекція повинна бути заблокована протягом усього перерахування. Щоб дозволити доступ до колекції декільком потокам для читання і запису, потрібно реалізувати власну синхронізацію.

Цього було досягнуто наступними змінами:

"Читання з перевіркою" недійсного значення не призводить до видалення недійсного значення - dotnet/runtime#67833 

 

Одночасне читання є безпечним для потоків - dotnet/runtime#68115.

Виявлення помилок протоколів HTTP/2 і HTTP/3

Протоколи HTTP/2 і HTTP/3 визначають коди помилок на рівні протоколу у RFC 7540, розділ 7 і RFC 9114, розділ 8.1, наприклад, REFUSED_STREAM (0x7) в HTTP/2 або H3_EXCESSIVE_LOAD (0x0107) в HTTP/3. На відміну від кодів HTTP-статусу, це низькорівнева інформація про помилки, яка не є важливою для більшості користувачів HttpClient, але вона допомагає в розширених сценаріях HTTP/2 або HTTP/3, зокрема grpc-dotnet, де розпізнавання помилок протоколу є життєво важливим для реалізації повторних спроб клієнта.

Було створено нове виключення HttpProtocolException для зберігання коду помилки на рівні протоколу у його властивості ErrorCode.

При безпосередньому виклику HttpClient, HttpProtocolException може бути внутрішнім виключенням HttpRequestException:

При роботі з відповіддю потоку HttpContent він передається безпосередньо:

HTTP/3

Підтримка HTTP/3 у HttpClient вже була реалізована у попередній версії .NET, тому основні зусилля у цій сфері було зосереджено на базовому System.Net.Quic. Попри це, було внесено декілька виправлень та змін у .NET 7.

 

Найважливішою зміною є те, що HTTP/3 тепер увімкнено за замовчуванням (dotnet/runtime#73153). Це не означає, що відтепер всі HTTP-запити будуть віддавати перевагу HTTP/3, але в певних випадках вони можуть оновитися до нього. Щоб це сталося, запит повинен вибрати оновлення версії за допомогою HttpRequestMessage.VersionPolicy, встановленого в RequestVersionOrHigher. Потім, якщо сервер оголосить HTTP/3 повноваження в заголовку Alt-Svc, HttpClient буде використовувати його для подальших запитів, див. RFC 9114, розділ 3.1.1.

Ось декілька інших цікавих змін:

1. Телеметрія HTTP була розширена на HTTP/3 - dotnet/runtime#40896.

2. Покращено деталі винятків у випадку неможливості встановлення з'єднання QUIC - dotnet/runtime#70949.

 

3. Виправлено правильне використання заголовка Host для ідентифікації імені сервера (SNI) - dotnet/runtime#57169.

QUIC

QUIC - це новий протокол транспортного рівня. Нещодавно його було стандартизовано у RFC 9000. Він використовує UDP як базовий протокол і є безпечним за своєю суттю, оскільки вимагає використання TLS 1.3, див. RFC 9001. Ще одна цікава відмінність від відомих транспортних протоколів, таких як TCP і UDP, полягає в тому, що він має вбудоване потокове мультиплексування на транспортному рівні. Це дозволяє мати декілька паралельних, незалежних потоків даних, які не впливають один на одного.

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

 

Протокол має багато переваг над TCP з TLS. Наприклад, швидше встановлення з'єднання, оскільки він не вимагає стільки обходів, як TCP з TLS. Або уникнення проблеми блокування головної лінії, коли один втрачений пакет не блокує дані всіх інших потоків. З іншого боку, використання QUIC має свої недоліки. Оскільки це новий протокол, його впровадження все ще триває і є обмеженим. Крім того, трафік QUIC може навіть блокуватися деякими мережевими компонентами.

QUIC в .NET

Реалізацію QUIC в .NET 5 представлено в бібліотеці System.Net.Quic. Однак до цього часу бібліотека була суто внутрішньою і слугувала лише для власної реалізації HTTP/3. З виходом .NET 7 бібліотека стає загальнодоступною і доступні її API. Оскільки в цій версії API використовувалися лише HttpClient та Kestrel, було вирішено залишити їх у вигляді функції попереднього перегляду.

Це дає можливість доопрацювати API в наступному релізі, перш ніж він набуде остаточного вигляду.

 

З точки зору реалізації, System.Net.Quic залежить від MsQuic, нативної реалізації протоколу QUIC. В результаті, підтримка платформи System.Net.Quic і залежності успадковані від MsQuic і задокументовані в залежності від платформи HTTP/3. Коротко кажучи, бібліотека MsQuic постачається як частина .NET для Windows. Для Linux libmsquic необхідно встановити вручну за допомогою відповідного менеджера пакунків. Для інших платформ, як і раніше, можна зібрати MsQuic вручну, чи то для SChannel, чи то для OpenSSL, і використовувати її з System.Net.Quic.

Огляд API

System.Net.Quic містить три основні класи, які дозволяють використовувати протокол:

QuicListener – клас на стороні сервера для приймання вхідних з'єднань.

QuicConnection – QUIC з'єднання, що відповідає RFC 9000 Section 5.

QuicStream – потік QUIC, що відповідає RFC 9000 Section 2.

Але перед будь-яким використанням цих класів користувацький код повинен перевірити, чи підтримується QUIC, оскільки libmsquic може бути відсутнім, або TLS 1.3 може не підтримуватися. Для цього і QuicListener, і QuicConnection мають статичну властивість IsSupported:

 

Зауважте, що наразі обидві ці властивості синхронізовані й показуватимуть однакове значення, але це може змінитися у майбутньому. Тому рекомендується перевірити QuicListener.IsSupported для серверних сценаріїв і QuicConnection.IsSupported для клієнтських.

QuicListener

 

QuicListener являє собою клас на стороні сервера, який приймає вхідні з'єднання від клієнтів. Слухач створюється і запускається за допомогою статичного методу QuicListener.ListenAsync. Метод приймає екземпляр класу QuicListenerOptions з усіма налаштуваннями, необхідними для запуску слухача і приймання вхідних з'єднань. Після цього слухач готовий роздавати з'єднання через AcceptConnectionAsync. З'єднання, що повертаються цим методом, завжди повністю з'єднані, що означає, що рукостискання TLS завершено і з'єднання готове до використання. Нарешті, щоб припинити прослуховування і звільнити всі ресурси, необхідно викликати DisposeAsync.

 

Приклад використання QuicListener:

 

Більш детальну інформацію про те, як було розроблено цей клас, можна знайти у QuicListener API Proposal (dotnet/runtime#67560).

QuicConnection

QuicConnection - це клас, який використовується як для серверних, так і для клієнтських QUIC-з'єднань. З'єднання на стороні сервера створюються внутрішньо слухачем і роздаються через QuicListener.AcceptConnectionAsync. Клієнтські з'єднання повинні бути відкриті й підключені до сервера. Як і у випадку зі слухачем, існує статичний метод QuicConnection.ConnectAsync, який створює та встановлює з'єднання. Він приймає екземпляр класу QuicClientConnectionOptions, аналогічного класу QuicServerConnectionOptions. Після цього робота зі з'єднанням не відрізняється між клієнтом і сервером. Він може відкривати вихідні потоки й приймати вхідні. Він також надає властивості з інформацією про з'єднання, такі як LocalEndPoint, RemoteEndPoint або RemoteCertificate.

Після завершення роботи зі з'єднанням його потрібно закрити та скинути. Протокол QUIC вимагає використання коду програмного рівня для негайного закриття, див. RFC 9000, розділ 10.2. Для цього можна викликати CloseAsync з кодом програмного рівня або, якщо ні, DisposeAsync використає код, наданий у QuicConnectionOptions.DefaultCloseErrorCode. У будь-якому випадку, DisposeAsync має бути викликано наприкінці роботи зі з'єднанням, щоб повністю звільнити всі пов'язані з ним ресурси.

Приклад використання QuicConnection:

 

Більш детально про те, як був розроблений цей клас, можна прочитати в QuicConnection API Proposal (dotnet/runtime#68902).

QuicStream

 

QuicStream - це тип, який використовується для надсилання та отримання даних у протоколі QUIC. Він походить від звичайного потоку і може використовуватися як такий, але він також пропонує кілька особливостей, які є специфічними для протоколу QUIC. По-перше, потік QUIC може бути однонапрямний або двонапрямний, див. RFC 9000, розділ 2.1. Двонапрямний потік може надсилати та отримувати дані з обох сторін, тоді як однонапрямний потік може тільки писати зі сторони, що ініціює та читати зі сторони, що приймає. Кожен одноранговий комп'ютер може обмежити кількість одночасних потоків кожного типу, див. QuicConnectionOptions.MaxInboundBidirectionalStreams та QuicConnectionOptions.MaxInboundUnidirectionalStreams.

Ще однією особливістю потоку QUIC є можливість явно закрити сторону запису посеред роботи з потоком, див. перевантаження CompleteWrites або WriteAsync-system-boolean-system-threading-cancellationtoken)) з аргументом completeWrites. Закриття сторони запису дає змогу одноранговій системі знати, що дані більше не надходитимуть, проте вона може продовжувати надсилати (у випадку двонапрямного потоку). Це корисно в таких сценаріях, як обмін HTTP-запитами/відповідями, коли клієнт надсилає запит і закриває сторону запису, щоб повідомити серверу, що на цьому вміст запиту закінчився. Сервер все ще може відправити відповідь після цього, але знає, що більше ніяких даних від клієнта не надійде. У випадку помилкових ситуацій можна перервати потік як на стороні запису, так і на стороні читання, див. розділ Переривання. Поведінка окремих методів для кожного типу потоку підсумована у наступній таблиці (зауважте, що і клієнт, і сервер можуть відкривати й приймати потоки):

 

На додаток до цих методів, QuicStream пропонує дві спеціалізовані властивості для отримання сповіщень про закриття потоку для читання або запису: ReadsClosed та WritesClosed. Обидві властивості повертають завдання, яке завершується закриттям відповідної сторони потоку, незалежно від того, чи було воно успішне, чи перериване, в останньому випадку завдання буде містити відповідний виняток. Ці властивості корисні, коли користувацькому коду потрібно знати про закриття сторони потоку без виклику ReadAsync або WriteAsync.

 

Нарешті, коли робота з потоком завершена, його потрібно вилучити за допомогою DisposeAsync. Програма переконається, що сторона читання та/або запису - залежно від типу потоку - закрита. Якщо потік не було прочитано належним чином до кінця, програма видасть еквівалент Abort(QuicAbortDirection.Read). Однак, якщо потік не було закрито на стороні запису, він буде поступово закритий, як це було б у випадку з CompleteWrites. Причина такої різниці полягає в тому, щоб переконатися, що сценарії, які працюють зі звичайним потоком, поводяться очікувано і ведуть до успішного завершення. Розглянемо наступний приклад:

Приклад використання QuicStream у клієнтському сценарії:

І приклад використання QuicStream у серверному сценарії:

Більш детально про те, як був розроблений цей клас, можна прочитати в QuicStream API Proposal (dotnet/runtime#69675).

Безпека

Узгодження API

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

До .NET 7 автентифікація Windows була доступна у високорівневих API, таких як HttpClient (схеми автентифікації Negotiate і NTLM), SmtpClient (схеми автентифікації GSSAPI й  NTLM), NegotiateStream, ASP.NET Core і у клієнтських бібліотеках SQL Server. Хоча вона охоплює більшість сценаріїв для кінцевих користувачів, проте обмежує можливості авторів бібліотек. Інші бібліотеки, такі як клієнт Npgsql PostgreSQL, MailKit, клієнт Apache Kudu та інші, повинні були вдаватися до різних хитрощів, щоб реалізувати ті ж схеми автентифікації для низькорівневих протоколів, які не були побудовані на HTTP або інших доступних високорівневих будівельних блоках.

.NET 7 представляє новий API, що надає низькорівневі будівельні блоки для виконання обміну автентифікацією для вищезгаданих протоколів, див. dotnet/runtime#69920. Як і всі інші API в .NET, він побудований з урахуванням кросплатформної сумісності. На Linux, macOS, iOS та інших подібних платформах він використовує системну бібліотеку GSSAPI. На Windows - бібліотеку SSPI. Для платформ, де системна реалізація недоступна, таких як Android і tvOS, існує обмежена реалізація лише для клієнтів.

Як користуватися API

Щоб зрозуміти, як працює API автентифікації, почнімо з прикладу того, як виглядає сеанс автентифікації в такому високорівневому протоколі, як SMTP. Приклад взято з документації до протоколу Microsoft, яка пояснює його більш детально.

Аутентифікація починається з того, що клієнт генерує маркер виклику. Потім сервер генерує відповідь. Клієнт обробляє відповідь, і на сервер надсилається новий запит. Цей обмін запитами/відповідями може відбуватися кілька разів. Він завершується, коли одна зі сторін відхиляє автентифікацію або коли обидві сторони приймають автентифікацію. Формат маркерів визначається протоколами автентифікації Windows, а інкапсуляція є частиною специфікації протоколу високого рівня. У цьому прикладі протокол SMTP готує код 334, щоб повідомити клієнту, що сервер надав відповідь на автентифікацію, а код 235 вказує на успішну автентифікацію.

Основна частина нового API зосереджена навколо нового класу NegotiateAuthentication. Він використовується для визначення контексту для автентифікації на стороні клієнта або на стороні сервера. Існують різні варіанти визначення вимог для створення сеансу автентифікації, наприклад, вимога шифрування або визначення конкретного протоколу (Negotiate, Kerberos або NTLM), який має бути використаний. Після визначення параметрів автентифікація продовжується шляхом обміну автентифікаційними запитами/відповідями між клієнтом і сервером. Для цього використовується метод GetOutgoingBlob. Він може працювати як з байтовими діапазонами, так і з рядками, закодованими в base64.

Наступний код виконає частину автентифікації для поточного користувача на тій самій машині як для клієнта, так і для сервера:

Після встановлення сеансу автентифікації екземпляр NegotiateAuthentication можна використовувати для підписання/шифрування вихідних повідомлень і перевірки/дешифрування вхідних повідомлень. Це робиться за допомогою методів Wrap і Unwrap.

Варіанти перевірки сертифіката

Коли клієнт отримує сертифікат сервера, або навпаки, якщо запитується сертифікат клієнта, сертифікат перевіряється за допомогою X509Chain. Перевірка відбувається завжди, навіть якщо передбачено RemoteCertificateValidationCallback, і під час перевірки можуть бути завантажені додаткові сертифікати. Було піднято декілька питань, оскільки не було способу контролювати таку поведінку. Серед них були прохання повністю запобігти завантаженню сертифікатів, поставити тайм-аут на нього або надати власне сховище для отримання сертифікатів. Щоб вирішити цю групу проблем, було вирішено ввести нову властивість CertificateChainPolicy в SslClientAuthenticationOptions і SslServerAuthenticationOptions. Мета цієї властивості - перевизначити поведінку SslStream за замовчуванням при побудові ланцюжка під час операції AuthenticateAsClientAsync / AuthenticateAsServerAsync. За звичайних обставин X509ChainPolicy будується автоматично у фоновому режимі. Але якщо вказати цю нову властивість, вона матиме пріоритет і буде використовуватися замість неї, надаючи користувачеві повний контроль над процесом перевірки сертифіката.

Використання ланцюгової політики може виглядати так:

Більше інформації можна знайти в пропозиції API (dotnet/runtime#71191).

Продуктивність

Більшість покращень продуктивності мережі в .NET 7 описано в статті Стівена "Покращення продуктивності в .NET 7 - Мережа", але деякі з них варто згадати ще раз.

Резюме TLS

Встановлення нового TLS-з'єднання є досить затратною операцією, оскільки вона вимагає декількох кроків і декількох циклів в обидва боки. У сценаріях, де з'єднання з одним і тим же сервером створюється дуже часто, час, витрачений на рукостискання, буде збільшуватися. TLS пропонує функцію для зменшення цього часу, яка називається відновленням сеансу, див. RFC 5246, розділ 7.3 і RFC 8446, розділ 2.2. Коротко кажучи, під час рукостискання клієнт може відправити ідентифікатор раніше встановленої TLS-сесії, і якщо сервер погоджується, контекст безпеки відновлюється на основі кешованих даних з попереднього з'єднання. Незважаючи на те, що механізми відрізняються для різних версій TLS, кінцева мета однакова - заощадити час в обидва боки і процесорний час при відновленні з'єднання з раніше підключеним сервером. Ця функція автоматично надається SChannel в Windows, але в OpenSSL в Linux для її включення знадобилося внести кілька змін:

1. На стороні сервера (без статусу) - dotnet/runtime#57079 та dotnet/runtime#63030.

2. Клієнтська частина - dotnet/runtime#64369.

3. Керування розміром кешу - dotnet/runtime#69065.

Якщо кешування контексту TLS не є бажаним, його можна вимкнути у всьому процесі за допомогою змінної оточення "DOTNET_SYSTEM_NET_SECURITY_DISABLETLSRESUME" або через AppContext.SetSwitch "System.Net.Security.TlsCacheSize".

“Зшивання” OCSP

 

“Зшивання” онлайн-сертифікатів (Online Certificate Status Protocol, OCSP) - це механізм надання сервером підписаного і позначеного часом доказу (OCSP-відповідь) того, що надісланий сертифікат не було відкликано, див. RFC 6961. В результаті клієнту не потрібно звертатися до самого OCSP-сервера, що зменшує кількість запитів, необхідних для встановлення з'єднання, а також навантаження на OCSP-сервер. А оскільки відповідь OCSP повинна бути підписана центром сертифікації (ЦС), вона не може бути підроблена сервером, що надає сертифікат. Ця функція TLS не використовувалась до цього випуску, більш детальну інформацію дивіться на dotnet/runtime#33377.

Узгодженість між платформами

Відомо, що деякі функції, які надає .NET, доступні лише на певних платформах. Але з кожним випуском команди намагаються ще більше скоротити цей розрив. У .NET 7 було внесено кілька змін у сфері мережевої безпеки, щоб усунути цю нерівність:

1. Підтримка автентифікації після рукостискання в Linux для TLS 1.3 - dotnet/runtime#64268

2. Віддалений сертифікат тепер налаштовується на Windows у SslClientAuthenticationOptions.LocalCertificateSelectionCallback - dotnet/runtime#65134

3. Підтримка надсилання імен довірених центрів сертифікації в TLS-рукостисканні на OSX та Linux - dotnet/runtime#65195.

WebSockets

Деталі відповіді на рукостискання WebSocket

 

До .NET 7 частина відповіді сервера на відкриття рукостискання WebSocket (HTTP-відповідь на запит оновлення) була прихована всередині реалізації ClientWebSocket, і всі помилки рукостискання з'являлися як WebSocketException без особливої деталізації, окрім повідомлення про виняток. Однак інформація про заголовки HTTP-відповіді та код статусу може бути важливою як у випадку збою, так і у випадку успіху.

У разі збою код стану HTTP може допомогти відрізнити помилки, які можна виправити, від помилок, які не можна виправити (наприклад, сервер взагалі не підтримує WebSockets, або це була просто транзиторна мережева помилка). Заголовки також можуть містити додаткову інформацію про те, як виправити ситуацію. Заголовки корисні навіть у випадку успішного рукостискання WebSocket, наприклад, вони можуть містити токен, прив'язаний до сесії, інформацію про версію підпротоколу або про те, що сервер може незабаром вийти з ладу.

У .NET 7 додано параметр CollectHttpResponseDetails до ClientWebSocketOptions, який дозволяє збирати дані відповіді на оновлення в екземплярі ClientWebSocket під час виклику ConnectAsync. Пізніше ви зможете отримати доступ до цих даних за допомогою властивостей HttpStatusCode і HttpResponseHeaders екземпляра ClientWebSocket, навіть якщо ConnectAsync згенерує виключення. Зверніть увагу, що у винятковому випадку інформація може бути недоступною, тобто якщо сервер ніколи не відповідав на запит.

Також зверніть увагу, що у випадку успішного з'єднання та після споживання даних HttpResponseHeaders ви можете зменшити обсяг пам'яті ClientWebSocket, встановивши властивість ClientWebSocket.HttpResponseHeaders в нуль.

Надання зовнішнього HTTP-клієнта

За замовчуванням ClientWebSocket використовує кешований статичний екземпляр HttpMessageInvoker для виконання HTTP-запиту на оновлення. Однак існують певні параметри ClientWebSocketOptions, які запобігають кешуванню виклику, такі як Proxy, ClientCertificates або Cookies. Екземпляр HttpMessageInvoker з такими параметрами небезпечно використовувати повторно, і його потрібно створювати кожного разу, коли викликається ConnectAsync. Це призводить до багатьох непотрібних розподілів і робить неможливим повторне використання пулу з'єднань HttpMessageInvoker.

.NET 7 дозволяє передавати наявний екземпляр HttpMessageInvoker (наприклад, HttpClient) до виклику ConnectAsync, використовуючи перевантаження ConnectAsync(Uri, HttpMessageInvoker, CancellationToken). У цьому випадку HTTP-запит на оновлення буде виконано з використанням наданого екземпляра.

Зверніть увагу, що у випадку передачі користувацького виклику HTTP, не слід встановлювати жодну з наступних опцій ClientWebSocketOptions, замість цього їх слід встановити на виклик HTTP:

ClientCertificates

Cookies

Credentials

Proxy

RemoteCertificateValidationCallback

 

UseDefaultCredentials

 

 

Ось як ви можете налаштувати всі ці параметри для екземпляра HttpMessageInvoker:

WebSockets через HTTP/2

NET 7 також додає можливість використовувати протокол WebSocket через HTTP/2, як описано у RFC 8441. При цьому WebSocket-з'єднання встановлюється через один потік на HTTP/2-з'єднанні. Це дозволяє використовувати одне TCP-з'єднання для декількох WebSocket-з'єднань і HTTP-запитів одночасно, що призводить до більш ефективного використання мережі.

Щоб увімкнути WebSockets через HTTP/2, ви можете встановити параметр ClientWebSocketOptions.HttpVersion у значення HttpVersion.Version20. Ви також можете увімкнути оновлення/відновлення вже використаної версії HTTP, встановивши властивість ClientWebSocketOptions.HttpVersionPolicy. Ці параметри поводитимуться так само, як і HttpRequestMessage.Version та HttpRequestMessage.VersionPolicy.

Наприклад, наступний код буде шукати HTTP/2 WebSockets, і якщо з'єднання з WebSocket не вдасться встановити, він повернеться до HTTP/1.1:

Комбінація HttpVersion.Version11 і HttpVersionPolicy.RequestVersionOrHigher призведе до тієї ж поведінки, що і вище, тоді як HttpVersionPolicy.RequestVersionExact заборонить оновлення/відновлення вже використаної версії HTTP.

За замовчуванням встановлено HttpVersion = HttpVersion.Version11 та HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrLower, що означає використання лише HTTP/1.1.

Можливість мультиплексування WebSocket-з'єднань і HTTP-запитів через одне HTTP/2-з'єднання є важливою частиною цієї функції. Щоб вона працювала належним чином, вам потрібно передавати й повторно використовувати той самий екземпляр HttpMessageInvoker (наприклад, HttpClient) з вашого коду при виклику ConnectAsync, тобто використовувати перевантаження ConnectAsync(Uri, HttpMessageInvoker, CancellationToken). Це дозволить повторно використовувати пул з'єднань в екземплярі HttpMessageInvoker для мультиплексування.

Source




Exception: Stack empty.

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading