В чем разница между PUT и PATCH?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между HTTP-методами PUT и PATCH
PUT и PATCH — это два HTTP-метода, используемые для обновления ресурсов на сервере, но они принципиально отличаются по семантике и способу применения. В контексте RESTful API понимание их различий критически важно для построения корректных и предсказуемых интерфейсов.
Ключевая семантика: Замена vs. Частичное обновление
PUT является идемпотентным методом, предназначенным для полной замены ресурса. Клиент отправляет на сервер представление ресурса, которое должно заменить существующее. Если ресурс ранее не существовал, он может быть создан (хотя эту роль чаще отводят методу POST).
// Пример PUT-запроса в Go для полного обновления пользователя
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func updateUserHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
http.Error(w, "Метод не поддерживается", http.StatusMethodNotAllowed)
return
}
var userUpdates User
if err := json.NewDecoder(r.Body).Decode(&userUpdates); err != nil {
http.Error(w, "Неверный формат данных", http.StatusBadRequest)
return
}
// ВАЖНО: Ожидается, что в userUpdates будут ВСЕ поля пользователя.
// Отсутствующие поля могут быть интерпретированы как нулевые значения.
// Происходит ПОЛНАЯ ЗАМЕНА ресурса.
saveUserToDB(userUpdates) // Записываем объект целиком
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(userUpdates)
}
PATCH, в отличие от PUT, предназначен для частичного обновления ресурса. Клиент отправляет только те изменения, которые необходимо применить, а не полное представление. PATCH не является идемпотентным по своей природе (хотя на практике его можно реализовать идемпотентно).
// Пример PATCH-запроса в Go для частичного обновления
type UserPatch struct {
Name *string `json:"name,omitempty"` // Указатели позволяют отличать отсутствие поля от нулевого значения
Age *int `json:"age,omitempty"`
}
func patchUserHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPatch {
http.Error(w, "Метод не поддерживается", http.StatusMethodNotAllowed)
return
}
var patch UserPatch
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
http.Error(w, "Неверный формат данных", http.StatusBadRequest)
return
}
currentUser := getUserFromDB()
// Применяем только переданные в запросе поля
if patch.Name != nil {
currentUser.Name = *patch.Name
}
if patch.Age != nil {
currentUser.Age = *patch.Age
}
saveUserToDB(currentUser)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(currentUser)
}
Сравнительная таблица
| Критерий | PUT | PATCH |
|---|---|---|
| Основное назначение | Полная замена ресурса | Частичное обновление ресурса |
| Идемпотентность | Да (повторный вызов даёт тот же результат) | Нет (в общем случае, зависит от реализации) |
| Тело запроса | Полное представление нового состояния ресурса | Набор изменений (дельта, patch-документ) |
| Создание ресурса | Может (если ресурс не существует) | Только для существующих ресурсов (обычно) |
| Размер запроса | Как правило, больше (передаются все поля) | Как правило, меньше (только изменяемые поля) |
Форматы для PATCH
Для PATCH важно определить формат передаваемых изменений. Наиболее распространёнными являются:
- JSON Merge Patch (RFC 7396): Простой формат, где в теле передаётся JSON-объект с изменяемыми полями. Не поддерживает явные операции вроде установки
nullили работы с массивами по индексам. - JSON Patch (RFC 6902): Более мощный и стандартизированный формат, использующий массив операций (add, remove, replace, move, copy, test).
// Пример JSON Patch запроса [{"op": "replace", "path": "/name", "value": "НовоеИмя"}]
type JSONPatchOperation struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
func jsonPatchHandler(w http.ResponseWriter, r *http.Request) {
var patches []JSONPatchOperation
if err := json.NewDecoder(r.Body).Decode(&patches); err != nil {
http.Error(w, "Неверный JSON Patch", http.StatusBadRequest)
return
}
// Применение каждой операции из массива patches к ресурсу
}
Практические рекомендации для Go-разработчика
- Используйте PUT, когда клиент может и должен отправить полное представление ресурса. Это упрощает валидацию и логику на сервере.
- Выбирайте PATCH, когда необходимо обновить одно-два поля большого объекта или когда передача всего объекта неэффективна по трафику.
- Всегда проверяйте существование ресурса при обработке PUT-запроса, если не хотите разрешать неявное создание.
- Для PATCH-метода строго документируйте поддерживаемый формат изменений (например, JSON Merge Patch или JSON Patch).
- Обеспечьте атомарность PATCH-операций. Все изменения в одном запросе должны применяться либо все, либо ни одно (например, в рамках транзакции БД).
- Будьте осторожны с конкурентным доступом. И для PUT, и для PATCH актуальны проблемы Lost Update. Рассмотрите использование ETag и заголовка
If-Matchдля реализации оптимистичной блокировки.
В Go-экосистеме при построении REST API с использованием популярных фреймворков (Gin, Echo, Chi) выбор между PUT и PATCH ложится на разработчика, так как эти фреймворки предоставляют роутинг для любых HTTP-методов. Ответственность за корректную обработку семантики каждого метода и выбор форматов данных (особенно для PATCH) лежит на команде разработки. Правильное применение этих методов повышает предсказуемость, эффективность и надёжность вашего API.