Программирование с нуля Как работают интернет и сайты: клиент, сервер, домены, HTTP
0%

Как работают интернет и сайты: клиент, сервер, домены, HTTP

Как работают интернет и сайты: клиент, сервер, домены, 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 создаёт их сам, бесплатно и сколько угодно

Как браузер на самом деле ищет адрес — он не бежит сразу на край света, а проверяет всё более далёкие «шкафы» с записями:

Слово кэш (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. Как данные вообще доезжают

Твой запрос не летит к серверу единым письмом. Он режется на пакеты — небольшие кусочки данных, у каждого написано, куда он едет и какой он по счёту. Пакеты идут через цепочку промежуточных устройств:

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

Разные пакеты одного запроса могут поехать разными путями и приехать не по порядку или потеряться. Собрать их обратно в правильном порядке и перезапросить потерянные — работа протокола 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 — успех.
  • 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 работает практически весь веб, а браузеры прямо ругаются на страницы без него.

Из чего состоит сайт: фронтенд, бэкенд и база данных

Слова, которые ты будешь слышать каждый день:

  • 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-Typeapplication/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, разбираемся, где вообще набирать код, и запускаем первую собственную программу.

Первая программа: устанавливаем инструменты и запускаем код

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

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

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

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