← Назад к вопросам

Что такое идемпотентность запросов http?

2.0 Middle🔥 161 комментариев
#REST API и HTTP

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Идемпотентность HTTP запросов

Определение

Идемпотентность — это свойство операции, при котором повторное выполнение той же операции даёт тот же результат, что и первое выполнение.

В контексте HTTP: идемпотентный запрос можно выполнить несколько раз без каких-либо побочных эффектов или изменений на сервере.

Первый запрос: GET /user/1  → возвращает Иванова
Второй запрос: GET /user/1  → возвращает Иванова
Третий запрос: GET /user/1  → возвращает Иванова

^ это идемпотентно (результат не меняется)

---

Первый запрос:  POST /transfer {from: A, to: B, amount: 100} → $100 переведено
Второй запрос:  POST /transfer {from: A, to: B, amount: 100} → $100 переведено ЕЩЁ РАЗ
Третий запрос:  POST /transfer {from: A, to: B, amount: 100} → $100 переведено ЕЩЁ РАЗ

^ это НЕ идемпотентно (результат меняется с каждым запросом)

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

GET — идемпотентный

import requests

# GET не меняет состояние сервера
response1 = requests.get("https://api.example.com/user/1")
response2 = requests.get("https://api.example.com/user/1")
response3 = requests.get("https://api.example.com/user/1")

# Все три запроса вернут одинаковый результат
print(response1.json() == response2.json() == response3.json())  # True

DELETE — идемпотентный

# DELETE идемпотентен
response1 = requests.delete("https://api.example.com/user/1")  # Удаляет пользователя
# Status: 200 OK

response2 = requests.delete("https://api.example.com/user/1")  # Пользователь уже удален
# Status: 404 Not Found (идемпотентное поведение)

response3 = requests.delete("https://api.example.com/user/1")  # Всё ещё нет
# Status: 404 Not Found

# Результат: пользователь удален после первого запроса
# Последующие запросы не меняют состояние (идемпотентность)

PUT — идемпотентный

# PUT полностью заменяет ресурс (идемпотентно)
data = {"name": "Ivan", "age": 30}

response1 = requests.put(
    "https://api.example.com/user/1",
    json=data
)  # Создаёт/обновляет пользователя с этими данными
# Status: 200 OK или 201 Created

response2 = requests.put(
    "https://api.example.com/user/1",
    json=data
)  # Тот же запрос — тот же результат
# Status: 200 OK

response3 = requests.put(
    "https://api.example.com/user/1",
    json=data
)  # Опять тот же результат
# Status: 200 OK

print(response1.json() == response2.json())  # True

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

POST — НЕ идемпотентный

# POST часто создаёт новые ресурсы (каждый раз новый)
data = {"message": "Hello", "timestamp": "2024-03-23"}

response1 = requests.post(
    "https://api.example.com/messages",
    json=data
)  # Создаёт сообщение с ID=1
# {"id": 1, "message": "Hello", "timestamp": "2024-03-23"}

response2 = requests.post(
    "https://api.example.com/messages",
    json=data
)  # Создаёт НОВОЕ сообщение с ID=2
# {"id": 2, "message": "Hello", "timestamp": "2024-03-23"}

response3 = requests.post(
    "https://api.example.com/messages",
    json=data
)  # Создаёт ЕЩЁ НОВОЕ сообщение с ID=3
# {"id": 3, "message": "Hello", "timestamp": "2024-03-23"}

# Результаты разные! POST не идемпотентен
print(response1.json() == response2.json())  # False (разные ID)

PATCH — НЕ идемпотентный

# PATCH обновляет ЧАСТь ресурса (может быть неидемпотентным)
update = {"age": 31}  # Увеличиваем возраст на 1

response1 = requests.patch(
    "https://api.example.com/user/1",
    json=update
)  # age: 30 → 31

response2 = requests.patch(
    "https://api.example.com/user/1",
    json=update
)  # age: 31 → 32 (меняется!)

response3 = requests.patch(
    "https://api.example.com/user/1",
    json=update
)  # age: 32 → 33 (меняется ещё!)

# PATCH неидемпотентен, потому что обновляет ЧАСТЬ ресурса
print(response1.json()["age"])  # 31
print(response2.json()["age"])  # 32
print(response3.json()["age"])  # 33

Таблица идемпотентности

МетодИдемпотентенКэшируемыйБезопасный
GET✓ Да✓ Да✓ Да
HEAD✓ Да✓ Да✓ Да
OPTIONS✓ Да✗ Нет✓ Да
DELETE✓ Да✗ Нет✗ Нет
PUT✓ Да✗ Нет✗ Нет
POST✗ Нет✗ Нет✗ Нет
PATCH✗ Нет✗ Нет✗ Нет

Проблема сетевых сбоев

Идемпотентность критична для надёжности:

# Сценарий: пользователь отправляет платёж
response = requests.post(
    "https://api.example.com/transfer",
    json={"from": "A", "to": "B", "amount": 100},
    timeout=5
)

# Но сеть не стабильна!
# Запрос был отправлен, но ответ не пришёл
# Клиент не знает: платёж прошёл или нет?

# ❌ Неправильное решение: повторить запрос
# Может быть 200$, 300$, 400$ и больше переведено!

# ✅ Правильное решение: использовать идемпотентный ключ
headers = {
    "Idempotency-Key": "transfer-2024-03-23-001"  # Уникальный ключ
}
response = requests.post(
    "https://api.example.com/transfer",
    json={"from": "A", "to": "B", "amount": 100},
    headers=headers
)

# Повторный запрос с тем же ключом:
response_retry = requests.post(
    "https://api.example.com/transfer",
    json={"from": "A", "to": "B", "amount": 100},
    headers=headers  # Тот же ключ!
)  
# Сервер видит, что это повторение
# Возвращает ТОТ ЖЕ результат, не выполняя операцию ещё раз

Реализация идемпотентности на сервере

from fastapi import FastAPI, Header
from typing import Optional
import uuid

app = FastAPI()

# БД для отслеживания идемпотентных ключей
IDEMPOTENT_KEYS = {}  # {key: result}

@app.post("/transfer")
async def transfer(
    from_account: str,
    to_account: str,
    amount: float,
    idempotency_key: Optional[str] = Header(None)
):
    """Идемпотентный трансфер денег"""
    
    # Если нет ключа, генерируем
    if not idempotency_key:
        idempotency_key = str(uuid.uuid4())
    
    # Проверяем, был ли уже такой запрос
    if idempotency_key in IDEMPOTENT_KEYS:
        # Возвращаем сохранённый результат
        return {"status": "success", "result": IDEMPOTENT_KEYS[idempotency_key]}
    
    # Первый раз: выполняем операцию
    try:
        result = {
            "from": from_account,
            "to": to_account,
            "amount": amount,
            "transaction_id": str(uuid.uuid4())
        }
        
        # Выполняем трансфер (в реальности в БД)
        # db.transfer(from_account, to_account, amount)
        
        # Сохраняем результат
        IDEMPOTENT_KEYS[idempotency_key] = result
        
        return {"status": "success", "result": result}
    
    except Exception as e:
        # В случае ошибки НЕ сохраняем ключ
        # Клиент может повторить
        return {"status": "error", "error": str(e)}

Best Practices для идемпотентности

Используйте правильные HTTP методы:

  • GET для чтения
  • PUT для полного обновления
  • DELETE для удаления
  • POST только если нужно создать новый ресурс

Для критических операций используйте Idempotency-Key:

headers = {"Idempotency-Key": "unique-key-per-request"}

На сервере сохраняйте результаты идемпотентных запросов:

# Проверяем кеш перед выполнением
if key in cache:
    return cache[key]
# Выполняем
result = execute()
cache[key] = result
return result

Очищайте старые ключи:

# Идемпотентные ключи можно хранить 24 часа
# Потом удалять (экономия памяти)

Документируйте идемпотентность в API:

POST /transfer
- Идемпотентен: нет
- Требует: Idempotency-Key header

Реальные примеры

Stripe API — платёжная система (очень критична идемпотентность):

# https://stripe.com/docs/api/idempotent_requests
headers = {"Idempotency-Key": "py_test_abc123"}
response = requests.post(
    "https://api.stripe.com/v1/charges",
    headers=headers,
    data={...}
)

AWS API — облачные сервисы:

# AWS требует ClientRequestToken для идемпотентности
response = client.run_instances(
    ImageId='ami-12345678',
    ClientToken='my-unique-token-2024-03-23'
)
# Второй запрос с тем же токеном вернёт существующие инстансы

Итог

Идемпотентность — это критическое свойство для надёжных API:

  • GET, DELETE, PUT — идемпотентны (безопасны для повторения)
  • POST, PATCH — не идемпотентны (каждое выполнение может быть разным)
  • Idempotency-Key — решение для критических операций (платежи, трансферы)
  • Сохранение результатов — на сервере нужно помнить результаты идемпотентных запросов

Если не учитывать идемпотентность, при сетевых сбоях могут возникнуть дублирования операций (двойное списание денег, создание дубликатов и т.д.).

Что такое идемпотентность запросов http? | PrepBro