Как работают интернет и сайты: клиент, сервер, домены, HTTP
Ты открываешь браузер, набираешь адрес, жмёшь Enter — и через полсекунды на экране страница. За эти полсекунды успевает произойти десяток вещей: твой компьютер узнаёт, где физически стоит нужный сервер, устанавливает с ним соединение, договаривается о шифровании, просит нужный документ, получает его, скачивает картинки и стили и рисует всё это на экране.
Звучит страшно, но каждый шаг по отдельности простой. Разберём их по очереди, без спешки. В прошлой статье — как устроен компьютер — мы говорили про один компьютер. Теперь их станет два: твой и чужой.
Небольшое честное предупреждение: в статье есть примеры на Python. Устанавливать Python мы будем в следующей статье, так что сейчас можно просто читать код глазами — я показываю, что он выводит. А когда установишь, обязательно вернись и запусти их сам: это займёт пять минут и очень хорошо укладывает материал в голове.
Одна большая идея: интернет — это разговор двух программ
Всё, что происходит в интернете, сводится к одной схеме:
- Клиент — программа, которая просит. Браузер, мобильное приложение, твой будущий скрипт на Python.
- Сервер — программа, которая отвечает на просьбы. Она постоянно запущена на каком-то компьютере и ждёт обращений.
Обрати внимание: сервер — это программа, а не железный ящик. Слово «сервер» используют и для компьютера в дата-центре, и для программы на нём, и путаница здесь обычное дело. На одном физическом компьютере может работать сразу двадцать серверных программ. И наоборот — ты прямо сегодня можешь запустить сервер на своём ноутбуке, мы это сделаем ниже.
Аналогия: клиент — посетитель кафе, сервер — кухня. Ты не заходишь на кухню и не берёшь еду сам. Ты делаешь заказ по понятным правилам, кухня его выполняет и отдаёт результат.
Где аналогия ломается (и это важно): кухня помнит, что ты сидишь за третьим столиком и уже заказывал суп. А базовый веб-сервер по умолчанию не помнит о тебе ничего. Каждый запрос для него — как будто ты пришёл впервые. Это свойство называют отсутствием состояния (stateless). Именно поэтому придумали куки и токены — маленькие «бирки», которые клиент прикладывает к каждой просьбе, чтобы сервер понял: «а, это опять ты». Без них сайт забывал бы, что ты вошёл в аккаунт, после каждого клика.
Что происходит, когда ты открываешь сайт
Вот весь путь целиком. Не пытайся запомнить сразу — дальше разберём каждый шаг отдельно.
Ключевая деталь, которая удивляет новичков: страница приходит не одним куском. Сначала браузер получает HTML — текстовый документ с содержанием и ссылками на всё остальное. Читая его, браузер понимает: «мне ещё нужны эти стили, эти картинки, этот скрипт» — и делает новые запросы. Одна страница легко может означать 50–100 отдельных запросов к серверу.
Шаг 1. Адрес: из чего состоит ссылка
Строка в адресной строке браузера называется URL (Uniform Resource Locator — «единообразный указатель на ресурс»). Проще говоря — адрес конкретного документа в интернете. У него есть чёткая структура:
Разберём по частям:
- Схема (
https://) — по каким правилам вести разговор. Почти всегдаhttps, но бываютhttp,ftp,mailto:. - Домен, он же хост (
blog.example.com) — с кем разговаривать, имя нужного сервера. - Порт (
:443) — в какую «дверь» этого сервера стучать. Обычно его не пишут, потому что дляhttpsон по умолчанию 443, а дляhttp— 80. - Путь (
/posts/internet) — что именно попросить у сервера. Похоже на путь к файлу, но файла с таким именем может и не существовать: сервер вправе сгенерировать ответ на лету. - Параметры запроса (
?page=2&sort=new) — уточнения. Начинаются с?, разделяются&, каждый в видеимя=значение. - Якорь (
#comments) — место на странице, куда прокрутить. Самое интересное: якорь вообще не отправляется на сервер, им пользуется только браузер.
Проверим на практике. В Python есть готовый инструмент для разбора адресов — модуль urllib.parse:
# Импортируем готовые функции: "импортировать" значит "взять из библиотеки
# и сделать доступным в моей программе".
from urllib.parse import urlparse, parse_qs
adres = "https://blog.example.com:443/posts/internet?page=2&sort=new#comments"
# urlparse разбирает строку-адрес на части и возвращает объект,
# у которого можно спросить каждую часть по имени.
razobrano = urlparse(adres)
print("схема: ", razobrano.scheme)
print("хост: ", razobrano.hostname)
print("порт: ", razobrano.port)
print("путь: ", razobrano.path)
print("запрос: ", razobrano.query)
print("якорь: ", razobrano.fragment)
# parse_qs отдельно разбирает параметры в удобный вид
print("параметры:", parse_qs(razobrano.query))
Что выведет:
схема: https
хост: blog.example.com
порт: 443
путь: /posts/internet
запрос: page=2&sort=new
якорь: comments
параметры: {'page': ['2'], 'sort': ['new']}
Заметь: в parse_qs значения лежат в квадратных скобках — это списки. Так сделано потому, что один и тот же параметр может встретиться в адресе несколько раз (?tag=книги&tag=кино). Про списки подробно поговорим в статье про коллекции.
Шаг 2. Домены и DNS: телефонная книга интернета
Компьютеры не умеют находить друг друга по красивым именам вроде example.com. Им нужен номер — IP-адрес, например 93.184.216.34. Имена придуманы для людей, потому что запомнить example.com реально, а пятнадцать цифр — не очень.
Перевод имени в номер делает DNS (Domain Name System — «система доменных имён»). Это огромный распределённый справочник. Аналогия — телефонная книга: ты знаешь имя, ищешь номер, звонишь по номеру.
Домен читается справа налево, от общего к частному:
blog . example . com
| | |
| | └── домен верхнего уровня: .com, .ru, .org
| └── имя, которое кто-то зарегистрировал
└── поддомен: владелец example.com создаёт их сам, бесплатно и сколько угодно
Как браузер на самом деле ищет адрес — он не бежит сразу на край света, а проверяет всё более далёкие «шкафы» с записями:
или публичный, например 8.8.8.8"] D --> E{"Он знает ответ?"} E -- да --> Z E -- нет --> F["Он идёт к корневым серверам,
потом к серверам зоны .com,
потом к серверам домена example.com"] F --> G["Ответ найден и запоминается
на несколько минут или часов"] G --> Z
Слово кэш (cache) здесь означает «сохранённая копия ответа, чтобы не спрашивать заново». Кэш — одно из самых частых слов в IT, и почти всегда оно значит именно это.
Спросить DNS можно прямо из Python:
# socket — встроенный модуль для работы с сетью на низком уровне
import socket
imya = "example.com"
# gethostbyname буквально: "получить хост по имени"
ip = socket.gethostbyname(imya)
print(f"{imya} живёт по адресу {ip}")
Вывод (у тебя цифры почти наверняка будут другими — и это нормально):
example.com живёт по адресу 93.184.216.34
Почему другие? Крупные сайты живут не на одном компьютере, а на десятках в разных странах, и DNS отдаёт тот, что ближе к тебе. Так что «один сайт = один IP-адрес» — упрощение, удобное для начала, но неверное в жизни.
Шаг 3. Как данные вообще доезжают
Твой запрос не летит к серверу единым письмом. Он режется на пакеты — небольшие кусочки данных, у каждого написано, куда он едет и какой он по счёту. Пакеты идут через цепочку промежуточных устройств:
или телефон"] --> B["Домашний роутер
Wi-Fi"] B --> C["Сеть провайдера"] C --> D["Магистральные каналы
между городами и странами"] D --> E["Дата-центр"] E --> F["Сервер сайта"] F -. ответ едет обратно тем же путём .-> A
Каждое устройство по пути смотрит только на одно: «куда этот пакет и кому передать его дальше, чтобы приблизить к цели». Никто не держит в голове весь маршрут целиком — как на почте, где сортировщик знает только следующий город.
Разные пакеты одного запроса могут поехать разными путями и приехать не по порядку или потеряться. Собрать их обратно в правильном порядке и перезапросить потерянные — работа протокола TCP. Тебе как начинающему разработчику почти никогда не придётся с ним возиться напрямую: он просто работает под капотом и даёт тебе иллюзию надёжной непрерывной «трубы» между клиентом и сервером.
Полезное следствие: физика никуда не девается. Сигнал до сервера в другом полушарии идёт по кабелю сотни миллисекунд туда и обратно. Отсюда все разговоры про «задержку» (latency) и про то, почему серверы стараются ставить ближе к пользователям.
Шаг 4. HTTP — язык, на котором клиент говорит с сервером
Соединение установлено. Теперь надо о чём-то договориться, и для этого нужен общий язык. Этот язык называется HTTP (HyperText Transfer Protocol — «протокол передачи гипертекста»). Протокол — это просто согласованный набор правил: кто говорит первым, в каком формате и что означает ответ.
Самое приятное: HTTP-запрос — это обычный текст. Вот как выглядит просьба «дай мне главную страницу»:
GET /posts/internet HTTP/1.1
Host: blog.example.com
User-Agent: Mozilla/5.0
Accept: text/html
Разберём построчно:
GET— метод, то есть что мы хотим сделать.GETзначит «дай почитать»./posts/internet— путь, что именно просим.HTTP/1.1— версия протокола.- Дальше идут заголовки — строки вида
Имя: значениес дополнительной информацией.Hostговорит, какой именно сайт нужен (на одном сервере их могут быть сотни),User-Agent— какой программой мы пришли,Accept— что готовы принять.
Основные методы, которые стоит знать сразу:
| Метод | Что означает | Бытовая аналогия |
|---|---|---|
GET |
получить данные, ничего не менять | прочитать объявление на доске |
POST |
отправить данные, создать что-то новое | заполнить и сдать анкету |
PUT / PATCH |
изменить существующее | исправить запись целиком / поправить одно поле |
DELETE |
удалить | снять объявление с доски |
Правило, которое важно понять сразу: GET не должен ничего менять на сервере. Ссылку с GET можно открыть сто раз подряд, и ничего страшного не произойдёт. Именно поэтому формы входа и оплаты отправляются методом POST, а не GET.
Сервер отвечает в том же текстовом виде:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1256
<!doctype html>
<html>
<head><title>Пример</title></head>
<body><h1>Привет!</h1></body>
</html>
Первая строка — самая важная: код состояния. 200 OK значит «всё хорошо, вот твои данные». Дальше снова заголовки, потом пустая строка, потом тело ответа — собственно содержимое.
Коды ответов: как их читать
Коды трёхзначные, и первая цифра говорит главное:
200 OK, 201 Created"] B -- "3xx" --> D["Перенаправление:
иди по другому адресу.
301, 302"] B -- "4xx" --> E["Ошибка на стороне клиента:
это ты попросил не то.
400, 401, 403, 404"] B -- "5xx" --> F["Ошибка на стороне сервера:
ты попросил нормально,
сервер сломался. 500, 502, 503"] E --> G["Чинить: проверить адрес,
права, данные запроса"] F --> H["Чинить: смотреть логи сервера,
клиент тут ни при чём"]
Мини-словарь самых частых кодов:
- 200 OK — успех.
- 301 / 302 — «сайт переехал», браузер сам пойдёт по новому адресу.
- 400 Bad Request — «я не понял твой запрос», обычно кривые данные.
- 401 Unauthorized — «ты не представился», нужен вход.
- 403 Forbidden — «я знаю, кто ты, но тебе сюда нельзя».
- 404 Not Found — «такого адреса у меня нет». Самый знаменитый код в интернете.
- 500 Internal Server Error — «на сервере упала программа».
- 502 / 503 — «сервер-посредник не смог достучаться до основного» / «сервер перегружен».
Разница между 4xx и 5xx — практически самое полезное, что можно вынести из этой главы. Когда что-то не работает, она сразу говорит, в чьём огороде искать проблему.
Буква S в HTTPS
https — это тот же HTTP, но весь разговор зашифрован. Без шифрования любой, через чьё оборудование проходят твои пакеты (кафе с бесплатным Wi-Fi, провайдер), может прочитать их как открытку. С шифрованием — видит только запечатанный конверт: адрес получателя виден, содержимое нет.
Работает это через сертификат — электронный документ, который сайт предъявляет браузеру. Сертификат выдаёт доверенный центр сертификации, и он подтверждает: «этот сервер действительно принадлежит владельцу example.com». Замочек в адресной строке означает ровно это и не означает, что сайт честный: мошеннический сайт тоже легко получает сертификат. Замочек говорит «связь защищена», а не «этим людям можно верить».
Сегодня по HTTPS работает практически весь веб, а браузеры прямо ругаются на страницы без него.
Из чего состоит сайт: фронтенд, бэкенд и база данных
Слова, которые ты будешь слышать каждый день:
содержание и структура"] C["CSS
внешний вид"] J["JavaScript
поведение и реакции"] end subgraph S["Сервер: то, чего не видно — бэкенд"] P["Программа
Python, Go, C#, Java..."] DB[("База данных
хранит информацию")] end K <-->|"HTTP-запросы и ответы"| S P <--> DB
- HTML — текст с разметкой: где заголовок, где абзац, где картинка. Скелет.
- CSS — правила оформления: цвета, шрифты, отступы. Одежда.
- JavaScript — единственный язык, который умеет выполнять браузер. Он делает страницу живой: реагирует на клики, подгружает данные без перезагрузки.
- Бэкенд — программа на сервере. Она решает, что показать: проверяет пароль, считает корзину, ходит в базу данных. Писать её можно почти на любом языке, в том числе на Python.
- База данных — программа для надёжного хранения и быстрого поиска информации.
Важное для новичка: пользователь может увидеть и изменить всё, что происходит у него в браузере. Поэтому проверки «а можно ли этому человеку удалить чужой комментарий» всегда делаются на сервере. Проверка только на фронтенде — это не защита, а вежливая просьба.
Сделай сам: свой сервер за одну команду
Проще всего понять клиент-серверную схему, побывав обеими сторонами. Python умеет поднимать простейший веб-сервер одной командой. Создай где-нибудь папку, положи в неё файл index.html с текстом:
<!doctype html>
<html>
<head><title>Мой первый сайт</title></head>
<body><h1>Привет! Это мой сервер.</h1></body>
</html>
Затем в этой папке выполни в терминале (терминалу посвящена статья про командную строку и Git, пока просто повтори команду):
python3 -m http.server 8000
Программа ответит примерно так и «зависнет» — это нормально, сервер работает и ждёт запросов:
Serving HTTP at :: port 8000 (http://[::]:8000/) ...
Теперь открой в браузере http://localhost:8000. Ты увидишь свою страницу.
localhost— специальное имя, означающее «этот же самый компьютер». Ему соответствует IP-адрес127.0.0.1. Запрос никуда не уходит в интернет.8000— тот самый порт, номер двери. Мы указали его явно, потому что 80 и 443 обычно заняты и требуют особых прав.
Загляни обратно в терминал: там появились строки вроде "GET / HTTP/1.1" 200 -. Это логи — записи сервера о том, что с ним происходило. Ты своими глазами видишь метод, путь и код ответа. Чтобы остановить сервер, нажми Ctrl + C.
Делаем HTTP-запрос из Python
Теперь наоборот: побудем клиентом. В Python это умеет встроенный модуль urllib.request.
from urllib.request import urlopen
# urlopen отправляет GET-запрос и возвращает объект ответа.
# Конструкция "with ... as ..." гарантирует, что соединение
# закроется само, даже если внутри что-то пойдёт не так.
with urlopen("https://example.com") as otvet:
print("Код ответа: ", otvet.status)
print("Тип содержимого: ", otvet.headers["Content-Type"])
# read() даёт сырые байты, decode превращает их в текст
stranica = otvet.read().decode("utf-8")
print("Длина страницы: ", len(stranica), "символов")
print("Первые 60 символов:")
print(stranica[:60])
Вывод будет примерно такой (длина и заголовки у тебя могут немного отличаться — сайты меняются):
Код ответа: 200
Тип содержимого: text/html; charset=UTF-8
Длина страницы: 1256 символов
Первые 60 символов:
<!doctype html><html lang="en"><head><title>Example Domain
Вот и всё: три строки кода сделали ровно то же, что браузер, — только без рисования на экране. Браузер по сути и есть очень навороченный HTTP-клиент с художественными способностями.
А теперь попросим то, чего нет:
from urllib.request import urlopen
from urllib.error import HTTPError
try:
with urlopen("https://example.com/takoy-stranicy-net") as otvet:
print("Всё хорошо, код:", otvet.status)
except HTTPError as oshibka:
# Сюда попадаем, если сервер ответил кодом 4xx или 5xx
print("Сервер ответил ошибкой.")
print("Код: ", oshibka.code)
print("Расшифровка:", oshibka.reason)
Сервер ответил ошибкой.
Код: 404
Расшифровка: Not Found
Конструкция try / except означает «попробуй сделать это, а если случится ошибка — не падай, а выполни вот это». Подробно разберём её в статье про ошибки и отладку.
API и JSON: когда данные нужны программе, а не человеку
HTML — формат для человека: там про шрифты и абзацы. Но часто одной программе нужны данные от другой, и оформление ей только мешает. Для этого сайты делают API (Application Programming Interface) — специальные адреса, которые отдают не страницу, а голые данные.
Чаще всего в формате JSON — это просто текст, устроенный как список пар «имя: значение»:
{
"gorod": "Казань",
"temperatura": 21,
"dozhd": false,
"prognoz": [22, 24, 19]
}
Читается почти как обычный текст: город такой-то, температура 21, дождя нет, прогноз на три дня. Работает это по тому же самому HTTP: тот же GET, те же коды ответов, отличается только Content-Type — application/json вместо text/html. Разбирать JSON в Python мы научимся в статье про файлы и форматы данных.
Типичные ошибки новичков и что означают сообщения
«Сайт не найден» / NameResolutionError / getaddrinfo failed. Дословно: «не удалось превратить имя в адрес». Сломался самый первый шаг — DNS. Причины: опечатка в домене, нет интернета, домен не существует. До сервера дело даже не дошло.
Connection refused — «в соединении отказано». Имя нашлось, компьютер найден, но на указанном порту никто не слушает. Классика: ты остановил свой локальный сервер и обновляешь страницу, или указал порт 8080 вместо 8000.
Connection timed out — «время ожидания истекло». Мы постучали и не дождались ответа вообще. Обычно это брандмауэр молча выбрасывает пакеты или сервер перегружен. Отличие от предыдущей ошибки: там нам сказали «нет», здесь просто промолчали.
Путаница http и https. Если сервер работает только по HTTPS, запрос на http:// в лучшем случае перенаправится, в худшем — не сработает. И наоборот: локальный python -m http.server не умеет HTTPS, к нему надо идти по http://localhost:8000.
«Я поменял файл, а в браузере всё старое». Скорее всего, виноват кэш. Обнови страницу с игнорированием кэша: Ctrl + F5 (в macOS Cmd + Shift + R).
Address already in use — «адрес уже используется». Ты пытаешься запустить сервер на порту, который занят другой программой (часто — твоим же сервером, забытым в соседнем окне терминала). Реши просто: возьми другой номер, например 8001.
Ожидание, что сервер «помнит» тебя. Помним про stateless: если ты не передал куку или токен, сервер не знает, что это ты. Многие первые баги в веб-разработке растут именно отсюда.
Смешение 404 и 500 при отладке. Увидел 404 — проверяй адрес и маршруты в своей программе. Увидел 500 — иди читать логи сервера, в браузере ответа нет.
Практические задания
Начни с первого, дальше по нарастающей. Первые два можно сделать вообще без установки Python.
1. Разбери адрес руками. Возьми любую длинную ссылку из своего браузера — из поиска, маркетплейса, YouTube — и выпиши на бумаге: схема, домен, путь, параметры, якорь. Затем поудаляй параметры по одному и посмотри, что изменится на странице.
Подсказка: у видео на YouTube параметр t отвечает за время начала — попробуй поменять его число.
2. Посмотри на настоящие запросы. Открой в браузере инструменты разработчика (F12), вкладку Network («Сеть») и обнови любую страницу. Ответь: сколько всего запросов ушло? Какой самый большой по размеру? Есть ли коды, отличные от 200?
Подсказка: если список пустой — открой инструменты и только потом обновляй страницу.
3. Свой сервер и своя страница. Повтори пример с python3 -m http.server, но добавь в папку вторую страницу about.html и картинку. Открой их по прямым адресам. Затем попроси у сервера заведомо несуществующий файл и найди в логах терминала код 404.
Подсказка: адрес второй страницы — http://localhost:8000/about.html.
4. Определи, куда указывают домены. Напиши программу, которая для списка сайтов выводит их IP-адреса, а если имя не находится — пишет об этом и продолжает работу, а не падает.
Подсказка: возьми socket.gethostbyname из статьи, оберни его в try / except socket.gaierror и пройди по списку циклом (циклы — в статье про циклы, можно и просто повторить код три раза).
5. Мини-исследование кодов ответа. Напиши программу, которая обращается к нескольким адресам и печатает для каждого код ответа: заведомо рабочий, заведомо несуществующий на реальном сайте и адрес с опечаткой в домене. Сделай так, чтобы программа дошла до конца и напечатала итог по всем трём.
Подсказка: тебе понадобятся три разных «ловушки»: HTTPError для кодов 4xx и 5xx, URLError для проблем с сетью и DNS. HTTPError нужно ловить первым.
Мини-итог
- Интернет — это разговор двух программ: клиента, который просит, и сервера, который отвечает.
- URL — адрес документа; он состоит из схемы, домена, порта, пути, параметров и якоря, и каждая часть имеет смысл.
- DNS переводит понятное человеку имя в IP-адрес, а результат кэшируется на каждом шагу.
- Данные едут пакетами через цепочку промежуточных устройств; надёжность обеспечивает TCP.
- HTTP — текстовый протокол: запрос состоит из метода, пути и заголовков, ответ — из кода состояния, заголовков и тела.
- Первая цифра кода ответа сразу говорит, где искать проблему: 4xx — у клиента, 5xx — на сервере.
- HTTPS шифрует разговор; замочек означает «связь защищена», а не «сайту можно верить».
- Сайт — это фронтенд в браузере и бэкенд с базой данных на сервере, а всё, что происходит у пользователя, доверять нельзя.
Если из всей статьи ты запомнишь только «клиент просит — сервер отвечает, а 4xx и 5xx показывают, кто виноват» — этого уже достаточно, чтобы уверенно двигаться дальше.
Полезные источники
- MDN: Как работает Веб — короткое официальное объяснение с картинками, на русском.
- MDN: Обзор HTTP — дальше по ссылкам вся справка по методам и кодам ответа.
- HTTP Cats — все коды ответов, проиллюстрированные котами. Несерьёзно, но запоминается намертво.
- Документация Python: urllib.request — официальная справка по модулю из наших примеров.
- How DNS works — комикс про DNS на английском, объясняет лучше многих учебников.
Что дальше
Теорию мы прошли: ты знаешь, как компьютер устроен внутри и как компьютеры разговаривают друг с другом. Пора перестать читать и начать писать — устанавливаем Python, разбираемся, где вообще набирать код, и запускаем первую собственную программу.