rails actioncable + swiftでwebsocketのhello world

iOSでWebSocketを少し試す必要があったのでそのときの手順をまとめておく。サーバー側はRails5.2のAPIモードでActionCableを使う。

railsインストール

$ bundle exec rails new ac_test --api

Channel作成

channel作成

$ ./bin/rails g chnnel chat

ChannelはコントローラのWebSocket版と考える。そのままだけど、subscribeはクライアントからsubscribeのメッセージを受け取ったら呼ばれる。

app/channels/chat_channel.rb

class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat:message"
  end

  def unsubscribed
  end
end

接続してみる

ここまでできたらサーバーを起動してWebSocketクライアントで接続してみる。

‎WebSocket Client on the Mac App Store
GUIのソフトを探したところこちらのソフトが使いやすそうだった。

f:id:yoppy0066:20190108195942p:plain:w400
ws://192.168.1.109:3000/cable を入力して「Connect」をクリック

rails serverのコンソール

Started GET "/cable" for 192.168.1.109 at 2019-01-08 18:33:16 +0900
Started GET "/cable/" [WebSocket] for 192.168.1.109 at 2019-01-08 18:33:16 +0900
Request origin not allowed: ws://192.168.1.109:3000
Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
Finished "/cable/" [WebSocket] for 192.168.1.109 at 2019-01-08 18:33:16 +0900

「Failed to upgrade to WebSocket...」と接続に失敗していることがわかる。原因は送信元のチェックで引っかかているようで、今回はテストのため全てのリクエストを許可する。

config/environments/development.rb

# すべての送信元からのリクエストを許可                                                                                                                                                                             
config.action_cable.disable_request_forgery_protection = true

サーバーを再起動して再度接続すると今度は「Successfully upgraded to WebSocket」と成功。

Started GET "/cable" for 192.168.1.109 at 2019-01-08 18:38:06 +0900
Started GET "/cable/" [WebSocket] for 192.168.1.109 at 2019-01-08 18:38:06 +0900
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)

f:id:yoppy0066:20190108200016p:plain:w400
クライアント側もtype:welcomのレスポンスを確認できた。

subscribeしてみる

引きつづき接続したままの状態でsubscribeする

f:id:yoppy0066:20190108200031p:plain:w400
「Body」に以下のJSONを入力して送信ボタンのアイコンをクリック

{"command":"subscribe","identifier":"{\"channel\":\"ChatChannel\"}"}

railsのコンソールに以下が出力されれば成功

ChatChannel is transmitting the subscription confirmation
ChatChannel is streaming from chat:message

この状態で、ChatChannelのMessageにメッセージを送信してクライアント側でレスポンスを取得できるか確認する

./bin/rails console

$ ChatChannel.broadcast_to('message', 'hello')

レスポンスがあればクライアントの右側のペインに出力されるはずだが何も表示されないので失敗

ruby on rails - ActionCable.server.broadcast from the console - Stack Overflow
こちらの記事によると、別プロセスから実行する場合(rails serverとrails consoleは別プロセス)の場合、adapter: asyncは使えないらしい。記事の通り、config/cable.ymlの設定をasyncからadapterに変更する。

development:
  adapter: async
↓
development:
  adapter: redis
  url: redis://localhost:6379

Gemfileにredisを追加して./bin/bundle install

gem 'redis'

macにredis-serverがインストースされていれば redis-server で起動する。されてなければ「brew install redis」等でインストールする。再度、rails consoleからメッセージを送信すると今度はメッセージが受信できることが確認できた。
f:id:yoppy0066:20190108200113p:plain:w400

ここまででサーバー側の実装は完了とする。

Swiftでクライアントを実装する

GitHub - tidwall/SwiftWebSocket: Fast Websockets in Swift for iOS and OSX
シンプルに試せそうだったのこちらを使う。READMEに書かれているとおり、Carthageでインストールする。Carthageでのライブラリのインストールについてはこちらを参考に。

ViewController.swift

import UIKit
import SwiftWebSocket

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        func initializeWebSocket() {

            let request = URLRequest(url: URL(string: "ws://192.168.1.109:3000/cable")!)
            let ws = WebSocket(request: request)

            ws.event.open = {
                // 接続に成功したらsubscribe発行
                let json = """
{"command":"subscribe","identifier":"{\\"channel\\":\\"ChatChannel\\"}"}
"""
                ws.send(json)
            }

            ws.event.close = {_,_,_ in
                // 接続が切れたら再接続を
                DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
                    initializeWebSocket()
                }
            }

            ws.event.message = {(response) in
                // メッセージ受信
                dump(response)
            }
        }

        // 接続
        initializeWebSocket()
    }
}

ほぼドキュメントに書かれている内容だが上記のようなプログラムを実行するとコンソールから動作が確認できた。ほとんどrails側の作業だった。。。チャンネルとストリーミングのところはあまり理解できてないのでチャットルームなり作って理解を深めたいと思う。とりあえず触りはこんなものかな。以上です。