Этап 1 — Браузер: что происходит до отправки пакета
Кажется, что после Enter браузер мгновенно идёт в сеть. На самом деле он успевает сделать десяток вещей внутри себя — и если ошибиться на этом этапе, никакая оптимизация дальше не спасёт.
1. Архитектура браузера: кто делает запрос
Современный браузер — не одна программа, а оркестр процессов. Когда ты вводишь URL и жмёшь Enter, в работу включаются как минимум три:
| Процесс | Что делает |
|---|---|
| Browser process (главный) | UI, адресная строка, вкладки, история. Решает «открыть URL — какой процесс этим займётся?» |
| Renderer process (по одному на сайт) | Парсит HTML/CSS/JS, выполняет JS, рисует страницу. Изолирован — упал сайт, остальные живы. |
| Network process (один на браузер) | Делает реальные сетевые запросы. Управляет соединениями, кэшем, куками. Через него идут запросы ВСЕХ вкладок. |
| GPU process | Рисует пиксели на экране. |
| Utility processes | Сервис-воркеры, расширения, аудио, видео. |
Эта изоляция (sandbox) — фундамент безопасности современных браузеров. Один сайт не может прочитать память другого, и атака на JS не вылезает наружу процесса.
Браузер = бизнес-центр с разными отделами
Browser process — это ресепшен: ты приходишь сюда, говоришь «хочу попасть в офис №42», и тебя направляют. Network process — это служба доставки, она ходит наружу за заказами. Renderer process — отдельный офис каждого арендатора, в котором делается реальная работа. Никто не лезет в чужой офис.
2. Парсинг URL — спецификация WHATWG
Когда ты ввёл app.example.com/login?next=/dashboard, браузер должен это разобрать. Это сложнее, чем кажется — есть целая спецификация WHATWG URL.
Шаг 1: дополнение схемы
Если ты не написал https://, браузер сам это добавит. Логика:
- Похоже на host? (есть точка, нет пробелов) — добавляет
https:// - Не похоже? — это поисковый запрос, отправляется в дефолтный поисковик
- На localhost / 127.0.0.1 / интранет-имя — попробует
http://
Шаг 2: разбор по компонентам
# Берём URL:
https://alice:s3cr3t@api.example.com:8443/v2/users/42?tab=info&ref=email#section-2
scheme # https
userinfo # alice:s3cr3t (раритет, опасен)
host # api.example.com
port # 8443
path # /v2/users/42
query # tab=info&ref=email
fragment # section-2 (не уходит на сервер!)
Шаг 3: нормализация
Браузер приводит URL к каноническому виду:
- Хост — в нижний регистр (
Example.COM→example.com) - Процентное кодирование (
%2Fи т.п.) разворачивается, где это безопасно - Удаляются «пустые» сегменты пути (
/a//b→/a/b) - Резолвятся
.и..
Шаг 4: IDN и Punycode
Если в имени домена есть не-ASCII (например пример.рф или café.com), браузер преобразует его в Punycode:
café.com → xn--caf-dma.com
яндекс.рф → xn--d1acpjx3f.xn--p1ai
Это нужно, потому что DNS не понимает Unicode напрямую. Punycode — это «безопасный» ASCII-аналог любого Unicode-имени.
Кириллическое «а» (U+0430) внешне неотличимо от латинского «a» (U+0061). Атакующий регистрирует аpple.com (с кириллической «а»), и пользователь не отличит от apple.com. Браузеры это знают и в подозрительных случаях показывают URL в Punycode (xn--pple-43d.com). Это твоя первая встреча с browser security.
3. Проверка локального HTTP-кэша
Прежде чем ходить в сеть, браузер смотрит — нет ли уже у меня этой страницы в кэше? И если есть — может ли я её использовать без перепроверки на сервере.
Кэшированный ответ имеет заголовки, которые сервер прислал в прошлый раз:
HTTP/2 200 OK
Cache-Control: public, max-age=300, immutable
ETag: "v2-7c8f3a"
Last-Modified: Wed, 15 May 2026 11:00:00 GMT
Date: Wed, 15 May 2026 11:55:00 GMT
Content-Type: text/html
Логика браузера:
- Свежий? Если
(now - Date) < max-ageи нет no-cache — отдаём из кэша моментально. Никакого сетевого запроса. - Просрочен, но есть ETag? Делаем условный запрос:
GET / HTTP/2 If-None-Match: "v2-7c8f3a". Сервер сравнит и ответит либо 304 Not Modified (используй кэш), либо новый 200 с новой версией. - no-store? Кэш игнорируется, всегда новый запрос.
- Кэша вообще нет? Делаем обычный запрос.
Это огромная экономия трафика. Большой сайт может отдавать 80% запросов из браузерного кэша.
4. Иерархия кэшей
Слово «кэш» в браузере — это на самом деле несколько кэшей:
| Кэш | Где | Срок жизни |
|---|---|---|
| Memory cache | В оперативке renderer-процесса | До закрытия вкладки |
| Disk cache | На диске | До явного очищения / нехватки места |
| Service Worker cache | API CacheStorage, управляется JS | Как настроит разработчик (даже offline) |
| Push cache | HTTP/2 server push (устарело) | Жизнь HTTP/2-соединения |
| Prefetch cache | Заранее загруженные ресурсы | ~5 минут |
Когда ты делаешь fetch — браузер по цепочке смотрит во все эти кэши. Service Worker (если зарегистрирован сайтом) имеет приоритет — он может вообще перехватить запрос и ответить «из своего кэша», не идя в сеть. Так работают PWA.
5. Fetch — единый API запросов
В современных браузерах ВСЕ исходящие запросы — будь то «открыть страницу», img src=, fetch() из JS, AJAX, iframe — проходят через Fetch Standard. Это набор шагов:
- CORS check. Запрос идёт на тот же origin (схема+хост+порт)? Тогда всё разрешено. Иначе — проверка CORS, возможно preflight.
- Service Worker intercept. Если зарегистрирован SW и его scope покрывает URL — ему даётся возможность перехватить.
- HTTP cache lookup. Описано выше.
- Content Security Policy. Если сайт прислал заголовок CSP, проверяется, разрешён ли этот URL для этого типа ресурса.
- Permissions Policy. Можно ли вообще этому контексту делать такие запросы.
- Network — если ничего не остановило, идём в Network process.
6. Preconnect, preload, prefetch — гонка за миллисекунды
Большие сайты экономят на каждом миллисекунде. Поэтому ещё до того, как HTML догрузится, браузер может начать соединение с сервером — параллельно с парсингом HTML.
В HTML это указывается так:
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="preconnect" href="https://api.example.com">
<link rel="preload" as="font" href="/Inter.woff2" crossorigin>
<link rel="prefetch" href="/dashboard">
| Подсказка | Что делает | Когда применять |
|---|---|---|
| dns-prefetch | Резолвит DNS заранее | Знаешь, что скоро понадобится этот домен (баннер, аналитика) |
| preconnect | DNS + TCP + TLS-handshake | На критичный домен — экономит ~100-200мс |
| preload | Сразу качает ресурс | Шрифты, ключевые JS — то, без чего страница не работает |
| prefetch | Качает в фон с низким приоритетом | Следующая вероятная страница |
| modulepreload | preload, но для JS-модулей | Современные ES-модули |
Если у тебя сайт критичен к производительности, в шаблон HTML добавляешь preconnect на все домены, с которых придут шрифты, картинки, аналитика. Эффект — Largest Contentful Paint улучшается на 50-200 мс. Это просто, бесплатно, и почему-то 80% сайтов этим пренебрегают.
7. 103 Early Hints — что нового в HTTP
HTTP-статус 103 Early Hints — относительно новая возможность. Сервер может прислать сначала «предварительный» ответ со списком ресурсов, которые точно понадобятся для страницы, ещё до того, как сама страница готова.
# Сервер начинает обрабатывать запрос…
# …и сразу шлёт промежуточный ответ:
HTTP/2 103 Early Hints
Link: </css/critical.css>; rel=preload; as=style
Link: </fonts/Inter.woff2>; rel=preload; as=font; crossorigin
# Браузер начинает качать эти ресурсы, пока сервер ещё думает.
# Когда сервер закончит — пришлёт обычный 200 OK с HTML.
HTTP/2 200 OK
Content-Type: text/html
<!DOCTYPE html>…
Это позволяет «склеить» подготовку страницы и загрузку её ресурсов параллельно. Cloudflare и Fastly поддерживают, AWS CloudFront — в beta. Запомни и предлагай на собеседовании, если речь о performance.
8. Connection reuse — почему второй запрос быстрее
Открыл сайт. Браузер установил TCP+TLS соединение — занимает 100-300мс. А потом этот же сайт делает запрос к API на том же домене. Что происходит?
Браузер переиспользует уже открытое соединение. Никакого нового TCP/TLS — просто отправляет следующий запрос. Это называется HTTP keep-alive (в HTTP/1.1) или multiplexing (в HTTP/2 и /3).
Сколько соединений браузер держит к одному домену?
- HTTP/1.1: до 6 параллельных соединений на хост (стандарт)
- HTTP/2 / HTTP/3: одно соединение, но в нём параллельно идёт сколько угодно запросов
Поэтому когда сайт переходит с HTTP/1.1 на HTTP/2, скорость может вырасти кратно — особенно при множестве мелких ресурсов.
9. Connection coalescing — секретная оптимизация
А что, если у тебя на сайте подключены ресурсы с разных поддоменов одного хоста — www.example.com, api.example.com, cdn.example.com? По логике нужно три соединения.
Но если все три указывают через DNS на одни и те же IP, и у них один сертификат с Subject Alternative Names, покрывающий все три имени, — браузер использует одно HTTP/2-соединение для всех трёх. Это и есть connection coalescing.
В цифрах: сайт-«монстр» с 20 поддоменами на одной инфраструктуре в HTTP/2 откроет 1-2 соединения вместо 20. Экономит 1-2 секунды на первой загрузке.
10. Referrer и куки — что улетает наружу
Прежде чем браузер отправит запрос, он подкладывает:
GET /v2/users/42 HTTP/2
Host: api.example.com
Referer: https://app.example.com/dashboard # откуда я пришёл
Origin: https://app.example.com # origin страницы (без пути)
User-Agent: Mozilla/5.0 ...
Accept-Language: ru-RU,ru;q=0.9,en;q=0.8
Sec-Ch-Ua: "Chrome";v="120", ...
Cookie: session=abc; csrf=xyz # куки для этого хоста
Какие куки попадают в запрос
Браузер просматривает cookie jar и берёт куки, у которых:
- Совпадает
Domain(или это поддомен, если кука разрешает) - Совпадает
Path(или префикс) - Атрибут
Secureтолько для HTTPS SameSiteразрешает текущий контекст:- Strict — только если запрос с того же сайта
- Lax — также при навигации с других сайтов (умолчание в Chrome)
- None — всегда (только с
Secure)
Referrer Policy
Заголовок Referer может раскрывать чувствительную информацию (например, путь с токеном). Сайты управляют через Referrer-Policy:
Referrer-Policy: strict-origin-when-cross-origin # умолчание сейчас
# Шлёт полный URL — на тот же origin. Только origin — на чужие HTTPS. Ничего — на HTTP.
11. CORS — почему запрос вообще может отказаться уйти
Кросс-доменные запросы (когда страница с одного origin делает запрос к другому) — это особый случай. Без специального разрешения сервера браузер их не отправит. Это Same-Origin Policy — фундамент веб-безопасности с 90-х.
Чтобы разрешить кросс-доменный запрос, сервер должен ответить заголовком:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Для «небезопасных» запросов (PUT, DELETE, не-простые заголовки) браузер сначала шлёт preflight — OPTIONS-запрос, который спрашивает: «можно ли мне сделать такой запрос?». Если ответ ok — браузер шлёт настоящий запрос.
«CORS error» — самая частая жалоба разработчиков фронтенда. И всегда — это сервер что-то не так настроил, а не браузер «неправильно работает». Чините на стороне ответов сервера: правильные Access-Control-Allow-Origin, корректная обработка OPTIONS, правильные Vary-заголовки.
12. CSP — Content Security Policy
Сайт может прислать заголовок CSP, говорящий браузеру: «загружай ресурсы только с этих источников, остальные блокируй». Это защита от XSS — даже если злоумышленник внедрил скрипт через дыру в HTML, браузер откажется его исполнить.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
Это твоя обязательная гигиена как DevSecOps. Сайт без CSP в 2026 году — это плохо.
13. Просмотреть всё это в DevTools
Открой Chrome / Firefox DevTools (F12) → вкладка Network. Сделай запрос. Вкладка Timing у каждого ресурса покажет:
Queued at: 0 ms # когда фетч встал в очередь
Started at: 12 ms # когда реально начался
Stalled: 4 ms # ожидание свободного соединения
DNS Lookup: 18 ms # резолвинг (если кэша не было)
Initial connection: 40 ms # TCP handshake
SSL: 35 ms # TLS handshake
Request sent: 0.3 ms # отправка запроса
Waiting (TTFB): 150 ms # ожидание ответа сервера
Content Download: 25 ms # скачивание тела
Каждая из этих секунд — отдельный этап в этом deep dive. Через 9 уроков ты будешь читать эту таблицу как родную.
14. Команды и упражнения
Через curl увидеть тот же запрос
# Показать всё, что curl шлёт и получает
curl -v --http2 https://example.com
# Эмулировать поведение браузера — нужный User-Agent, follow redirects
curl -v -L \
-H "User-Agent: Mozilla/5.0" \
-H "Accept: text/html,application/xhtml+xml,*/*;q=0.8" \
https://example.com
# Получить разбивку времени по фазам
curl -o /dev/null -s -w \
"DNS: %{time_namelookup}s
Connect: %{time_connect}s
TLS: %{time_appconnect}s
TTFB: %{time_starttransfer}s
Total: %{time_total}s
" https://example.com
Посмотреть свой HTTP-кэш
Chrome: chrome://net-internals/#httpCache — список всех закэшированных ответов. Можно изучить.
Тестировать preconnect
# Открой DevTools → Performance → Record
# Загрузи страницу один раз с preconnect, второй — без.
# Сравни LCP (Largest Contentful Paint).
15. Что с этим делает DevOps
- Правильные Cache-Control. Не выставил — браузеры угадывают, и обычно неоптимально. Статика —
max-age=31536000, immutable(с уникальным хэшем в имени). HTML —no-cache. - HSTS. Заголовок
Strict-Transport-Securityзаставляет браузер всегда ходить по HTTPS — даже если пользователь ввёл http://. Включи + добавь домен в HSTS preload list. - Early Hints. Поддерживается Cloudflare/Fastly. Включаешь — TTFB до критичных ресурсов падает.
- HTTP/2+. Обязательно. HTTP/1.1 в 2026 — это значит ты что-то делаешь не так.
- CORS-настройки. Не «
Access-Control-Allow-Origin: *» с креденшелами — это уязвимость. Только конкретные origin. - CSP-настройки. Обязательно.
frame-ancestors 'none'или конкретные — защита от clickjacking. - Security headers. X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy — настрой все. Проверяй на securityheaders.com.
Чек-лист «понимаю это»
- ☐ Объясню разделение browser process / renderer / network
- ☐ Разберу URL по компонентам, помню что fragment не уходит на сервер
- ☐ Понимаю Punycode и homograph-атаки
- ☐ Знаю иерархию кэшей и условный запрос с ETag
- ☐ Различаю preconnect / preload / prefetch / dns-prefetch
- ☐ Знаю, что такое 103 Early Hints
- ☐ Объясню connection reuse и coalescing
- ☐ Понимаю SameSite-куки и CORS preflight
- ☐ Помню, зачем нужен CSP
- ☐ Сделал
curl -wи прочитал тайминги