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

لويجي مونتانيز
لويجي مونتانيز

مقدمة

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

البدء بـ WebSockets

لقد حظيت مواصفات WebSockets بقدر كبير من الاهتمام لما تتيحها: مقبس 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. كتبه {0}Pratik Naik{/1}، وهو عضو في فريق Ruby on Rails الأساسي. يوفر Cramp لغة خاصة بالنطاق لتطبيقات الويب في الوقت الفعلي، وهو خيار مثالي لمطوّري برامج الويب Ruby. سيتعرف من هم على دراية بكتابة وحدات التحكم في Ruby on Rails على نمط كرامب:

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 يضعها على رأس آلة الأحداث التي لا تمنع المستخدم، هناك عدة اعتبارات يجب وضعها في الاعتبار:

  • يجب استخدام برامج تشغيل قاعدة البيانات التي لا تحظر قواعد البيانات، مثل 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 "الكلاسيكية". وعلى الرغم من أنها أقل كفاءة من منظور مؤتمر I/O على قرص التخزين والشبكة، غير أن استطلاعات AJAX بسطت التنفيذ الفني لمؤتمر Stream Congress. والأهم من ذلك، تم التخلص من الحاجة إلى تطبيق 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 هو تطبيق "رقيق" ويعمل على المنفذ 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. يُحقق polyfill من Remy Sharp هذا من خلال تنفيذ الاستقراء الطويل في المتصفحات التي لا تدعم EventSource في الأصل. ولهذا السبب، تعمل EventSource حاليًا على جميع المتصفحات الحديثة التي تم تفعيل JavaScript فيها.

الخلاصة

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