Какой HTTP-код ответа следует возвращать при использовании POST для создания ресурса, который уже существует?
Я создаю сервер, который позволяет клиентам хранить объекты. Эти объекты полностью формируются на стороне клиента, включая идентификаторы объектов, которые являются постоянными на протяжении всего жизненного цикла объекта.
Я определил API так, чтобы клиенты могли создавать или изменять объекты с помощью PUT:
PUT /objects/{id} HTTP/1.1
...
{JSON-представление объекта}
{id} - это идентификатор объекта (определенный клиентом), поэтому он является частью Request-URI.
Теперь я также рассматриваю возможность позволить клиентам создавать объекты с помощью POST:
POST /objects/ HTTP/1.1
...
{JSON-представление объекта, включая ID}
Поскольку POST предназначен для операции “добавления”, я не уверен, что делать в случае, если объект уже существует. Следует ли рассматривать этот запрос как запрос на изменение или следует возвращать какой-либо код ошибки (какой именно)?
Самым подходящим кодом HTTP-ответа при попытке POST-запроса создать ресурс, который уже существует, является 409 Conflict. Этот код состояния указывает, что запрос не может быть выполнен из-за конфликта с текущим состоянием целевого ресурса. В вашем случае, когда клиент пытается отправить POST-запрос с объектом, ID которого уже существует в вашей системе, сервер должен вернуть статус 409, чтобы четко сообщить, что создание ресурса не удалось, потому что ресурс уже существует.
Содержание
- Понимание метода POST и создания ресурсов
- Рекомендуемый ответ: 409 Conflict
- Альтернативные коды ответов
- Спецификации RFC 7231
- Практические аспекты реализации
- Лучшие практики для проектирования вашего API
- Тело ответа и заголовки для 409 Conflict
Понимание метода POST и создания ресурсов
Метод HTTP POST определен в RFC 7231 как метод, который “предполагает, что целевой ресурс обработает представление, включенное в запрос, в соответствии со специфической семантикой самого ресурса”. POST может использоваться для различных целей, включая:
- Создание нового ресурса, когда сервер назначает URI
- Добавление данных к существующему ресурсу
- Обработку данных без обязательного создания нового ресурса
В вашем проекте API клиенты предоставляют полное представление объекта, включая ID, в теле POST-запроса к /objects/. Это отличается от типичного случая использования, когда POST создает ресурсы без указания ID, который затем назначается сервером.
Рекомендуемый ответ: 409 Conflict
409 Conflict широко считается наиболее подходящим кодом ответа для POST-запросов, которые пытаются создать ресурсы, уже существующие. Этот код состояния специально предназначен для указания:
409 Conflict
Запрос не может быть выполнен из-за конфликта с текущим состоянием целевого ресурса. Этот код используется в ситуациях, когда пользователь может разрешить конфликт и повторно отправить запрос.
Как упоминается в обсуждении на Stack Overflow, 409 Conflict является подходящим ответом, когда:
- Запрос пытается создать ресурс, который уже существует
- Конфликт вызван нарушением ограничений уникальности
- Клиенту необходимо быть проинформированным о существующем ресурсе
Руководство по кодам состояния HTTP подтверждает, что 409 является наиболее подходящим подходом, и рекомендуется включать URL конфликтующего ресурса в заголовок Location вместе с описательным сообщением об ошибке в теле ответа.
Альтернативные коды ответов
Хотя 409 Conflict является основным рекомендованным решением, в зависимости от конкретных выбранных вами вариантов проектирования API можно рассмотреть несколько других кодов состояния:
303 See Other
Согласно RFC 7231, 303 See Other “МОЖЕТ использоваться, если результат обработки POST будет эквивалентен представлению существующего ресурса”. Это может быть уместно, если:
- Вы хотите перенаправить клиента на существующий ресурс
- Операция POST фактически извлечет существующий ресурс, а не создаст новый
200 OK или 204 No Content
Если вы решаете рассматривать конфликтующие POST-запросы как операции модификации, а не как ошибки, вы можете вернуть:
- 200 OK: Если обновление было успешным и вы хотите вернуть обновленный ресурс
- 204 No Content: Если обновление было успешным, но вы не хотите возвращать никакого содержимого
Этот подход рассматривает POST как операцию “upsert” (обновить или вставить), но он отклоняется от принципов RESTful, поскольку POST не предназначен для идемпотентности.
422 Unprocessable Entity
Некоторые разработчики предлагают использовать 422 для ошибок валидации, включая случай, когда ресурс уже существует. Однако это менее распространено для данной конкретной ситуации и обычно предназначено для семантических ошибок валидации, а не для конфликтов с существующими ресурсами.
Спецификации RFC 7231
RFC 7231 предоставляет авторитетную спецификацию для семантики HTTP. Хотя в ней явно не рассматривается сценарий “ресурс уже существует” для POST, она определяет семантику соответствующих кодов состояния:
409 Conflict: “Запрос не может быть выполнен из-за конфликта с текущим состоянием целевого ресурса. Этот код используется в ситуациях, когда пользователь может разрешить конфликт и повторно отправить запрос.”
201 Created: “Запрос был выполнен и привел к созданию одного или нескольких новых ресурсов.”
В спецификации отмечается, что “RFC 7231 (HTTP 1.1) не требует всегда создавать ресурс; поэтому идемпотентный POST также является допустимым решением”, согласно одному обсуждению.
Практические аспекты реализации
При реализации ответа 409 Conflict в вашем API учтите следующие практические аспекты:
Обнаружение конфликтов
Вам необходимо реализовать логику для обнаружения, когда объект с запрашиваемым ID уже существует. Это обычно включает:
- Извлечение ID объекта из тела запроса
- Проверку, существует ли объект с таким ID в вашем хранилище
- Возврат 409, если конфликт обнаружен
Понятные для клиента сообщения об ошибках
Предоставляйте четкие сообщения об ошибках, которые помогут разработчикам понять, что пошло не так:
{
"error": "Conflict",
"message": "Объект с ID '123' уже существует",
"existing_resource": "/objects/123"
}
Рассмотрения идемпотентности
Если вы хотите сделать ваши POST-операции идемпотентными, вы можете реализовать:
- Предоставляемые клиентом временные метки или номера версий
- Логику дедупликации на стороне сервера
- Условные запросы с использованием ETags или заголовков If-Match
Лучшие практики для проектирования вашего API
Учитывая ваш конкретный случай использования, когда клиенты предоставляют ID объектов, вот некоторые лучшие практики:
1. Будьте последовательны в поведении PUT
Поскольку у вас уже есть конечные точки PUT для создания/модификации объектов по ID, рассмотрите, должно ли POST вести себя иначе:
PUT /objects/{id} - Всегда обновляет/создает объект с указанным ID
POST /objects/ - Создает новый объект с ID, назначенным сервером (рекомендуется)
2. Рассмотрите использование POST для ID, назначаемых сервером
RESTful подход предполагает использование POST для создания ресурсов, когда сервер назначает ID, и PUT для создания/обновления ресурсов, когда клиент указывает ID.
3. Предоставляйте четкую документацию
Четко документируйте поведение вашего API:
- Что происходит при отправке POST с существующим объектом
- Что клиент должен делать при получении ответа 409
- Как проверить, существует ли объект перед созданием
4. Рассмотрите возможность использования условных запросов
Реализуйте поддержку условных запросов, чтобы позволить клиентам проверять, существует ли ресурс, перед попыткой его создания:
HEAD /objects/{id} - Проверить, существует ли объект
GET /objects/{id} - Получить объект, если он существует
Тело ответа и заголовки для 409 Conflict
При возврате ответа 409 Conflict включайте соответствующие заголовки и содержимое тела:
Рекомендуемые заголовки
HTTP/1.1 409 Conflict
Content-Type: application/json
Location: /objects/{существующий-id}
Рекомендуемое тело ответа
{
"error": "Conflict",
"error_code": "RESOURCE_EXISTS",
"message": "Объект с указанным ID уже существует",
"existing_resource": "/objects/{существующий-id}",
"suggestion": "Используйте PUT для обновления существующего ресурса или проверьте, существует ли объект перед созданием"
}
Этот ответ предоставляет клиенту:
- Четкое указание типа ошибки
- Конкретный ресурс, вызвавший конфликт
- Предложения по разрешению конфликта
- Ссылку на существующий ресурс (через заголовок Location)
Источники
- HTTP response code for POST when resource already exists - Stack Overflow
- Which HTTP response code for “This email is already registered”? - Stack Overflow
- What is standard HTTP error code when a resource already exists? - Google Groups
- RESTful POST request, If the record already exists on POST data - Stack Overflow
- HTTP response code when resource creation POST fails due to existing matching resource - Stack Overflow
- RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
- HTTP Status Code for POST Request When Resource Already Exists - CopyProgramming
Заключение
На основе лучших практик REST API и спецификаций HTTP, 409 Conflict является наиболее подходящим кодом ответа, когда POST-запрос пытается создать ресурс, который уже существует. Этот подход:
- Четко communicates конфликт клиенту
- Соблюдает семантику HTTP, определенную в RFC 7231
- Предоставляет клиентам полезную информацию для разрешения проблемы
- Сохраняет последовательность с принципами проектирования RESTful
Для вашего конкретного проекта API рассмотрите, хотите ли вы:
- Использовать 409 Conflict и рассматривать POST как операцию создания только
- Реализовать поведение “upsert” (возвращать 200/204 для обновлений)
- Модифицировать ваше API для использования POST только для ID, назначаемых сервером, и PUT для ID, указываемых клиентом
Ключевым является четкая и последовательная документация выбранного поведения во всех конечных точках API, чтобы разработчики понимали, как эффективно взаимодействовать с вашим сервисом.