1 什麼是 Action Cable?
Action Cable 將 WebSocket 與您 Rails 應用程式的其餘部分無縫整合。它允許您以與其餘 Rails 應用程式相同的樣式和形式用 Ruby 編寫即時功能,同時仍保持效能和可擴展性。這是一個全端產品,同時提供用戶端 JavaScript 框架和伺服器端 Ruby 框架。您可以存取使用 Active Record 或您選擇的 ORM 編寫的整個網域模型。
2 術語
Action Cable 使用 WebSocket 而非 HTTP 請求-回應協定。Action Cable 和 WebSocket 都引入了一些較不熟悉的術語
2.1 連線
連線構成用戶端-伺服器關係的基礎。單一 Action Cable 伺服器可以處理多個連線實例。每個 WebSocket 連線都有一個連線實例。如果單一使用者使用多個瀏覽器分頁或裝置,他們可能會有多個 WebSocket 開啟至您的應用程式。
2.2 消費者
WebSocket 連線的用戶端稱為消費者。在 Action Cable 中,消費者由用戶端 JavaScript 框架建立。
2.3 通道
每個消費者都可以依序訂閱多個通道。每個通道封裝一個邏輯工作單元,類似於一般 MVC 設定中控制器的作用。例如,您可以有一個 ChatChannel
和一個 AppearancesChannel
,而消費者可以訂閱這些通道中的任何一個或兩個。至少,消費者應訂閱一個通道。
2.4 訂閱者
當消費者訂閱通道時,他們會作為訂閱者。訂閱者和通道之間的連線,稱之為訂閱,這並不令人意外。消費者可以任意次數地作為特定通道的訂閱者。例如,消費者可以同時訂閱多個聊天室。(請記住,一個實體使用者可能有多個消費者,每個開啟至您連線的分頁/裝置一個)。
2.5 發布/訂閱
發布/訂閱 指的是訊息佇列模式,其中資訊的傳送者(發布者)將資料傳送至接收者(訂閱者)的抽象類別,而不指定個別的接收者。Action Cable 使用此方法在伺服器和多個用戶端之間進行通訊。
2.6 廣播
廣播是一個發布/訂閱連結,其中廣播器傳輸的任何內容都會直接傳送至串流該命名廣播的通道訂閱者。每個通道可以串流零個或多個廣播。
3 伺服器端元件
3.1 連線
對於伺服器接受的每個 WebSocket,都會實例化一個連線物件。此物件會成為從此建立的所有通道訂閱的父物件。除了驗證和授權之外,連線本身不處理任何特定的應用程式邏輯。WebSocket 連線的用戶端稱為連線消費者。個別使用者會針對他們開啟的每個瀏覽器分頁、視窗或裝置建立一對消費者-連線。
連線是 ApplicationCable::Connection
的實例,它擴充 ActionCable::Connection::Base
。在 ApplicationCable::Connection
中,您會授權傳入的連線,並在可以識別使用者時繼續建立連線。
3.1.1 連線設定
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
此處 identified_by
指定一個連線識別碼,可用於稍後尋找特定連線。請注意,任何標記為識別碼的項目都會在從連線建立的任何通道實例上自動建立一個同名的委派。
此範例依賴於您已在應用程式的其他位置處理使用者驗證,並且成功的驗證會使用使用者 ID 設定加密的 Cookie。
然後,當嘗試新連線時,Cookie 會自動傳送至連線實例,並且您可以使用它來設定 current_user
。透過依此相同的目前使用者來識別連線,您還可以確保稍後可以檢索給定使用者的所有開啟連線(並且如果使用者被刪除或未經授權,可能會將它們全部中斷連線)。
如果您的驗證方法包括使用會話,則您可以使用 Cookie 儲存來儲存會話,您的會話 Cookie 名稱為 _session
,而使用者 ID 金鑰為 user_id
,您可以使用此方法
verified_user = User.find_by(id: cookies.encrypted["_session"]["user_id"])
3.1.2 例外處理
依預設,未處理的例外會被捕捉並記錄到 Rails 的記錄器。如果您想要全域攔截這些例外並將它們回報給外部錯誤追蹤服務(例如),您可以使用 rescue_from
來完成此操作
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
rescue_from StandardError, with: :report_error
private
def report_error(e)
SomeExternalBugtrackingService.notify(e)
end
end
end
3.1.3 連線回呼
ActionCable::Connection::Callbacks
提供回呼掛鉤,在將命令傳送至用戶端時(例如訂閱、取消訂閱或執行動作時)會叫用這些掛鉤
3.2 通道
一個通道 (channel) 封裝了一個邏輯工作單元,類似於典型 MVC 架構中控制器所做的事情。預設情況下,當您第一次使用通道產生器時,Rails 會建立一個父類別 ApplicationCable::Channel
(繼承自 ActionCable::Channel::Base
) 來封裝您通道之間的共享邏輯。
3.2.1 父通道設定
# app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
您自己的通道類別可以像這些範例一樣
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end
然後,消費者可以訂閱這些通道中的一個或兩個。
3.2.2 訂閱
消費者訂閱通道,充當訂閱者。他們的連線稱為訂閱。產生的訊息然後會根據通道消費者發送的識別符號,路由到這些通道訂閱。
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
# Called when the consumer has successfully
# become a subscriber to this channel.
def subscribed
end
end
3.2.3 例外處理
與 ApplicationCable::Connection
一樣,您也可以在特定通道上使用 rescue_from
來處理引發的例外。
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
rescue_from "MyError", with: :deliver_error_message
private
def deliver_error_message(e)
# broadcast_to(...)
end
end
3.2.4 通道回呼
ActionCable::Channel::Callbacks
提供了在通道生命週期中調用的回呼掛鉤。
before_subscribe
after_subscribe
(別名為on_subscribe
)before_unsubscribe
after_unsubscribe
(別名為on_unsubscribe
)
4 客戶端元件
4.1 連線
消費者需要他們端上的連線實例。這可以使用以下 JavaScript 建立,Rails 預設會產生此 JavaScript。
4.1.1 連接消費者
// app/javascript/channels/consumer.js
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
這將準備好一個消費者,預設會連線到您伺服器上的 /cable
。在您也至少指定了一個您有興趣訂閱的訂閱之前,不會建立連線。
消費者可以選擇性地接收一個參數,指定要連線的 URL。這可以是字串或返回字串的函式,該字串將在 WebSocket 開啟時調用。
// Specify a different URL to connect to
createConsumer('wss://example.com/cable')
// Or when using websockets over HTTP
createConsumer('https://ws.example.com/cable')
// Use a function to dynamically generate the URL
createConsumer(getWebSocketURL)
function getWebSocketURL() {
const token = localStorage.get('auth-token')
return `wss://example.com/cable?token=${token}`
}
4.1.2 訂閱者
消費者透過建立對給定通道的訂閱來成為訂閱者
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })
// app/javascript/channels/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "AppearanceChannel" })
雖然這會建立訂閱,但稍後將描述響應接收到的數據所需的功能。
消費者可以多次充當給定通道的訂閱者。例如,消費者可以同時訂閱多個聊天室。
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
5 客戶端-伺服器互動
5.1 串流
串流 提供通道將已發佈的內容 (廣播) 路由到其訂閱者的機制。例如,以下程式碼在 :room
參數的值為 "Best Room"
時,使用 stream_from
訂閱名為 chat_Best Room
的廣播。
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
然後,在您 Rails 應用程式中的其他地方,您可以透過呼叫 broadcast
來廣播到這樣的房間。
ActionCable.server.broadcast("chat_Best Room", { body: "This Room is Best Room." })
如果您有一個與模型相關的串流,那麼廣播名稱可以從通道和模型產生。例如,以下程式碼使用 stream_for
訂閱類似 posts:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
的廣播,其中 Z2lkOi8vVGVzdEFwcC9Qb3N0LzE
是 Post 模型的 GlobalID。
class PostsChannel < ApplicationCable::Channel
def subscribed
post = Post.find(params[:id])
stream_for post
end
end
然後,您可以透過呼叫 broadcast_to
來廣播到此通道。
PostsChannel.broadcast_to(@post, @comment)
5.2 廣播
廣播 是一種發佈/訂閱連結,其中發佈者傳輸的任何內容都會直接路由到正在串流該命名廣播的通道訂閱者。每個通道可以串流零個或多個廣播。
廣播純粹是一個線上佇列,並且與時間相關。如果消費者沒有串流 (訂閱給定的通道),他們稍後連線時將不會收到廣播。
5.3 訂閱
當消費者訂閱通道時,他們會充當訂閱者。此連線稱為訂閱。然後,會根據纜線消費者發送的識別符號,將傳入的訊息路由到這些通道訂閱。
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
5.4 將參數傳遞給通道
您可以在建立訂閱時,將參數從客戶端傳遞到伺服器端。例如
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
作為 subscriptions.create
的第一個參數傳遞的物件會成為纜線通道中的 params 雜湊。需要關鍵字 channel
。
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
# Somewhere in your app this is called, perhaps
# from a NewCommentJob.
ActionCable.server.broadcast(
"chat_#{room}",
{
sent_by: "Paul",
body: "This is a cool chat app."
}
)
5.5 重新廣播訊息
一個常見的用例是將一個客戶端發送的訊息重新廣播到任何其他連接的客戶端。
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
def receive(data)
ActionCable.server.broadcast("chat_#{params[:room]}", data)
end
end
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
// data => { sent_by: "Paul", body: "This is a cool chat app." }
}
})
chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
所有連接的客戶端都將收到重新廣播,包括發送訊息的客戶端。請注意,params 與您訂閱通道時的 params 相同。
6 完整堆疊範例
以下設定步驟在兩個範例中都很常見
6.1 範例 1:使用者出現
這是一個簡單的通道範例,用於追蹤使用者是否在線上以及他們所在的頁面。(這對於建立存在功能很有用,例如,如果使用者在線上,則在使用者名稱旁邊顯示綠點)。
建立伺服器端外觀通道
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
def subscribed
current_user.appear
end
def unsubscribed
current_user.disappear
end
def appear(data)
current_user.appear(on: data["appearing_on"])
end
def away
current_user.away
end
end
當啟動訂閱時,會觸發 subscribed
回呼,並且我們藉此機會說「目前使用者確實已出現」。該出現/消失 API 可以由 Redis、資料庫或其他任何內容支援。
建立客戶端外觀通道訂閱
// app/javascript/channels/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("AppearanceChannel", {
// Called once when the subscription is created.
initialized() {
this.update = this.update.bind(this)
},
// Called when the subscription is ready for use on the server.
connected() {
this.install()
this.update()
},
// Called when the WebSocket connection is closed.
disconnected() {
this.uninstall()
},
// Called when the subscription is rejected by the server.
rejected() {
this.uninstall()
},
update() {
this.documentIsActive ? this.appear() : this.away()
},
appear() {
// Calls `AppearanceChannel#appear(data)` on the server.
this.perform("appear", { appearing_on: this.appearingOn })
},
away() {
// Calls `AppearanceChannel#away` on the server.
this.perform("away")
},
install() {
window.addEventListener("focus", this.update)
window.addEventListener("blur", this.update)
document.addEventListener("turbo:load", this.update)
document.addEventListener("visibilitychange", this.update)
},
uninstall() {
window.removeEventListener("focus", this.update)
window.removeEventListener("blur", this.update)
document.removeEventListener("turbo:load", this.update)
document.removeEventListener("visibilitychange", this.update)
},
get documentIsActive() {
return document.visibilityState === "visible" && document.hasFocus()
},
get appearingOn() {
const element = document.querySelector("[data-appearing-on]")
return element ? element.getAttribute("data-appearing-on") : null
}
})
6.1.1 客戶端-伺服器互動
客戶端 透過
createConsumer()
連線到伺服器。(consumer.js
)。伺服器 透過current_user
識別此連線。客戶端 透過
consumer.subscriptions.create({ channel: "AppearanceChannel" })
訂閱外觀通道。(appearance_channel.js
)伺服器 識別到已為外觀通道啟動新的訂閱,並執行其
subscribed
回呼,在current_user
上呼叫appear
方法。(appearance_channel.rb
)客戶端 識別到已建立訂閱並呼叫
connected
(appearance_channel.js
),進而呼叫install
和appear
。appear
在伺服器上呼叫AppearanceChannel#appear(data)
,並提供一個{ appearing_on: this.appearingOn }
的資料雜湊。這是可能的,因為伺服器端通道實例會自動公開類別上宣告的所有公共方法 (回呼除外),以便可以透過訂閱的perform
方法作為遠端程序呼叫來存取這些方法。伺服器 接收到在
current_user
識別的連線之外觀通道上要求appear
動作的請求 (appearance_channel.rb
)。伺服器 從資料雜湊中檢索帶有:appearing_on
金鑰的資料,並將其設定為傳遞給current_user.appear
的:on
金鑰的值。
6.2 範例 2:接收新的 Web 通知
外觀範例是關於透過 WebSocket 連線將伺服器功能公開給客戶端調用。但是,WebSockets 的偉大之處在於它是雙向的。因此,現在,讓我們展示一個範例,其中伺服器在客戶端上調用動作。
這是一個 Web 通知通道,可讓您在廣播到相關串流時觸發客戶端 Web 通知。
建立伺服器端 Web 通知通道
# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
def subscribed
stream_for current_user
end
end
建立客戶端 Web 通知通道訂閱
// app/javascript/channels/web_notifications_channel.js
// Client-side which assumes you've already requested
// the right to send web notifications.
import consumer from "./consumer"
consumer.subscriptions.create("WebNotificationsChannel", {
received(data) {
new Notification(data["title"], { body: data["body"] })
}
})
從您應用程式中的其他位置將內容廣播到 Web 通知通道實例
# Somewhere in your app this is called, perhaps from a NewCommentJob
WebNotificationsChannel.broadcast_to(
current_user,
title: "New things!",
body: "All the news fit to print"
)
WebNotificationsChannel.broadcast_to
呼叫會在目前訂閱配接器的 pubsub 佇列中,為每個使用者放置一個具有不同廣播名稱的訊息。對於 ID 為 1 的使用者,廣播名稱將為 web_notifications:1
。
通道已指示將到達 web_notifications:1
的所有內容直接串流到客戶端,方法是調用 received
回呼。作為參數傳遞的資料是作為第二個參數傳送到伺服器端廣播呼叫的雜湊,JSON 編碼以進行跨線傳輸,並解壓縮為到達 received
的資料參數。
6.3 更完整的範例
請參閱 rails/actioncable-examples 儲存庫,以取得如何在 Rails 應用程式中設定 Action Cable 並新增通道的完整範例。
7 設定
Action Cable 有兩個必需的設定:訂閱配接器和允許的請求來源。
7.1 訂閱配接器
預設情況下,Action Cable 會在 config/cable.yml
中尋找組態檔。該檔案必須為每個 Rails 環境指定一個配接器。有關配接器的其他資訊,請參閱依賴項部分。
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: redis://10.10.3.153:6381
channel_prefix: appname_production
7.1.1 配接器設定
以下是終端使用者可用的訂閱配接器清單。
7.1.1.1 Async 配接器
Async 配接器適用於開發/測試,不應在生產環境中使用。
7.1.1.2 Redis 配接器
Redis 配接器要求使用者提供指向 Redis 伺服器的 URL。此外,可以提供 channel_prefix
,以避免在多個應用程式使用相同的 Redis 伺服器時發生通道名稱衝突。請參閱Redis Pub/Sub 文件以了解更多詳細資訊。
Redis 配接器也支援 SSL/TLS 連線。所需的 SSL/TLS 參數可以在組態 YAML 檔案中的 ssl_params
金鑰中傳遞。
production:
adapter: redis
url: rediss://10.10.3.153:tls_port
channel_prefix: appname_production
ssl_params:
ca_file: "/path/to/ca.crt"
提供給 ssl_params
的選項會直接傳遞給 OpenSSL::SSL::SSLContext#set_params
方法,並且可以是 SSL 上下文的任何有效屬性。請參閱OpenSSL::SSL::SSLContext 文件以取得其他可用的屬性。
如果您在防火牆後方對 Redis 配接器使用自我簽署憑證,並選擇跳過憑證檢查,則 ssl verify_mode
應設定為 OpenSSL::SSL::VERIFY_NONE
。
不建議在生產環境中使用 VERIFY_NONE
,除非您完全了解安全性隱含。為了為 Redis 配接器設定此選項,組態應為 ssl_params: { verify_mode: <%= OpenSSL::SSL::VERIFY_NONE %> }
。
7.1.1.3 PostgreSQL 配接器
PostgreSQL 配接器使用 Active Record 的連線池,因此使用應用程式的 config/database.yml
資料庫設定進行連線。這未來可能會變更。 #27214
PostgreSQL 對於 NOTIFY
(在幕後用於傳送通知的命令) 有 8000 位元組的限制,這在處理大型酬載時可能會是一個限制。
7.2 允許的請求來源
Action Cable 只會接受來自指定來源的請求,這些來源會作為陣列傳遞到伺服器組態。來源可以是字串或規則運算式的實例,將針對它們執行匹配檢查。
config.action_cable.allowed_request_origins = ["https://rubyonrails.com", %r{http://ruby.*}]
若要停用並允許來自任何來源的請求
config.action_cable.disable_request_forgery_protection = true
預設情況下,當在開發環境中執行時,Action Cable 允許來自 localhost:3000 的所有請求。
7.3 消費者配置
若要設定 URL,請在您的 HTML 版面配置的 HEAD 中加入對 action_cable_meta_tag
的呼叫。這會使用通常透過環境設定檔中的 config.action_cable.url
設定的 URL 或路徑。
7.4 工作者池配置
工作者池用於隔離於伺服器的主線程之外執行連線回呼和通道動作。Action Cable 允許應用程式設定工作者池中同時處理的執行緒數量。
config.action_cable.worker_pool_size = 4
另外,請注意您的伺服器必須提供至少與工作者數量相同的資料庫連線數。預設工作者池大小設定為 4,這表示您必須至少有 4 個資料庫連線可用。您可以在 config/database.yml
中透過 pool
屬性變更此設定。
7.5 用戶端記錄
用戶端記錄預設為停用。您可以透過將 ActionCable.logger.enabled
設定為 true 來啟用此功能。
import * as ActionCable from '@rails/actioncable'
ActionCable.logger.enabled = true
7.6 其他配置
另一個常見的配置選項是套用至每個連線記錄器的日誌標籤。以下範例在標記時,若使用者帳戶 ID 可用則使用該 ID,否則使用「no-account」。
config.action_cable.log_tags = [
-> request { request.env["user_account_id"] || "no-account" },
:action_cable,
-> request { request.uuid }
]
如需所有配置選項的完整列表,請參閱 ActionCable::Server::Configuration
類別。
8 執行獨立 Cable 伺服器
Action Cable 可以作為 Rails 應用程式的一部分執行,也可以作為獨立伺服器執行。在開發中,作為 Rails 應用程式的一部分執行通常沒問題,但在生產環境中,您應該將其作為獨立伺服器執行。
8.1 在應用程式中
Action Cable 可以與您的 Rails 應用程式一起執行。例如,若要在 /websocket
上偵聽 WebSocket 請求,請將該路徑指定給 config.action_cable.mount_path
。
# config/application.rb
class Application < Rails::Application
config.action_cable.mount_path = "/websocket"
end
如果 action_cable_meta_tag
在版面配置中被調用,您可以使用 ActionCable.createConsumer()
連線至 cable 伺服器。否則,路徑會指定為 createConsumer
的第一個參數(例如 ActionCable.createConsumer("/websocket")
)。
對於您建立的每個伺服器實例,以及伺服器產生的每個工作者,您也會有一個新的 Action Cable 實例,但 Redis 或 PostgreSQL 配接器會讓訊息在連線之間保持同步。
8.2 獨立
cable 伺服器可以與您正常的應用程式伺服器分開。它仍然是一個 Rack 應用程式,但它有自己的 Rack 應用程式。建議的基本設定如下
# cable/config.ru
require_relative "../config/environment"
Rails.application.eager_load!
run ActionCable.server
然後啟動伺服器
$ bundle exec puma -p 28080 cable/config.ru
這會在 28080 埠啟動一個 cable 伺服器。若要告知 Rails 使用此伺服器,請更新您的配置
# config/environments/development.rb
Rails.application.configure do
config.action_cable.mount_path = nil
config.action_cable.url = "ws://127.0.0.1:28080" # use wss:// in production
end
最後,請確保您已正確設定消費者。
8.3 注意事項
WebSocket 伺服器無法存取會話,但它可以存取 cookies。當您需要處理驗證時,可以使用此功能。您可以在這篇文章中看到一種使用 Devise 的方法。
9 相依性
Action Cable 提供訂閱配接器介面來處理其 pubsub 內部。預設情況下,包含異步、內聯、PostgreSQL 和 Redis 配接器。新 Rails 應用程式中的預設配接器是異步(async
)配接器。
Ruby 端的功能建構在 websocket-driver、nio4r 和 concurrent-ruby 之上。
10 部署
Action Cable 由 WebSocket 和執行緒的組合提供支援。框架的管道和使用者指定的通道工作都是透過利用 Ruby 的原生執行緒支援在內部處理的。這表示您可以毫無問題地使用所有現有的 Rails 模型,只要您沒有犯任何執行緒安全錯誤。
Action Cable 伺服器實作 Rack socket hijacking API,因此無論應用程式伺服器是否為多執行緒,都允許使用多執行緒模式在內部管理連線。
因此,Action Cable 可以與 Unicorn、Puma 和 Passenger 等常見伺服器搭配使用。
11 測試
您可以在測試指南中找到有關如何測試您的 Action Cable 功能的詳細說明。