wscatでAction Cableと通信する
Railsで/cable
などのエンドポイントにAction CableをマウントするとWebSocketサーバとして利用できます。wscatを使ってAction CableによるWebSocket APIと対話的に通信するために、送信するデータの形式などを調べました。
準備
wscat
npmでインストールできます。
$ npm install -g wscat
Action Cable
この記事ではRails 5.1.6を使います。今回は、APIモードのRailsアプリケーションにAction Cableをマウントします(Action Cableサーバを独立に起動することも可能)。まず、適当にアプリを作ります。
$ rails new --api action-cable-sample
$ cd action-cable-sample
$ bin/rails g scaffold message body:string
$ bin/rails db:migrate
config/application.rb
でaction_cable/engine
を読み込み、さらにマウントパスを指定します。/cable
にマウントするのがRails wayの様子なのでそうします。
# config/application.rb
require_relative 'boot'
require "rails"
# ...
require "action_cable/engine"
# ...
module ActionCableSample
class Application < Rails::Application
# ...
config.api_only = true
config.action_cable.mount_path = '/cable'
end
end
また、デフォルトではCSRF対策で同じオリジンからしかWebSocket通信できないので、開発環境ではどのオリジンからでもWebSocket通信できるように設定します。
# config/environments/development.rb
Rails.application.configure do
# ...
config.action_cable.disable_request_forgery_protection = true
end
あとはAction Cableのチャンネルを適当に作ります。
$ bin/rails g channel message
MessageChannel
は次のように書いておきます。
class MessageChannel < ApplicationCable::Channel
def subscribed
stream_from 'message_channel'
end
def unsubscribed
end
end
また、今回はmessages#create
が成功したときにWebSocket経由でメッセージをブロードキャストします。
class MessagesController < ApplicationController
# ...
def create
@message = Message.new(message_params)
if @message.save
ActionCable.server.broadcast 'message_channel', body: @message.body
render json: @message, status: :created, location: @message
else
render json: @message.errors, status: :unprocessable_entity
end
end
end
実際に通信する
次のコマンドでWebSocketサーバへ接続します。HTTPでリクエストしてからWebSocketへアップグレードする処理などは自動でやってくれます。
$ wscat -c localhost:3000/cable
connected (press CTRL+C to quit)
< {"type":"welcome"}
>
Action Cableへ送信するデータにはsubscribe
, message
, unsubscribe
の3種類があります。次の形式でデータを送信することでAction Cableとやりとりできます。
{"command":"subscribe","identifier":"{\"channel\":\"MessageChannel\"}"}
{"command":"message","identifier":"{\"channel\":\"MessageChannel\"","data":"\"action\":\"chat\"}"} # "chat"は例です
{"command":"unsubscribe","identifier":"{\"channel\":\"MessageChannel\"}"}
Action CableはフルスタックアプリでJSを書いて使うことを想定されているためか、このあたりの仕様はREADMEやRails Guidesを見てもとくにドキュメント化されていないようでした。仕様を把握するにはAction Cableのコードを読む必要があります。
ActionCable::Connection::Subscriptions#execute_command
で受信したデータを解析し、command
に指定された文字列subscribe
, message
, unsubscribe
によって処理を分岐しています。message
を送信したときはActionCable::Channel::Base#perform_action
に移り、受信データのaction
で指定された名前を持つチャンネルのメソッドを動的に呼び出しています。
また、キー"identifier"
の値が文字列化されたJSONになっているのは、この文字列がActionCable::Connection::Subscriptions
の中でActiveSupport::JSON.decode
に渡るからです。
実際に上述した形式のsubscribe
のデータを送ると、チャンネルを購読できます。
> {"command":"subscribe","identifier":"{\"channel\":\"MessageChannel\"}"}
< {"identifier":"{\"channel\":\"MessageChannel\"}","type":"confirm_subscription"}
その後、コントローラのアクション内からブロードキャストするとメッセージを受信できます。
# curlで叩く
$ curl --request POST --url http://localhost:3000/messages --header 'content-type: application/json' --data '{"message":{"body":"test"}}'
# wscatでデータを受信する
< {"identifier":"{\"channel\":\"MessageChannel\"}","message":{"body":"test"}}