Action Cableを使ったRubyでのリアルタイム通信実装方法

Action Cableは、Ruby on Railsの標準ライブラリとして提供されており、WebSocketを活用してリアルタイム通信を実現するための強力なツールです。本記事では、Action Cableを使ってリアルタイムなデータ更新を可能にする方法について解説します。たとえば、ユーザー同士のチャット機能や通知システムなど、即時性が求められる機能は、Action Cableを使うことで効率的に構築できます。リアルタイム通信の基本から始め、サーバー設定やクライアント実装、さらに具体的な応用例まで順を追って説明します。

目次

WebSocketとリアルタイム通信の基本概念


WebSocketは、サーバーとクライアント間で双方向のリアルタイム通信を可能にするプロトコルです。従来のHTTP通信ではクライアントがリクエストを送信し、サーバーがそれに応じてレスポンスを返す「リクエスト・レスポンス」モデルが一般的ですが、リアルタイム性が求められるアプリケーションではこの方式に限界があります。

リアルタイム通信の必要性


リアルタイム通信は、以下のような場面で必要となります。

  • チャットアプリ:新しいメッセージが瞬時に表示される必要がある。
  • 通知システム:ユーザーに即座に通知を届ける必要がある。
  • リアルタイムデータ更新:例えば、株価やセンサー情報などの即時更新が必要なデータの表示。

WebSocketの仕組みと利点


WebSocketは、HTTPプロトコルを経てサーバーとクライアント間で一度接続を確立すると、その後は継続的に双方向通信が可能となります。これにより、サーバーは必要なタイミングでクライアントにデータをプッシュでき、クライアントも随時サーバーにデータを送信可能です。

リアルタイム通信の基盤として、WebSocketはリクエスト待ちのタイムラグを解消し、瞬時のデータ更新を実現します。

Action Cableの概要と利用するメリット

Action Cableは、Ruby on Railsに組み込まれているWebSocketサポートフレームワークで、リアルタイム通信機能を簡単にRailsアプリケーションに組み込むことができます。WebSocketを利用することで、クライアントとサーバー間でのデータ交換が迅速に行われるため、ユーザーによりインタラクティブな体験を提供できます。

Action Cableの特徴

  • Railsとのシームレスな統合:Railsアプリの一部として簡単に利用可能で、他のRailsコンポーネント(モデルやコントローラなど)ともスムーズに連携します。
  • 複数接続の管理:複数のクライアント接続を効率的に管理し、リアルタイムのデータ配信が可能です。
  • サーバーサイドのスケーラビリティ:Action CableはRedisなどを利用してマルチサーバー環境にも対応しており、スケーラブルなリアルタイムアプリケーションの構築をサポートします。

Action Cableを利用するメリット

  1. 実装の簡便さ:WebSocketを使ったリアルタイム通信が、Railsの標準機能として提供されているため、追加のライブラリや複雑な設定なしで実現できます。
  2. リアルタイムなUI更新:クライアント間でのメッセージ交換や通知更新など、即座のUI更新が求められるアプリケーションに最適です。
  3. セキュリティの確保:Railsの認証や権限管理と統合できるため、リアルタイム通信にも堅固なセキュリティを維持しやすいです。

Action Cableの利用により、リアルタイムで双方向のデータ更新が必要な機能が迅速に構築でき、ユーザー体験をより充実させることが可能です。

Action Cableのセットアップ方法

Railsプロジェクトにおいて、Action Cableを利用するためには、まず基本的なセットアップを行う必要があります。ここでは、Action Cableを使えるようにするための初期設定手順を説明します。

1. Gemfileの確認


Rails 5以降のバージョンでは、Action Cableが標準で含まれているため、Gemfileに特別な追加は不要です。ただし、WebSocketを効率的に動作させるため、Redisを利用する場合は以下の行をGemfileに追加し、インストールします。

gem 'redis'

その後、bundle installコマンドを実行してRedisをインストールします。

2. Action Cableのルーティング設定


次に、Action Cableの接続エンドポイントを設定するために、config/routes.rbファイルに以下のコードを追加します。

mount ActionCable.server => '/cable'

この設定により、/cableというURLエンドポイントでAction Cableの接続を行えるようになります。

3. Redisの設定(オプション)


リアルタイム通信のパフォーマンスを向上させるため、Redisをバックエンドに設定することが推奨されます。config/cable.ymlファイルでRedisを指定する設定を行います。

development:
  adapter: redis
  url: redis://localhost:6379/1
  channel_prefix: your_app_development

production:
  adapter: redis
  url: redis://localhost:6379/1
  channel_prefix: your_app_production

これで、開発環境および本番環境でRedisを使用したリアルタイム通信が行えるようになります。

4. JavaScriptファイルの設定


クライアント側でWebSocketを利用できるようにするため、RailsのJavaScriptファイルでAction Cableをインポートします。app/javascript/channels/index.jsファイルに次のコードを追加し、Action Cableのチャンネルを管理できるようにします。

import { createConsumer } from "@rails/actioncable";

export default createConsumer();

これでAction Cableの基本設定が完了です。次のステップでは、具体的なチャネルの作成とメッセージの送受信を実装していきます。

WebSocketサーバーの設定と起動

Action Cableでリアルタイム通信を行うためには、WebSocketサーバーを正しく設定して起動する必要があります。ここでは、WebSocketサーバーを設定し、Railsアプリケーション内でリアルタイム通信を実現するための手順を説明します。

1. WebSocketサーバーの設定


Railsでは、Action Cableを通してWebSocketサーバーが自動的に稼働しますが、設定を調整することでより効率的に動作させることが可能です。以下の設定を確認・変更しましょう。

まず、config/environments/development.rbconfig/environments/production.rbでAction Cableの設定を追加・確認します。

開発環境 (development.rb) の例

config.action_cable.url = "ws://localhost:3000/cable"
config.action_cable.allowed_request_origins = [ 'http://localhost:3000', /http:\/\/127\.0\.0\.1:.*/ ]

本番環境 (production.rb) の例

config.action_cable.url = "wss://yourdomain.com/cable"
config.action_cable.allowed_request_origins = [ 'https://yourdomain.com' ]
  • config.action_cable.url:WebSocket接続のエンドポイントURLを指定します。
  • config.action_cable.allowed_request_origins:接続を許可するオリジンを指定し、セキュリティを高めます。

2. サーバーの起動と確認


設定が完了したら、Railsサーバーを起動し、WebSocketが正しく動作しているか確認します。Railsの標準サーバーであるPumaは、デフォルトでWebSocketをサポートしています。

rails server

サーバーが起動したら、ブラウザでhttp://localhost:3000/cableにアクセスし、WebSocketが正しくセットアップされているか確認できます。通常はエラーが表示されない限り、これでサーバーの設定は完了です。

3. WebSocketサーバーの動作確認方法


正しく設定されていれば、Action Cableを使ってブラウザとサーバー間のリアルタイム通信が可能です。これを確認するために、次の手順でWebSocketの接続状況を確認します。

  1. ブラウザのデベロッパーツールを開き、「ネットワーク」タブに移動します。
  2. http://localhost:3000/cableにアクセスしてWebSocketが接続されているか確認します。
  3. 正常に接続されていれば、WebSocket通信が「ws://localhost:3000/cable」から発信されていることが確認できます。

以上で、WebSocketサーバーの設定と起動が完了しました。次のセクションでは、Action Cableのチャネルを作成し、リアルタイム通信のメッセージ送受信を実装していきます。

チャネルの作成とメッセージ送受信の実装

Action Cableのリアルタイム通信機能を利用するためには、まずチャネル(Channel)を作成し、メッセージの送受信を実装する必要があります。チャネルは、クライアントとサーバーのやり取りを行うWebSocket上の通信レイヤーで、特定のトピックやルームに分けてメッセージを管理することが可能です。

1. チャネルの生成


まず、チャネルを生成するために、以下のコマンドを実行します。この例では「ChatChannel」という名前のチャネルを作成します。

rails generate channel Chat

このコマンドを実行すると、app/channels/chat_channel.rbapp/javascript/channels/chat_channel.jsの2つのファイルが生成されます。

サーバー側のチャネル設定(`app/channels/chat_channel.rb`)


チャネルファイルでは、クライアントがサーバーに接続したときやメッセージを受信したときの処理を定義します。

class ChatChannel < ApplicationCable::Channel
  def subscribed
    # クライアントがチャネルにサブスクライブ(接続)したときの処理
    stream_from "chat_channel"
  end

  def unsubscribed
    # クライアントがチャネルから切断されたときの処理
  end

  def speak(data)
    # クライアントからメッセージを受け取ったときの処理
    ActionCable.server.broadcast("chat_channel", message: data['message'])
  end
end
  • subscribed:クライアントがチャネルに接続した際に呼び出されます。
  • unsubscribed:クライアントがチャネルから切断された際に呼び出されます。
  • speak:クライアントから受信したメッセージ(data['message'])を、他のクライアントにブロードキャストします。

2. クライアント側の設定(`app/javascript/channels/chat_channel.js`)


次に、クライアントサイドでメッセージを送受信するための設定を行います。チャネルファイルでサブスクリプションを設定し、リアルタイムでメッセージを受信できるようにします。

import consumer from "./consumer";

consumer.subscriptions.create("ChatChannel", {
  connected() {
    // クライアントがチャネルに接続されたときに呼び出される
    console.log("Connected to the chat channel.");
  },

  disconnected() {
    // クライアントがチャネルから切断されたときに呼び出される
  },

  received(data) {
    // サーバーからメッセージを受信したときに呼び出される
    const messages = document.getElementById('messages');
    messages.insertAdjacentHTML('beforeend', `<p>${data.message}</p>`);
  },

  speak(message) {
    // クライアントからサーバーへメッセージを送信する
    return this.perform('speak', { message: message });
  }
});
  • connected:クライアントがチャネルに接続したときに実行されます。
  • disconnected:クライアントがチャネルから切断されたときに実行されます。
  • received:サーバーからメッセージを受信したときに実行され、HTML要素にメッセージを追加します。
  • speak:サーバーのspeakメソッドを呼び出し、クライアントからのメッセージを送信します。

3. フロントエンドからのメッセージ送信


クライアント側でメッセージを送信するために、HTMLフォームとJavaScriptを用意します。以下のように、フォームからメッセージを入力し、送信することでリアルタイムでデータが送られるようにします。

<div id="messages"></div>
<input type="text" id="message_content" placeholder="メッセージを入力してください">
<button onclick="sendMessage()">送信</button>

<script>
  function sendMessage() {
    const content = document.getElementById("message_content").value;
    App.chat.speak(content);
    document.getElementById("message_content").value = "";
  }
</script>

以上の設定により、Action Cableを使ったチャネルでのメッセージの送受信が可能となります。この手順でサーバーとクライアント間の通信を実現し、リアルタイムでのデータ更新が行えるようになります。

クライアントサイドの設定と実装

クライアントサイドの実装では、Action Cableのチャネルを通してリアルタイムでデータを受信し、画面上に表示する機能を追加します。これにより、ブラウザ上でのリアルタイム通信が実現し、Webページの更新なしにデータが反映されるようになります。

1. チャネルへの接続とデータ受信の設定


Action Cableのクライアントサイドでの設定は、app/javascript/channels/chat_channel.jsに記述します。このファイルで、サーバーからのデータを受信して画面に反映する処理を定義します。

import consumer from "./consumer";

const chatChannel = consumer.subscriptions.create("ChatChannel", {
  connected() {
    // チャネル接続時に呼ばれる
    console.log("Connected to ChatChannel");
  },

  disconnected() {
    // チャネル切断時に呼ばれる
    console.log("Disconnected from ChatChannel");
  },

  received(data) {
    // サーバーからメッセージを受信したときに呼ばれる
    const messagesContainer = document.getElementById("messages");
    messagesContainer.insertAdjacentHTML("beforeend", `<p>${data.message}</p>`);
  },

  speak(message) {
    // クライアントからサーバーへメッセージを送信
    this.perform("speak", { message: message });
  }
});

export default chatChannel;
  • connected:クライアントがChatChannelに接続されたときに呼び出され、接続確認のログを表示します。
  • disconnected:チャネルから切断された際に呼び出され、切断の確認ログを表示します。
  • received:サーバーからメッセージを受信すると、そのメッセージが表示されるようにHTML要素にメッセージ内容を追加します。
  • speak:クライアントからのメッセージ送信をperformメソッドでサーバー側に伝えます。

2. HTMLの作成


クライアントがメッセージを送信し、リアルタイムでメッセージを表示するためのHTMLを作成します。このHTMLにはメッセージの入力フィールドと送信ボタン、メッセージを表示するためのコンテナを含めます。

<div id="messages" style="border: 1px solid #ddd; padding: 10px; max-height: 300px; overflow-y: auto;"></div>
<input type="text" id="message_content" placeholder="メッセージを入力してください">
<button onclick="sendMessage()">送信</button>
  • messagesコンテナ:受信したメッセージが表示される部分です。
  • message_contentフィールド:ユーザーがメッセージを入力するテキストボックスです。
  • 送信ボタン:入力されたメッセージを送信するためのボタンです。

3. メッセージ送信機能の実装


次に、送信ボタンをクリックするとメッセージがサーバーに送信されるよう、JavaScriptでsendMessage関数を定義します。

function sendMessage() {
  const content = document.getElementById("message_content").value;
  chatChannel.speak(content); // チャネルのspeakメソッドを呼び出し
  document.getElementById("message_content").value = ""; // 送信後に入力フィールドをクリア
}

この関数は、テキストボックスの内容を取得し、chatChannel.speakメソッドを通してサーバーに送信します。メッセージを送信後、入力フィールドはクリアされます。

4. チャネルの自動接続の設定


app/javascript/channels/index.jsに、先ほど定義したchat_channel.jsを読み込むことで、ページが読み込まれる際に自動でチャネルに接続できるようにします。

import "./chat_channel";

これで、クライアントサイドでの実装が完了です。これにより、ユーザーがメッセージを入力して送信すると、サーバーを経由して全クライアントにメッセージがリアルタイムで表示されるようになります。リアルタイム通信を活用した動的なWebアプリケーションの基盤が完成しました。

接続のテストとデバッグ方法

Action Cableを利用したリアルタイム通信が正しく動作しているかを確認するために、接続のテストやデバッグ方法を学びます。テストを通して、エラーを特定し、円滑に動作するアプリケーションを構築しましょう。

1. WebSocket接続の確認


接続の確認は、ブラウザのデベロッパーツールを使うと便利です。

  • ネットワークモニタリング:ブラウザでアプリケーションを開き、デベロッパーツールの「ネットワーク」タブに移動し、ws://localhost:3000/cableの接続が確立されているか確認します。
  • WebSocketが正常に接続されていれば、Statusが「101 Switching Protocols」と表示され、接続状態が維持されます。
  • もし接続に失敗している場合、「接続拒否」や「404 Not Found」などのエラーメッセージが表示されます。

2. クライアント側のコンソールログ


chat_channel.jsconnecteddisconnectedメソッドにログを追加しておくと、接続状態を追跡できます。クライアントサイドで次のようなエラーが出力される場合があります。

  • Disconnectedのエラー:サーバーの設定(特にconfig/cable.yml)を確認し、正しいURLとオリジンが指定されているか確認します。
  • 「Uncaught Error: Rejected by server」エラー:Railsサーバーが立ち上がっていないか、/cableのエンドポイントがルート設定されていない可能性があります。

3. サーバー側のログ


Railsサーバーのログにも、Action Cableに関する接続・切断の情報やエラーメッセージが出力されます。

Started GET "/cable" for 127.0.0.1 at 2023-XX-XX XX:XX:XX +0000
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)

上記のようなログが表示されれば、サーバー側でWebSocket接続が確立していることが確認できます。エラーが出た場合、Railsログにあるエラー内容を参考にして、原因を特定します。

4. Action Cableコンソールでのデバッグ


Action Cableは、RailsのActionCableサーバーコンソールでもデバッグが可能です。接続が確立しているクライアントに対して手動でメッセージを送信し、クライアント側でメッセージが受信されるかを確認できます。

ActionCable.server.broadcast("chat_channel", message: "テストメッセージ")

このコマンドを実行すると、chat_channelにサブスクライブしているクライアントにテストメッセージが送信されます。これによって、クライアントが正常にメッセージを受信しているかどうか確認できます。

5. Redisサーバーの確認(オプション)


Redisをバックエンドに使用している場合、Redisが正常に動作しているかを確認します。Redisサーバーがダウンしていると、リアルタイム通信が機能しなくなるため、以下のコマンドでRedisの状態を確認します。

redis-cli ping

このコマンドで「PONG」が返ってくれば、Redisサーバーは正常に稼働しています。

6. よくあるエラーと対処方法

  • クライアントが接続を拒否されるconfig/environmentsの設定が不適切で、オリジンが拒否されている可能性があります。
  • WebSocketが一時的に切断される:サーバー負荷やネットワーク問題が原因の場合があります。環境設定でタイムアウトや再接続間隔を適切に設定します。

これらの手順で、接続のテストとデバッグが行えます。接続の確認からRedisサーバーの確認まで、各種テスト方法を実施して、リアルタイム通信が正常に動作することを確認してください。

応用例:チャット機能の構築

Action Cableを使用して、リアルタイムで動作するシンプルなチャット機能を構築します。この応用例では、メッセージを送信すると即座に他のユーザーにも表示される仕組みを実装します。

1. メッセージモデルの作成


まず、メッセージをデータベースに保存するためのモデルを作成します。これにより、過去のメッセージも閲覧できるようにします。

rails generate model Message content:text
rails db:migrate

生成されたMessageモデルを利用し、メッセージ内容(content)を保存することができます。

モデルの設定(`app/models/message.rb`)


メッセージモデルには、バリデーションや関連付けを追加します。

class Message < ApplicationRecord
  validates :content, presence: true
end

2. チャネルの更新


次に、チャネルを使ってメッセージをブロードキャストし、他のユーザーにリアルタイムで配信します。

app/channels/chat_channel.rbを以下のように更新します。

class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_channel"
  end

  def speak(data)
    # メッセージをデータベースに保存し、ブロードキャスト
    message = Message.create!(content: data['message'])
    ActionCable.server.broadcast("chat_channel", message: render_message(message))
  end

  private

  def render_message(message)
    ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
  end
end

ここでは、speakメソッドで受け取ったメッセージをMessageモデルに保存し、保存されたメッセージを全てのクライアントにブロードキャストしています。

3. 部分テンプレートの作成


メッセージの表示形式を管理するために、部分テンプレート(_message.html.erb)を作成します。

app/views/messages/_message.html.erb

<p><%= message.content %></p>

これにより、各メッセージが統一されたフォーマットで表示されます。

4. クライアント側のチャネル設定


app/javascript/channels/chat_channel.jsで、受信したメッセージをチャット画面に表示するコードを追加します。

import consumer from "./consumer";

consumer.subscriptions.create("ChatChannel", {
  connected() {
    console.log("Connected to ChatChannel");
  },

  received(data) {
    const messagesContainer = document.getElementById("messages");
    messagesContainer.insertAdjacentHTML("beforeend", data.message);
  },

  speak(message) {
    this.perform("speak", { message: message });
  }
});

5. チャット画面の作成


Railsのビューでチャット画面を作成し、メッセージを表示・送信できるようにします。

app/views/chat/show.html.erb

<div id="messages">
  <%= render @messages %>
</div>

<input type="text" id="message_content" placeholder="メッセージを入力してください">
<button onclick="sendMessage()">送信</button>

<script>
  function sendMessage() {
    const content = document.getElementById("message_content").value;
    if (content) {
      App.chat.speak(content);
      document.getElementById("message_content").value = "";
    }
  }
</script>

ここで、@messagesには過去のメッセージが含まれるように、コントローラで事前に定義しておきます。

6. コントローラの設定


チャット画面で過去のメッセージを表示するために、ChatControllerを作成します。

class ChatController < ApplicationController
  def show
    @messages = Message.all
  end
end

ルートも設定し、/chatでこのビューにアクセスできるようにします。

config/routes.rb

get '/chat', to: 'chat#show'

7. 動作確認


サーバーを起動し、複数のブラウザで/chatにアクセスして、メッセージがリアルタイムで反映されるか確認します。メッセージを入力して送信ボタンを押すと、他のブラウザにも即座にメッセージが表示されれば成功です。

これで、Action Cableを使ったシンプルなチャット機能が完成しました。リアルタイムのメッセージ更新が可能になり、即時性のあるチャットが実現できます。

セキュリティ対策と認証方法

リアルタイム通信を提供するチャット機能では、セキュリティ対策が重要です。Action Cableを使ったWebSocket通信においても、アクセス制御や認証を適切に行い、不正アクセスを防ぐ必要があります。

1. 認証の設定


Action Cableでは、現在のユーザーを認証するためにApplicationCable::Connectionクラスを利用します。ユーザーがログインしているかを確認し、認証に失敗した場合は接続を拒否するよう設定します。

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 current_user = User.find_by(id: cookies.signed[:user_id])
        current_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

ここでは、クッキーに保存されたユーザーIDを用いてユーザーを特定し、認証できなければreject_unauthorized_connectionを呼び出して接続を拒否しています。

2. チャネルでのアクセス制御


特定のユーザーやグループのみにチャネルへのアクセスを許可したい場合、subscribedメソッドでアクセス制御を実装します。例えば、チャットルームのメンバーだけが特定のチャネルに参加できるように設定できます。

class ChatChannel < ApplicationCable::Channel
  def subscribed
    if current_user.allowed_to_join?(params[:room_id])
      stream_from "chat_channel_#{params[:room_id]}"
    else
      reject
    end
  end
end

このコードでは、ユーザーが参加を許可されたチャットルームのみをstream_fromで購読させ、その他のルームにはアクセスできないように制限しています。

3. WebSocket通信におけるデータ保護


WebSocket通信では、SSL/TLS暗号化を行い、通信内容が第三者に傍受されないようにします。本番環境では必ずwss://で通信を行い、通信の暗号化を徹底します。

4. CSRF対策


Action Cableの通信にもCSRFトークンを用いることで、クロスサイトリクエストフォージェリ攻撃からアプリケーションを守ります。通常、RailsのCSRFトークンが自動で挿入されるため、セキュリティが向上します。

5. 接続の監視とログ記録


接続を監視し、異常な接続が発生した場合にはログに記録して、セキュリティの維持を図ります。ログファイルを確認することで、アクセス状況や不正なアクセスの兆候を把握できます。

これらの対策を通じて、Action Cableを用いたリアルタイム通信のセキュリティが強化され、不正アクセスやデータ漏洩のリスクが軽減されます。

まとめ

本記事では、Ruby on RailsのAction Cableを使ったリアルタイム通信の実装方法について解説しました。WebSocketの基本概念から始まり、RailsでのAction Cableのセットアップ、チャネルの作成、クライアント側の実装、セキュリティ対策に至るまで、リアルタイム通信を安全に行うための手順を紹介しました。また、具体例としてシンプルなチャット機能を構築し、即時性のあるデータ更新を実現しました。適切な設定とセキュリティ対策を施し、リアルタイムなユーザー体験を提供するアプリケーションを構築してみてください。

コメント

コメントする

目次