كيفية تجنب قيود المعدل باستخدام مفتاح API إداري؟

أتلقى رسالة “429 Too Many Requests” لطلبات واجهة برمجة التطبيقات (API) إلى نسختي المستضافة ذاتيًا حتى مع:

  • زيادة DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE إلى 600
    • قمت بتعيين هذا في قسم env من app.yml ثم قمت بتشغيل ./launcher rebuild وتأكدت من تعيين المتغير في الحاوية التي أعيد بناؤها.
    • هذا يتجاوز بكثير عدد الطلبات في الدقيقة التي أحاولها.
  • مفتاح واجهة برمجة تطبيقات إداري غير مقيد.

يبدو أن هذا قد تمت مناقشته من قبل دون إجابة واضحة حول سبب عدم عمل تغيير DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE:

كيف يمكنني التأكد من أن طلبات واجهة برمجة التطبيقات باستخدام مفتاح/مستخدم إداري لا تخضع للحد من المعدل؟

مرحباً @aas،

هل يمكنك تقديم بعض السياق؟

  • كم عدد طلبات واجهة برمجة التطبيقات (API) التي تقوم بها؟ في الثانية، الدقيقة، الساعة، في اليوم
  • هل أنت متأكد من أنك تستخدم مفتاح واجهة برمجة تطبيقات إداري؟
  • هل كل هذه الطلبات تأتي من نفس عنوان IP؟ ربما بسبب وكيل عكسي؟

هل يمكن أن يكون nginx أو قطعة أخرى من البرامج هي التي تسبب لك هذا الخطأ؟

إعجاب واحد (1)

مرحباً @Bas،

أعتذر عن التأخر في الرد!

أنا الآن ألقي نظرة على هذا مرة أخرى حيث أطلقنا تكاملاً مع Discourse ونريد التأكد من عدم مواجهة أي مشاكل متعلقة بتحديد المعدل.

لقد اختبرته بمفتاح جديد للتأكد من أنه غير مقيد بأي شكل من الأشكال. للتوضيح، ماذا تقصد بالضبط بمفتاح API للمسؤول؟

لقد أنشأت مفتاحًا بالإعدادات التالية:

يقول، “مفتاح API ليس له قيود وجميع نقاط النهاية متاحة.”

أنا أختبر هذا عن طريق إجراء طلبات API من قشرة Python محلية، لذا فهي تأتي من نفس عنوان IP. واجهنا أيضًا حدود المعدل عند تشغيل برنامج نصي على خادمنا. في تلك الحالة، جاءت جميع الطلبات من نفس عنوان IP.

لقد أكدت أن حد المعدل يتم الوصول إليه باستخدام الكود التالي:

async def get_topic_post_stream(topic_id):
    url = f"{DISCOURSE_URL}/t/{topic_id}"
    async with httpx.AsyncClient(headers=HEADERS) as client:
        topic = await client.get(url)
    return topic.status_code


async def get_topic_post_streams(topic_ids):
    tasks = [functools.partial(get_topic_post_stream, topic_id) for topic_id in topic_ids]
    topics = await aiometer.run_all(
        tasks,
        # max_per_second=1,
        )
    return topics

# فقط احصل على شريحة من 15 من المواضيع في topic_ids للاختبار.
topics = asyncio.run(get_topic_post_streams(topic_ids[:15]))

لاحظ أن المعلمة max_per_second معطلة، مما يؤدي إلى عدم وجود قيود على عدد الطلبات.

يكتمل هذا في 2.05 ثانية ويُرجع 2 من أصل 15 طلبًا 429.

عندما أقوم بتشغيله مع max_per_second=1، يكتمل كل شيء بنجاح.

أخبرني إذا كان بإمكاني تقديم أي تفاصيل إضافية. شكرًا!

@Bas، إليك ما يعادل كود بايثون بلغة جافاسكريبت لتسهيل إعادة إنتاجه باستخدام وحدة تحكم أدوات المطور:

const DISCOURSE_URL = '';
const HEADERS = {
    'Api-Key': '',
    'Api-Username': '',
    'Content-Type': 'application/json'
};


const topicIds = Array.from({ length: 100 }, (_, i) => i + 1);

async function getTopicPostStream(topicId) {
    const url = `${DISCOURSE_URL}/t/${topicId}`;
    const response = await fetch(url, { headers: HEADERS });
    return response.status;
}

async function getTopicPostStreams(topicIds) {
    const results = await Promise.all(topicIds.map(topicId => getTopicPostStream(topicId)));
    return results;
}

// لا تقم بتحديد معدل الطلبات وشاهد أنك تحصل على اثنين من 429.
(async () => {
    const topics = await getTopicPostStreams(topicIds.slice(0, 15));
    console.log(topics);
})();

async function getTopicPostStreamsRateLimited(topicIds) {
    const results = [];
    for (const topicId of topicIds) {
        const result = await getTopicPostStream(topicId);
        results.push(result);
        await new Promise(resolve => setTimeout(resolve, 1000)); // تأخير لمدة ثانية واحدة
    }
    return results;
}

// طلب واحد في الثانية يعيد جميع الـ 200.
(async () => {
    const topics = await getTopicPostStreamsRateLimited(topicIds.slice(0, 15));
    console.log(topics);
})();
إعجاب واحد (1)

إذا كان عليّ أن أخمن، فهذه على الأرجح هي المشكلة. أنت لا تخضع لقيود المعدل على واجهة برمجة التطبيقات، ولكن على أساس عنوان IP الخاص بك.

يمكنك إلقاء نظرة على الإعدادات لكل عنوان IP هنا: Available settings for global rate limits and throttling - #27

يرجى إعلامي إذا كان هذا يحل مشكلتك بالفعل :slight_smile:

إعجاب واحد (1)

شكراً، @Bas!

يبدو لي أنه لا ينبغي أن أتلقى هذه الـ 429 بغض النظر عن أي إعدادات مذكورة في هذا المنشور. في المثال الذي قدمته، أرسلت 15 طلبًا وهو أقل من جميع حدود واجهة برمجة التطبيقات الافتراضية. فعلت ذلك باستخدام مفتاح واجهة برمجة تطبيقات إداري واسم مستخدم.

المثال لا يتجاوز الحدود الافتراضية لكل عنوان IP:

حتى أنه لا يتجاوز حدود غير المسؤولين:

تغيير DISCOURSE_MAX_REQS_PER_IP_MODE إلى warn أو none لم يساعد.

هل فاتني شيء؟ :thinking:

بالمناسبة، قمت بتغيير الإعدادات عن طريق تحرير app.yml وتشغيل ./launcher destroy app && ./launcher start app.

يمكنني رؤية في /var/log/nginx/access.log أن عنوان IP صحيح، لذلك لا أعتقد أن Discourse يعتبر أن جميع الطلبات تأتي من نفس عنوان IP.

يمكنني أيضًا رؤية عناوين IP للمستخدمين في لوحة الإدارة.

هذه هي الإعدادات التي قمت بتعديلها:

  DISCOURSE_MAX_ADMIN_API_REQS_PER_MINUTE: 1200
  DISCOURSE_MAX_USER_API_REQS_PER_MINUTE: 60
  DISCOURSE_MAX_REQS_PER_IP_MODE: none
  DISCOURSE_MAX_REQS_PER_IP_PER_10_SECONDS: 100
  DISCOURSE_MAX_REQS_PER_IP_PER_MINUTE: 400

تعديل: لقد تحققت للتو من محتويات الاستجابة لأحد الطلبات الفاشلة ولاحظت أنه ذكر nginx:

<html>\r\n<head><title>429 Too Many Requests</title></head>\r\n<body>\r\n<center><h1>429 Too Many Requests</h1></center>\r\n<hr>\n<center>nginx</center>\r\n</body>\r\n</html>\r\n

سأقوم بمزيد من التحقيق في المواضيع التي تذكر nginx.

إعجاب واحد (1)

تبدو الأقسام ذات الصلة في إعدادات nginx كالتالي:

limit_req_zone $binary_remote_addr zone=flood:10m rate=12r/s;
limit_req_zone $binary_remote_addr zone=bot:10m rate=200r/m;
limit_req_status 429;
limit_conn_zone $binary_remote_addr zone=connperip:10m;
limit_conn_status 429;
server {
  listen 80;
  return 301 https://community.ankihub.net$request_uri;
}

و

  location @discourse {
add_header Strict-Transport-Security 'max-age=31536000'; # تذكر الشهادة لمدة عام وتصل تلقائيًا إلى HTTPS لهذا النطاق
  limit_conn connperip 20;
  limit_req zone=flood burst=12 nodelay;
  limit_req zone=bot burst=100 nodelay;
    proxy_set_header Host $http_host;
    proxy_set_header X-Request-Start "t=${msec}";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $thescheme;
    proxy_pass http://discourse;
  }
}

أسئلتي المتبقية الآن هي:

  • هل يجب علي تعديل كلا القسمين لمطابقة إعدادات Discourse الخاصة بي؟ أم فقط القيم الخاصة بـ location @discourse؟

  • ما هي الطريقة الصحيحة لتعديل هذه القيم والحفاظ على التغييرات عبر عمليات إعادة البناء؟
    أفترض أنه يمكنني تعديل إعدادات nginx مباشرة في الحاوية ثم إيقاف/بدء الحاوية. ولكن يبدو أن هذه القيم جاءت في الأصل من templates/web.ratelimited.template.yml وقد يتم استبدالها عند إعادة البناء؟

شكراً جزيلاً على مساعدتك! :pray:

إعجاب واحد (1)

أوه، الآن نحن نخرج عن منطقة راحتي أخشى.\n\nإذا كنت مقيدًا بمعدل nginx، فإن العبث بهذه الإعدادات وجعلها أقل تقييدًا منطقي. لست متأكدًا مما إذا كان Nginx يمكنه وضع قائمة بيضاء لعناوين IP؟

شيء مثل

map $remote_addr $exclude_from_limit {
    default 0;
    192.168.1.1 1;
    192.168.1.2 1;
}

ثم قم بتغليف الحدود في if

        if ($exclude_from_limit = 0) {
            limit_req zone=flood burst=24 nodelay;
            limit_req zone=bot burst=400 nodelay;
            limit_conn connperip 20;
        }

نعم، يجب عليك إجراء بعض الاستبدالات باستخدام pups أثناء البناء لجعله دائمًا، انظر على سبيل المثال web.ssl.template.yml حول كيفية التعامل مع ذلك.

أو يمكنك نسيان هذا وجعل برنامج نصي لعميل API الخاص بك يعمل ببطء أكبر عن طريق إدراج بعض فترات السكون في أماكن استراتيجية. ← النهج الموصى به

4 إعجابات

مثلما يحدث في rescue عندما يتم تحديد معدل الطلبات. هذا ما أحب أن أفكر أنني أفعله عادةً.

إعجاب واحد (1)

ما هو الحد الأقصى لمعدل طلبات واجهة برمجة التطبيقات المستدام في الدقيقة أو الثانية في التثبيت القياسي؟

لقد جربت طلبًا واحدًا في الثانية ويبدو أنني وصلت إلى حد. بينما يعمل طلب واحد كل 7 ثوانٍ بشكل جيد.