HTTP від 1.1 до 3
Навіщо це
Section titled “Навіщо це”Сторінка завантажується за дві секунди на офісному Wi-Fi і за дванадцять
у поїзді. Канал у поїзді не в шість разів гірший, просто втрачений пакет
там трапляється частіше, і протокол по-різному реагує на втрату. Або інша
ситуація: за nginx стоять три копії застосунку, а top на одній з них
червоний, на двох інших порожній. Або: після деплою користувачі «не бачать»
нової версії скрипту, хоча файл на сервері вже інший. Усе це вирішується
на рівні HTTP, а точніше, на межі HTTP і транспорту.
HTTP виглядає найпростішим протоколом курсу: текст, рядок запиту, кілька заголовків. Сам формат справді простий, його можна набрати руками. Складність живе навколо нього: скільки з’єднань відкрити, коли їх закрити, хто має право зберегти відповідь і на скільки, що робити, коли посередині стоїть проксі. Швидкість залежить від цих рішень більше, ніж від формату.
Передумови. TCP, рукостискання і повторні передачі (модуль 11), перевантаження (модуль 12), DNS (модуль 13). Шифрування, на якому сьогодні тримається майже весь HTTP, розібране в модулі 15; тут ми лише беремо його до відома.
Запит і відповідь на рівні байтів
Section titled “Запит і відповідь на рівні байтів”HTTP/1.1 (RFC 9112 для формату повідомлень, RFC 9110 для семантики) — текстовий
протокол. Клієнт відкриває TCP-з’єднання і пише в нього рядок запиту,
заголовки, порожній рядок і, можливо, тіло. Рядки закінчуються парою байтів CRLF
(\r\n). Сервер відповідає рядком стану, заголовками, порожнім рядком і тілом.
Це можна перевірити без жодного HTTP-клієнта, самим nc. Нижче локальний
nginx із однією сторінкою:
printf 'GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8080HTTP/1.1 200 OKServer: nginx/1.24.0 (Ubuntu)Date: Wed, 30 Sep 2026 07:17:06 GMTContent-Type: text/htmlContent-Length: 6Last-Modified: Wed, 30 Sep 2026 07:17:00 GMTConnection: closeETag: "6abcb76c-6"Cache-Control: max-age=60Accept-Ranges: bytes
helloЦе вся відповідь, байт у байт. Порожній рядок відділяє заголовки від тіла,
а Content-Length: 6 каже, скільки байтів тіла читати: hello плюс
перенесення рядка. Без цього поля (або без Transfer-Encoding: chunked)
клієнт не знав би, де закінчується тіло, і мусив би чекати, поки сервер закриє
з’єднання.
curl -v показує те саме з розміткою напрямку. Рядки з > йдуть від клієнта,
рядки з < приходять від сервера:
curl -sv http://127.0.0.1:8080/ -o /dev/null* Trying 127.0.0.1:8080...* Connected to 127.0.0.1 (127.0.0.1) port 8080> GET / HTTP/1.1> Host: 127.0.0.1:8080> User-Agent: curl/8.5.0> Accept: */*>< HTTP/1.1 200 OK< Server: nginx/1.24.0 (Ubuntu)< Content-Type: text/html< Content-Length: 6< Connection: keep-alive< ETag: "6abcb76c-6"< Cache-Control: max-age=60Єдиний заголовок, без якого запит HTTP/1.1 недійсний, — Host. Він з’явився,
бо на одній IP-адресі живе багато сайтів, і сервер має знати, про який
із них питають. Ім’я в Host — те саме, що ви ввели в адресний рядок,
а DNS тут уже ні до чого (модуль 13): на цьому етапі
адреса відома.
Методи, коди, заголовки
Section titled “Методи, коди, заголовки”Метод каже, що робити з ресурсом. Назви тут менш цікаві, ніж властивості:
- Безпечний метод не змінює стан на сервері:
GET,HEAD,OPTIONS. - Ідемпотентний метод можна повторити з тим самим результатом: додатково
PUTіDELETE.POSTне ідемпотентний: два повтори — два замовлення.
Від цих властивостей на практиці залежить, чи має право клієнт або проксі самостійно повторити запит після обриву з’єднання, і чи можна відправляти запит у 0-RTT (нижче в розділі про QUIC).
Код відповіді складається з трьох цифр, і найважливіша перша: 2xx успіх, 3xx перенаправлення
(і 304, відповідь «не змінилось», про яку буде в розділі про кеш), 4xx помилка
клієнта, 5xx помилка сервера. Для діагностики корисно пам’ятати
502 («проксі отримав від бекенду сміття або нічого»), 503 («бекенд
перевантажений або вимкнений») і 504 («бекенд не відповів у строк»).
Проксі, що стоїть перед застосунком, видає саме їх, і за ними видно, з якого
боку шукати проблему.
Заголовки мають вигляд пар «ім’я: значення», і протокол розширюють саме ними:
кому потрібна нова можливість, той вигадує заголовок. Найчастіше зустрінете
Content-Type, Content-Length, Host, Cookie, Authorization,
Cache-Control, Location, User-Agent.
Скільки з’єднань і коли їх закрити
Section titled “Скільки з’єднань і коли їх закрити”У HTTP/1.0 на кожен запит відкривалося нове TCP-з’єднання, а після відповіді закривалося. Ціна цього — рукостискання TCP (один круговий рейс, модуль 11) перед кожним запитом, а для HTTPS ще й TLS. Те саме з’єднання, якщо йому дати попрацювати, обходиться без повторного рукостискання і встигає розігнати вікно перевантаження, тож наступні запити йдуть швидше (модуль 12).
Тому HTTP/1.1 зробив постійне з’єднання (keep-alive) поведінкою за замовчуванням:
після відповіді воно лишається відкритим, поки якась зі сторін його не закриє
(Connection: close) або не спрацює таймаут простою. Перевірити легко: curl
з двома адресами використає одне з’єднання.
curl -sv http://127.0.0.1:8080/ http://127.0.0.1:8080/ -o /dev/null -o /dev/null 2>&1 \ | grep -E 'Re-using|Connected|GET'* Connected to 127.0.0.1 (127.0.0.1) port 8080> GET / HTTP/1.1* Re-using existing connection with host 127.0.0.1> GET / HTTP/1.1Обидва запити пройшли одним з’єднанням з одним рукостисканням. Але HTTP/1.1 дозволяє на з’єднанні лише один запит «в польоті» одночасно: наступний можна відправити тільки після того, як прийшла вся відповідь на попередній. Специфікація має конвеєризацію (pipelining), яка дозволяє надсилати запити не чекаючи, але відповіді все одно повертаються в порядку запитів, і на практиці браузери її вимкнули, бо проксі й сервери реалізували її ненадійно.
Блокування головою черги на рівні HTTP
Section titled “Блокування головою черги на рівні HTTP”Так виникає блокування головою черги (head-of-line blocking, HOL) першого роду. Якщо перший запит на з’єднанні — важкий (звіт, що генерується три секунди), то за ним чекають усі наступні, хоч би вони були дрібними й миттєвими, а на сторінці з сотнею ресурсів це вже катастрофа.
Обхід був грубим, але ефективним. Браузер відкриває кілька паралельних
з’єднань до одного сервера (зазвичай близько шести), а розробники додали
до цього доменне шардування (img1.example.com, img2.example.com), щоб
обійти обмеження на з’єднання до одного імені, і склеювання ресурсів у
великі файли. Кожне додаткове з’єднання коштує рукостискання, пам’яті на обох кінцях
і власного вікна перевантаження, яке розганяється з нуля, тож шість з’єднань
змагаються між собою за той самий вузький канал.
HTTP/2: один TCP, багато потоків
Section titled “HTTP/2: один TCP, багато потоків”HTTP/2 (RFC 9113) залишив семантику без змін: ті самі методи, коди й заголовки. Змінився спосіб їх пересилання. Замість тексту використано бінарне кадрування (binary framing). Усе, що йде з’єднанням, поділено на кадри (frame), а кожен кадр починається з 9-байтового заголовка: довжина (3 байти), тип (1), прапорці (1) і ідентифікатор потоку (4 байти, з яких один біт зарезервований).
Головна ідея криється в ідентифікаторі потоку. Один запит із відповіддю становить потік (stream), і в одному TCP-з’єднанні одночасно живе багато потоків. Кадри різних потоків перемежовуються в каналі: шматок відповіді на потік 1, шматок на потік 3, знову на потік 1. Приймач збирає їх за номером потоку. Такий спосіб називають мультиплексуванням (multiplexing). Він знімає блокування першого роду: важка відповідь більше не заступає дрібні, бо їхні кадри пролітають між її кадрами.
Інші помітні зміни в HTTP/2:
- Стиснення заголовків. Заголовки типового запиту (
Cookie,User-Agent,Accept) майже не змінюються від запиту до запиту, і HTTP/1.1 пересилав їх кілобайтами щоразу. HTTP/2 застосовує схему HPACK (RFC 7541): обидва кінці ведуть таблицю вже надісланих заголовків, і повторний заголовок передається як короткий індекс. - Керування потоком. У кожного потоку і у всього з’єднання є власне вікно (окремо від вікна TCP), щоб повільний споживач одного потоку не займав усі буфери.
- Пріоритети були в оригінальній специфікації, але реалізації робили їх по-різному, і схему згодом спростили. Тому не покладайтеся на те, що «важливі ресурси» поїдуть першими.
Як домовляються про версію
Section titled “Як домовляються про версію”У URL версія не вказана. Для https:// клієнт і сервер погоджують її прямо
в TLS-рукостисканні розширенням ALPN (Application-Layer Protocol Negotiation):
клієнт перелічує, що вміє (h2, http/1.1), сервер вибирає. Видно в curl -v:
curl -skv --http2 https://127.0.0.1:8443/ -o /dev/null 2>&1 | grep -E 'ALPN|HTTP/2'* ALPN: curl offers h2,http/1.1* ALPN: server accepted h2* using HTTP/2> GET / HTTP/2< HTTP/2 200Специфікація допускає й HTTP/2 без шифрування (його називають h2c), але браузери його не підтримують, тож на практиці HTTP/2 — це HTTP/2 поверх TLS.
Якщо запустити curl --parallel із кількома адресами до одного сервера HTTP/2, кожен запит
отримає власний потік у тому самому з’єднанні (рядки OPENED stream), а псевдозаголовки
:method, :scheme, :authority, :path замінюють собою рядок запиту й Host:
* [HTTP/2] [1] OPENED stream for https://127.0.0.1:8443/* [HTTP/2] [1] [:method: GET]* [HTTP/2] [1] [:scheme: https]* [HTTP/2] [1] [:authority: 127.0.0.1:8443]* [HTTP/2] [1] [:path: /]Блокування головою черги на рівні TCP
Section titled “Блокування головою черги на рівні TCP”HTTP/2 вирішив проблему прикладного рівня, але переніс її на нижчий. Усі потоки їдуть в одному TCP-з’єднанні, а TCP доставляє застосунку байти строго по порядку (модуль 11). Якщо втрачено один сегмент, ядро утримує все, що прийшло за ним, доки повторна передача не заповнить дірку. TCP бачить лише один потік байтів і не знає, що в ньому кадри різних потоків, тож на паузу стають усі потоки, хоч би втрата зачепила лише один.
На чистому каналі HTTP/2 виграє в HTTP/1.1, а на каналі з помітними втратами (мобільна мережа, поганий Wi-Fi) його перевага менша, бо шість з’єднань HTTP/1.1 втрачають пакети незалежно одне від одного, а одне з’єднання HTTP/2 після втрати зупиняється цілком. Розв’язати це можна тільки змінивши транспорт.
QUIC і HTTP/3
Section titled “QUIC і HTTP/3”Змінити TCP у самому ядрі й на кожному маршрутизаторі та мережевому екрані інтернету не вдається: проміжні пристрої вивчають TCP і псують те, чого не знають. Тому нового транспорту шукали над UDP, який проходить скрізь. Результат — QUIC (RFC 9000): транспорт, реалізований у бібліотеці користувацького простору, а не в ядрі, що працює поверх UDP (зазвичай порт 443). HTTP/3 (RFC 9114) — це HTTP, відображений на QUIC.
Що дає QUIC і що він бере на себе:
- Незалежні потоки в самому транспорті. QUIC знає про потоки, тож втрата пакета зупиняє тільки ті потоки, чиї дані в ньому були. Другий рід блокування зникає.
- Своє керування втратами й перевантаженням. Нумерація пакетів, підтвердження й повторні передачі, а також алгоритм перевантаження (модуль 12) живуть у QUIC, а не в ядрі. Плата за це — процесорний час у користувацькому просторі: QUIC зазвичай дорожчий за TCP на гігабітних швидкостях, хоча розрив зменшують оптимізаціями.
- Шифрування вбудоване. QUIC використовує TLS 1.3 (модуль 15) для рукостискання й шифрує майже всю власну службову інформацію. Незашифрованого QUIC немає. Транспортне й криптографічне рукостискання відбуваються разом, тож до першого запиту потрібен один круговий рейс замість двох у TCP + TLS 1.3.
- Ідентифікатор з’єднання. Зʼєднання TCP визначається четвіркою «IP і порт з обох боків». Змінилась адреса клієнта (телефон перейшов з Wi-Fi на мобільну мережу), і з’єднання мертве. У QUIC пакети несуть ідентифікатор з’єднання (connection ID), тому воно переживає зміну адреси. Це називається міграцією з’єднання (connection migration).
0-RTT і його ціна
Section titled “0-RTT і його ціна”Якщо клієнт уже раніше говорив із сервером, він може відправити перший запит одразу разом з першим пакетом, використавши ключі з попередньої сесії. Це 0-RTT: нуль додаткових кругових рейсів. За швидкість доводиться платити: дані 0-RTT не захищені від повторного відтворення (replay). Атакувальник, який бачив пакет, може відправити його серверу ще раз, і сервер, не маючи спільного з клієнтом свіжого значення, не відрізнить копію від оригіналу.
Тому 0-RTT дозволяють лише для запитів, повтор яких нешкідливий, тобто для
безпечних, ідемпотентних методів (GET без побічних ефектів). «Купити» по 0-RTT не можна.
Проте й цього мало: обробник, що змінює стан у відповідь на GET,
став би вразливим, тож частина відповідальності лежить на авторі застосунку.
Як клієнт дізнається, що сервер вміє HTTP/3
Section titled “Як клієнт дізнається, що сервер вміє HTTP/3”Клієнт не знає наперед, чи сервер відповість на UDP 443, тож спершу
він, як правило, іде звичайним HTTPS, а сервер у відповіді сповіщає заголовком
Alt-Svc: h3=":443" (з часом дії): «я є і на HTTP/3». Наступне з’єднання
клієнт спробує вже по QUIC. Про можливість HTTP/3 можна повідомляти й через
записи HTTPS у DNS (модуль 13), тоді перша спроба вже піде
по QUIC. Якщо UDP на шляху блокується (корпоративні мережеві екрани роблять
це часто), клієнт тихо повертається на HTTP/2.
Кешування
Section titled “Кешування”Найшвидший запит — той, що не дійшов до сервера. Кеш можуть тримати браузер, проміжний проксі й CDN, і поведінкою всіх керують заголовки відповіді (RFC 9111).
Свіжість. Cache-Control: max-age=60 означає: наступні 60 секунд відповідь можна
віддавати без запиту до сервера. no-store забороняє зберігати взагалі. Поширене
хибне очікування пов’язане з no-cache: він не забороняє зберігати, а вимагає
перед кожним використанням перевіряти в сервера, чи копія ще актуальна.
private дозволяє тримати копію лише в кеші користувача (браузері),
public — ще й у спільних (проксі, CDN).
Якщо явного строку немає, кеш може сам оцінити свіжість за Last-Modified.
Перевірка (revalidation). Коли строк вийшов, кеш не викидає копію, а питає
сервер: «Чи змінилося?» Для цього сервер видає валідатор: ETag (відбиток
версії) або Last-Modified. Клієнт повертає його в If-None-Match чи
If-Modified-Since, і якщо нічого не змінилося, сервер відповідає коротким 304
без тіла:
curl -si http://127.0.0.1:8080/ -H 'If-None-Match: "6abcb76c-6"'HTTP/1.1 304 Not ModifiedServer: nginx/1.24.0 (Ubuntu)Last-Modified: Wed, 30 Sep 2026 07:17:00 GMTConnection: keep-aliveETag: "6abcb76c-6"Cache-Control: max-age=60ETag збігся, тіло не передавалось. Якби ми підставили інше значення,
сервер віддав би 200 і повний файл. Так ці запити економлять смугу навіть
тоді, коли кешувати без запиту не можна.
Vary. Одна адреса може давати різні відповіді залежно від заголовків
запиту (мова, стиснення). Заголовок Vary: Accept-Encoding каже кешу
розрізняти копії за цим заголовком. Забутий Vary призводить до відомої
помилки: користувач із gzip отримує копію, яку зберіг клієнт без нього.
Історія з «користувачі не бачать нового скрипта» має стандартне розв’язання:
ресурсам з довгим max-age дають у назві відбиток вмісту (app.3f9a1c.js)
замість того, щоб міняти файл на місці. Нова версія має нову адресу, і про старий кеш
турбуватися не треба. Сама сторінка, що посилається на файл, лишається з
коротким строком.
Проксі, балансувальник, CDN
Section titled “Проксі, балансувальник, CDN”Між клієнтом і застосунком у реальній системі стоїть кілька посередників, і треба розуміти, на якому рівні кожен із них працює.
Зворотний проксі (reverse proxy) приймає з’єднання клієнтів від імені сервера
й сам відкриває з’єднання до бекендів. На відміну від прямого проксі, що діє від
імені клієнта, про нього знає лише адміністратор сервера. Він розвантажує
застосунок від TLS, стиснення й повільних клієнтів, кешує, перезаписує адреси,
ділить трафік за шляхами (/api — сюди, /static — туди).
З’єднання від клієнта і з’єднання до бекенду різні, і параметри в них незалежні. Скажімо, клієнтові проксі розмовляє HTTP/2, а до бекенду ходить HTTP/1.1. Ще цікавіше поводиться пул з’єднань. Якщо проксі налаштований тримати з’єднання до бекендів відкритими, то чотири запити від клієнтів обслуговуються двома з’єднаннями:
# у конфігурації nginx: upstream із двома бекендами й keepalive 8,# proxy_http_version 1.1 і proxy_set_header Connection ""for i in 1 2 3 4; do curl -s http://127.0.0.1:8081/; donebackend-1backend-2backend-1backend-2ss -tn state established '( sport = :9001 or sport = :9002 )'Recv-Q Send-Q Local Address:Port Peer Address:Port0 254 127.0.0.1:9002 127.0.0.1:445900 254 127.0.0.1:9001 127.0.0.1:36742Запити чергуються між бекендами (за замовчуванням nginx розподіляє круговим
методом, round-robin), а з’єднань до бекендів усього два: по одному на кожен, і
вони переживають запити. Без keepalive і без Connection: "" кожен запит
коштував би нового рукостискання TCP усередині вашого датацентру, а на високих
навантаженнях це швидко з’їдає порти й доводить до TIME_WAIT
(модуль 11).
Проксі мусить передати бекенду, хто справжній клієнт: у нього самого IP-адреса
клієнта загубилась, бекенд бачить адресу проксі. Це розв’язують заголовки
X-Forwarded-For (де-факто стандарт) чи стандартизований Forwarded. Якщо
бекенд довіряє такому заголовку від кого завгодно, то будь-хто може підробити
свою «адресу»; довіряти можна лише тому, що дописав ваш власний проксі.
Балансувальник (load balancer) розподіляє запити між кількома копіями бекенду й перевіряє їхнє здоров’я (health check), щоб не слати запити на мертву. Працювати він може на рівні L4 або L7.
- L4 працює із з’єднаннями TCP/UDP. Він бачить IP-адреси й порти, вибирає
бекенд (за хешем п’ятірки, за кількістю з’єднань) і пересилає або проксує потік
байтів, не розуміючи, що всередині. Дуже швидкий, але сліпий: не може вирішити
«цей шлях на цей бекенд», не бачить кодів відповіді, не може повторити запит
на іншому бекенді. У Linux це, зокрема, IPVS і
stream-режимnginx. - L7 розуміє HTTP: розбирає запит, дивиться на шлях, заголовки, cookie і вирішує окремо для кожного запиту. Може повторити ідемпотентний запит на іншому бекенді, якщо перший не відповів. Дорожчий за процесор, бо парсить кожен запит, а для HTTPS ще й розшифровує.
Обидва потребують визначення «мертвого бекенду». Проста перевірка «порт
відкритий» не помічає застосунку, що завис, тому на L7 здоров’я перевіряють
справжнім запитом (GET /healthz) із очікуваним кодом і строком.
Політика вибору бекенду буває різна: круговий обхід, найменше активних з’єднань, хеш
від клієнта для «липких» сесій. Вибір залежить від того, наскільки
однакові запити за вагою: коли одні запити на мілісекунди, а інші на секунди,
круговий обхід перекошує навантаження, і виграє «найменше з’єднань».
CDN (content delivery network) — це розподілена по світу мережа кешуючих
зворотних проксі. Клієнт потрапляє до найближчого вузла (за допомогою DNS
або anycast-маршрутизації — тема модуля 8), і якщо
потрібна відповідь є в кеші вузла, вона віддається звідти, за кілька мілісекунд
замість сотні до першоджерела (origin). Так зменшується і затримка (вузол
ближче), і навантаження на origin. Кешовані на CDN відповіді підкоряються
тим самим заголовкам Cache-Control (s-maxage задає строк саме для спільних
кешів), а очищення кешу до кінця строку — окрема операція, яку надають
самі CDN.
Як це насправді в Linux
Section titled “Як це насправді в Linux”Усе, що вище, можна відтворити локально за кілька хвилин.
sudo apt-get install -y nginx curl netcat-openbsdМінімальний nginx для експериментів, який не чіпає системного (у каталозі
/tmp/w14 лежить www/index.html):
pid /tmp/w14/nginx.pid;error_log /tmp/w14/err.log;events {}http { access_log /tmp/w14/acc.log; server { listen 127.0.0.1:8080; root /tmp/w14/www; add_header Cache-Control "max-age=60"; }}nginx -c /tmp/w14/nginx.conf # стартcurl -sv http://127.0.0.1:8080/ -o /dev/nullkill "$(cat /tmp/w14/nginx.pid)" # зупинкаЩо ще робити:
- Спробуйте
curl -sv --http1.0 …таConnection: closeчерезncі порівняйте, коли закривається з’єднання (ss -tnу сусідньому терміналі). - Для HTTP/2 потрібен TLS: зробіть самопідписаний сертифікат
(
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes …), уnginx1.24 пишітьlisten 127.0.0.1:8443 ssl http2;. У новіших версіях директива інша (http2 on;), і на старій вона дасть помилку конфігурації. - Знайдіть у
curl -vрядкиALPNіConnection #0 … left intactта поясніть, що вони означають. ss -tnпоказує з’єднання до бекендів. Порівняйте їх кількість з кількістю запитів ізkeepaliveі без нього.- У
tcpdump -i lo -A port 8080видно той самий текст, що і вnc.
Типові помилки розуміння
Section titled “Типові помилки розуміння”«HTTP/2 завжди швидший за HTTP/1.1». На каналах з малою втратою так, а на поганих одне TCP-з’єднання, у якому все стоїть через один втрачений сегмент, може програвати кільком незалежним. Цю проблему знімає лише перехід на HTTP/3.
«HTTP/3 — це HTTP/2 з іншим номером». Семантика та сама, змінився транспорт: замість TCP працює QUIC поверх UDP, зі своїми потоками, втратами й шифруванням. Звідси й більшість практичних відмінностей: UDP може блокуватись, QUIC живе у користувацькому просторі, і він переживає зміну адреси.
«Cache-Control: no-cache забороняє кешувати». Забороняє no-store. Директива no-cache
дозволяє зберегти відповідь, але вимагає перевіряти її актуальність перед кожним використанням.
«304 — це помилка». Це нормальна успішна відповідь на умовний запит: тіла
немає, бо клієнт уже має актуальну копію.
«Балансувальник просто розкидає запити порівну». Порівну розподіляються з’єднання чи запити, але не робота. Кілька довгих запитів на одному бекенді перекосять навантаження, а L4-балансувальник не бачить різниці між запитами всередині одного з’єднання.
«Клієнт бачить IP-адресу сервера застосунку». Якщо стоїть проксі чи CDN, клієнт
бачить їхню адресу, а бекенд бачить адресу проксі, тож справжню адресу клієнта
він знає лише з X-Forwarded-For (якщо проксі цей заголовок додав).
Перевір себе
Лабораторна
Section titled “Лабораторна”A6. Зворотний проксі й балансувальник — ви напишете власний проксі з круговим розподілом, перевіркою здоров’я й таймаутами, і виміряєте його під навантаженням. Там з’явиться той самий пул з’єднань, що описаний вище. Цикл подій можна взяти з лабораторної A7 курсу «Операційні системи».
Джерела
Section titled “Джерела”- RFC 9110 (HTTP Semantics), RFC 9111 (HTTP Caching), RFC 9112 (HTTP/1.1)
- RFC 9113 (HTTP/2), RFC 7541 (HPACK)
- RFC 9000 (QUIC), RFC 9114 (HTTP/3)
- Grigorik I., High Performance Browser Networking, розділи про HTTP/1.x, HTTP/2 і TLS (є безкоштовно онлайн)
man curl, документаціяnginx(ngx_http_proxy_module,ngx_http_upstream_module)