دراسة حالة - آخر الأخبار في الوقت الفعلي في مجموعة البث

مقدمة

من خلال WebSockets وEventSource، تتيح لغة HTML5 للمطوّرين إنشاء تطبيقات ويب تتواصل مع خادم في الوقت الفعلي. تقدّم خدمة بثّ جلسات الكونغرس (المتوفّرة في سوق Chrome الإلكتروني) آخر الأخبار مباشرةً حول أعمال الكونغرس الأمريكي. ويبثّ البث المباشر آخر الأخبار من مجلسَي النواب والشيوخ، بالإضافة إلى آخر الأخبار ذات الصلة، وآخر تغريدات أعضاء الكونغرس، وغيرها من آخر الأخبار على وسائل التواصل الاجتماعي. من المفترض أن يظلّ التطبيق مفتوحًا طوال اليوم لأنّه يرصد أعمال الكونغرس.

بدءًا من WebSockets

لقد حظيت مواصفات WebSockets باهتمام كبير لما تتيحه من استخدام مقبس TCP ثنائي الاتجاه ومستقر بين المتصفّح والخادم. لا يتم فرض أي تنسيق بيانات على مقبس TCP، فالمطوّر حر في تحديد بروتوكول المراسلة. من الناحية العملية، فإنّ تمرير كائنات JSON كسلَسل هو الخيار الأكثر ملاءمةً. رمز JavaScript من جهة العميل للاستماع إلى آخر الأخبار المباشرة هو رمز بسيط وواضح:

var liveSocket = new WebSocket("ws://streamcongress.com:8080/live");

liveSocket.onmessage = function (payload) {
  addToStream(JSON.parse(payload.data).reverse());
};

على الرغم من أنّ توافق المتصفّح مع WebSockets بسيط، لا يزال التوافق من جهة الخادم في المرحلة التأسيسية. توفّر Socket.IO على Node.js أحد الحلول الأكثر نضجًا وقوة من جهة الخادم. إنّ الخادم المستنِد إلى الأحداث، مثل Node.js، هو الخيار المناسب لاستخدام WebSockets. بالنسبة إلى عمليات التنفيذ البديلة، يمكن لمطوّري Python استخدام Twisted وTornado، في حين يمكن لمطوّري Ruby استخدام EventMachine.

لمحة عن Cramp

Cramp هو إطار عمل ويب غير متزامن لـ Ruby يعمل على EventMachine. المؤلف: براتيك، أحد أعضاء فريق Ruby on Rails الأساسي توفّر Cramp لغة خاصة بنطاق معيّن (DSL) لتطبيقات الويب في الوقت الفعلي، ما يجعلها خيارًا مثاليًا لمطوّري تطبيقات الويب باستخدام Ruby. سيتعرّف المستخدمون المألوفون بكتابة عناصر التحكّم في Ruby on Rails على أسلوب Cramp:

require "rubygems"
require "bundler"
Bundler.require
require 'cramp'
require 'http_router'
require 'active_support/json'
require 'thin'

Cramp::Websocket.backend = :thin

class LiveSocket < Cramp::Websocket
periodic_timer :check_activities, :every => 15

def check_activities
    @latest_activity ||= nil
    new_activities = find_activities_since(@latest_activity)
    @latest_activity = new_activities.first unless new_activities.empty?
    render new_activities.to_json
end
end

routes = HttpRouter.new do
add('/live').to(LiveSocket)
end
run routes

بما أنّ Cramp يقع على عاتق أجهزة EventMachine التي لا تؤدي إلى حظر الإعلانات، هناك العديد من الاعتبارات التي يجب أخذها في الاعتبار:

  • يجب استخدام برامج تشغيل قاعدة بيانات لا تؤدي إلى الحظر، مثل MySQLPlus وem-mongo.
  • يجب استخدام خوادم ويب مستندة إلى الأحداث. الدعم مُدمَج لكل من رفيع وقوس قزح.
  • يجب تشغيل تطبيق Cramp بشكل منفصل عن تطبيق Rails الرئيسي الذي يشغّل Stream Congress، وتتم إعادة تشغيله ومراقبته بشكلٍ مستقل.

القيود الحالية

واجهت بروتوكولات WebSockets انتكاسة في 8 كانون الأول (ديسمبر) 2010 عندما تم نشر ثغرة أمنية. أزال كلّ من Firefox وOpera إمكانية استخدام WebSockets في المتصفّح. على الرغم من عدم توفّر polyfill JavaScript خالص، يتوفّر عنصر احتياطي Flash تم اعتماده على نطاق واسع. ومع ذلك، فإنّ الاعتماد على Flash ليس مثاليًا. على الرغم من أنّ Chrome وSafari لا يزالان يتوافقان مع WebSockets، تبيّن لنا أنّه يجب استبدال WebSockets لتتوافق مع جميع المتصفّحات الحديثة بدون الاعتماد على Flash.

الرجوع إلى الاستطلاع باستخدام AJAX

تم اتخاذ القرار بالتوقف عن استخدام WebSockets والعودة إلى طلبات AJAX القديمة. على الرغم من أنّ طلبات AJAX للاستطلاع أقل فعالية من منظور I/O على القرص والشبكة، إلا أنّها سهّلت التنفيذ الفني لميزة "بث المؤتمرات". والأهم من ذلك أنّه تم إلغاء الحاجة إلى تطبيق Cramp منفصل. تم توفير نقطة نهاية AJAX بدلاً من ذلك من خلال تطبيق Rails. تم تعديل الرمز البرمجي من جهة العميل للسماح بالاستطلاع من خلال jQuery AJAX:

var fillStream = function(mostRecentActivity) {
  $.getJSON(requestURL, function(data) {
    addToStream(data.reverse());

    setTimeout(function() {
      fillStream(recentActivities.last());
    }, 15000);
  });
};

AJAX polling, though, is not without its downsides. Relying on the HTTP request/response cycle means that the server sees constant load even when there aren't any new updates. And of course, AJAX polling doesn't take advantage of what HTML5 has to offer.

## EventSource: The right tool for the job

Up to this point, a key factor was ignored about the nature of Stream Congress: the app only needs to stream updates one way, from server to client - downstream. It didn't need to be real-time, upstream client-to-server communication. 

In this sense, WebSockets is overkill for Stream Congress. Server-to-client communication is so common that it's been given a general term: push. In fact, many existing solutions for WebSockets, from the hosted [PusherApp](http://pusherapp.com) to the Rails library [Socky](https://github.com/socky), optimize for push and don't support client-to-server communication at all.

Enter EventSource, also called Server-Sent Events. The specification compares favorably to WebSockets in the context to server to client push:

- A similar, simple JavaScript API on the browser side.
- The open connection is HTTP-based, not dropping to the low level of TCP.
- Automatic reconnection when the connection is closed.

### Going Back to Cramp

In recent months, Cramp has added support for EventSource. The code is very similar to the WebSockets implementation:

```ruby
class LiveEvents < Cramp::Action
self.transport = :sse

periodic_timer :latest, :every => 15

def latest
@latest_activity ||= nil
new_activities = find_activities_since(@latest_activity)
@latest_activity = new_activities.first unless new_activities.empty?
render new_activities.to_json
end
end

routes = HttpRouter.new do
add('/').to(LiveEvents)
end
run routes

من المهمّ أخذ مشكلة EventSource في الاعتبار، وهي أنّه لا يُسمح بعمليات الربط بين نطاقات مختلفة. وهذا يعني أنّه يجب عرض تطبيق Cramp من نطاق streamcongress.com نفسه المستخدَم في تطبيق Rails الرئيسي. ويمكن تنفيذ ذلك من خلال الخادم الوكيل في خادم الويب. وبافتراض أن تطبيق Cramp يتم تشغيله من خلال Things (نحيف) ويعمل على المنفذ 8000، فإنّ إعدادات Apache تبدو كما يلي:

LoadModule  proxy_module             /usr/lib/apache2/modules/mod_proxy.so
LoadModule  proxy_http_module        /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule  proxy_balancer_module    /usr/lib/apache2/modules/mod_proxy_balancer.so

<VirtualHost *:80>
  ServerName streamcongress.com
  DocumentRoot /projects/streamcongress/www/current/public
  RailsEnv production
  RackEnv production

  <Directory /projects/streamcongress/www/current/public>
    Order allow,deny
    Allow from all
    Options -MultiViews
  </Directory>

  <Proxy balancer://thin>
    BalancerMember http://localhost:8000
  </Proxy>

  ProxyPass /live balancer://thin/
  ProxyPassReverse /live balancer://thin/
  ProxyPreserveHost on
</VirtualHost>

تضبط هذه الإعدادات نقطة نهاية EventSource في ‎streamcongress.com/live.

حشو بوليستر ثابت

من أهم مزايا EventSource مقارنةً بـ WebSockets أنّ المحتوى الاحتياطي يستند بالكامل إلى JavaScript، بدون الاعتماد على Flash. يحقّق البوليفيلر الذي أنشأه "رامي شارب" ذلك من خلال تنفيذ ميزة "الاستماع الطويل" في المتصفّحات التي لا تتيح استخدام EventSource بشكلٍ أصلي. وبالتالي، يعمل EventSource حاليًا على جميع المتصفحات الحديثة التي تم تفعيل JavaScript فيها.

الخاتمة

توفّر لغة HTML5 العديد من إمكانات تطوير الويب الجديدة والمثيرة. باستخدام WebSockets وEventSource، أصبح لدى مطوّري البرامج على الويب الآن معايير واضحة ومحددة لتفعيل تطبيقات الويب في الوقت الفعلي. ولكن لا يستخدم جميع المستخدمين متصفّحات حديثة. يجب مراعاة ميزة "التراجع السلس" عند اختيار تنفيذ هذه التقنيات. ولا تزال الأدوات المتوفّرة من جانب الخادم في WebSockets وEventSource في بداياتها. ومن المهم مراعاة هذه العوامل عند تطوير تطبيقات HTML5 في الوقت الفعلي.