Почти всё, что вы делаете в вебе, — открываете страницу, отправляете форму, дёргаете чужой API — это обмен HTTP-запросами и ответами. HTTP (HyperText Transfer Protocol) — это простой текстовый диалог между клиентом и сервером: клиент говорит «дай мне вот это» или «сохрани вот это», сервер отвечает «держи» или «не могу, вот почему». Никакой магии: если понять анатомию одного запроса и одного ответа, понятен будет весь протокол.

Хорошая новость — HTTP человекочитаемый. Запрос и ответ можно распечатать глазами и прочитать как письмо: сверху что просят, ниже условия, в конце — содержимое. Разберём эту структуру по частям, а потом посмотрим на методы, статусы и заголовки — три вещи, которыми backend-разработчик пользуется каждый день.

Анатомия запроса и ответа

Любой HTTP-запрос состоит из четырёх частей, идущих строго по порядку: стартовая строка, заголовки, пустая строка и (необязательно) тело.

Вот как выглядит запрос целиком:

POST /orders HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGc...

{"productId": 42, "quantity": 2}

Разберём построчно:

  • Стартовая строка POST /orders HTTP/1.1 — что делаем (метод POST), с чем (путь /orders) и по какой версии протокола.
  • Заголовки — пары «имя: значение», метаданные запроса: на каком хосте искать ресурс, в каком формате тело, кто мы такие.
  • Пустая строка — граница, отделяющая заголовки от тела. Без неё сервер не поймёт, где кончились метаданные.
  • Тело — сами данные. У GET его обычно нет, у POST/PUT — есть.

Ответ устроен так же зеркально:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /orders/1001

{"id": 1001, "status": "created"}

Стартовая строка ответа несёт код статуса (201) и его текстовую расшифровку (Created). Дальше — те же заголовки, пустая строка и тело. Вот и весь протокол: диалог из таких сообщений.

Методы: что мы хотим сделать

Метод — это глагол запроса, он говорит серверу о намерении. Основных пять:

  • GET — «дай мне ресурс». Чтение, ничего не меняет. Запрос списка заказов, одной страницы, картинки — всё это GET.
  • POST — «создай новое» или «выполни действие». Отправка формы, создание заказа. Каждый вызов, как правило, создаёт новую сущность.
  • PUT — «замени ресурс целиком» вот этим содержимым. Если ресурса нет — создаст, если есть — перезапишет полностью.
  • PATCH — «измени ресурс частично». Прислать только те поля, что поменялись, а не весь объект.
  • DELETE — «удали ресурс».

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

Безопасные и идемпотентные методы

Два свойства методов, которые звучат заумно, но решают вполне практичную задачу — что будет, если запрос повторить.

Безопасный (safe) метод ничего не меняет на сервере. GET безопасен: сколько раз ни запрашивай страницу, состояние сервера не сдвинется. Поэтому браузер спокойно делает GET при переходе по ссылке, а прокси — кэширует.

Идемпотентный метод можно повторить сколько угодно раз, и результат будет тем же, что после первого раза. PUT идемпотентен: «поставь адрес X» — хоть один раз, хоть пять, в итоге адрес равен X. DELETE тоже: удалили один раз, повторный запрос застанет ресурс уже удалённым — состояние не изменилось. А вот POST не идемпотентен: три POST /orders создадут три заказа.

Почему это важно для backend. Сети ненадёжны: ответ может потеряться по дороге, и клиент, не дождавшись, повторит запрос. Если метод идемпотентен, повтор безопасен — ничего не задвоится. Если нет (как POST), повтор может создать дубликат, и защищаться от этого приходится отдельно — ключом идемпотентности, при котором сервер узнаёт повторную попытку и не создаёт вторую сущность.

МетодБезопасныйИдемпотентный
GETдада
PUTнетда
DELETEнетда
POSTнетнет
PATCHнетнет

Коды статусов: что ответил сервер

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

2xx — успех. Всё хорошо.

  • 200 OK — стандартный успешный ответ, тело содержит результат.
  • 201 Created — создан новый ресурс (типичный ответ на POST); в заголовке Location — адрес созданного.

3xx — перенаправление. Ресурс не здесь, иди в другое место.

  • 301 Moved Permanently — переехал навсегда, запомни новый адрес.

4xx — ошибка клиента. Виноват тот, кто прислал запрос: не то попросил, не так оформил.

  • 400 Bad Request — запрос кривой: невалидный JSON, не хватает поля.
  • 401 Unauthorized — не представился, нужна аутентификация (кто ты?).
  • 403 Forbidden — представился, но прав на это нет (я знаю, кто ты, но нельзя).
  • 404 Not Found — такого ресурса нет.
  • 409 Conflict — конфликт с текущим состоянием: например, создаёшь то, что уже существует.

5xx — ошибка сервера. Клиент всё сделал правильно, сломалось на стороне сервера.

  • 500 Internal Server Error — что-то упало внутри, необработанное исключение.
  • 502 Bad Gateway — сервер выступал посредником и получил невнятный ответ от того, к кому обращался.
  • 503 Service Unavailable — сервис временно недоступен (перегрузка, обслуживание).

Практическая разница между 4xx и 5xx огромна. 4xx — «исправь запрос, повтор с тем же телом не поможет». 5xx — «на моей стороне беда, попробуй позже» — вот такие ответы часто имеет смысл повторять с задержкой.

Ключевые заголовки

Заголовки — это условия сделки, метаданные вокруг тела. Их десятки, но для старта хватит нескольких:

  • Content-Type — в каком формате тело: application/json, text/html, image/png. По нему получатель понимает, как разбирать содержимое.
  • Authorization — учётные данные: Bearer <token>. Так клиент доказывает, кто он.
  • Cache-Control — правила кэширования: no-store (не хранить вообще), max-age=3600 (можно держать час). Управляет тем, будут ли браузеры и прокси переспрашивать сервер или отдадут сохранённую копию.
  • Location — куда смотреть: адрес нового ресурса при 201 или адрес переезда при 3xx.

Заголовки есть и в запросе, и в ответе, и часть из них парная: клиент говорит Accept: application/json («хочу JSON»), сервер отвечает Content-Type: application/json («вот тебе JSON»).

HTTP stateless: каждый запрос сам по себе

Важное свойство: HTTP не имеет состояния (stateless). Сервер не помнит предыдущий запрос — каждое сообщение самодостаточно и несёт всё нужное для обработки. Второй запрос не знает, что был первый.

Аналогия — разговор с оператором, который после каждой фразы теряет память. Чтобы он вас понял, в каждой реплике приходится заново называть себя и суть дела. Именно поэтому Authorization шлётся с каждым запросом: сервер не помнит, что минуту назад вы уже представились.

Для backend это скорее подарок. Раз сервер не хранит контекст между запросами, любой запрос может обработать любой экземпляр сервиса — можно поднять десять копий за балансировщиком, и они взаимозаменяемы. Состояние (сессии, корзины) выносят в общее хранилище, а сами серверы остаются одинаковыми и легко масштабируются.

Где это применяется

HTTP — это фундамент, на котором стоит почти весь backend: REST-API, вебхуки, обращения между микросервисами. Владеть им — значит понимать не только как отправить запрос, но и как правильно ответить.

  • Возвращайте правильные статусы. Создали ресурс — 201, а не 200. Не нашли — 404, а не 200 с пустым телом. Невалидный ввод — 400, нет прав — 403. Клиенты (и мониторинг) принимают решения по коду статуса, поэтому «всегда 200, а ошибка в теле» ломает всю автоматику вокруг.
  • Разделяйте 4xx и 5xx честно. Ошибка валидации — это 4xx (виноват запрос), а не 500. Если отдавать 500 на кривой ввод, метрики покажут ложную аварию, а клиент начнёт зря ретраить то, что повтором не чинится.
  • Держите идемпотентность под ретраи. Операции, которые клиент может безопасно повторить (замена, удаление), проектируйте идемпотентными. Для неидемпотентных создающих операций закладывайте ключ идемпотентности, чтобы потерянный ответ и повторный запрос не создали дубль — платёж или заказ дважды.

Где спотыкаются начинающие:

  • Отдают 200 на всё, даже на ошибки. Тогда клиент вынужден парсить тело, чтобы понять, успех это или провал, — а весь смысл кодов статусов в том, чтобы это было видно сразу.
  • Путают 401 и 403. 401 — не знаю, кто ты (нужна аутентификация); 403 — знаю, но тебе нельзя (нет прав). Разные проблемы и разные исправления.
  • Считают GET местом для изменений. «Дёрну GET /delete?id=5» — и однажды поисковый робот или предзагрузчик браузера пройдётся по ссылкам и снесёт данные. GET обязан быть безопасным.
  • Забывают, что HTTP stateless. Ждут, что сервер «помнит» прошлый запрос, и не шлют аутентификацию заново — а он не помнит ничего.

Что учить дальше

HTTP не живёт в вакууме. Снизу его несёт транспорт — как запрос вообще доезжает до сервера, разобрано в статье про модели OSI и TCP/IP. Поверх HTTP кладётся шифрование: тот же протокол, но в защищённом канале — это HTTPS и TLS. Сам протокол тоже развивался — чем HTTP/2 и HTTP/3 отличаются от старого HTTP/1.1, смотрите в статье про версии HTTP. А когда что-то в этом обмене ломается — таймауты, повторы, коды 5xx — на помощь приходят приёмы из статьи про надёжность.

Следующий большой шаг — как из этих методов и статусов собрать удобный и предсказуемый интерфейс сервиса. Это уже проектирование API: продолжение — в разделе про REST API.