1. Успех не всегда 200 OK
Вы уже видите, что один и тот же ресурс можно читать, создавать или удалять разными методами. Теперь к этому нужен второй слой контракта: как сервер одним числом объясняет, чем именно всё закончилось. Сначала разберём разные виды успеха — так потом будет проще так же точно различать и ошибки.
Иногда кажется, что можно жить очень просто: если всё хорошо — всегда возвращаем 200, если всё плохо — «какой‑нибудь 500». На короткой дистанции это даже работает, как работает скотч: держит, пока вы на него смотрите. Но как только у вас появляется хотя бы один клиент (Postman — уже клиент), контракт начинает жить своей жизнью, и точность статуса внезапно экономит нервы всем участникам.
Представьте, что вы заказали пиццу. Есть большая разница между сообщениями: «OK, заказ принят», «OK, заказ создан, вот его номер», и «OK, мы всё сделали, но никаких деталей не будет». Если вы всегда будете отвечать «OK», пользователь (и клиентский код) начнут додумывать: «OK… а что именно OK?». HTTP-статусы как раз и придуманы, чтобы не заставлять клиента гадать.
В нашем курсе эта точность особенно важна, потому что мы постепенно строим мышление backend-разработчика. Backend — это не только «вернуть данные», но и вернуть предсказуемое поведение: клиент должен понимать, что произошло, по одним и тем же правилам каждый раз.
Класс 2xx: смысл успеха
Когда видите статус из группы 2xx, это означает, что сервер успешно обработал запрос. Но «успешно» в HTTP — не эмоциональная оценка («мы молодцы»), а техническое описание результата. Разные 2xx отвечают на разные вопросы: «Есть ли что читать в ответе?», «Создали ли мы новый ресурс?», «Нужно ли клиенту ожидать тело?».
Важно поймать одну мысль: HTTP-статус — это часть контракта, и он должен совпадать по смыслу с тем, что реально произошло. То есть вы выбираете статус не «по привычке» и не «потому что так проще», а потому что статус должен объяснять клиенту, какой именно успех случился.
В рамках нашего учебного набора мы фиксируем три базовых «успешных» ответа, которые вы будете встречать постоянно:
- 200 OK — успех, обычно с полезным телом ответа;
- 201 Created — успех, где создан новый ресурс;
- 204 No Content — успех, где тела нет и быть не должно.
И ещё один маленький, но важный нюанс: тело ответа (body) — штука необязательная. Очень многие начинающие думают, что «ответ = body». Но нет: ответ в HTTP — это статус + заголовки + (возможно) body. Бывает успех без body — и это не «урезанный успех», а вполне нормальная ситуация.
2. 200 OK: успех с полезным телом
200 OK — это самый частый успешный статус, поэтому он коварный: к нему легко привыкнуть как к «универсальной таблетке». Но лучше относиться к нему иначе: 200 — это ответ «успех, и вот результат, который вам имеет смысл прочитать». Если вы возвращаете данные ресурса, список ресурсов или результат операции в виде полезной информации — 200 выглядит естественно.
Самый понятный сценарий 200 — чтение данных. Например, GET коллекции или GET одного ресурса. Вы ещё не пишете сервер в этом модуле, но уже можете думать контрактом. Для будущего local API проекта ReadLater Starter это будет звучать так: «получить список книг в моём списке чтения» или «получить одну конкретную запись по id». В таких случаях клиент ожидает body, потому что пришёл «за данными».
На уровне «мини-примера» (без реального сервера) это можно описать почти как обычную структуру данных:
// HTTP-метод запроса (что делаем)
String method = "GET";
// Путь (к какому ресурсу обращаемся)
String path = "/api/v1/reading-list/42";
// HTTP-статус ответа (как сервер описывает результат)
int status = 200;
// Тело ответа: то, что клиент реально будет читать и обрабатывать
String body = "Clean Code"; // Представим, что это полезные данные
Смысл здесь не в том, что body обязано быть строкой. Смысл в том, что 200 — это хороший выбор, когда ответ содержит полезный результат, который клиент будет читать и обрабатывать.
Чуть ближе к «как будет в реальном API» (но всё ещё без раннего ухода в JSON и сервер) можно представить, что body — это JSON-строка:
// Упрощённая модель HTTP-ответа: только статус и тело
record SimpleHttpResponse(int status, String body) {}
SimpleHttpResponse response = new SimpleHttpResponse(
200,
// JSON как текст: в реальном API это часто будет сериализованный объект
"""
{"id":42,"title":"Clean Code"}
"""
);
System.out.println(response.status()); // 200 (клиент понимает: успех, можно читать body)
System.out.println(response.body()); // {"id":42,"title":"Clean Code"} (то, что будем парсить)
Обратите внимание: клиенту удобно. Он видит 200 и понимает, что можно парсить body как «нормальный результат». Это влияет даже на простую Postman-проверку: вы уже не просто смотрите на красивый JSON, вы видите согласованность статуса и содержимого.
Важно также не путать 200 с «созданием». Если вы сделали POST и реально создали новый ресурс, 200 не будет ошибкой уровня «сервер упал», но будет слабым сигналом. Клиенту придётся понимать создание по косвенным признакам (например, «появился id»), а это хуже, чем чёткий статус 201.
3. 201 Created: создание ресурса
201 Created — это статус, который часто забывают, потому что «ну 200 же тоже успех». Но в нормальном контракте 201 — это очень полезная точность: он говорит клиенту не просто «всё хорошо», а «мы создали новый ресурс». То есть на стороне сервера появилось что-то новое, и это «что-то» теперь имеет свою идентичность (обычно свой id и свой адрес).
Самый типичный сценарий — POST на коллекцию. В нашем домене ReadLater это будет выглядеть примерно как «добавить новую книгу в мой список чтения». Клиент отправляет данные (пока не важно, где именно они лежат — про body подробнее будет позже), сервер создаёт запись, генерирует ей id, и возвращает 201.
Мини-пример в терминах «сценарий запроса/ответа»:
// Создаём новый элемент в коллекции
String method = "POST";
// Публикуем в коллекцию, а не в конкретный id
String path = "/api/v1/reading-list";
// Статус "Created" прямо говорит: появился новый ресурс
int status = 201;
// В реальном API здесь часто возвращают представление созданного ресурса (например, с id)
String body = "created item";
Что важно понять: 201 не означает «мы просто сделали какую-то операцию». Он означает именно создание. Если ничего нового не появилось, не надо «для красоты» ставить 201. Это как поставить на двери табличку «Выход», хотя там кладовка: формально дверь есть, но пользователь будет очень удивлён.
Часто вместе с 201 сервер сообщает клиенту, где находится созданный ресурс (например, «вот адрес /api/v1/reading-list/43»). В HTTP это обычно делается через заголовок Location. Но чтобы не забежать вперёд, зафиксируем это как мысль: 201 — это «создали», а «как сообщить адрес созданного» — мы аккуратно разберём в следующей части дня, когда будем говорить про headers.
При этом 201 не запрещает иметь body. Наоборот, во многих API удобно вернуть представление созданного объекта (например, уже с присвоенным id). Просто держите в голове простое правило: главный смысл 201 — создание, а наличие body — это уже вопрос удобства для клиента.
4. 204 No Content: успех без тела
204 No Content звучит как «ну, наверное, сервер поленился ответить». На самом деле это отличный статус для ситуаций, когда операция прошла успешно, но в ответе нет полезного тела, и клиенту не нужно ничего парсить. Это особенно логично там, где результат — сам факт изменения состояния, а не «данные для чтения».
Самый понятный пример — DELETE. Вы удалили ресурс. Что дальше? Можно, конечно, вернуть 200 и написать «deleted» в body. Но это часто выглядит как лишний шум: клиент и так видит, что операция успешна, а полезных данных нет. Для таких случаев 204 — красивый контракт: «успех, но тела не будет».
Мини-пример:
// Удаляем конкретный ресурс
String method = "DELETE";
String path = "/api/v1/reading-list/42";
// "No Content" означает: успех есть, но тела НЕ будет
int status = 204;
// В реальном ответе тела вообще не должно быть (и часто в коде его просто нет как сущности)
String body = null;
И вот здесь важный момент дня: если выбран 204, тело ответа должно отсутствовать. Не «пустая строка», не «{} на всякий случай», не «ok». Именно отсутствовать. Почему? Потому что 204 — это сигнал клиенту «не читай тело». Клиент (или библиотека) может даже не пытаться его прочитать. А вы в ответ отправили body — и получили контракт, который сам себе противоречит.
Если хочется «вернуть что-то полезное» после операции изменения, то чаще всего это уже повод вернуться к 200 и действительно вернуть полезные данные. Но если полезных данных нет — 204 выигрывает своей честностью: он не заставляет клиента притворяться, что есть что парсить.
5. Выбор: 200, 201, 204
Когда вы впервые начинаете проектировать HTTP-ответы, мозг просит железобетонное правило: «Скажи мне один код на все случаи, и я пойду пить чай». Но реальность чуть богаче. Хорошая новость: между 200, 201 и 204 можно выбирать очень простым способом, если вы честно отвечаете на два вопроса: «создали ли мы новый ресурс?» и «есть ли что читать в ответе?».
Ниже — компактная таблица выбора, которая хорошо работает в рамках нашего курса:
| Что произошло на сервере | Есть полезное body для клиента? | Статус |
|---|---|---|
| Мы просто успешно вернули данные (чтение / результат операции) | Да | 200 OK |
| Мы создали новый ресурс | Обычно да (но не обязательно) | 201 Created |
| Операция успешна, но возвращать нечего | Нет | 204 No Content |
Если вы больше любите схемы, то это можно запомнить так:
flowchart TD
A["Запрос обработан успешно"] --> B{"Создан новый ресурс?"}
B -->|Да| C["201 Created"]
B -->|Нет| D{"Есть полезные данные в ответе?"}
D -->|Да| E["200 OK"]
D -->|Нет| F["204 No Content"]
Эта схема не претендует на покрытие всего мира HTTP. В мире есть 202, 206, 304 и куча других кодов. Но методическая цель курса — чтобы вы уверенно различали самые частые и базовые случаи и не делали из 200 единственный молоток, которым забивают и гвозди, и шурупы, и иногда собственный палец.
6. Типичные ошибки при работе с 2xx-ответами
Ошибка №1: «Всё успешное — это 200».
Это самая частая привычка новичка: 200 воспринимается как «success = true». Проблема в том, что клиенту часто важно различать чтение, создание и «успешно, но без данных». Если вы всегда ставите 200, клиент начинает угадывать смысл по body или по косвенным признакам, а контракт становится менее предсказуемым.
Ошибка №2: ставить 201, когда ресурс на самом деле не создавался.
Иногда 201 ставят просто потому, что операция «важная» или потому что где-то видели в примере. Но 201 — это не «успех повышенной важности», а конкретное утверждение: «создан новый ресурс». Если нового ресурса нет, вы даёте клиенту ложную информацию, а дальше начинаются странные ожидания: «А где мой новый id? А где адрес нового объекта?».
Ошибка №3: отправлять body вместе с 204.
Это прямое противоречие внутри ответа. 204 говорит клиенту: «тела не будет». Клиент может не читать body вообще, а вы туда что-то записали. В итоге часть клиентов будет вести себя «как получится», а вы получите труднообъяснимые баги вида «в Postman вижу текст, а в коде почему-то пусто».
Ошибка №4: путать «нет данных» с «ошибка».
Часто начинающие думают: «если нечего вернуть — значит какая-то проблема». Но «нечего вернуть» может быть абсолютно нормальным успешным исходом, особенно для удаления. Для этого и существует 204: он говорит «успех», даже если body отсутствует. (Про реальные ошибки и коды 4xx/5xx мы поговорим в следующей лекции — там как раз будет весь «негативный мир».)
Ошибка №5: выбирать код ответа после того, как уже «написали тело».
Очень типичный порядок мыслей: сначала придумали body, потом в конце «а, точно, статус». В итоге статус получается случайным. Более здоровая привычка — сначала ответить на вопрос «что произошло?», затем выбрать статус, и только потом решать, нужно ли тело ответа и какое именно. Так контракт получается согласованным, а не собранным из кусочков.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ