← Назад к вопросам
Что должно содержаться в запросе для поддержки stateless взаимодействия?
2.0 Middle🔥 71 комментариев
#Python Core#Soft Skills#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Stateless взаимодействие: что должно содержаться в запросе
Stateless архитектура — это фундаментальный принцип REST. Разберу подробно, что должно включаться в запрос и почему это важно.
Что такое stateless взаимодействие?
Stateless означает, что каждый запрос содержит всю необходимую информацию для обработки. Сервер не хранит контекст запроса между вызовами.
Противоположность: Stateful взаимодействие
# Плохо: Stateful (сервер помнит состояние)
session = {}
@app.post("/login")
def login(credentials):
# Сохраняем состояние на сервере
session['user_id'] = 123
session['username'] = 'john'
session['role'] = 'admin'
return {"message": "Logged in"}
@app.get("/profile")
def get_profile():
# Полагаемся на состояние, сохраненное ранее
user_id = session.get('user_id')
if not user_id:
return {"error": "Not logged in"}
return {"user_id": user_id}
# Проблемы:
# - Нельзя масштабировать (если 2+ сервера)
# - Если сервер перезагрузится, сессия потеряется
# - Сложно с клиентской стороны отследить состояние
Что должно содержаться в stateless запросе?
1. Идентификация (Authentication)
# Запрос должен содержать информацию о том, кто совершает запрос
from fastapi import FastAPI, Header, HTTPException
import jwt
app = FastAPI()
SECRET_KEY = "your-secret-key"
# Вариант 1: Bearer Token (рекомендуется)
@app.get("/api/v1/profile")
def get_profile(authorization: str = Header(None)):
"""
Запрос должен содержать:
Authorization: Bearer <JWT_TOKEN>
Сервер декодирует JWT и узнает:
- user_id
- username
- role
- expiration
"""
if not authorization:
raise HTTPException(status_code=401, detail="Missing token")
try:
token = authorization.replace("Bearer ", "")
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
user_id = payload['user_id']
return {"user_id": user_id, "username": payload['username']}
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
# Вариант 2: API Key
@app.get("/api/v1/data")
def get_data(api_key: str = Header(None)):
"""
Запрос должен содержать:
X-API-Key: sk_live_abc123def456
"""
if not api_key or not validate_api_key(api_key):
raise HTTPException(status_code=401, detail="Invalid API key")
return {"data": "sensitive information"}
2. Контекст операции
# Запрос должен содержать всю информацию о том, что нужно сделать
@app.post("/api/v1/orders")
def create_order(
authorization: str = Header(...),
body: dict # JSON body
):
"""
Полный запрос для создания заказа:
POST /api/v1/orders
Authorization: Bearer <token>
Content-Type: application/json
{
"user_id": 123,
"items": [
{
"product_id": 456,
"quantity": 2,
"price": 29.99
}
],
"delivery_address": "123 Main St",
"delivery_method": "express",
"payment_method": "credit_card"
}
Сервер получает ВСЕ данные и может обработать заказ,
не обращаясь к памяти сессии или другим источникам контекста
"""
user_id = extract_user_from_token(authorization)
order = {
'user_id': user_id,
'items': body['items'],
'delivery_address': body['delivery_address'],
'delivery_method': body['delivery_method']
}
# Обработка заказа на основе полной информации из запроса
save_order(order)
return {"order_id": 789}
3. Параметры и фильтры
# Все параметры фильтрации должны быть в запросе
from fastapi import Query
@app.get("/api/v1/orders")
def list_orders(
authorization: str = Header(...),
skip: int = Query(0),
limit: int = Query(10),
status: str = Query(None),
sort_by: str = Query("created_at"),
sort_order: str = Query("desc")
):
"""
GET /api/v1/orders?skip=0&limit=10&status=pending&sort_by=created_at&sort_order=desc
Все параметры фильтрации и сортировки включены в URL.
Сервер не помнит предыдущий запрос.
Если клиент отправит этот же URL, результат будет идентичен.
"""
user_id = extract_user_from_token(authorization)
filters = {
'user_id': user_id,
'skip': skip,
'limit': limit,
'status': status,
'sort_by': sort_by,
'sort_order': sort_order
}
return get_orders_filtered(filters)
4. Идемпотентность (Idempotency Key)
import uuid
from datetime import datetime
# Для операций, которые нельзя выполнять дважды
@app.post("/api/v1/payments")
def process_payment(
authorization: str = Header(...),
idempotency_key: str = Header(...), # Важно для stateless!
body: dict
):
"""
POST /api/v1/payments
Authorization: Bearer <token>
Idempotency-Key: unique-key-12345
{
"amount": 100.00,
"currency": "USD",
"payment_method": "credit_card"
}
Проблема: если клиент отправит запрос дважды,
платеж может быть обработан дважды.
Решение: сохраняем idempotency_key и результат.
Если видим уже сохраненный ключ, возвращаем старый результат.
"""
# Проверка: был ли уже обработан этот запрос?
existing_payment = get_payment_by_idempotency_key(idempotency_key)
if existing_payment:
return existing_payment # Возвращаем сохраненный результат
# Новый платеж
user_id = extract_user_from_token(authorization)
payment = {
'user_id': user_id,
'amount': body['amount'],
'idempotency_key': idempotency_key,
'created_at': datetime.utcnow()
}
result = charge_payment(payment)
save_payment_result(idempotency_key, result)
return result
5. Информация об API версии
# Запрос должен указывать, какую версию API использует
@app.get("/api/v1/users/{user_id}")
def get_user_v1(user_id: int, authorization: str = Header(...)):
"""
Версия указана в URL: /api/v1/
Если выпустим v2 с другим форматом,
старые клиенты продолжат использовать v1.
"""
return {"user_id": user_id, "name": "John"}
@app.get("/api/v2/users/{user_id}")
def get_user_v2(user_id: int, authorization: str = Header(...)):
"""
Новая версия API с другим форматом
"""
return {
"id": user_id,
"profile": {"name": "John", "email": "john@example.com"}
}
6. Временные метки и версионирование
from datetime import datetime
@app.put("/api/v1/users/{user_id}")
def update_user(
user_id: int,
authorization: str = Header(...),
body: dict
):
"""
PUT /api/v1/users/123
{
"name": "Jane",
"email": "jane@example.com",
"version": 2 # Оптимистичная блокировка
}
Запрос содержит версию объекта для обнаружения конфликтов.
Если на сервере уже есть версия 3, обновление отклоняется.
Это позволяет избежать потери изменений в распределённой системе.
"""
user_id_from_token = extract_user_from_token(authorization)
# Проверка версии
current_user = get_user(user_id)
if current_user['version'] != body.get('version'):
return {
"error": "Conflict",
"current_version": current_user['version'],
"status_code": 409
}
# Обновление
updated_user = {
**current_user,
**body,
'version': current_user['version'] + 1,
'updated_at': datetime.utcnow()
}
save_user(updated_user)
return updated_user
Пример полного stateless запроса
# Реальный пример: работа с заказами
from fastapi import FastAPI, Header, HTTPException
import jwt
app = FastAPI()
SECRET_KEY = "secret"
@app.post("/api/v1/users/{user_id}/orders")
def create_order(
user_id: int,
authorization: str = Header(...),
idempotency_key: str = Header(...),
body: dict
):
"""
POST /api/v1/users/123/orders
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{
"items": [
{"product_id": 456, "quantity": 2},
{"product_id": 789, "quantity": 1}
],
"delivery_address": "123 Main St",
"delivery_date": "2024-03-30",
"gift_message": "Happy birthday!"
}
Запрос ПОЛНОСТЬЮ автономен:
1. Authorization содержит информацию о пользователе
2. Idempotency-Key гарантирует идемпотентность
3. Body содержит все данные для создания заказа
4. URL указывает на конкретного пользователя и v1 API
5. Сервер НЕ полагается на память сессии
Результат: сервер может быть перезагружен, масштабирован,
клиент может повторить запрос и получить идентичный результат.
"""
# 1. Валидация токена
try:
token = authorization.replace("Bearer ", "")
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
token_user_id = payload['user_id']
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
# 2. Проверка совпадения user_id
if token_user_id != user_id:
raise HTTPException(status_code=403, detail="Forbidden")
# 3. Проверка идемпотентности
existing_order = get_order_by_idempotency_key(idempotency_key)
if existing_order:
return existing_order
# 4. Создание заказа
order = {
'user_id': user_id,
'items': body['items'],
'delivery_address': body['delivery_address'],
'delivery_date': body['delivery_date'],
'gift_message': body.get('gift_message'),
'idempotency_key': idempotency_key,
'status': 'pending'
}
order_id = save_order(order)
return {
"order_id": order_id,
"status": "pending",
"created_at": datetime.utcnow().isoformat()
}
Чеклист для stateless запроса
stateless_checklist = {
"authentication": {
"✓": "Запрос содержит credentials (token, API key)",
"✗": "Сервер ищет сессию в памяти"
},
"complete_context": {
"✓": "Все данные для обработки в самом запросе",
"✗": "Сервер должен искать что-то в БД (кроме валидации)"
},
"idempotency": {
"✓": "Есть Idempotency-Key для операций изменения",
"✗": "Одинаковый запрос даст разные результаты"
},
"versioning": {
"✓": "API версия указана в URL или заголовке",
"✗": "Клиент и сервер полагаются на подразумеваемую версию"
},
"parameters": {
"✓": "Все фильтры и параметры в URL/body/headers",
"✗": "Состояние фильтрации хранится на сервере"
},
"reproducibility": {
"✓": "Одинаковый запрос → одинаковый результат",
"✗": "Результат зависит от истории предыдущих запросов"
}
}
Почему stateless важен?
benefits = {
"scalability": "Любой сервер может обработать любой запрос",
"reliability": "Сбой одного сервера не потеряет контекст",
"caching": "Легко кэшировать (каждый запрос независим)",
"distribution": "Работает с load balancer и multiple servers",
"testing": "Легко тестировать (нет зависимостей от порядка)",
"debugging": "Логи одного запроса полностью описывают операцию"
}
Заключение
Stateless запрос должен содержать:
- Аутентификацию (кто совершает запрос)
- Контекст операции (что нужно сделать)
- Все параметры (все фильтры и настройки)
- Идемпотентность (для небезопасных операций)
- Версию API (для обратной совместимости)
- Всё необходимое — сервер ничего не помнит
Это основа масштабируемых, надёжных REST API.