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

При какой HTTP ошибке нельзя повторять запрос

1.3 Junior🔥 251 комментариев
#Python Core

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

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

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

# HTTP ошибки и повторные попытки (Retries)

Главное правило: различай идемпотентные и неидемпотентные операции

Повторять запрос или нет зависит от типа ошибки и типа HTTP метода. Есть ошибки, при которых повторять НЕЛЬЗЯ, потому что это может привести к двойной обработке.

Ошибки, при которых НЕЛЬЗЯ повторять запрос

1. 4xx — Ошибки клиента (обычно НЕ повторяют)

Это означает, что клиент что-то сделал неправильно. Повтор не поможет:

import requests
from requests.exceptions import HTTPError

def safe_request_with_retry(url, data):
    """Правильная обработка ошибок с повторами"""
    
    for attempt in range(3):
        try:
            response = requests.post(url, json=data)
            response.raise_for_status()  # Выбросит HTTPError
            return response.json()
        except HTTPError as e:
            status = e.response.status_code
            
            # 4xx ошибки — НЕ повторяем
            if 400 <= status < 500:
                print(f"Клиентская ошибка {status}: не будет повторено")
                raise  # Сразу выбрасываем
            
            # 5xx ошибки — ПОВТОРЯЕМ
            elif 500 <= status < 600:
                print(f"Серверная ошибка {status}: повторяем попытку {attempt + 1}")
                if attempt == 2:
                    raise  # Последняя попытка
            else:
                raise

2. 400 Bad Request — невалидные данные

Клиент отправил невалидные данные. Повтор того же запроса только выбросит ту же ошибку.

# ❌ ПЛОХО: повторяем запрос с ошибкой
def bad_retry():
    for _ in range(3):
        response = requests.post('https://api.example.com/data', 
                                json={"age": "not_a_number"})  # Невалидно
        if response.status_code == 400:
            continue  # Повторяем бесполезно!

# ✅ ХОРОШО: исправляем данные перед повтором
def good_retry():
    data = {"age": "invalid"}
    
    response = requests.post('https://api.example.com/data', json=data)
    if response.status_code == 400:
        # Исправляем ошибку в данных
        data["age"] = 30  # Правильный формат
        response = requests.post('https://api.example.com/data', json=data)
    
    return response

3. 401 Unauthorized — проблема с аутентификацией

Токен истёк или некорректен. Просто повторить запрос не поможет.

def request_with_auth():
    headers = {"Authorization": "Bearer invalid_token"}
    
    for attempt in range(3):
        response = requests.get('https://api.example.com/user', headers=headers)
        
        if response.status_code == 401:
            # НЕ просто повторяем, а обновляем токен
            headers["Authorization"] = f"Bearer {get_new_token()}"
            # Теперь повторяем с новым токеном
            response = requests.get('https://api.example.com/user', headers=headers)
            break
    
    return response

def get_new_token():
    # Получить новый токен от сервера
    return "new_valid_token"

4. 403 Forbidden — нет прав доступа

Пользователь не имеет прав. Повторное выполнение не изменит права.

def request_with_permission():
    response = requests.get('https://api.example.com/admin/data')
    
    if response.status_code == 403:
        # НЕ повторяем просто так
        # Нужно предоставить правильные права
        print("Доступ запрещён. Требуется повышение прав")
        raise PermissionError("Insufficient permissions")
    
    return response

5. 404 Not Found — ресурс не существует

Ресурс не найден. Повтор не поможет (разве что ожидаем, что ресурс будет создан).

def get_user(user_id):
    """Попытка получить пользователя"""
    url = f'https://api.example.com/users/{user_id}'
    
    for attempt in range(3):
        response = requests.get(url)
        
        if response.status_code == 404:
            # Не повторяем — ресурс не существует
            print(f"Пользователь {user_id} не найден")
            return None
        
        elif response.status_code == 200:
            return response.json()
    
    return None

6. 409 Conflict — конфликт состояния

Операция конфликтует с текущим состоянием. Зависит от контекста.

def update_resource():
    # Попытка обновить ресурс с версией
    data = {"name": "New Name", "version": 1}
    
    response = requests.put('https://api.example.com/resource/123', json=data)
    
    if response.status_code == 409:
        # Конфликт версий — нужно получить актуальную версию
        current = requests.get('https://api.example.com/resource/123').json()
        data["version"] = current["version"]
        
        # Повторяем с обновленной версией
        response = requests.put('https://api.example.com/resource/123', json=data)
    
    return response

7. 422 Unprocessable Entity — невозможно обработать

Данные синтаксически правильны, но логически неприемлемы. Повтор не поможет.

def create_user():
    # Email уже существует — это бизнес-логика, не ошибка сервера
    data = {"name": "John", "email": "existing@example.com"}
    
    response = requests.post('https://api.example.com/users', json=data)
    
    if response.status_code == 422:
        # НЕ повторяем это же значение
        errors = response.json()["errors"]
        print(f"Ошибка валидации: {errors}")
        # Пользователь должен изменить email
        return None
    
    return response.json()

Ошибки, при которых МОЖНО/НУЖНО повторять запрос

Серверные ошибки (5xx)

import time
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def requests_with_retry():
    """Правильная стратегия повторных попыток"""
    session = requests.Session()
    
    # Стратегия повторов
    retry_strategy = Retry(
        total=3,                           # Максимум 3 попытки
        backoff_factor=1,                  # 1, 2, 4 секунды между попытками
        status_forcelist=[429, 500, 502, 503, 504],  # Повторяем при этих кодах
        allowed_methods=["HEAD", "GET", "OPTIONS", "PUT", "DELETE"]  # Безопасные методы
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    
    return session

session = requests_with_retry()
response = session.get('https://api.example.com/data')

429 Too Many Requests — лимит запросов

def request_with_rate_limit():
    """Обработка лимита частоты запросов"""
    for attempt in range(5):
        response = requests.get('https://api.example.com/data')
        
        if response.status_code == 429:
            # Получаем Retry-After заголовок
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Лимит достигнут, ждём {retry_after} сек")
            time.sleep(retry_after)
            # ПОВТОРЯЕМ запрос
            continue
        
        elif response.status_code == 200:
            return response.json()
    
    raise Exception("Не удалось получить данные после 5 попыток")

503 Service Unavailable — сервис недоступен

def request_with_service_unavailable():
    """Обработка временной недоступности"""
    max_retries = 3
    
    for attempt in range(max_retries):
        response = requests.get('https://api.example.com/data')
        
        if response.status_code == 503:
            # Сервис временно недоступен, ПОВТОРЯЕМ
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # Экспоненциальная задержка
                print(f"Сервис недоступен, повтор через {wait_time} сек")
                time.sleep(wait_time)
                continue
            else:
                raise Exception("Сервис недоступен после 3 попыток")
        
        elif response.status_code == 200:
            return response.json()
    
    return None

Таблица: какие ошибки повторять

КодОписаниеПовторятьПричина
400Bad Request❌ НЕТНевалидные данные клиента
401Unauthorized⚠️ УСЛОВНОНужно обновить авторизацию
403Forbidden❌ НЕТНет прав доступа
404Not Found❌ НЕТРесурс не существует
409Conflict⚠️ УСЛОВНОЗависит от типа конфликта
422Unprocessable❌ НЕТБизнес-логика не позволяет
429Too Many Requests✅ ДАВременное ограничение
500Internal Server Error✅ ДАВременная проблема сервера
502Bad Gateway✅ ДАСетевая проблема
503Service Unavailable✅ ДАСервер временно недоступен
504Gateway Timeout✅ ДАTimeout в сетевой цепи

Важный момент: идемпотентность методов

# GET, HEAD, OPTIONS, DELETE — идемпотентные (безопасны для повтора)
def safe_request():
    # Можно повторять при любых ошибках
    response = requests.get('https://api.example.com/data')  # ✅
    response = requests.delete('https://api.example.com/data/123')  # ✅

# POST, PUT (без идемпотентного ключа) — НЕ идемпотентные
def unsafe_request():
    # НЕЛЬЗЯ просто повторять при ошибке
    response = requests.post('https://api.example.com/users', 
                            json={"name": "John"})  # ❌ может создать дубликат
    
    # Если сервер не ответил, мы не знаем, был ли создан пользователь
    # Повтор может создать дубликат!

Правильная стратегия повторов

import requests
import time
from requests.exceptions import RequestException

class SmartRetry:
    @staticmethod
    def should_retry(status_code, method):
        """Определяет, стоит ли повторять запрос"""
        
        # Никогда не повторяем 4xx (кроме 429)
        if 400 <= status_code < 500 and status_code != 429:
            return False
        
        # Всегда повторяем 5xx
        if 500 <= status_code < 600:
            return True
        
        # Повторяем 429
        if status_code == 429:
            return True
        
        return False
    
    @staticmethod
    def request_with_retry(method, url, max_retries=3, **kwargs):
        """Выполняет запрос с правильной стратегией повторов"""
        
        for attempt in range(max_retries):
            try:
                response = requests.request(method, url, **kwargs)
                
                if SmartRetry.should_retry(response.status_code, method):
                    if attempt < max_retries - 1:
                        wait_time = 2 ** attempt
                        print(f"Повтор через {wait_time} сек (попытка {attempt + 1})")
                        time.sleep(wait_time)
                        continue
                    else:
                        raise Exception(f"Не удалось выполнить запрос после {max_retries} попыток")
                
                return response
            
            except RequestException as e:
                if attempt < max_retries - 1:
                    time.sleep(2 ** attempt)
                    continue
                else:
                    raise
        
        return None

# Использование
response = SmartRetry.request_with_retry('GET', 'https://api.example.com/data')
print(response.json())

Заключение

НЕ повторяйте запрос при:

  • 400-499 (кроме 429) — ошибки клиента, повтор не поможет
  • 401, 403 — проблемы с авторизацией
  • 404 — ресурс не существует
  • 422 — невозможно обработать (бизнес-логика)

ПОВТОРЯЙТЕ запрос при:

  • 429 — лимит частоты запросов (используйте Retry-After)
  • 500-599 — серверные ошибки (используйте exponential backoff)
  • Таймауты и сетевые ошибки

Всегда учитывайте идемпотентность метода: POST обычно опасен для повтора, GET всегда безопасен.

При какой HTTP ошибке нельзя повторять запрос | PrepBro