أحداث مرسلة من الخادم (Server-Sent Events) بلغة Rust: إرسال البيانات في الوقت الفعلي بدون WebSocket

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

متى يكون WebSocket أكثر من اللازم، أو لماذا عادت SSE إلى الموضة

\n\n

تخيل: تحتاج إلى إرسال بيانات من الخادم إلى العميل في الوقت الفعلي. أسعار العملات، إشعارات الرسائل الجديدة، سجلات الخادم، تقدم معالجة الملفات. أول ما يتبادر إلى الذهن هو WebSocket. ولكن ماذا لو لم تكن بحاجة إلى قناة ثنائية الاتجاه؟ ماذا لو كان العميل يستمع فقط، والخادم يتحدث فقط؟

\n\n

هنا تظهر الأحداث المرسلة من الخادم (SSE) — بروتوكول يسمح للخادم بإرسال البيانات إلى العميل عبر اتصال HTTP عادي. بدون مصافحة إضافية، بدون بروتوكولات معقدة، بدون صداع مع الوكلاء وموازنات التحميل. ونعم، في Rust يعمل هذا بشكل رائع.

\n\n

في هذه المقالة، سنشرح كيفية إعداد SSE بلغة Rust من الصفر باستخدام الأطر الشائعة. سترى أمثلة عملية، وستتعرف على المخاطر الخفية، وستفهم متى يكون SSE هو الخيار الأفضل لك.

\n\n

ما هي الأحداث المرسلة من الخادم (SSE) ولماذا Rust؟

\n\n

SSE هو معيار W3C (واجهة برمجة تطبيقات EventSource) يسمح للخادم بإرسال بيانات متدفقة إلى العميل عبر HTTP. يتصل العميل بنقطة نهاية، ويبدأ الخادم في إرسال رسائل بتنسيق text/event-stream. يعمل كل شيء عبر HTTP/1.1 أو HTTP/2 العادي، ولا يتطلب وكلاء خاصين، وهو مدعوم من جميع المتصفحات الحديثة.

\n\n

لماذا Rust؟ لأن Rust يمنحنا:

\n
    \n
  • عدم التزامن بدون مفاجآت — Tokio و async-std يسمحان بالحفاظ على آلاف اتصالات SSE على خيط واحد
  • \n
  • أمان الذاكرة — لا تسريبات، لا حالات سباق في التدفق متعدد الخيوط
  • \n
  • استهلاك ضئيل للموارد — اتصال SSE واحد يستهلك كيلوبايتات قليلة
  • \n
  • النظام البيئي — Actix Web، Rocket، Axum — جميعها تدعم SSE بشكل مدمج أو مع إضافات بسيطة
  • \n
\n\n

كيف يعمل SSE: تحليل البروتوكول

\n\n

يقوم العميل بإنشاء EventSource في المتصفح، مع تحديد URL. يستجيب الخادم برأس Content-Type: text/event-stream ويبدأ في إرسال أسطر بالتنسيق:

\n\n
data: مرحباً بالعالم!\\n\\n
\n\n

كل رسالة هي كتلة حيث data: تحتوي على الحمولة. يتم فصل الرسائل بسطرين جديدين. يمكن إرسال JSON، أرقام، نصوص — أي شيء. بالإضافة إلى ذلك، هناك حقول id و event و retry.

\n\n

إليك مثال بسيط لرسالة SSE:

\n\n
id: 1\nevent: update\ndata: {"status": "processing", "progress": 42}\n\n
\n\n

يتلقى العميل بلغة JavaScript هذا كحدث update مع حقل data يحتوي على JSON.

\n\n

كتابة خادم SSE بلغة Rust باستخدام Actix Web

\n\n

لنبدأ مع الإطار الأكثر شيوعًا — Actix Web. سنقوم بتثبيت التبعيات:

\n\n
[dependencies]\nactix-web = "4"\nactix-rt = "2"\ntokio = { version = "1", features = ["full"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"
\n\n

الآن سنكتب نقطة نهاية بسيطة ترسل الوقت الحالي كل ثانيتين:

\n\n
use actix_web::{get, web, App, HttpRequest, HttpResponse, HttpServer, middleware};\nuse actix_web::rt::time;\nuse futures::stream::StreamExt;\nuse std::time::Duration;\n\n#[get("/events")]\nasync fn stream_events(req: HttpRequest) -> HttpResponse {\n    let mut rx = tokio::sync::broadcast::channel::<String>(100).1;\n\n    let stream = async_stream::stream! {\n        loop {\n            let message = format!("data: {}\\n\\n", chrono::Local::now());\n            yield Ok::<_, actix_web::Error>(\n                web::Bytes::from(message)\n            );\n            time::sleep(Duration::from_secs(2)).await;\n        }\n    };\n\n    HttpResponse::Ok()\n        .insert_header(("Content-Type", "text/event-stream"))\n        .insert_header(("Cache-Control", "no-cache"))\n        .insert_header(("Connection"                

المدونات

توصيات الكتب