wscatでAction Cableと通信する

Railsで/cableなどのエンドポイントにAction CableをマウントするとWebSocketサーバとして利用できます。wscatを使ってAction CableによるWebSocket APIと対話的に通信するために、送信するデータの形式などを調べました。

準備

wscat

github.com

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.rbaction_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"}}

参考