การสร้างฟีเจอร์เรียลไทม์ใน Rails ทำได้ง่ายกว่ามากกับไลบรารี เช่น Action Cable ใน AppSignal Academy ในตอนนี้ เราจะเจาะลึกเกี่ยวกับการอัปเดตตามเวลาจริงและเล่นกับการสร้างเซิร์ฟเวอร์ WebSocket แบบมินิมอล เพื่อดูว่ามันทำงานอย่างไรภายใต้ประทุน
เราจะสร้างแอปพลิเคชันที่ผลักดัน และใช้ Pub/Sub ผ่าน WebSocket . ก่อนที่เราจะเริ่มต้นเกี่ยวกับโค้ด เรามาใช้เวลาสักเล็กน้อยเพื่ออธิบายความหมายของแนวคิดทั้งสามนี้:
-
ดัน หมายถึงการส่งข้อมูลไปยังผู้รับแทนที่จะมีการสำรวจข้อมูลผู้รับสำหรับข้อมูลนั้น สิ่งที่ต้องมีในการอัปเดตแบบเรียลไทม์ เช่น ราคาหุ้น แอปพลิเคชันแชท หรือคอนโซลการดำเนินการ
-
ผับ/ย่อย หรือเผยแพร่และสมัครสมาชิกเป็นรูปแบบการโต้ตอบสำหรับการผลักดันข้อมูลที่ TIBCO ได้รับความนิยมใน Wall Street ในปี 1990 ผู้รับสมัครสมาชิกเรื่องและรอให้ผู้เผยแพร่ส่งข้อมูลไปยังหัวเรื่องนั้น เป็นเรื่องปกติที่จะรวมการจับคู่รูปแบบสัญลักษณ์แทนเพื่อให้ตรงกับข้อความที่เผยแพร่ไปยังผู้ฟัง แม้ว่าการใช้งานที่ง่ายกว่าบางรายการจะใช้เฉพาะช่องที่มีชื่อแทนการใช้สัญลักษณ์แทนในหัวเรื่อง ฉันเริ่มต้นที่ TIBCO ในช่วงแรกๆ ดังนั้นฉันจึงชอบความยืดหยุ่นของการจับคู่รูปแบบสัญลักษณ์แทน
-
WebSocket เป็นโปรโตคอลสำหรับการแลกเปลี่ยนข้อมูล โดยปกติแล้วจะเป็นระหว่างเว็บเบราว์เซอร์และแอปพลิเคชัน การเชื่อมต่อ HTTP ได้รับการอัพเกรดเป็นการเชื่อมต่อ WebSocket จากนั้นข้อมูลสามารถส่งได้ทั้งสองทางระหว่างจุดปลายทั้งสอง WebSockets สามารถส่งข้อมูลจากแอปพลิเคชันไปยังเบราว์เซอร์ได้ นอกจากนี้ยังมีกลไกอื่นนอกเหนือจาก POST หรือ PUT สำหรับการส่งข้อมูลจากโค้ด JavaScript ในเบราว์เซอร์กลับไปยังแอปพลิเคชัน น่ารักดีนะ ว่าไหม
ภายใต้ประทุน
ลองดูตัวอย่างการทำงานของเซิร์ฟเวอร์ WebSocket กัน จากเบราว์เซอร์ ไคลเอ็นต์พยายามทำการเชื่อมต่อ WebSocket กับเซิร์ฟเวอร์ด้วยโค้ด JavaScript
var sock = new WebSocket("ws://" + document.URL.split("/")[2] + "/upgrade");
เซิร์ฟเวอร์ได้รับคำขอ HTTP พร้อมตัวบ่งชี้ว่ามีการร้องขอการอัพเกรด โดยทั่วไป เซิร์ฟเวอร์อนุญาตให้แอปพลิเคชันตัดสินใจว่าจะอัพเกรดหรือไม่ มันขึ้นอยู่กับ API ที่จัดเตรียมให้กับแอพอย่างไร เซิร์ฟเวอร์ที่รองรับ Rack มีตัวเลือกในการจี้ซ็อกเก็ตและให้นักพัฒนาจัดการรายละเอียดโปรโตคอลทั้งหมด หรือตาม PR ที่เสนอ การตอบสนองต่อการอัพเกรดก็เพียงพอแล้ว
การอัพเกรดเป็นชุดของการแลกเปลี่ยนระหว่างเซิร์ฟเวอร์และไคลเอนต์ เบราว์เซอร์ทั้งหมดและอัญมณีเซิร์ฟเวอร์บางตัวซ่อนรายละเอียดเหล่านี้ เมื่อสร้างการเชื่อมต่อแล้ว จะสามารถแลกเปลี่ยนข้อความตามโปรโตคอล WebSocket
เวทมนตร์ภายใต้ประทุนจะจัดการกับการเข้ารหัส ถอดรหัส และโปรโตคอลการแลกเปลี่ยนข้อความ ข้อความเป็นโครงสร้างไบนารีที่มีความกว้างคงที่พร้อมเพย์โหลดต่อท้าย เข้ารหัสโดยใช้ SHA1 โปรโตคอล WebSocket ประกอบด้วยประเภทข้อความและการแลกเปลี่ยนหลายประเภท เช่น จังหวะปิงปอง/ปิงปอง และการแลกเปลี่ยนข้อความเปิดและปิด นั่นคือความมหัศจรรย์ที่เซิร์ฟเวอร์ดำเนินการโดยไม่ใช้วิธีการเชื่อมต่อ-จี้
ดำน้ำ
เราจะใช้ตัวอย่างของเธรดนาฬิกาที่เริ่มเผยแพร่เวลาปัจจุบันไปยังไคลเอนต์ที่รับฟังทั้งหมด เราจะใช้ Agoo เพื่อสร้างเซิร์ฟเวอร์ของเราเพราะมันรวดเร็วและรักษาความซับซ้อนให้น้อยที่สุด
เราจะเริ่มต้นด้วย JavaScript บางส่วนในฐานะไคลเอนต์โดยแสดงเวลาปัจจุบันบนหน้า HTML หลังจากสร้าง WebSocket
. ใหม่แล้ว onopen
มีการตั้งค่าการโทรกลับที่เปลี่ยนสถานะองค์ประกอบ HTML onmessage
โทรกลับอัปเดต onmessage
องค์ประกอบ HTML การเรียกกลับเป็นรูปแบบการออกแบบทั่วไปเมื่อทำงานกับการเรียกแบบอะซิงโครนัส เช่น เผยแพร่และสมัครการแลกเปลี่ยน
<!-- websocket.html -->
<html>
<body>
<p id="status">...</p>
<p id="message">... waiting ...</p>
<script type="text/javascript">
var sock = new WebSocket(
"ws://" + document.URL.split("/")[2] + "/upgrade"
);
sock.onopen = function () {
document.getElementById("status").textContent = "connected";
};
sock.onmessage = function (msg) {
document.getElementById("message").textContent = msg.data;
};
</script>
</body>
</html>
เมื่อลูกค้าทำเสร็จแล้ว เรามาติดตั้งเซิร์ฟเวอร์ ซึ่งเป็นแอปพลิเคชัน Ruby โดยใช้ Rack API Clock
ตัวคลาสเองจะเป็นตัวจัดการสำหรับคำขอ HTTP ทั้งหมดใน /upgrade
เส้นทาง. หากคำขอเป็นการอัพเกรด เราเพียงแค่ส่งคืน Success ด้วยรหัสสถานะ HTTP 200 มิฉะนั้น เราจะส่งคืน 404 สำหรับ Page Not Found อีกขั้นตอนเดียวใน #call
method คือการกำหนดตัวจัดการ WebSocket
class Clock
def self.call(env)
unless env['rack.upgrade?'].nil?
env['rack.upgrade'] = Clock
[ 200, { }, [ ] ]
else
[ 404, { }, [ ] ]
end
end
end
API ขึ้นอยู่กับการโทรกลับ การโทรกลับอย่างเดียวที่เราสนใจสำหรับเซิร์ฟเวอร์ของเราคือ #on_open
โทรกลับซึ่งช่วยให้เราสร้างการสมัครรับเรื่อง "เวลา" ข้อความจะถูกแลกเปลี่ยนผ่านช่องทางที่ระบุตามหัวเรื่องหรือหัวข้อ #on_open
จะถูกเรียกเมื่อมีการเชื่อมต่อเว็บซ็อกเก็ต
class Clock
# ...
def self.on_open(client)
client.subscribe('time')
end
end
ตอนนี้ มาเริ่มเผยแพร่ด้วยเธรดที่เผยแพร่เวลาทุกวินาที การเรียกไปที่ Agoo.publish
ส่งข้อความในหัวข้อ "เวลา" จากนั้นสมาชิกทั้งหมดจะได้รับข้อความ เซิร์ฟเวอร์ติดตามการสมัครรับข้อมูลและการเชื่อมต่อ และส่งข้อความไปยังไคลเอนต์ JavaScript ซึ่งอัปเดตองค์ประกอบ HTML
require 'agoo'
Thread.new {
loop do
now = Time.now
Agoo.publish('time', "%02d:%02d:%02d" % [now.hour, now.min, now.sec])
sleep(1)
end
}
รหัสอื่นที่จำเป็นเท่านั้นคือรหัสที่เริ่มต้นและเริ่มต้นเซิร์ฟเวอร์ การเรียกไปที่ Agoo::Server.handle(:GET, '/upgrade', Clock)
บอกให้เซิร์ฟเวอร์ฟังคำขอ HTTP GET บน /upgrade
เส้นทาง URL และส่งคำขอเหล่านั้นไปยัง Clock
ระดับ. ซึ่งจะทำให้การกำหนดเส้นทางเกิดขึ้นนอก Ruby เพื่อเพิ่มประสิทธิภาพและความยืดหยุ่น
Agoo::Server.init(6464, '.', thread_count: 0)
Agoo::Server.handle(:GET, '/upgrade', Clock)
Agoo::Server.start
เราเกือบจะอยู่ที่นั่นแล้ว รันเซิร์ฟเวอร์ด้วยคำสั่งนี้
$ ruby pubsub.rb
รายการบันทึกควรปรากฏขึ้นโดยแสดงสิ่งต่อไปนี้ ซึ่งบ่งชี้ว่าเซิร์ฟเวอร์กำลังทำงานและรับฟังบนพอร์ต 6464
I 2018/08/14 19:49:45.170618000 INFO: Agoo 2.5.0 with pid 40366 is listening on https://:6464.
ถึงเวลาดูว่าใช้งานได้หรือไม่
มาเปิด https://localhost:6464/websocket.html กันเถอะ หลังจากการสั่นไหวครั้งแรกเมื่อสร้างการเชื่อมต่อ สถานะการเชื่อมต่อและเวลาควรแสดงขึ้น เวลาจะเพิ่มขึ้นทุก ๆ วินาทีตามเข็มนาฬิกา
connected
19:50:12
ขอแสดงความยินดีกับการเผยแพร่และสมัครเว็บแอปพลิเคชัน;-)
ในตอนของวันนี้ เรามาดูการใช้ WebSocket เหตุการณ์ฝั่งเซิร์ฟเวอร์ (SSE) เสนอทางเลือกอื่นในการทำเช่นเดียวกัน และเราได้รวม SSE ไว้ในตัวอย่างซอร์สโค้ดแบบเต็มแล้ว หากคุณต้องการทราบข้อมูลเพิ่มเติม โปรดดูที่เซิร์ฟเวอร์ Agoo ที่เราใช้หรือเซิร์ฟเวอร์ Iodine WebSocket
หากคุณมีคำถามหรือความคิดเห็นใด ๆ อย่าลังเลที่จะวางสาย @AppSignal