บทนำ
HTML5 ช่วยให้นักพัฒนาซอฟต์แวร์สร้างเว็บแอปที่สื่อสารกับเซิร์ฟเวอร์แบบเรียลไทม์ได้ผ่าน WebSockets และ EventSource สตรีมสภาครัฐ (มีให้บริการใน Chrome เว็บสโตร์) ให้ข้อมูลอัปเดตแบบเรียลไทม์เกี่ยวกับการทำงานของสภาครัฐสหรัฐอเมริกา แพลตฟอร์มนี้จะสตรีมการอัปเดตราคาพื้นจากทั้งสภาและวุฒิสภา ข่าวสารอัปเดตที่เกี่ยวข้อง ทวีตจากสมาชิกรัฐสภา และการอัปเดตในโซเชียลมีเดียอื่นๆ แอปนี้มีไว้เพื่อให้เปิดอยู่ตลอดทั้งวันเนื่องจากจะบันทึกกิจกรรมของสมาชิกสภาคองเกรส
เริ่มต้นด้วย WebSockets
ข้อกำหนด WebSockets ได้รับความสนใจอย่างมากเนื่องจากสิ่งที่ทำได้คือ ซ็อกเก็ต TCP แบบ 2 ทิศทางที่เสถียรระหว่างเบราว์เซอร์กับเซิร์ฟเวอร์ ไม่มีการกำหนดรูปแบบข้อมูลในซ็อกเก็ต 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 บทความนี้เขียนโดย Pratik Naik สมาชิกทีมหลักของ 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 ได้ยกเลิกการรองรับ WebSocket ในเบราว์เซอร์แล้ว แม้ว่าจะไม่มี polyfill ของ JavaScript ล้วนๆ แต่ก็มีเนื้อหาสำรองสำหรับ Flash ที่ใช้กันอย่างแพร่หลาย อย่างไรก็ตาม การพึ่งพา Flash นั้นไม่เหมาะอย่างยิ่ง แม้ว่า Chrome และ Safari จะสนับสนุน WebSockets ต่อไป แต่เห็นได้ชัดว่าการสนับสนุนเบราว์เซอร์รุ่นใหม่ทั้งหมดโดยไม่ต้องใช้ Flash จึงจำเป็นต้องมีการเปลี่ยน WebSockets
การเปลี่ยนกลับไปใช้การสำรวจ AJAX
การตัดสินใจนี้ตัดสินใจที่จะออกจาก WebSockets และกลับไปใช้การจัดทำแบบสำรวจ AJAX "แบบเดิมๆ" แม้ว่าจะมีประสิทธิภาพน้อยกว่ามากจากมุมมอง I/O ของดิสก์และเครือข่าย แต่การโหวต AJAX ก็ทำให้การใช้งานทางเทคนิคของ Stream Congress ง่ายขึ้น ที่สำคัญที่สุดคือคุณไม่จำเป็นต้องใช้แอป Cramp อีกต่อไป แอป Rails จะเป็นผู้ระบุปลายทาง AJAX แทน โค้ดฝั่งไคลเอ็นต์ได้รับการแก้ไขให้รองรับการเรียกข้อมูล AJAX ของ jQuery ดังนี้
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 ทำงานด้วย Thin และทำงานบนพอร์ต 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
Polyfill ที่เสถียร
ข้อดีที่สําคัญที่สุดอย่างหนึ่งของ EventSource เหนือ WebSocket คือ Fallback นั้นใช้ JavaScript ทั้งหมดโดยที่ไม่จําเป็นต้องใช้ Flash polyfill ของ Remy Sharp บรรลุเป้าหมายนี้ได้โดยใช้การโพลแบบยาวในเบราว์เซอร์ที่ไม่รองรับ EventSource ตั้งแต่ต้น ดังนั้น EventSource จึงทำงานได้ในเบราว์เซอร์สมัยใหม่ทั้งหมดที่เปิดใช้ JavaScript
บทสรุป
HTML5 เปิดประตูสู่การพัฒนาเว็บในรูปแบบใหม่ที่น่าตื่นเต้นมากมาย WebSockets และ EventSource ช่วยให้นักพัฒนาเว็บมีมาตรฐานที่ชัดเจนและเข้าใจง่ายเพื่อเปิดใช้เว็บแอปแบบเรียลไทม์ แต่มีเพียงผู้ใช้บางรายเท่านั้นที่จะใช้งานเบราว์เซอร์ที่ทันสมัย คุณต้องพิจารณาการลดระดับอย่างราบรื่นเมื่อเลือกที่จะนำเทคโนโลยีเหล่านี้มาใช้ และเครื่องมือฝั่งเซิร์ฟเวอร์สําหรับ WebSocket และ EventSource ยังอยู่ในช่วงเริ่มต้น คุณควรคำนึงถึงปัจจัยเหล่านี้เมื่อพัฒนาแอป HTML5 แบบเรียลไทม์