鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了一點 ActionMailer
串接 AWS SES
來寄信 的使用方式,今天就來看一下 ActionCable
,夠夠~
什麼是 websocket
呢?就是可以讓 Client 端和 Server 端,建立起一道專屬的雙向溝通通道,Client 端和 Server 端可以透過這個通道互相傳訊息,雖然說是雙向的通道,但主要是設計給 Server 傳訊息給 Client 端的,為什麼呢?因為 Client 要傳訊息給 Server 有太多方法了(Web APIs),但 Server 要傳給 Client 端卻很難,主要是 Server 無法得知 Client 是不是準備好接收資料。
這個雙向通道的話呢,是使用 Publish/Subscribe
的設計模式,沒錯,就是我們在 Youtube 會使用的 訂閱 功能,當你訂閱某個頻道之後,如果頻道有更新,你就會收到頻道更新的推播通知,就是這樣子的概念。
而 ActionCable
就是方便我們在開發網頁時,可以讓 Server 推播訊息給網頁用戶們,在還沒有 ActionCable
出現之前,Rails 的開發者還需要自己使用第三方 gem 來整合 websocket,然後另外開 websocket server 來做這些事,現在有 ActionCable
之後,可以不需要另外開 websocket server 了,直接整合在 Rails server 裡,而且可以和 ActiveRecord
整合,直接使用資料庫內的資料推播,是真的挺方便的,不過在設定的過程中,還是遇到一些坑,待會跟大家分享~
因為 websocket 是要建立 Client 端和 Server 端的通訊,所以 ActionCable 分為兩個部分來設定,一個是 Server 端的設定,主要是要「替 clients 提供通道」、「透過通道廣播訊息給 Subscribers」,另一個是 Client 端的設定(如果你的 Rails server 只是 API server、前端是獨立拆出來做的話,就可以跳過 Client 端的設定),主要是要「連上 websocket 後要做什麼反應?」、「接受訊息後該在網頁上做什麼反應?」、「斷線時要做什麼反應?」
假設我們要做的是,建立 Server 向網頁使用者的通知管道
用 rails generator 產生一個新的 channel
$ rails g channel notification
invoke test_unit
create test/channels/notification_channel_test.rb
identical app/channels/application_cable/channel.rb
identical app/channels/application_cable/connection.rb
create app/channels/notification_channel.rb
create app/javascript/channels/notification_channel.js
gsub app/javascript/channels/notification_channel.js
append app/javascript/channels/index.js
產生幾個檔案,其中就已經包含了 server 端設置需要的檔案 + client 端需要的檔案
我們先到 app/channels/notification_channel.rb
看,然後用 stream_from
來建立一個叫做 notification_channel
的通道,並且新增一個叫做 def receive(data)
的 method,這個 method 是讓 client 透過 websocket 傳訊息過來的地方
# app/channels/notification_channel.rb
class NotificationChannel
def subscribed
puts("Someone subscribed")
stream_from("notification_channel")
end
def unsubscribed
puts("Someone unsubscribed")
end
def receive(data)
puts("Received: #{data}")
end
end
綁定 websocket 位址(有兩種綁定方法,擇一就好)
方法一:透過 config/routes.rb
這邊比較常見的有綁定 '/cable'
也有 '/websocket'
,目前我看起來是沒什麼差別
Rails.application.routes.draw do
mount ActionCable.server => '/websocket'
end
方法二:透過 config/application.rb
這邊比較常見的有綁定 '/cable'
也有 '/websocket'
,目前我看起來是沒什麼差別
config.action_cable.mount_path = '/websocket'
隨便放個空白首頁,$ rails g controller home_controller index
,再把 routes 的 root 指過去
# config/routes.rb
root 'home#index'
到這邊就已經設定完 server 端的部分了~
這邊要注意一點,Rails 是個全端框架,代表雖然你看起來都是在 Rails 裡面開發,但有些程式碼不是在 Rails server 執行,而是透過網路把程式碼送到 Client 端的瀏覽器裡面執行,也就是 html、js、css 等檔案,要區分一下這個概念,在開發的時候才不會搞不清楚現在在做什麼
在 app/views/layouts/application.html.erb
的 ,加入 action_cable_meta_tag
這點就是我踩很久的雷坑QQ,我爬了很多文章,也幾乎都沒有寫這行,官方文件也是很簡略地用文字帶過,沒認真看真的是會漏掉
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
...
<%= action_cable_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>
這個 helper tag,會在 html 產生,也就是幫我們告訴瀏覽器說,websocket 的位址在哪裡,如果你在 server 設定的位址是 /cable
,那這個 action_cable_meta_tag
也會跟著改成 /cable
<html>
<head>
<meta name="action-cable-url" content="/websocket">
...
</head>
...
</html>
在 app/javascript/channels/consumer.js
從 actioncable 引入 createConsumer
function,方便其他 channel 使用
import { createConsumer } from '@rails/actioncable'
export default createConsumer()
在 /app/javascript/channels/notification_channel.js
訂閱 Notification Channel,這樣 server 才能透過這個 channel 傳訊息到網頁使用者,這邊設定三個 function,分別是 connected
、disconnected
、received(data)
,分別代表「訂閱成功建立連線時要做的反應」、「取消訂閱斷開連線時的反應」、「接收到來自 server 的訊息該做的反應」,我們先設定接收到訊息的時候,就先跳出個警告匡就好
ps. 注意這邊的 js code 是在使用者的瀏覽器執行的
// /app/javascript/channels/notification_channel.js
import consumer from "channels/consumer"
consumer.subscriptions.create("NotificationChannel", {
connected() {
console.log("Connected to NotificationChannel")
},
disconnected() {
console.log("Disconnected to NotificationChannel")
},
received(data) {
// Called when there's incoming data on the websocket for this channel
alert("Received: " + data['message'])
}
});
到這邊 client 端也設置好了~
$ rails s
啟動 rails server
打開瀏覽器,到 127.0.0.1:3000/
檢查 Terminal 有沒有相關的 log,如果沒有,那應該是上面有做錯,請檢查一下
Started GET "/websocket" for 127.0.0.1 at 2022-09-14 10:00:04 +0800
Started GET "/websocket" [WebSocket] for 127.0.0.1 at 2022-09-14 10:00:04 +0800
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
NotificationChannel is transmitting the subscription confirmation
NotificationChannel is streaming from notification_channel
讓 Server 來推送一些訊息試試看,$ rails c
[1] pry(main)> ActionCable.server.broadcast('notification_channel', '你好啊')
網頁上應該就會收到訊息,到這邊就完成初步的 ActionCable
連接拉~
action_cable_meta_tag
真的讓我卡很久,超級無敵煩躁,想說奇怪我都照著做了,文章我也都算認真看了,怎麼還是沒出來 (╯•̀ὤ•́)╯
最後我好像是到 youtube 查有沒有人實作的影片,看到某一部有做這個設定,我才照著加上去,結果就可以了,事後我去翻 RailsGuide 文件,結果佔超小篇幅,直接被我忽略 XD
總之好不容易有試成功,明天來試試看用 ActionCable 做點應用吧,我們明天見~