Какие методы HTTP являются идемпотентными и в чем их отличие от неидемпотентных?

Ответ

Идемпотентность в контексте HTTP означает, что многократное выполнение одного и того же запроса приводит к тому же состоянию ресурса на сервере, как если бы запрос был выполнен один раз.

Идемпотентные методы:

  • GET: Запрашивает представление ресурса. Не изменяет состояние сервера. Безопасен и идемпотентен.
  • PUT: Полностью заменяет ресурс по указанному URI. При повторном вызове с теми же данными ресурс просто будет перезаписан тем же самым содержимым, и итоговое состояние не изменится.
  • DELETE: Удаляет ресурс. Первый запрос удалит ресурс (изменит состояние), а все последующие будут возвращать 404 Not Found, но состояние системы (отсутствие ресурса) останется прежним.
  • HEAD: Аналогичен GET, но без тела ответа. Также идемпотентен.
  • OPTIONS: Запрашивает информацию о доступных опциях для ресурса. Не меняет состояние.

Неидемпотентные методы:

  • POST: Обычно используется для создания нового дочернего ресурса. Каждый успешный POST-запрос создает новую сущность. Например, отправка комментария к посту. Повторный запрос создаст дубликат комментария.

Условно идемпотентный метод:

  • PATCH: Применяет частичные изменения к ресурсу. Его идемпотентность зависит от самой операции. Например, PATCH /items/1 { "op": "add", "value": 10 } не будет идемпотентным, так как каждый вызов будет увеличивать значение на 10. А PATCH /items/1 { "op": "set", "field": "name", "value": "new_name" } будет идемпотентным.

Пример в Go, демонстрирующий разницу:

// Предположим, у нас есть хранилище пользователей в памяти
var users = make(map[string]string)

// POST /users - Неидемпотентный
// Каждый вызов создает нового пользователя с уникальным ID
func createUser(w http.ResponseWriter, r *http.Request) {
    userID := fmt.Sprintf("user-%d", time.Now().UnixNano())
    users[userID] = "New User"
    w.WriteHeader(http.StatusCreated)
    fmt.Fprintf(w, "Created user with ID: %s", userID)
}

// PUT /users/user123 - Идемпотентный
// Сколько бы раз ни вызывался, состояние user123 будет одинаковым
func updateUser(w http.ResponseWriter, r *http.Request) {
    // В реальном приложении ID будет из URL, например, с помощью gorilla/mux
    userID := "user123"
    users[userID] = "Updated User"
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "User %s updated", userID)
}