Основы Computer Science Как работает веб: DNS, HTTP, браузер, клиент и сервер
0%

Как работает веб: DNS, HTTP, браузер, клиент и сервер

Как работает веб: DNS, HTTP, браузер, клиент и сервер

В предыдущей статье трека мы разобрали, как компьютеры вообще обмениваются данными: как байты нарезаются на пакеты, как IP доставляет их между машинами, а TCP собирает обратно в надёжный поток (Как компьютеры общаются). Но «надёжный поток байтов между двумя IP-адресами» — это ещё не веб. Между этим потоком и той страницей, что вы читаете сейчас, лежит целый этаж абстракций: система имён, которая превращает example.com в число; протокол HTTP, который договаривается о том, что именно байты значат; и браузер, который из полученного текста строит живой интерактивный документ.

Веб — это не «интернет». Интернет — это трубы (статья 11). Веб — это один из сервисов, работающих поверх этих труб: договорённость о том, как клиенты просят у серверов документы и как серверы их отдают. Придумал её Тим Бернерс-Ли в ЦЕРН в 1989–1991 годах, собрав три кирпича, которые живы до сих пор: URL (как адресовать ресурс), HTTP (как его запросить) и HTML (как его описать). Разберём весь путь по порядку — так, как он разворачивается, когда вы вводите адрес и нажимаете Enter. Это один из лучших сквозных примеров того, как слои абстракции складываются в работающую систему, поэтому пройдём его медленно и до дна.

Клиент и сервер: базовая модель

Веб построен на модели клиент — сервер. Сервер — это программа (не «железный ящик»), которая постоянно слушает сеть и ждёт запросов; получив запрос, отвечает и снова ждёт. Клиент — программа, которая инициирует общение: браузер, мобильное приложение, curl, поисковый робот. Клиент всегда говорит первым, сервер отвечает. Один сервер обслуживает миллионы клиентов; один клиент за одну страницу обращается к десяткам серверов.

Ключевое свойство этой модели в вебе — HTTP не имеет состояния (stateless). Каждый запрос самодостаточен: сервер по умолчанию не помнит, что вы к нему уже обращались минуту назад. Это кажется недостатком (как тогда работает «вход в аккаунт»?), но именно оно позволило вебу масштабироваться: раз сервер ничего не помнит между запросами, любой из тысячи серверов за балансировщиком может обслужить любой ваш запрос. Как поверх этой беспамятности всё-таки строят сессии — разберём ниже.

URL: как адресуется ресурс

Прежде чем что-то запросить, надо это что-то назвать. В вебе имя ресурса — URL (Uniform Resource Locator). Он выглядит как одна строка, но на деле это структурированный адрес, где каждая часть отвечает на свой вопрос: по какому протоколу, к какому серверу, к какому его ресурсу и с какими параметрами обращаться.

Анатомия URL: схема, хост, порт, путь, строка запроса, фрагмент

  • Схема (https) — по какому протоколу говорить. Для веба это http или https (HTTP поверх шифрования), но бывают ftp, mailto, ws (веб-сокеты).
  • Хост (api.example.com) — доменное имя сервера. Именно его предстоит превратить в IP-адрес через DNS. Слева — поддомены (api), справа — регистрируемое имя (example.com).
  • Порт (:443) — номер «двери» на сервере (см. про порты в статье 11). Обычно опускают: для http подразумевается 80, для https443.
  • Путь (/v2/users) — какой именно ресурс на сервере нужен. Раньше это был путь к файлу на диске; сегодня чаще — маршрут, который сервер разбирает программно.
  • Строка запроса (?id=42&sort=name) — набор параметров ключ=значение через &.
  • Фрагмент (#profile) — якорь внутри документа. Важнейшая деталь: фрагмент на сервер не отправляется вообще, его обрабатывает только браузер. Это регулярно удивляет новичков.

Стоит сразу отметить протекающую абстракцию: в URL можно писать не-ASCII символы и пробелы, но по проводам они уходят в процентном кодировании (%20 вместо пробела, %D0%BF для кириллической буквы). Отсюда классические баги, когда + в строке запроса означает пробел, а в пути — уже нет.

Шаг 1. DNS: превращаем имя в адрес

Компьютеры маршрутизируют пакеты по IP-адресам (93.184.216.34), а люди помнят имена (example.com). Мост между ними — DNS (Domain Name System), гигантская распределённая телефонная книга интернета. Прежде чем послать хоть один байт запроса, браузер должен узнать IP хоста из URL.

DNS устроен иерархически, и это ключ к тому, почему он вообще работает в масштабе всей планеты: ни одна машина не хранит всю книгу. Имя читается справа налево как дерево: . (корень) → .com (домен верхнего уровня, TLD) → example.comapi.example.com. За каждый уровень отвечают свои серверы, и поиск спускается по дереву.

Разберём участников:

  • Резолвер (recursive resolver) — сервер вашего провайдера или публичный вроде 8.8.8.8 (Google) / 1.1.1.1 (Cloudflare). Именно он делает всю грязную работу обхода дерева, а браузеру возвращает готовый ответ.
  • Корневые серверы знают, где искать серверы каждого TLD (.com, .org, .ru).
  • Серверы TLD знают, где искать авторитетный сервер конкретного домена.
  • Авторитетный сервер хранит настоящие записи домена: A (IPv4-адрес), AAAA (IPv6), CNAME (псевдоним), MX (почта), TXT (произвольные данные, часто для верификации).

Без кэширования DNS немедленно рухнул бы: каждый клик слал бы запросы к корневым серверам. Спасает кэш и TTL (time-to-live): каждая запись живёт заданное число секунд, и резолвер, браузер и ОС держат её у себя. Отсюда две практические истины. Первая: обычно DNS-поиск почти мгновенен, потому что ответ уже в кэше. Вторая — знаменитая головная боль: сменив IP-адрес сайта, вы ждёте «распространения DNS» — на самом деле ждёте, пока по всему миру истекут закэшированные старые записи. Поэтому перед переездом TTL заранее уменьшают.

Тонкость безопасности: обычный DNS ходит открытым текстом по UDP, и провайдер (или атакующий в той же сети) видит, какие имена вы запрашиваете, и может их подменить. Отсюда DNS over HTTPS/TLS (DoH/DoT), шифрующий сами запросы к резолверу.

Шаг 2. Соединение: TCP и TLS

IP у нас есть — теперь нужно надёжное соединение до порта 443. Браузер открывает TCP-соединение через трёхстороннее рукопожатие (SYN → SYN-ACK → ACK), разобранное в статье 11. Это один круг туда-обратно (1 RTT) ещё до того, как отправлен хоть байт HTTP.

Для https сверху добавляется TLS-рукопожатие — установка шифрования. Клиент и сервер договариваются о версии протокола и наборе шифров, сервер предъявляет сертификат (подписанный доверенным центром сертификации документ, доказывающий «я действительно example.com»), и стороны вырабатывают общий сеансовый ключ. С этого момента весь HTTP-трафик зашифрован — провайдер видит только с каким сервером вы говорите, но не что передаёте. TLS 1.3 ужал рукопожатие до 1 RTT (а при повторном подключении — до нуля).

Обратите внимание, сколько кругов туда-обратно набегает до первого байта данных: DNS + TCP + TLS. На межконтинентальном канале с задержкой 150 мс это уже полсекунды на пустом месте. Поэтому веб так одержим сокращением RTT: постоянные соединения, TLS 1.3, размещение серверов ближе к пользователю (CDN, о них ниже).

Шаг 3. HTTP: язык запросов и ответов

Канал установлен — пора говорить по делу. HTTP (HyperText Transfer Protocol) — это протокол уровня приложения, простой до элегантности: клиент шлёт запрос, сервер шлёт ответ, и оба — обычный текст с жёсткой структурой из четырёх частей: стартовая строка, заголовки, пустая строка, тело.

Анатомия HTTP-запроса и ответа: стартовая строка, заголовки, пустая строка, тело

Запрос начинается со стартовой строки: метод, путь и версия. Метод объявляет намерение:

Метод Смысл Меняет данные? Идемпотентен?
GET получить ресурс нет да
POST создать / отправить данные да нет
PUT заменить ресурс целиком да да
PATCH частично изменить да нет
DELETE удалить да да
HEAD как GET, но только заголовки нет да

Различие безопасных (не меняющих состояние: GET, HEAD) и идемпотентных (повтор даёт тот же результат) методов — не педантизм, а контракт, на который опираются кэши, прокси и повторные попытки при обрыве. Браузер спокойно повторяет GET при сбое, но переспросит вас перед повтором POST — потому что второй POST может создать второй заказ.

Ответ начинается со статусной строки: версия, числовой код состояния и текстовая причина. Коды сгруппированы по первой цифре, и эту таксономию стоит держать в голове:

Практическое правило: 4xx — виноват клиент (не туда постучался, нет прав, кривые данные), 5xx — виноват сервер (упал, перегружен, за ним лежит зависимость). Спутать их — значит чинить не ту сторону.

Заголовки — метаданные пары «ключ: значение», и именно в них живёт вся тонкая механика веба. Host (какой из виртуальных сайтов на этом IP нужен — без него один сервер не мог бы держать тысячи доменов), Content-Type (что в теле: text/html, application/json, image/png), Content-Length, Accept-* (что клиент готов принять), Authorization, Cache-Control, Set-Cookie. Тело несёт полезную нагрузку: у ответа это сам документ, у POST — отправляемые данные; у GET тела обычно нет.

Вот тот же запрос-ответ вживую, через curl:

$ curl -v https://api.example.com/v2/users?id=42
> GET /v2/users?id=42 HTTP/1.1        # стартовая строка запроса
> Host: api.example.com               # обязательный заголовок
> Accept: application/json
>                                      # пустая строка — конец заголовков
< HTTP/1.1 200 OK                      # статусная строка ответа
< Content-Type: application/json
< Cache-Control: max-age=60
<                                      # пустая строка
< {"users":[{"id":42,"name":"Ann"}]}  # тело ответа

Эволюция HTTP: та же семантика, всё быстрее транспорт

Семантика (методы, коды, заголовки) от версии к версии почти не менялась — менялось то, как эти сообщения едут по проводу, ради скорости.

Ключевая боль, которую лечили: в HTTP/1.1 по одному соединению запросы шли строго по очереди (head-of-line blocking — один медленный ответ держит остальные), поэтому браузеры открывали по 6 соединений на домен. HTTP/2 ввёл мультиплексирование: много параллельных «потоков» в одном соединении. Но так как всё это лежало на одном TCP, потеря одного пакета тормозила все потоки. HTTP/3 перенёс транспорт на QUIC поверх UDP, где потоки независимы. Снаружи для вашего кода это невидимо — тот же GET, тот же 200 OK; протекает лишь тогда, когда вы отлаживаете производительность.

Шаг 4. Браузер: из текста в живую страницу

Сервер вернул тело — обычно HTML. Дальше начинается вторая половина истории, целиком на стороне клиента. Браузер — это не «просмотрщик текста», а сложный runtime, который должен разобрать документ, догрузить десятки связанных ресурсов и превратить всё это в пиксели и интерактивность.

Что здесь важно понять:

  1. Парсинг HTML в DOM. Браузер читает HTML и строит DOM (Document Object Model) — дерево объектов, по одному узлу на тег. DOM — это уже не текст, а структура в памяти, с которой работает JavaScript.
  2. Подзапросы каскадом. Встретив <img>, <link rel="stylesheet">, <script>, браузер запускает новые HTTP-запросы — и каждый может тянуть свой DNS-резолв, соединение и ответ. Одна «страница» — это часто 50–100 запросов к разным серверам. Вот почему в первом SVG путь «DNS → соединение → HTTP» проходится не раз, а десятки раз.
  3. CSSOM и render tree. Параллельно из CSS строится CSSOM; вместе с DOM он даёт render tree — только видимые элементы с вычисленными стилями.
  4. Layout → paint → composite. Браузер считает геометрию каждого элемента (layout), растеризует их в слои (paint) и собирает слои воедино (composite). Любое изменение размеров запускает дорогой повторный reflow — отсюда советы по производительности фронтенда «не трогай layout в цикле».
  5. JavaScript оживляет страницу. JS-движок (V8 в Chrome, разобранный как пример JIT в статье От кода к исполнению) исполняет скрипты, которые меняют DOM, шлют фоновые запросы (fetch/AJAX) и перерисовывают части страницы без перезагрузки. Так статический документ превратился в приложение.

Исторически важен именно пункт 5: сначала веб отдавал готовые HTML-страницы целиком, каждый клик — новая загрузка. Сегодня распространён обратный подход — сервер отдаёт почти пустой HTML и пачку JavaScript, который уже в браузере рисует интерфейс и общается с сервером через JSON-API. Обе модели живут бок о бок, и выбор между ними — большая тема прикладной разработки.

Собираем всё вместе: полный путь запроса

Теперь склеим все шаги в одну картину — что происходит от Enter до первого пикселя.

Заметьте: сервер на шаге «запросить данные» сам стал клиентом — для базы данных. Как эти данные хранятся и почему обращение к ним переживает выключение сервера — тема следующей статьи. А само «строки результата» скрывает ещё один пласт: SQL, индексы, транзакции.

Как веб помнит вас, будучи беспамятным

Мы говорили: HTTP не имеет состояния. Как же тогда сайт узнаёт вас между запросами? Ответ — клиент носит удостоверение с собой в каждом запросе. Классический механизм — cookie. Сервер в ответе шлёт заголовок Set-Cookie: session=abc123, браузер сохраняет его и прикладывает Cookie: session=abc123 к каждому последующему запросу к этому домену. Сервер по этому идентификатору находит вашу сессию в своём хранилище.

Вариаций много: серверные сессии (в cookie лишь ссылка, данные на сервере) против токенов (JWT — вся информация в самом токене, сервер ничего не хранит); заголовок Authorization: Bearer ... для API. Но принцип один: раз сервер не помнит — клиент доказывает, кто он, в каждом запросе. Отсюда же и класс уязвимостей (кража cookie, CSRF, XSS), к которым мы вернёмся в статье Основы безопасности; флаги HttpOnly, Secure, SameSite на cookie — как раз оборона этого рубежа.

Кэширование и CDN: не делать работу дважды

Самый быстрый запрос — тот, которого не было. Веб пронизан кэшами на каждом уровне. Браузерный кэш хранит скачанные ресурсы согласно заголовку Cache-Control: max-age=.... Если ресурс мог измениться, работает условный запрос: браузер шлёт If-None-Match с сохранённым тегом ETag, и сервер, если ничего не поменялось, отвечает 304 Not Modified с пустым телом — экономя трафик. CDN (Content Delivery Network) — сеть серверов-зеркал по всему миру: статику (картинки, CSS, видео) отдаёт ближайший к вам узел, сокращая тот самый RTT. Именно поэтому крупный сайт грузится одинаково быстро из Токио и из Берлина.

Где эти абстракции протекают

Веб — башня протекающих абстракций, и знание их стыков отличает того, кто «чинит по наитию», от того, кто понимает систему.

  • «Загрузилось у меня — загрузится у всех». DNS-кэш, старые записи, региональные CDN и разный RTT означают, что у другого пользователя всё иначе. «У меня работает» — не диагноз.
  • HTTPS шифрует не всё. Содержимое — да, но имя хоста (через SNI и DNS) обычно видно. И замок в адресной строке говорит лишь «канал шифрован и это правда тот домен» — не «сайт честный». Фишинговый сайт тоже бывает с валидным сертификатом.
  • localhost и 127.0.0.1 — не всегда одно. Первое идёт через резолвинг имени (/etc/hosts, IPv6 ::1), второе — прямой IPv4. На этом ломаются локальные окружения.
  • CORS. Браузер из соображений безопасности запрещает JS со страницы a.com свободно читать ответы с b.com, пока b.com явно не разрешит это заголовками. Первое столкновение с CORS сбивает с толку всех — это не баг, а политика браузера, не сервера.
  • Кэш — источник половины «мистики». «Я же поправил, а браузер показывает старое» — почти всегда закэшированный ресурс. Отсюда приём cache busting: добавлять хеш в имя файла (app.9f2a.js), чтобы новая версия имела новый URL.
  • Состояние соединения хрупко. За NAT, прокси и балансировщиками «одно TCP-соединение» может рваться и переустанавливаться; веб-сокеты и long-polling живут с этим постоянно.

Типичные заблуждения

  • «Веб = интернет». Веб (HTTP/HTML/URL) — лишь один сервис поверх интернета; почта, видеозвонки, игры используют те же трубы, но не веб.
  • «URL — это адрес файла на диске сервера». Раньше — да; сегодня путь почти всегда обрабатывается программно, за ним нет никакого файла.
  • «DNS — это часть HTTP». Нет, это отдельная система (обычно поверх UDP), срабатывающая до всякого HTTP. Её сбой роняет доступ к сайту при полностью живом сервере.
  • «Сервер помнит, что я залогинен». Сам HTTP — нет; помнить помогает cookie/токен, который клиент носит с собой в каждом запросе.
  • «GET и POST — просто два способа послать форму». У них разный контракт: GET безопасен, идемпотентен и кэшируется, POST — нет. Отправлять деньги через GET — беда.

Мини-итог

Путь от Enter до страницы — это лестница абстракций, каждая ступень которой стоит на предыдущей. URL структурированно называет ресурс. DNS иерархически и с кэшированием превращает имя в IP. Поверх IP поднимаются TCP (надёжность) и TLS (шифрование и подлинность), и лишь затем по этому каналу идёт HTTP — простой текстовый протокол запрос-ответ с методами, кодами состояния и заголовками, не имеющий собственной памяти о клиенте. Браузер превращает полученный HTML в DOM, догружает каскад ресурсов, строит render tree и оживляет страницу через JavaScript. Беспамятность HTTP компенсируют cookie и токены, а скорость — кэши и CDN. Понимая, где каждая из этих абстракций протекает — от DNS-кэша до CORS и HTTPS, — вы отлаживаете веб как систему, а не гадаете. Это и есть тот «верхний этаж» стека, ради которого строились все нижние слои трека.

Источники для углубления: спецификации HTTP — RFC 9110 (семантика) и RFC 9114 (HTTP/3); учебник и справочник MDN Web Docs: HTTP; DNS — RFC 1034/1035; книга Ilya Grigorik, «High Performance Browser Networking» (бесплатно на hpbn.co).

Что дальше

На пути запроса сервер сам обратился к базе данных — и вернул данные, которые пережили его перезапуск. Как именно данные хранятся так, чтобы не исчезнуть при выключении, чем файл отличается от базы данных и что гарантируют транзакции — в следующей статье трека.

Как данные переживают выключение: файлы, БД, транзакции — обзор

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов