1. المشكلة
عند تكوين Discourse باستخدام شبكة توصيل المحتوى (CDN)، يحدث خطأ شائع:
/message-bus/204234de907442e8b77e153786a58e5b/poll
فشل الاتصال / انتهاء المهلة / رمز حالة غير طبيعي
التأثير:
-
فشل الإشعارات: النقطة الحمراء (رسالة خاصة/رد جديد) لم تعد تظهر في الوقت الفعلي؛ قد تصل التحديثات متأخرة أو لا تصل على الإطلاق.
-
تعطل التحديثات في الوقت الفعلي: المشاركات/الإعجابات/الأصوات الجديدة لا يتم تحديثها تلقائيًا؛ يلزم تحديث الصفحة يدويًا.
-
تدهور تجربة المستخدم: تظهر رسائل “فقد الاتصال”؛ يتأخر التفاعل.
-
زيادة حمل الخادم: يستمر الواجهة الأمامية في إعادة محاولة الاستقصاء، مما يضيف ضغطًا على المصدر.
السبب: يستخدم Discourse الاستقصاء الطويل للحفاظ على الاتصال في الوقت الفعلي. تفرض العديد من شبكات توصيل المحتوى (CDNs) التخزين المؤقت الافتراضي، أو مهلات زمنية قصيرة، أو تحديات/فحوصات جدار الحماية، أو التخزين المؤقت للاتصالات الطويلة، مما يتسبب في انقطاعات أو استجابات مخزنة مؤقتًا تكسر السلوك في الوقت الفعلي.
2. النهج العام (الاستقرار أولاً)
-
فصل النطاق: توجيه MessageBus عبر نطاق مخصص يتصل مباشرة بالمصدر.
- نطاق المنتدى (عبر CDN): https://bbs.example.com
- نطاق MessageBus (بدون CDN): https://messagebus.example.com
-
طبقة الوكيل العكسي (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;
}
ملاحظة أمنية: إذا تم استخدام 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 والاتصال.