Почти всё, что вы делаете в вебе, — открываете страницу, отправляете форму, дёргаете чужой 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.