خطای محدودیت درخواست چه می‌گوید؟

وقتی یک سرویس پاسخ ۴۲۹ می‌دهد، معمولاً سهمیه یا آهنگ درخواست‌ها از حد پذیرفته‌شده گذشته است. تکرار فوری همان درخواست می‌تواند فشار را بیشتر کند و مدت خطا را طولانی‌تر سازد. ابتدا مشخص کنید محدودیت برای حساب، نشانی شبکه یا کل سرویس اعمال می‌شود. این مقاله یک مصرف‌کنندهٔ کوچک می‌سازد که فقط درخواست خواندنی می‌فرستد و تعداد تلاش‌ها و مدت مکث را روشن نگه می‌دارد.

سرآیند Retry-After می‌تواند تعداد ثانیه یا یک تاریخ استاندارد وب باشد. نمونه هر دو حالت را می‌خواند و زمان گذشته را به مکث صفر تبدیل می‌کند. اگر سرور بیش از سقف مجاز برنامه انتظار خواسته باشد، ابزار متوقف می‌شود؛ کوتاه‌کردن آن زمان و ارسال زودتر، رعایت دستور سرور نیست. نبود یا نامعتبر بودن سرآیند به عقب‌نشینی تصادفی محدود منجر می‌شود، نه یک حلقهٔ بی‌پایان.

قرارداد انتظار و بودجهٔ تلاش مجدد

در این نمونه، تعداد تلاش‌ها بین یک تا پنج است و هر تماس شبکه زمان انتظار جداگانه دارد. این کنترل‌ها با مهلت سراسری یک کار طولانی یکسان نیستند. برای اجرای تولیدی، زمان کل، تعداد اتصال و سهمیهٔ مشترک همهٔ کارگرها را هم در نظر بگیرید. بازآزمایی یک عملیات پرداخت یا ثبت سفارش، بدون قرارداد جلوگیری از تکرار اثر، نباید از همین الگوی درخواست خواندنی کپی شود.

چهار فایل زیر را در یک پوشه ذخیره کنید. ابتدا python -m unittest test_retry_client.py را اجرا کنید. آزمون‌ها قالب‌های سرآیند و یک پاسخ واقعی ۴۲۹ از سرور محلی را بررسی می‌کنند؛ زمان خواب در آزمون ثبت می‌شود تا آزمایش سریع و قابل تکرار باشد. برای دیدن مکث واقعی، python demo_server.py را در یک پنجره و python demo_client.py را در پنجرهٔ دیگر اجرا کنید.

سرور آزمایش در نخستین درخواست، انتظار یک‌ثانیه‌ای اعلام می‌کند و بعد پاسخ موفق می‌دهد. خروجی مصرف‌کننده باید ready با مقدار درست باشد. این آزمایش ثابت نمی‌کند سیاست محدودیت یک ارائه‌دهندهٔ واقعی دقیقاً همین رفتار را دارد. وضعیت حساب، سند سهمیه، سرآیندهای واقعی و زمان‌های ثبت‌شدهٔ همان سرویس را بررسی کنید و در گزارش عمومی، کلید دسترسی یا دادهٔ خصوصی درخواست را نگه ندارید.

فایل‌های کامل و قابل اجرا

چهار فایل زیر کامل‌اند؛ پیش‌نیاز، ترتیب اجرا و روش بررسی در متن آمده است. کدها خلاصه یا پنهان نشده‌اند.

1. فایل کامل retry_client.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

import email.utils
import json
import random
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone


def retry_delay(value, now=None):
    if not value:
        return None
    value = value.strip()
    if value.isascii() and value.isdigit():
        return float(value)
    try:
        target = email.utils.parsedate_to_datetime(value)
        if target.tzinfo is None:
            return None
        return max(0.0, (target - (now or datetime.now(timezone.utc))).total_seconds())
    except (TypeError, ValueError, OverflowError):
        return None


def fetch_json(url, attempts=3, max_wait=30, sleep=time.sleep):
    if not 1 <= attempts <= 5 or max_wait < 0:
        raise ValueError('Invalid retry budget')
    for attempt in range(attempts):
        try:
            with urllib.request.urlopen(url, timeout=5) as response:
                body = response.read(1_000_001)
                if len(body) > 1_000_000:
                    raise ValueError('Response exceeds byte limit')
                return json.loads(body)
        except urllib.error.HTTPError as error:
            if error.code != 429 or attempt == attempts - 1:
                raise
            delay = retry_delay(error.headers.get('Retry-After'))
            if delay is None:
                delay = random.uniform(0, min(max_wait, 2 ** attempt))
            if delay > max_wait:
                raise  # Do not retry earlier than the server requested.
            error.close()
            sleep(delay)

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

2. فایل کامل demo_server.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class Handler(BaseHTTPRequestHandler):
    requests = 0

    def do_GET(self):
        type(self).requests += 1
        limited = type(self).requests == 1
        body = b'{"ready":true}'
        self.send_response(429 if limited else 200)
        if limited:
            self.send_header('Retry-After', '1')
        self.send_header('Content-Type', 'application/json')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)


if __name__ == '__main__':
    ThreadingHTTPServer(('127.0.0.1', 8087), Handler).serve_forever()

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

3. فایل کامل demo_client.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

import json
from retry_client import fetch_json

if __name__ == '__main__':
    print(json.dumps(fetch_json('http://127.0.0.1:8087/')))

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

4. فایل کامل test_retry_client.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

import threading
import unittest
from datetime import datetime, timezone
from http.server import ThreadingHTTPServer
from demo_server import Handler
from retry_client import fetch_json, retry_delay


class RetryTests(unittest.TestCase):
    def test_header_formats(self):
        now = datetime(2026, 10, 27, tzinfo=timezone.utc)
        self.assertEqual(retry_delay('12', now), 12)
        self.assertEqual(retry_delay('Tue, 27 Oct 2026 00:00:05 GMT', now), 5)
        self.assertIsNone(retry_delay('invalid', now))

    def test_real_429_then_success(self):
        Handler.requests = 0
        server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
        thread = threading.Thread(target=server.serve_forever, daemon=True)
        thread.start()
        sleeps = []
        try:
            self.assertTrue(fetch_json(f'http://127.0.0.1:{server.server_port}/', sleep=sleeps.append)['ready'])
            self.assertEqual(sleeps, [1])
            self.assertEqual(Handler.requests, 2)
        finally:
            server.shutdown(); server.server_close(); thread.join()


if __name__ == '__main__':
    unittest.main()

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

اجرای نمونه و معیار پایان بررسی

اگر خطا پیوسته تکرار می‌شود، فقط تعداد تلاش‌ها را بالا نبرید. درخواست‌های مشابه را ادغام کنید، نتیجهٔ مناسب را در حافظهٔ نهان بگذارید و نرخ تولید کار را با ظرفیت مقصد هماهنگ کنید. معیار موفقیت، کاهش درخواست بیهوده و پاسخ قابل پیگیری در زمان محدود است. این چهار فایل نقطهٔ شروع بررسی‌اند و به‌تنهایی جای سیاست سهمیه، پایش یا هماهنگی چند فرایند را نمی‌گیرند.

چک‌لیست اجرای عملی

  • سرآیند انتظار با هر دو قالب آزمون شود.
  • سقف تلاش و مکث مشخص بماند.
  • عملیات تغییردهندهٔ داده جداگانه ارزیابی شود.
  • خطای دائمی با افزایش بی‌پایان تلاش پنهان نشود.

منابع

تدوین و راهنمای عملی: دانشنامه لیان.