Когда вы открываете браузер, набираете адрес и нажимаете Enter, между этим моментом и появлением первой картинки на экране проходит, как правило, от ста до восьмисот миллисекунд. За эту долю секунды происходит каскад событий, в котором участвуют десятки протоколов, несколько физических машин на разных континентах, криптографические вычисления и сложная подсистема рендеринга внутри самого браузера. Большинство пользователей воспринимают это как мгновенное «открылся сайт». Инженер, который понимает, что именно происходит на каждом шаге, видит совсем другую картину: цепочку запросов и ответов, каждый из которых можно измерить, оптимизировать, сломать и защитить.
В этой статье мы пройдём весь путь запроса максимально подробно — на примере открытия https://example.com. Это не случайный выбор: домен example.com зарезервирован документом RFC 2606 (а позже RFC 6761) специально для примеров и документации, поэтому на нём безопасно демонстрировать любые команды, не задевая чужую инфраструктуру. Мы разберём, как браузер превращает строку URL в сетевое соединение, как работает DNS, что происходит во время TCP-рукопожатия, как TLS устанавливает зашифрованный канал, как устроены HTTP-запрос и ответ, что делает сервер за своим публичным адресом, и как браузер превращает полученный HTML в пиксели на экране. Сразу оговоримся: конкретные IP-адреса example.com со временем меняются (так, исторические 93.184.216.34, а затем 93.184.215.14 относились к инфраструктуре Edgecast/Verizon, а с 2024 года домен обслуживается напрямую под управлением IANA/ICANN на других адресах), поэтому все адреса в примерах приведены иллюстративно и могут не совпадать с тем, что вернёт ваш dig сегодня.
Чтобы говорить точно, договоримся о модели. Существуют две классические модели сетевого взаимодействия. Семиуровневая модель OSI (ISO/IEC 7498-1) — академический эталон: физический, канальный, сетевой, транспортный, сеансовый, представления и прикладной уровни. Практическая же модель, по которой реально работает интернет, — это четырёхуровневый стек TCP/IP (описан в RFC 1122): канальный (link), межсетевой (internet, здесь живёт IP), транспортный (TCP/UDP) и прикладной (application, здесь HTTP, DNS, TLS). OSI удобна для разговора об абстракциях, TCP/IP — для понимания того, что происходит в реальных пакетах. Дальше по тексту мы будем опираться в основном на TCP/IP, упоминая уровни OSI там, где это проясняет картину.
Этапы пути запроса ложатся на эти уровни так: разбор URL и формирование HTTP-сообщения — прикладной уровень; DNS — тоже прикладной протокол, но обслуживающий все остальные; TCP и TLS — транспорт и слой поверх него; IP — межсетевой уровень; а Ethernet, Wi-Fi, оптика — канальный и физический. Один «клик» прошивает весь стек сверху вниз на вашей машине, летит через сеть в виде последовательности кадров и пакетов, поднимается вверх по стеку на сервере, и так же возвращается обратно. Понимание того, на каком уровне живёт каждая проблема, — половина успеха при отладке: «сайт не открывается» может означать сбой DNS, обрыв TCP, ошибку валидации сертификата или зависший JavaScript, и это совершенно разные классы неисправностей.
Разбор URL: из чего состоит адрес и что браузер делает до сети
Прежде чем уйти в сеть, браузер должен понять, что вы ввели. URL (а точнее URI — Uniform Resource Identifier, формализованный в RFC 3986) имеет строгую грамматику. Полная форма выглядит так:
Разберём https://example.com:443/search?q=tls#results по компонентам:
- scheme (схема) —
https. Определяет протокол и поведение по умолчанию. Дляhttpsпорт по умолчанию 443, дляhttp— 80, дляftp— 21. По RFC 3986 единственный формально обязательный компонент URI — это именно scheme; всё остальное опционально. Authority (а вместе с ней и host) может отсутствовать — например, вmailto:user@example.com,urn:isbn:0451450523илиfile:///etc/hosts(с пустым host). Для HTTP(S)-URL host, разумеется, присутствует практически всегда — без него некуда подключаться. - userinfo — необязательная часть
user:password@. В современных браузерах для HTTP практически запрещена и считается фишинговым вектором (например,https://example.com@evil.tldвизуально выглядит как поход наexample.com, а реально ведёт наevil.tld), поэтому Chrome и Firefox её подсвечивают или вырезают. - host —
example.com. Доменное имя или IP-адрес. Может быть IPv4 (например,93.184.215.14), IPv6 в квадратных скобках (например,[2606:2800:21f:cb07:6820:80da:af6b:8b2c]) или зарегистрированное имя. Скобки вокруг IPv6 обязательны, чтобы двоеточия адреса не путались с разделителем порта. - port —
:443. Если опущен, берётся порт по умолчанию для схемы. - path —
/search. Иерархический путь к ресурсу. - query —
?q=tls. Набор параметровключ=значение, разделённых&. - fragment —
#results. Важная деталь: фрагмент никогда не отправляется на сервер. Он обрабатывается только на стороне клиента — браузер использует его для прокрутки к якорю или маршрутизации в SPA.
Перед отправкой браузер выполняет нормализацию URL по правилам RFC 3986: приводит схему и host к нижнему регистру (доменные имена регистронезависимы), применяет процентное кодирование (percent-encoding) к небезопасным символам — пробел становится %20, кириллица кодируется в UTF-8 байты, а интернационализированные домены (IDN) преобразуются в Punycode (xn--). Также схлопываются сегменты . и .. в пути и убираются избыточные слэши. Нормализация — не косметика: для same-origin policy и кэширования https://Example.COM/a и https://example.com/a должны считаться одним и тем же origin, иначе сломались бы и безопасность, и кэш.
Отдельного внимания заслуживает превращение http в https. Часто пользователь набирает просто example.com или явно http://, но соединение всё равно идёт по HTTPS — и это происходит ещё до любого сетевого запроса. Работает механизм HSTS (HTTP Strict Transport Security, RFC 6797). Сервер однажды присылает заголовок:
После этого браузер запоминает: для этого домена в течение max-age секунд (здесь два года) любой http://-запрос нужно внутренне переписать в https:// ещё до выхода в сеть. Важно понимать механику точно: RFC 6797 не предписывает для этого апгрейда никакого HTTP-статус-кода — никакого реального сетевого ответа не происходит, переписывание URL целиком локальное. В DevTools Chromium это отображается как условный 307 Internal Redirect, но это артефакт диагностики конкретного браузера, а не часть протокола (Firefox показывает иначе). Так закрывается окно для атаки sslstrip, при которой злоумышленник перехватывает первый незашифрованный запрос и удерживает жертву на HTTP.
Но что с самым первым посещением, когда HSTS ещё не записан? Для этого существует HSTS preload list — список доменов, зашитый прямо в исходный код браузеров (Chromium, Firefox, Safari) и пополняемый через сервис hstspreload.org. Домены из этого списка считаются HTTPS-only с самого начала, ещё до первого контакта. Флаг preload в заголовке выше как раз сигнализирует о согласии владельца домена попасть в этот список. Таким образом, для значительной части веба апгрейд до HTTPS — это решение, принятое локально, мгновенно и без единого сетевого пакета.
DNS: как имя превращается в адрес
Сеть не знает, что такое example.com — она оперирует IP-адресами. Преобразование имени в адрес выполняет DNS (Domain Name System), фундамент которого описан в RFC 1034 (концепции) и RFC 1035 (реализация), с десятками последующих дополнений. Это распределённая иерархическая база данных, и понять её устройство критически важно: DNS — одновременно одна из самых нагруженных, самых хрупких и самых атакуемых подсистем интернета.
В разрешении имени участвуют три типа серверов:
- Stub-резолвер — это не сервер, а тонкий клиент внутри вашей ОС (часть libc, systemd-resolved, dnsapi.dll в Windows). Он не умеет искать сам — он просто формирует запрос и отправляет его рекурсивному резолверу, адрес которого получен по DHCP или прописан вручную (например,
1.1.1.1или8.8.8.8). - Рекурсивный резолвер (рекурсор) — сервер провайдера или публичный (Cloudflare, Google, Quad9). Именно он берёт на себя всю тяжёлую работу: проходит иерархию и возвращает stub-резолверу готовый ответ. У него есть большой кэш.
- Авторитативные серверы — хранят «истину» о конкретных зонах: корневые (root), серверы доменов верхнего уровня (TLD) и серверы конкретного домена.
Ключевой момент: stub-резолвер задаёт рекурсивный запрос («дай мне финальный ответ»), а рекурсор для его выполнения делает серию итеративных запросов («скажи, кого спросить дальше»). Для example.com это выглядит так:
- Рекурсор спрашивает один из 13 логических корневых серверов (a.root-servers.net … m.root-servers.net, физически — тысячи anycast-инстансов по всему миру): «где
example.com?». Корень не знает, но отвечает: «спроси серверы зоны.com, вот их NS-записи и адреса». Адреса корневых серверов резолвер знает заранее из вшитого файла подсказок (root hints). - Рекурсор идёт к TLD-серверу
.com(управляется Verisign): «гдеexample.com?». Тот отвечает: «вот авторитативные NS-серверы этого домена». - Рекурсор идёт к авторитативному серверу
example.comи получает финальную A-запись с IP-адресом.
DNS оперирует ресурсными записями (RR). Вот основные типы, которые встречаются на пути запроса:
| Тип | Назначение | Пример значения (иллюстративный) |
|---|---|---|
A | Имя → IPv4-адрес | 93.184.215.14 |
AAAA | Имя → IPv6-адрес | 2606:2800:21f:cb07:6820:80da:af6b:8b2c |
CNAME | Каноническое имя (алиас) | www → example.com. |
NS | Какие серверы авторитативны для зоны | a.iana-servers.net. |
MX | Почтовый сервер домена (с приоритетом) | 10 mail.example.com. |
TXT | Произвольный текст (SPF, DKIM, верификация) | v=spf1 -all |
SOA | Start of Authority: параметры зоны и TTL | серийный номер, refresh, expire, minimum |
Значения IP в таблице исторические и приведены лишь как иллюстрация формата — актуальный адрес example.com сегодня иной.
Чтобы система не падала под нагрузкой, каждая запись имеет TTL (time to live) — сколько секунд её можно держать в кэше. Рекурсор кэширует ответы и отдаёт их без повторного обхода иерархии, пока TTL не истёк. Существует и negative caching (RFC 2308): отрицательные ответы (NXDOMAIN — домена нет — и NODATA — имя есть, но записи нужного типа нет) тоже кэшируются, чтобы не долбить авторитативные серверы запросами о несуществующих именах. Время такого кэша по RFC 2308 §5 задаётся минимумом из поля MINIMUM в SOA-записи и TTL самой SOA-записи, возвращённой в секции authority (а не TTL запрашиваемой записи, которой при NXDOMAIN попросту нет).
По транспорту DNS традиционно использует UDP на порту 53 — это быстро, без установки соединения. Исторический лимит размера DNS-сообщения по UDP без EDNS составлял 512 байт (RFC 1035 §2.3.4). Если ответ больше, сервер выставляет флаг TC (truncated), и резолвер повторяет запрос по TCP/53. Чтобы не платить за это, придумали EDNS0 (RFC 6891) — расширение, позволяющее анонсировать поддержку UDP-пакетов большего размера (часто 1232–4096 байт) и передавать дополнительные флаги (включая DO — DNSSEC OK). Без EDNS0 современный DNS с подписями просто не помещался бы в 512 байт и постоянно скатывался бы в TCP.
Классический DNS отправляется открытым текстом, что создаёт две проблемы — приватность (провайдер и любой на пути видят, что вы запрашиваете) и целостность (ответ можно подделать). Решения:
- Приватность транспорта. DoT (DNS over TLS, RFC 7858, порт 853), DoH (DNS over HTTPS, RFC 8484, порт 443 — неотличим от обычного веб-трафика) и DoQ (DNS over QUIC, RFC 9250). Они шифруют канал между stub/браузером и рекурсором.
- Целостность данных. DNSSEC (RFC 4033–4035) добавляет к записям криптографические подписи (
RRSIG,DNSKEY,DS). Подписывается не отдельная запись, а RRset — весь набор записей одного имени и типа. В зоне обычно два ключа: KSK (Key Signing Key — подписывает только наборDNSKEY) и ZSK (Zone Signing Key — подписывает остальные RRset зоны); хеш KSK публикуется у родителя как записьDS. Так выстраивается цепочка доверия от корня: корневая зона черезDSподтверждает ключ.com,.com— ключexample.com. Если хоть одно звено не сходится, валидирующий резолвер вернётSERVFAIL. Важно понимать: DNSSEC обеспечивает аутентичность и целостность, но не конфиденциальность — для приватности нужны DoH/DoT.
Без этих механизмов DNS уязвим к спуфингу и отравлению кэша (cache poisoning). Классическая атака Камински (2008) использовала предсказуемость идентификаторов транзакций: злоумышленник заваливал резолвер поддельными ответами с угаданным 16-битным Query ID до прихода настоящего, подменяя записи в кэше. Защита — рандомизация Query ID и порта источника (RFC 5452), что увеличивает пространство угадывания с 16 до ~32 бит, а радикально — DNSSEC. CISA и профильные CERT регулярно публикуют рекомендации по защите DNS-инфраструктуры именно из-за её роли как точки отказа.
Посмотреть на всё это можно командой dig:
В строке RRSIG поля читаются так: A — тип покрываемого RRset; 8 — алгоритм подписи (RSA/SHA-256); 2 — число labels в имени; 86400 — original TTL; далее время истечения (expiration) и начала действия (inception) подписи в формате YYYYMMDDHHMMSS; 12345 — key tag (идентификатор ключа DNSKEY); example.com. — имя подписавшей зоны (signer); и наконец сама подпись в base64.
TCP: надёжный транспорт под капотом
Получив IP-адрес, браузер открывает транспортное соединение. Для классического HTTPS это TCP (Transmission Control Protocol, RFC 9293, консолидировавший старый RFC 793). IP-уровень сам по себе ненадёжен: пакеты могут теряться, дублироваться, приходить не по порядку. TCP надстраивает поверх этого надёжный, упорядоченный, потоковый канал: он нумерует байты, подтверждает доставку, переспрашивает потерянное и собирает поток в исходном порядке.
Соединение начинается с трёхстороннего рукопожатия (three-way handshake). Его задача — синхронизировать начальные порядковые номера (ISN, Initial Sequence Number) обеих сторон и подтвердить, что канал двусторонний:
Флаг SYN (synchronize) запрашивает синхронизацию, ACK (acknowledgment) подтверждает. Поле seq — номер первого байта в сегменте, ack — номер следующего ожидаемого байта. ISN не нулевой, а случайный — это защита от подмены и от наложения старых сегментов на новое соединение (RFC 6528). После третьего пакета соединение в состоянии ESTABLISHED, и данные можно слать. Здесь же кроется неизбежная плата: рукопожатие стоит один полный RTT (round-trip time) — туда и обратно — ещё до того, как уйдёт первый байт HTTP.
Соединение идентифицируется четвёркой (4-tuple): IP источника, порт источника, IP назначения, порт назначения. Серверный порт фиксирован (80 для HTTP, 443 для HTTPS), клиентский выбирается из эфемерного диапазона. TCP-соединение проходит через конечный автомат состояний: LISTEN → SYN-SENT → SYN-RECEIVED → ESTABLISHED → FIN-WAIT → TIME-WAIT → CLOSED (это упрощённый путь; полный автомат RFC 9293 включает также CLOSE-WAIT, LAST-ACK и CLOSING на стороне, закрывающей соединение второй). Состояние TIME-WAIT удерживает закрытое соединение на время, концептуально равное 2×MSL (Maximum Segment Lifetime), чтобы запоздавшие сегменты не попали в новое соединение с той же четвёркой. На практике это значение зависит от ОС: в Linux суммарный TIME-WAIT обычно около 60 секунд, а не строго 240.
Важные параметры размера. MTU (Maximum Transmission Unit) — максимальный размер кадра на канальном уровне, для Ethernet это 1500 байт. MSS (Maximum Segment Size) — максимум полезных данных в одном TCP-сегменте, обычно MTU минус заголовки IP и TCP (около 1460 байт для IPv4). Стороны анонсируют MSS в опциях SYN. Если пакет превысит MTU где-то на пути, он либо фрагментируется, либо (при флаге DF, Don't Fragment) отбрасывается с ICMP-сообщением «Fragmentation needed» — отсюда классическая проблема «PMTUD black hole», когда файрвол режет эти ICMP, и соединение зависает на отправке крупных пакетов при работающем «пинге».
TCP управляет скоростью двумя независимыми механизмами. Управление потоком (flow control) защищает получателя: каждая сторона анонсирует receive window (rwnd) — сколько байт она готова принять, чтобы не переполнить свой буфер. Управление перегрузкой (congestion control, RFC 5681) защищает сеть: отправитель не знает её ёмкости, поэтому начинает осторожно. В фазе slow start congestion window (cwnd) растёт экспоненциально — начальное окно по RFC 6928 равно 10 сегментам (IW=10), и при каждом полученном ACK окно увеличивается, удваиваясь примерно за RTT. Так продолжается, пока cwnd не достигнет порога ssthresh или не случится потеря; тогда алгоритм переходит в фазу congestion avoidance с линейным ростом (примерно +1 MSS за RTT). Потери обнаруживаются либо по таймауту, либо быстрее — по трём дублирующим ACK (механизм fast retransmit/fast recovery, а с SACK, RFC 2018, получатель точечно сообщает, какие именно блоки пришли). Конкретный алгоритм определяет поведение под нагрузкой: CUBIC (по умолчанию в Linux) — loss-based, наращивает окно по кубической функции от времени с последней потери; BBR (Google) принципиально иной — он не ждёт потерь, а строит модель узкого места, оценивая bottleneck bandwidth и минимальный RTT, и держит темп около произведения «полоса × задержка» (BDP).
Чтобы сэкономить тот самый RTT на рукопожатии, придумали TCP Fast Open (TFO, RFC 7413): при повторном соединении клиент предъявляет криптографический cookie, выданный сервером ранее, и может отправить данные прямо в SYN-пакете, не дожидаясь завершения рукопожатия. На практике TFO внедрялся тяжело из-за «залипания» промежуточных устройств (middleboxes), которые не понимают данные в SYN, и сегодня его роль во многом перехватил QUIC, о котором речь пойдёт в разделе про HTTP/3.
TLS: как канал становится защищённым
TCP-канал установлен, но он открытый — всё передаётся открытым текстом. Поверх него поднимается TLS (Transport Layer Security), который обеспечивает три свойства:
- Конфиденциальность — данные шифруются, перехватчик видит только зашифрованный поток.
- Целостность — любое изменение байтов на лету обнаруживается (через AEAD-шифры со встроенной аутентификацией).
- Аутентичность — клиент убеждается, что говорит именно с
example.com, а не с подставным сервером.
(Эти три свойства не следует путать с классической «CIA-триадой» информационной безопасности — Confidentiality, Integrity, Availability; доступность TLS как раз не гарантирует.)
Актуальная версия — TLS 1.3 (RFC 8446, 2018). Её главное отличие от TLS 1.2 — радикальное упрощение и ускорение. TLS 1.2 требовал двух RTT на рукопожатие и допускал устаревшие, небезопасные наборы шифров (RSA key exchange без forward secrecy, CBC-режимы, RC4). TLS 1.3 выбросил всё легаси: оставлены только AEAD-шифры (AES-GCM, ChaCha20-Poly1305), обмен ключами — только эфемерный (EC)DHE, а само рукопожатие сократилось до одного RTT, причём бо́льшая его часть теперь зашифрована.
Разберём рукопожатие TLS 1.3 по шагам:
- ClientHello. Клиент шлёт поддерживаемые версии, список шифронаборов, случайное число (
client_random) и — ключевое новшество — расширениеkey_shareс уже сгенерированной эфемерной публичной частью для обмена ключами. То есть клиент не ждёт, пока сервер выберет группу, — он угадывает её заранее (чаще всего X25519). Здесь же в открытом виде передаётся SNI (Server Name Indication) — имя запрашиваемого хоста, без которого один IP не смог бы обслуживать множество HTTPS-сайтов с независимыми сертификатами. - ServerHello. Сервер выбирает шифронабор, шлёт
server_randomи свою частьkey_share. После этого сообщения обе стороны уже могут вычислить общий секрет и ключи сессии. - Зашифрованная часть. Всё дальнейшее идёт уже под шифрованием:
EncryptedExtensions,Certificate(цепочка сертификатов сервера),CertificateVerify(подпись над хешем рукопожатия, доказывающая владение приватным ключом) иFinished(MAC всего рукопожатия для защиты от подмены и downgrade). Клиент проверяет сертификат, отвечает своимFinished— и канал готов к передаче HTTP.
Обмен ключами строится на ECDHE (Elliptic Curve Diffie-Hellman Ephemeral). Слово ephemeral (эфемерный) здесь важнее всего: для каждой сессии генерируется новая пара ключей, которая после неё уничтожается. Это даёт forward secrecy (совершенную прямую секретность) — даже если злоумышленник позже украдёт приватный ключ сервера, он не сможет расшифровать ранее перехваченные сессии, потому что сессионные ключи нигде не хранились и не выводятся из долгоживущего ключа сервера. Сам по себе Диффи-Хеллман уязвим к MITM, поэтому он подкрепляется аутентификацией через сертификат (CertificateVerify).
Доверие к серверу обеспечивает PKI (Public Key Infrastructure, X.509, RFC 5280). Сертификат example.com подписан промежуточным CA, тот — корневым CA, чей сертификат уже лежит в доверенном хранилище ОС или браузера. Браузер строит цепочку от сертификата сервера до известного ему корня. Что именно проверяется:
| Проверка | Что подтверждает |
|---|---|
Срок действия (notBefore/notAfter) | Сертификат не просрочен и уже вступил в силу |
| Соответствие имени (SAN) | Домен из URL есть в Subject Alternative Name |
| Цепочка до доверенного корня | Подписи валидны вплоть до корня в trust store |
| Назначение ключа (Key Usage / EKU) | Сертификат разрешён для серверной аутентификации |
| Статус отзыва (CRL / OCSP) | Сертификат не отозван CA досрочно |
| Certificate Transparency (SCT) | Сертификат опубликован в публичных CT-логах |
Отдельно про отзыв. Сертификат может быть скомпрометирован до истечения срока, тогда CA его отзывает. Проверяется это через CRL (Certificate Revocation List — длинные списки отозванных, неудобны) или OCSP (Online Certificate Status Protocol — точечный запрос статуса). У OCSP есть проблема приватности и задержки, поэтому применяют OCSP stapling (RFC 6066, Certificate Status Request): сервер сам периодически получает у OCSP-responder'а CA подписанный «штамп» о валидности и прикладывает его к рукопожатию, избавляя браузер от отдельного запроса; срок годности штампа ограничен полем nextUpdate. Современный Chrome дополнительно требует, чтобы для публично доверенного сертификата присутствовали SCT (Signed Certificate Timestamp) — доказательства публикации в логах Certificate Transparency; без них соединение будет отклонено. То есть CT — это не только постфактум-аудит, но и часть обязательной валидации.
Два важных нюанса современного TLS. Во-первых, SNI передаётся открытым текстом — это давняя дыра в приватности: даже при зашифрованном канале наблюдатель видит, к какому домену вы подключаетесь. Решает это ECH (Encrypted Client Hello) — расширение, шифрующее ClientHello целиком с помощью публичного ключа, опубликованного в DNS. Во-вторых, 0-RTT: при возобновлении сессии TLS 1.3 позволяет отправить данные прикладного уровня в первом же пакете, экономя RTT. Но за это платят безопасностью — 0-RTT данные уязвимы к replay-атакам (их можно перехватить и переслать повторно), поэтому в 0-RTT допустимы только идемпотентные запросы. На серверной стороне применяют anti-replay меры: одноразовые session tickets и «окна свежести» (freshness window), отбраковывающие повторы вне допустимого временного интервала; RFC 8446 явно предупреждает о рисках 0-RTT.
HTTP: язык, на котором говорят браузер и сервер
Зашифрованный канал готов — теперь по нему течёт HTTP (Hypertext Transfer Protocol), семантика которого сегодня формализована в RFC 9110, а версии — в RFC 9112 (HTTP/1.1), RFC 9113 (HTTP/2) и RFC 9114 (HTTP/3). HTTP — запрос-ответный протокол без состояния (stateless): сервер по умолчанию не помнит предыдущие запросы, состояние навешивается сверху через cookies и токены. Сама семантика (методы, статусы, заголовки) в RFC 9110 определена независимо от версии; меняется лишь кодирование на проводе — текстовое в 1.1 и бинарное в 2/3.
Запрос состоит из строки запроса (метод, цель, версия), заголовков и опционального тела:
Ответ — из строки статуса, заголовков и тела:
Методы определяют намерение и обладают разными гарантиями. Здесь критически важны два свойства из RFC 9110: безопасный метод (safe — не меняет состояние сервера) и идемпотентный (idempotent — повторение даёт тот же результат, что одно выполнение).
| Метод | Назначение | Безопасный | Идемпотентный |
|---|---|---|---|
GET | Получить ресурс | да | да |
HEAD | Как GET, но только заголовки | да | да |
OPTIONS | Узнать возможности (используется в CORS preflight) | да | да |
POST | Создать/отправить данные | нет | нет |
PUT | Заменить ресурс целиком | нет | да |
PATCH | Частично изменить | нет | нет |
DELETE | Удалить ресурс | нет | да |
Идемпотентность — не теоретическая тонкость: на ней строится логика повторных попыток. Прокси и клиенты могут безопасно повторить GET или PUT при таймауте, но не POST — иначе можно случайно создать два заказа. Именно поэтому платёжные API часто вводят свой ключ идемпотентности (Idempotency-Key), возвращая для повтора тот же результат вместо второй транзакции.
Статус-коды делятся на пять классов:
| Класс | Значение | Примеры |
|---|---|---|
1xx | Информационные | 100 Continue, 101 Switching Protocols |
2xx | Успех | 200 OK, 201 Created, 204 No Content |
3xx | Перенаправление | 301 Moved Permanently, 304 Not Modified, 307 Temporary Redirect |
4xx | Ошибка клиента | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests |
5xx | Ошибка сервера | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
Стоит запомнить пару тонкостей: 301 против 308 и 302 против 307 различаются тем, сохраняется ли метод при редиректе (307/308 гарантируют, что POST не превратится в GET); 304 Not Modified отдаётся без тела в ответ на условный запрос; 401 требует аутентификации (с заголовком WWW-Authenticate), тогда как 403 означает «вы опознаны, но доступ запрещён»; 429 обычно сопровождается заголовком Retry-After.
Ключевые заголовки, влияющие на путь запроса:
- Кэширование.
Cache-Control(max-age,no-cache,private,immutable,stale-while-revalidate) управляет временем жизни и стратегией обновления.ETag— версия ресурса; при повторном запросе клиент шлётIf-None-Match, и сервер может ответить304 Not Modifiedбез тела, экономя трафик и время. - Cookies. Сервер ставит
Set-Cookie, клиент возвращаетCookie. АтрибутыSecure,HttpOnly,SameSiteкритичны для защиты от кражи сессий и CSRF. - CORS.
Access-Control-Allow-Originи компания управляют доступом из чужого источника — без них браузер блокирует кросс-доменные запросы (same-origin policy). «Непростые» запросы предваряются preflight-запросомOPTIONS. - Security-заголовки.
Content-Security-Policy(защита от XSS),Strict-Transport-Security(HSTS),X-Content-Type-Options: nosniff,Referrer-Policy— рекомендованы OWASP Secure Headers Project.
Эволюция версий — это история борьбы с задержкой. HTTP/1.1 ввёл keep-alive (переиспользование TCP-соединения для нескольких запросов), но страдал от head-of-line blocking: запросы в одном соединении обрабатываются строго по очереди, медленный ответ блокирует все следующие. Обходили это открытием 6+ параллельных соединений на домен — дорого по ресурсам и плохо для congestion control.
HTTP/2 (RFC 9113) сделал протокол бинарным и ввёл мультиплексирование: множество логических потоков (streams) внутри одного TCP-соединения, фреймы которых идут вперемешку. Заголовки сжимаются алгоритмом HPACK — это статическая таблица типовых заголовков (например, :method: GET), динамическая таблица, накапливаемая на время соединения, и Huffman-кодирование строковых значений; вместе они убирают избыточность повторяющихся заголовков. HTTP/2 также вводил приоритизацию потоков (дерево зависимостей и веса) и server push, но на практике приоритизация оказалась капризной, а server push признан неэффективным и фактически свёрнут — его нишу заняли resource hints (preload/preconnect). Главное: HTTP/2 убрал HoL-блокировку на уровне HTTP — но не на уровне TCP. Потеря одного пакета по-прежнему тормозит все потоки, ведь TCP гарантирует строгий порядок байтов для всего соединения.
HTTP/3 (RFC 9114) решает и это, переехав с TCP на QUIC (RFC 9000) — транспорт поверх UDP. В QUIC потоки независимы на транспортном уровне, поэтому потеря пакета в одном потоке не блокирует остальные. Вдобавок QUIC объединяет транспортное и криптографическое рукопожатие (TLS 1.3 встроен), что даёт установку соединения за 1-RTT, а при возобновлении — 0-RTT. Сжатие заголовков в HTTP/3 берёт на себя QPACK — адаптация HPACK, устойчивая к переупорядочиванию пакетов.
Серверная сторона: что происходит до генерации ответа
С точки зрения браузера сервер — это один IP и один сертификат, но за ним обычно стоит целая цепочка компонентов. Запрос, придя на публичный адрес, сначала попадает на балансировщик нагрузки (L4 или L7), который распределяет соединения между множеством машин и часто терминирует TLS — то есть именно он, а не приложение, расшифровывает трафик и снимает с бэкенда дорогую криптографию.
Дальше запрос идёт на обратный прокси (reverse proxy, например nginx или Envoy). Он маршрутизирует по пути и хосту, отдаёт статику, ограничивает частоту запросов (rate limiting), фильтрует подозрительные запросы (часто в связке с WAF) и проксирует динамику на бэкенд. Само приложение (на любом стеке — Node, Go, Python, JVM) выполняет бизнес-логику. Здесь развилка по способу формирования ответа: для классической страницы сервер рендерит HTML-шаблон на сервере, для API — сериализует данные в JSON. И тот, и другой путь обычно требует обращений к базе данных и кэшам в памяти (Redis/Memcached), а нередко и синхронных вызовов соседних микросервисов; именно эти обращения, особенно медленные SQL-запросы и сетевые round-trip'ы между сервисами, чаще всего и определяют задержку. Время от входа запроса до первого байта ответа — это TTFB (Time To First Byte), и в нём «сидит» основная серверная задержка.
Поверх всего этого работает CDN (Content Delivery Network) — географически распределённая сеть кэширующих узлов. Кэш в вебе многослоен, и полезно держать в голове всю лестницу: кэш браузера → edge-узел CDN → origin shield → кэш приложения → база данных. Запрос за популярным ресурсом в идеале не доходит дальше ближайшего edge-узла. Эффективность слоя описывают через cache hit ratio (доля ответов, отданных из кэша без похода к origin); промах (miss) дороже, так как тянется к источнику. Важно, что CDN согласует своё поведение с теми же HTTP-заголовками из предыдущего раздела: Cache-Control и ETag задают TTL и условную ревалидацию на edge так же, как и в браузере, а директива stale-while-revalidate позволяет отдать слегка устаревшую копию мгновенно и обновить кэш в фоне, не заставляя пользователя ждать. Для example.com многие подресурсы вполне могли прийти не с исходного сервера, а с CDN-узла в вашем городе, что и срезает RTT, разгружает origin и поглощает всплески трафика, включая DDoS.
Рендеринг: как HTML становится пикселями
Браузер получил HTML — но это лишь текст. Превращение его в изображение называется critical rendering path и состоит из нескольких этапов.
Сначала парсер HTML строит DOM (Document Object Model) — древовидное представление документа. Параллельно CSS (из <style>, внешних файлов и инлайна) парсится в CSSOM (CSS Object Model). DOM отвечает на вопрос «что есть на странице», CSSOM — «как это выглядит». Эти два дерева объединяются в render tree — дерево только видимых узлов с применёнными стилями (элементы с display: none в него не попадают, в отличие от visibility: hidden, который место занимает).
Дальше — layout (он же reflow): браузер вычисляет геометрию — точные координаты и размеры каждого элемента в пикселях с учётом окна просмотра. После — paint: заливка пикселей слоёв (цвета, текст, тени, картинки). И наконец composite: браузер собирает отрисованные слои в правильном порядке (с учётом z-index, прозрачности, трансформаций), нередко на GPU, и выводит итоговый кадр. Понимание этой цепочки имеет прямую практическую цену: изменение геометрии (ширина, позиция) запускает дорогой reflow всего поддерева, тогда как анимация через transform и opacity обрабатывается на этапе composite и не трогает layout/paint — именно поэтому такие анимации «дешёвые» и плавные.
Эти же этапы напрямую отражаются в Core Web Vitals — пользовательских метриках Google, по которым измеряют реальную производительность: LCP (Largest Contentful Paint) — момент отрисовки крупнейшего видимого элемента, упирается в скорость paint и доставки ресурсов; CLS (Cumulative Layout Shift) — суммарный незапланированный сдвиг макета, то есть качество layout/reflow; INP (Interaction to Next Paint, сменивший FID) — отзывчивость на ввод, упирающаяся в занятость основного потока. Перевод абстрактных «layout/paint» в эти измеримые числа и есть язык, на котором веб-разработчики обсуждают рендеринг.
Критически важная деталь — JavaScript блокирует парсер. Когда HTML-парсер встречает <script> без атрибутов, он останавливается: скрипт нужно скачать и выполнить до продолжения парсинга, потому что он может менять DOM через document.write. Поэтому есть атрибуты:
defer— скрипт качается параллельно, выполняется после построения DOM, в порядке следования. Идеален для кода, зависящего от DOM.async— качается параллельно, выполняется сразу как загрузился (порядок не гарантирован). Для независимых скриптов (аналитика).
CSS тоже блокирует рендеринг (но не парсинг DOM): браузер не отрисует страницу, пока не построит CSSOM, иначе мелькнул бы нестилизованный контент (FOUC). Поэтому критический CSS стараются доставить как можно раньше, а лучше — заинлайнить.
Наконец, при парсинге браузер встречает ссылки на подресурсы — картинки, шрифты, дополнительные скрипты и стили. Для каждого из них запускается новый цикл: возможно, новый DNS-резолв (если другой домен), новое TCP/TLS-соединение (или переиспользование существующего), новый HTTP-запрос. То есть весь путь, который мы разобрали, для одной страницы повторяется десятки раз. Браузер оптимизирует это: использует preload scanner (заранее сканирует HTML на ресурсы), переиспользует соединения и применяет приоритизацию. Разработчик может помочь явными resource hints: <link rel="dns-prefetch"> заранее резолвит DNS чужого домена, rel="preconnect" прогревает заодно TCP и TLS к нему, а rel="preload" форсирует раннюю загрузку конкретного критичного ресурса. По сути это способ запустить ранние этапы нашего пути (DNS → TCP → TLS) для будущих запросов заранее — и именно здесь цикл «открытия сайта» замыкается сам на себя.
Где живёт задержка и где живут атаки
Теперь, имея полную карту пути, разложим бюджет задержки и поверхность атаки. Каждый этап стоит времени — обычно измеряемого в RTT, — и каждый имеет свои угрозы. Для соединения с нуля характерна такая последовательность: DNS (часто 1 RTT, если нет в кэше), TCP-рукопожатие (1 RTT), TLS 1.3-рукопожатие (1 RTT), затем TTFB (запрос + работа сервера). На дальних маршрутах, где RTT составляет 100–150 мс, эти круги суммируются: только до первого байта контента легко набегает 300–450 мс ещё до того, как пойдёт сам HTML — отсюда ценность кэширования, keep-alive, TLS-resumption, 0-RTT и географически близких CDN-узлов, сокращающих сам RTT.
| Этап | Источник задержки | Типичная угроза | Защита |
|---|---|---|---|
| URL/HSTS | — | sslstrip, downgrade на http | HSTS + preload |
| DNS | 1 RTT (промах кэша) | cache poisoning, спуфинг | DNSSEC, DoH/DoT, рандомизация порта |
| TCP | 1 RTT (handshake) | SYN-flood, RST-инъекция | SYN cookies, фильтрация |
| TLS | 1 RTT (1.3) | MITM, fake/expired cert, downgrade | валидация цепочки, OCSP, CT, TLS 1.3 |
| HTTP | TTFB | инъекции, XSS, CSRF, утечка cookie | CSP, SameSite, валидация ввода |
| Рендеринг | парсинг + ресурсы | XSS через DOM, malicious script | CSP, Subresource Integrity |
Несколько слов об угрозах предметно. DNS-спуфинг подменяет ответ, уводя пользователя на сервер злоумышленника, — отсюда необходимость DNSSEC и шифрованного транспорта. MITM/downgrade пытается заставить стороны использовать слабую версию протокола или подсунуть свой сертификат; TLS 1.3 убрал уязвимые шифры и защищает рукопожатие от понижения версии, а HSTS не даёт откатиться на HTTP. Проблемы сертификатов (просрочка, отзыв, mis-issuance недобросовестным или скомпрометированным CA) частично решает прозрачность сертификатов — Certificate Transparency (RFC 6962): публичные append-only логи, куда обязаны попадать все выданные публично доверенные сертификаты, что позволяет владельцу домена заметить чужой сертификат на своё имя. На уровне HTTP живёт классика веб-безопасности: XSS, CSRF, инъекции — систематизированы в OWASP Top 10, а конкретные техники атак каталогизированы в MITRE ATT&CK и базе слабостей CWE (с привязкой к публичным CVE). Для оценки и координации уязвимостей существует FIRST (хранитель системы оценки CVSS), а практические руководства по защите инфраструктуры публикуют CISA и NIST (в частности, серия SP 800). Эта привязка к стандартам — не формальность: она даёт общий язык, на котором инженеры, аудиторы и вендоры обсуждают одни и те же риски.
Практика: смотрим путь запроса своими глазами
Теория обретает смысл, когда вы видите её в инструментах. Разберём три.
dig example.com показывает работу DNS. Полезные ключи: +trace проводит итеративный обход иерархии от корня (вы увидите делегации root → TLD → authoritative своими глазами), +dnssec запрашивает подписи, +short даёт только результат.
curl -v https://example.com показывает TCP, TLS и HTTP разом. Флаг -v (verbose) печатает каждый шаг:
Строки с * — диагностика curl (соединение, TLS, проверка сертификата), > — отправленный запрос, < — полученный ответ. Здесь видно версию TLS, шифронабор, результат валидации сертификата, версию HTTP и заголовки ответа. Обратите внимание: в выводе запрос идёт по HTTP/2 (h2), хотя в разделе про HTTP мы для наглядности показывали сообщение в текстовом HTTP/1.1. Противоречия нет: клиент и сервер при рукопожатии (через расширение ALPN в TLS) согласуют наивысшую общую версию — обычно h2, а с QUIC и h3, — поэтому реальный вывод чаще бинарный, а текстовый формат удобен лишь для чтения человеком.
Вкладка Network в DevTools даёт визуальную хронологию. Открыв её и перезагрузив страницу, вы увидите водопад (waterfall) запросов, а для каждого — разбивку по фазам в подсказке Timing: Queued/Stalled, DNS Lookup, Initial connection, SSL, Request sent, Waiting (TTFB), Content Download. Это буквально все этапы нашей статьи, измеренные в миллисекундах для конкретного запроса. Вкладка Security покажет цепочку сертификатов и версию протокола, а колонка Protocol — h2 или h3, то есть какую версию HTTP реально согласовали стороны.
Заключение
Открытие сайта — это не одно действие, а слаженный конвейер: разбор URL и решение об HTTPS, превращение имени в адрес через иерархию DNS, надёжное TCP-соединение, криптографическое рукопожатие TLS, обмен HTTP-сообщениями, работа серверной инфраструктуры и, наконец, рендеринг в браузере. Каждый этап измерим, каждый можно ускорить и каждый имеет собственную модель угроз. Инженер, который держит в голове эту полную карту, отлаживает проблемы и проектирует защиту совсем иначе, чем тот, для кого «сайт просто открылся».
Всё описанное здесь можно не только прочитать, но и потрогать руками: на интерактивных стендах вводных курсов «Основы сетей» и «Как устроен веб» вы сами запускаете dig и curl, разбираете TLS-рукопожатие по пакетам и наблюдаете путь запроса от URL до пикселя в реальном времени.
Обсуждение · 0 комментариев