← Назад к вопросам
Является ли запрос по шаблону idempotent?
2.0 Middle🔥 301 комментариев
#Безопасность#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Идемпотентность запросов (Idempotency)
Идемпотентный запрос — это запрос, который может быть выполнен несколько раз подряд с одинаковым результатом, без изменения состояния сервера или получения разных ответов. Это критически важное понятие для проектирования надёжных API и обработки сетевых ошибок.
Определение идемпотентности
Запрос идемпотентен, если:
- Первое выполнение даёт результат X
- Второе выполнение даёт результат X
- N-е выполнение даёт результат X
- Состояние сервера остаётся неизменным
HTTP методы и идемпотентность
Идемпотентные методы:
# GET — только читает данные, ничего не меняет
GET /users/123
GET /users/123 # Второй раз вернёт то же самое
# PUT — заменяет ресурс полностью (предсказуемый результат)
PUT /users/123
{
"name": "John",
"email": "john@example.com"
}
# Выполнено 1 раз или 5 раз — результат одинаковый
# DELETE — удаляет ресурс (идемпотентен)
DELETE /users/123
DELETE /users/123 # Второй раз вернёт 404, но состояние сервера не изменится
# HEAD — только проверяет, ничего не меняет
HEAD /users/123
Не идемпотентные методы:
# POST — создаёт новый ресурс каждый раз
POST /users
{
"name": "John"
}
# Первый раз создаёт пользователя с id=1
# Второй раз создаёт пользователя с id=2
# Результаты разные!
# PATCH — изменяет ресурс частично (может быть неидемпотентным)
PATCH /users/123
{
"age": +1 # Увеличить возраст на 1
}
# Первый раз age становится 31
# Второй раз age становится 32
# Результаты разные!
Примеры в коде
Идемпотентный API (FastAPI):
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
users_db = {}
class User(BaseModel):
id: int
name: str
# GET — идемпотентен
@app.get("/users/{user_id}")
def get_user(user_id: int):
if user_id not in users_db:
raise HTTPException(status_code=404)
return users_db[user_id]
# PUT — идемпотентен (полностью заменяет)
@app.put("/users/{user_id}")
def update_user(user_id: int, user: User):
users_db[user_id] = user
return users_db[user_id]
# DELETE — идемпотентен (удаляет и всё)
@app.delete("/users/{user_id}")
def delete_user(user_id: int):
if user_id in users_db:
del users_db[user_id]
return {"deleted": True}
# POST — НЕ идемпотентен (создаёт каждый раз)
@app.post("/users")
def create_user(user: User):
users_db[user.id] = user
return users_db[user.id]
Проблема: как сделать POST идемпотентным
В реальных системах часто нужно сделать POST идемпотентным (например, при повторе платежа). Решение — использовать Idempotency Key:
from fastapi import Header, HTTPException
from typing import Optional
idempotency_store = {} # В реальности — база данных
@app.post("/payments")
def create_payment(
amount: float,
idempotency_key: Optional[str] = Header(None)
):
# Если мы уже видели этот ключ — вернуть кэшированный результат
if idempotency_key in idempotency_store:
return idempotency_store[idempotency_key]
# Иначе обработать платёж
result = {
"payment_id": 12345,
"amount": amount,
"status": "completed"
}
# Кэшировать результат
if idempotency_key:
idempotency_store[idempotency_key] = result
return result
Клиент отправляет Idempotency Key:
import requests
import uuid
idempotency_key = str(uuid.uuid4())
# Первый запрос
response1 = requests.post(
"http://api.example.com/payments",
json={"amount": 100},
headers={"Idempotency-Key": idempotency_key}
)
# Второй запрос с тем же ключом
response2 = requests.post(
"http://api.example.com/payments",
json={"amount": 100},
headers={"Idempotency-Key": idempotency_key}
)
# response1.json() == response2.json()
assert response1.json() == response2.json()
Практическое применение
Обработка сетевых ошибок:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# Идемпотентные запросы можно безопасно повторять
session = requests.Session()
retry = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
Adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', Adapter)
session.mount('https://', Adapter)
# GET безопасно повторять
response = session.get("/api/users/123")
# POST нужно как-то защищать (Idempotency Key)
response = session.post("/api/users", json={"name": "John"})
Таблица идемпотентности
| Метод | Идемпотентен | Примеры |
|---|---|---|
| GET | Да | Получение данных |
| PUT | Да | Обновление всего ресурса |
| DELETE | Да | Удаление ресурса |
| PATCH | Нет* | Частичное обновление |
| POST | Нет** | Создание нового |
| HEAD | Да | Проверка доступности |
- PATCH может быть идемпотентным, если замещает поля целиком ** POST может быть идемпотентным с Idempotency Key
Заключение
Понимание идемпотентности критично для:
- Проектирования надёжных API
- Обработки сетевых сбоев
- Безопасности платёжных систем
- Распределённых систем
Повсегда проверяйте: безопасно ли повторить данный запрос?