كيف يعمل Centrifugo
يقع Centrifugo بين تطبيقك ومستخدميه. لا يُبقي خادمك الخلفي — Laravel أو Django أو Express أو Rails أو غيرها — أي اتصال مفتوحًا: يوقّع رمزًا لكل مستخدم، ثم ينشر الرسائل بطلب HTTP بسيط. أما Centrifugo فيحافظ على اتصالات WebSocket للمتصفحات وتطبيقات الجوال، ويوصل كل رسالة إلى مشتركي القناة المعنية.
لهذا الفصل فائدة ملموسة: يبقى تطبيقك بلا حالة وسهل النشر، ويتسع جزء الزمن الحقيقي في ملف Go تنفيذي واحد يستهلك بضع عشرات من الميغابايت من الذاكرة في وضع الخمول.
ما يقدّمه Centrifugo
- اتصالات WebSocket — مع حزم رسمية للعملاء بلغات JavaScript وDart/Flutter وSwift وJava/Android وGo وPython.
- واجهة نشر — طلب HTTP POST مع الترويسة
X-API-Key، من أي لغة. - رموز JWT — يقرر خادمك الخلفي من يتصل وبأي قنوات، ويتحقق Centrifugo من التوقيع.
- اشتراكات من جهة الخادم — تنتقل قائمة قنوات المستخدم داخل رمزه.
- لوحة إدارة — حالة العقدة والعملاء المتصلون وأوامر الواجهة من المتصفح.
- إعدادات افتراضية حذرة — لا اتصال مجهول، ولا نشر من المتصفح دون إذن صريح.
Centrifugo مقارنة بخدمة مستضافة
مرّر الجدول أفقيًا
| Centrifugo على VPS | Pusher وAbly | |
|---|---|---|
| الفوترة | سعر الخادم دون حد للرسائل | حسب الاتصالات والرسائل |
| مسار البيانات | على خادمك | عبر المزوّد |
| بروتوكول العميل | WebSocket وحزم رسمية | WebSocket وحزم مملوكة |
| المصادقة | JWT يوقّعه خادمك الخلفي | توقيع خاص بالخدمة |
| التشغيل | على عاتقك | يتولاه المزوّد |
المتطلبات
خادم افتراضي بنظام Ubuntu 24.04 بذاكرة 1 غيغابايت ومساحة 10 غيغابايت، مع Docker وإضافة Compose، ونطاق فرعي يشير إلى الخادم، مثل rt.example.com. HTTPS ليس اختياريًا: لا يستطيع موقع يُقدَّم عبر HTTPS فتح اتصالات غير wss://، ويجب ألا يمر مفتاح الواجهة بنص واضح أبدًا.
تثبيت Centrifugo باستخدام Docker Compose
توليد الأسرار
أنشئ مجلد التطبيق وملف
.envيحمل أربعة أسرار مختلفة: كلمة مرور اللوحة، والمفتاح الذي يوقّع جلساتها، ومفتاح واجهة الخادم، ومفتاح HMAC لرموز العملاء.mkdir -p /opt/centrifugo && cd /opt/centrifugo { echo "ADMIN_PASSWORD=$(openssl rand -hex 12)" echo "SECRET_KEY=$(openssl rand -hex 24)" echo "ENCRYPTION_KEY=$(openssl rand -hex 16)" echo "ENCRYPTION_KEY_HEX64=$(openssl rand -hex 32)" } > .env chmod 600 .envكتابة ملف docker-compose.yml
يُنشر المنفذ على الواجهة المحلية فقط: لا يصل إليه إلا nginx. استبدل
https://app.example.comبعنوان موقعك.services: centrifugo: image: centrifugo/centrifugo:v6.10.2 restart: unless-stopped command: ["centrifugo"] ports: - "127.0.0.1:8000:8000" environment: CENTRIFUGO_ADMIN_ENABLED: "true" CENTRIFUGO_ADMIN_PASSWORD: "${ADMIN_PASSWORD}" CENTRIFUGO_ADMIN_SECRET: "${SECRET_KEY}" CENTRIFUGO_HTTP_API_KEY: "${ENCRYPTION_KEY}" CENTRIFUGO_CLIENT_TOKEN_HMAC_SECRET_KEY: "${ENCRYPTION_KEY_HEX64}" CENTRIFUGO_CLIENT_ALLOWED_ORIGINS: "https://app.example.com" CENTRIFUGO_HEALTH_ENABLED: "true" ulimits: nofile: soft: 65535 hard: 65535التشغيل والتحقق
شغّل الحاوية واستعلم نقطة الصحة؛ يجب أن تجيب بـ
{}.docker compose up -d curl -fsS http://127.0.0.1:8000/health
لا تنشر المنفذ 8000 على العنوان العام أبدًا
يخدم المنفذ نفسه اتصالات العملاء وواجهة الخادم ولوحة الإدارة. أبقه على 127.0.0.1 ومرّ دائمًا عبر الوكيل العكسي HTTPS. ولا تضع أبدًا ENCRYPTION_KEY أو ENCRYPTION_KEY_HEX64 في شيفرة تُرسل إلى المتصفح: الأول يسمح بالنشر على أي قناة، والثاني بصنع رموز لأي مستخدم.
وضع Centrifugo خلف nginx
يبدأ اتصال WebSocket بطلب HTTP يطلب تبديل البروتوكول. يجب أن يمرر nginx هذا الطلب، وألا يقطع اتصالًا يبقى صامتًا بضع دقائق. في كتلة server الخاصة بنطاقك الفرعي، مع شهادة Let's Encrypt:
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
}بعد nginx -t وإعادة التحميل، يعرض https://rt.example.com شاشة الدخول إلى اللوحة.
النشر من الخادم الخلفي
النشر هو إرسال كائن JSON إلى قناة. تصلح لذلك أي لغة قادرة على إرسال طلب HTTP:
curl -X POST https://rt.example.com/api/publish \
-H "X-API-Key: $ENCRYPTION_KEY" \
-d '{"channel": "commandes", "data": {"id": 1042, "statut": "payee"}}'يؤكد الرد {"result":{}} قبول الرسالة. دون الترويسة X-API-Key، أو بمفتاح خاطئ، يجيب Centrifugo بـ 401.
توقيع رموز الاتصال
لا يتصل المتصفح إلا برمز JWT يوقّعه خادمك الخلفي بـ ENCRYPTION_KEY_HEX64. تحمل المطالبة sub معرّف المستخدم، وexp تاريخ الانتهاء، وchannels قائمة القنوات التي يُشترك فيها تلقائيًا. مثال بلغة Python:
import time, jwt # pip install pyjwt
def centrifugo_token(user_id: str) -> str:
claims = {"sub": user_id, "exp": int(time.time()) + 3600, "channels": ["commandes"]}
return jwt.encode(claims, ENCRYPTION_KEY_HEX64, algorithm="HS256")ضع هذه الدالة خلف مسار موثّق في تطبيقك: لا ينبغي أن يحصل على رمز إلا مستخدم مسجّل الدخول لديك.
ما المسموح به للعميل
إعدادات هذا التثبيت مغلقة عن قصد. لا يستطيع المتصفح الاشتراك في قناة من تلقاء نفسه ولا النشر فيها: لا يتلقى إلا القنوات المذكورة في المطالبة channels داخل رمزه. يقرر خادمك الخلفي، عند كل إصدار للرمز، من يستمع إلى ماذا — لا يرى العميل إلا طلباته، ولا يرى موظف الدعم إلا طابور فريقه.
في الدردشة إذن، يمر النشر أيضًا عبر خادمك الخلفي: يرسل المتصفح الرسالة إلى تطبيقك، فيتحقق منها ويحفظها في قاعدة البيانات ثم ينشرها عبر الواجهة. يكلّف هذا الالتفاف بضع أجزاء من الثانية ويُبقي بيدك الإشراف والسجل.
وأخيرًا، اجعل مدة صلاحية الرموز قصيرة، ساعة مثلًا. بدل تمرير رمز ثابت، دع الحزمة تطلب رمزًا جديدًا من تطبيقك عند انتهاء صلاحيته:
const client = new Centrifuge('wss://rt.example.com/connection/websocket', {
getToken: async () => {
const res = await fetch('/realtime/token', { credentials: 'include' });
return (await res.json()).token;
},
});المسار /realtime/token خاص بتطبيقك، ويتحقق من جلسة المستخدم قبل توقيع أي شيء.
ربط متصفح
من جهة العميل، تفتح الحزمة الرسمية centrifuge (npm) الاتصال وتسلّم الرسائل:
import { Centrifuge } from 'centrifuge';
const client = new Centrifuge('wss://rt.example.com/connection/websocket', {
token: tokenFromYourBackend,
});
client.on('publication', (ctx) => console.log(ctx.channel, ctx.data));
client.connect();إذا فشل الاتصال، فابدأ بالسجلات (docker compose logs centrifugo). تعني الرسالة «request Origin is not authorized» أن أصل موقعك غائب عن CENTRIFUGO_CLIENT_ALLOWED_ORIGINS، وتشير «invalid token» إلى رمز موقّع بشكل خاطئ أو منتهي الصلاحية.
لوحة الإدارة
افتح https://rt.example.com وأدخل ADMIN_PASSWORD: لا تطلب اللوحة اسم مستخدم. تعرض إصدار العقدة وعدد العملاء والمستخدمين المتصلين، وتتيح إرسال أوامر الواجهة — نشر رسالة تجريبية أو سرد القنوات النشطة — دون كتابة شيفرة. إنها أسرع وسيلة للتحقق من خروج الرسالة قبل البحث عن الخلل في المتصفح.
التوسّع والتحديثات
يستخدم هذا الدليل محرك الذاكرة: عقدة واحدة دون أي اعتماد. يناسب معظم التطبيقات، ويتحمل خادم متواضع عددًا كبيرًا من الاتصالات المتزامنة، ولذلك رُفع حد الملفات المفتوحة في docker-compose.yml. وحين لا تكفي عقدة واحدة، يستطيع Centrifugo توزيع الحمل على عدة عقد مرتبطة عبر Redis. للتحديث، غيّر وسم الصورة بعد قراءة ملاحظات الإصدار، ثم شغّل docker compose pull && docker compose up -d؛ تعود الاتصالات تلقائيًا لأن حزم العملاء تعيد الاتصال من تلقاء نفسها.