Action Cable 即时通讯

WebSocket 简介

由于 HTTP 的限制,早期浏览器要作即时通讯,有各种奇技淫巧 Polling, Comet, Long Polling 等等,直到浏览器支援的标准出现 WebSocket 才开始进入正轨。

WebSocket 通讯协定可以让浏览器和服务器,进行持续性的双向连线沟通。目前支援程度还不错 Can I use?

img1

img2

(图示来源 https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable)

但由于需要与 Server 端进行持续性的连线,对于原本采用 Process-Based 设计的 Rails 比较不适合,因此会将这部分的元件拆出去,解法有:

  • 将即时通讯的部分拆出去成为独立的 Ruby 元件,独立于 Rails 处理一般 HTTP requests 流量的 Process 之外,详见下一节。
  • 整套改成用其他程式语言,例如 Node.js 有

另一个与 WebSocket 类似功能的网络标准是 SSE,但是功能比较少只能单向,而且跨浏览器支援度又不好,虽然 Rails 有内建(因为对 server 比较好实作)但很少人使用。

认识 Pub/Sub 订阅模型

Publish–subscribe pattern 发布/订阅设计模式是一种在即时通讯上很常用的架构,可以将通讯拆成发布方和订阅方,发布方异步地将讯息传送给不定数量的订阅方。Pub/Sub 部分可以从你的主应用 Process 外,独立出来成为一个单独的运作元件。

Pub/Sub middleware

附带有 Pub/Sub 功能的数据库:

  • Redis
  • PostgreSQL

或是专门的 Message Queue 软件中的一个功能:

Pub/Sub 模型 + WebSocket 的 Ruby 框架

第三方服务 Pub/Sub + WebSocket

Rails 5: ActionCable 简介和安装

因为越来越多的即时通讯需求,Rails 在 5.0 之后新增的 ActionCable 元件来支援 WebSocket,整合了后端 Rails 和前端 JavaScript 呼叫接口,可以很方便的开发即时通讯的应用。

ActionCable 官方文件

img

(图示来源 https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable)

安装方式

  • 升级 Rails5 并使用 gem "puma",不能用 webrick 了
  • 安装 Redis
  1. brew install redis
  2. brew services start redis
  • 修改 config/cable.yml 把 async 都改成用 redis
  • 修改 config/application.rb 加上 config.action_cable.mount_path = '/cable'
  • 修改 app/assets/javascripts/application.js 要加载 cable.js

ActionCable 应用

即时聊天室是最常见的应用,让我们用这个例子来举例。

单一公开聊天室

WebSocket 虽然支援双向通讯,但是在实作上最重要需要即时的部分,其实只有接收资料。送资料给 Server 其实不一定需要走 WebSocket 连线,也可以用 Ajax。以下两篇教学文章,刚好分别用了不同的方式来实作:

方法一:送出的部分沿用 ajax request,如同 Real-Time Rails: Implementing WebSockets in Rails 5 with Action Cable 这篇教学

方法二:送出的部分也走 websocket 路线,如同 Create a Chat App with Rails 5, ActionCable, and Devise 这篇教学

范例程式: https://github.com/ihower/rails-exercise-ac9/tree/rails5

img

在 ActionCable 中可以用 partial template 在 server-side 回传给浏览器,这样就可以重用服务器的 erb template。

注意到用 WebSocket 连线,如果浏览器有跳页的话,是会重新连线的。这就是为什么 SPA(Single-Page Application) 的形式比较适合做即时通讯,在那个应用页面中,所有的操作都是用 Ajax,这就才不会跳页。而在 Rails 中则是搭配了 Turbolinks 的 Ajax 换页效果,才不会每页都重连 Websocket (什么? 你听我在 Ajax 一章的建议已经拆除 Turbolinks 了??)

多个聊天室、整合 Devise 认证、Broadcast 的部分改成用 ActiveJob

参考 Create a Chat App with Rails 5, ActionCable, and Devise 这篇教学

当 Broadcast 的呼叫是发生在一般 request/response lifecycle 时,建议改放在 ActiveJob 里,因为需要通知的用户可能很多。

其他常见的即时通讯应用

其他 ActionCable 没有帮你作的事情

参考 https://blog.ably.io/rails-5-actioncable-the-good-and-bad-parts-1b56c3b31404

Abrupt failures can corrupt state, no ACK/NACK support, Message Ordering 等等都是没有保证的

例如如果你重新整理一下网页,WebSocket 会重新连线,那这中间断掉时间发布的新讯息,你就不会收到。

ActionCable 布署