كيفية إصلاح أخطاء Discourse Forum /message-bus أو Long Polling

1. المشكلة

عند تكوين Discourse باستخدام شبكة توصيل المحتوى (CDN)، يحدث خطأ شائع:

/message-bus/204234de907442e8b77e153786a58e5b/poll
فشل الاتصال / انتهاء المهلة / رمز حالة غير طبيعي

التأثير:

  • فشل الإشعارات: النقطة الحمراء (رسالة خاصة/رد جديد) لم تعد تظهر في الوقت الفعلي؛ قد تصل التحديثات متأخرة أو لا تصل على الإطلاق.

  • تعطل التحديثات في الوقت الفعلي: المشاركات/الإعجابات/الأصوات الجديدة لا يتم تحديثها تلقائيًا؛ يلزم تحديث الصفحة يدويًا.

  • تدهور تجربة المستخدم: تظهر رسائل “فقد الاتصال”؛ يتأخر التفاعل.

  • زيادة حمل الخادم: يستمر الواجهة الأمامية في إعادة محاولة الاستقصاء، مما يضيف ضغطًا على المصدر.

السبب: يستخدم Discourse الاستقصاء الطويل للحفاظ على الاتصال في الوقت الفعلي. تفرض العديد من شبكات توصيل المحتوى (CDNs) التخزين المؤقت الافتراضي، أو مهلات زمنية قصيرة، أو تحديات/فحوصات جدار الحماية، أو التخزين المؤقت للاتصالات الطويلة، مما يتسبب في انقطاعات أو استجابات مخزنة مؤقتًا تكسر السلوك في الوقت الفعلي.


2. النهج العام (الاستقرار أولاً)

  • فصل النطاق: توجيه MessageBus عبر نطاق مخصص يتصل مباشرة بالمصدر.

  • طبقة الوكيل العكسي (Nginx):

    • تمكين CORS لـ /message-bus.
    • تعطيل التخزين المؤقت للوكيل، وتخفيف المهلات الزمنية، وحظر التخزين المؤقت بشكل صريح.
  • طبقة CDN (إذا كانت لا تزال مستخدمة):

    • تكوين /message-bus/* مع عدم التخزين المؤقت، ومهلة زمنية ممتدة، وعدم وجود تحديات JavaScript/CAPTCHA/حدود معدل، وتمرير ملفات تعريف الارتباط/المصادقة.
    • أو تجاوز CDN تمامًا.

3. خطوات التنفيذ

1) تكوين متغيرات بيئة Discourse

قم بتحرير app.yml (عادةً في /var/discourse/containers/app.yml) وأضف/عدّل تحت env::

env:
  DISCOURSE_MESSAGE_BUS_REDIS_ENABLED: true
  DISCOURSE_LONG_POLLING_BASE_URL: "https://messagebus.example.com"

طبق التغييرات (النشر الرسمي):

cd /var/discourse
./launcher rebuild app

شرح:

  • يخبر DISCOURSE_LONG_POLLING_BASE_URL الواجهة الأمامية باستخدام نطاق MessageBus.
  • يجب أن يظل REDIS_ENABLED ممكّنًا.

2) DNS والشهادة

  • وجّه messagebus.example.com مباشرة إلى المصدر، متجاوزًا CDN (أفضل ممارسة).
  • قم بإعداد شهادة HTTPS صالحة للنطاق.

3) Nginx (نطاق MessageBus) الوكيل العكسي و CORS

أضف أو حدّث ما يلي في كتلة الخادم لـ messagebus.example.com:

location ^~ /message-bus {

    # (1) معالجة خيارات CORS (OPTIONS)
    if ($request_method = OPTIONS) {
        add_header 'Access-Control-Allow-Origin' 'https://bbs.example.com' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,discourse-deferred-track-view-topic-id,discourse-present,discourse-track-view,discourse-deferred-track-view,x-silence-logger,dont-chunk,x-shared-session-key' always;
        add_header 'Access-Control-Max-Age' 1728000 always;
        add_header 'Content-Type' 'text/plain; charset=UTF-8' always;
        add_header 'Content-Length' 0 always;
        return 204;
    }

    # (2) الوكيل العكسي إلى Discourse
    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
    # أو، إذا كان مستقلاً:
    # proxy_pass http://127.0.0.1:3000;

    # (3) منع رؤوس CORS المكررة
    proxy_hide_header Access-Control-Allow-Origin;
    proxy_hide_header Access-Control-Allow-Credentials;
    proxy_hide_header Access-Control-Allow-Methods;
    proxy_hide_header Access-Control-Allow-Headers;
    proxy_hide_header Access-Control-Max-Age;

    # (4) CORS العادي للطلبات
    add_header 'Access-Control-Allow-Origin' 'https://bbs.example.com' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With,discourse-deferred-track-view-topic-id,discourse-present,discourse-track-view,discourse-deferred-track-view,x-silence-logger,dont-chunk,x-shared-session-key' always;

    # (5) إعدادات استقرار الاستقصاء الطويل
    proxy_read_timeout    120s;
    proxy_send_timeout    120s;
    proxy_connect_timeout 60s;

    proxy_buffering off;
    add_header X-Accel-Buffering no always;

    # (6) حظر التخزين المؤقت صراحةً
    add_header Cache-Control "no-store, no-cache, must-revalidate" always;
}

:warning: ملاحظة أمنية: إذا تم استخدام Access-Control-Allow-Credentials: true، فيجب ألا يكون Origin هو *؛ يجب أن يتطابق مع نطاق المنتدى الدقيق.


4) قواعد CDN (إذا كان المنتدى لا يزال خلف CDN)

موصى به: قم بتعيين عدم التخزين المؤقت + مهلة زمنية ممتدة + تجاوز WAF/حدود المعدل لهذه المسارات:

مثال على التعبير العادي:

^/(session|login|message-bus|admin|u|users)(/|$)

السياسة:

  • عدم وجود تخزين مؤقت للمتصفح/العقدة (no-store/no-cache).
  • مهلة القراءة/الخمول للواجهة الخلفية ≥ 60-120 ثانية.
  • تعطيل تحديات JavaScript/CAPTCHA/إدارة الروبوتات.
  • تمرير ملفات تعريف الارتباط ورؤوس المصادقة (لا تقم بإزالتها).

4. كيفية التحقق من النجاح

1) أدوات مطوري المتصفح → الشبكة

في صفحة المنتدى:

  • لاحظ /message-bus/…/poll.
  • يجب أن “يتعلق” الطلب لمدة ~20-60 ثانية، ثم يعود بـ 200 (ربما فارغًا).
  • يتم تشغيل طلب الاستقصاء التالي تلقائيًا.

تحقق من رؤوس الاستجابة:

  • Access-Control-Allow-Origin: https://bbs.example.com
  • Cache-Control: no-store
  • لا يوجد Age، X-Cache: HIT، أو CF-Cache-Status: HIT (يعني أنه لم يتم تخزينه مؤقتًا).

مشاكل شائعة:

  • أخطاء ثابتة 10 ثانية/30 ثانية → مهلة زمنية للحافة/المصدر.
  • 504/524: انتهاء المهلة.
  • 499: انقطاع الطبقة الوسيطة.
  • 403/401: حظر WAF/المصادقة.

2) فحص سريع عبر سطر الأوامر (اختياري)

تحقق من الاتصال والرؤوس (ليس استقصاء كاملاً):

curl -I "https://messagebus.example.com/message-bus/health-check" \
  -H "Origin: https://bbs.example.com"

ملاحظة: تتطلب الاستقصاءات الفعلية سياق الجلسة؛ هذا يتحقق فقط من CORS والاتصال.

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