見出し画像

Pusher で WebSocket 通信を試してみる

Pusher という、リアルタイムアプリケーション構築のための API を提供するサービスがあります。

今回は Pusher の Channels という機能を利用して、サーバーからクライアントへメッセージを送信するサンプルを作ってみました。

Channels App を作成する

Pusher のアカウントを作ると、作成後に以下のような App 登録画面が表示されます。

・リージョンに Asia Pacific (Tokyo) を選択
・フロントエンドは Vanilla JS を選択
・バックエンドは Ruby を選択
で、アプリを新規に登録します。

Channel の種類

Pusher には以下の Channel があります。実際の要件では、ほとんどのケースで Private か Presence が必要になると思います。

Public Channel
全員入れるチャンネル。

Private Channel
認証が通れば入れるチャンネル。

Presence Channel
認証が通れば入れるチャンネル。メンバーという概念を持ち、誰がオンラインかなどを管理可能。現時点で、メンバーは最大100名までという制限がある模様。

※ 詳細は公式のドキュメントをご参照ください。

Public Channel を試してみる

まずは動作確認のために Public Channel を利用してみます。

フロントエンド

Pusher.logToConsole = true;

const pusher = new Pusher('xxx', {
  cluster: 'ap3',
  forceTLS: true
});

const channel = pusher.subscribe(`my-channel`);
channel.bind('my-event', (data) => {
  alert(JSON.stringify(data));
});

実行すると、Pusher の WebSocket と繋がります。

サーバーサイド

pusher_client = Pusher::Client.new(
  app_id: 'xxx',
  key: 'xxx',
  secret: 'xxx',
  cluster: 'ap3',
  encrypted: true
)

pusher_client.trigger("my-channel", 'my-event', {
  message: 'hello world'
})

実行すると、クライアント側にメッセージが送信され alert が表示されます。とても簡単ですね!

Private Channel を試してみる

Public Channel では、Pusher の app_id/key さえ知っていれば誰でも subscribe できてしまいます。アクセス可能な Channel を制限したい場合は、Private/Presence Channel のどちらかを利用するようです。今回は Private Channel を試してみたいと思います。

サーバーサイド

Private Channel を利用するには、サーバーサイド側に認証処理を行う API を追加する必要があります。この API はクライアントから XMLHttpRequest でPOST リクエストされることになります。

今回自分で試したコードはお見せできる内容でなかったので...公式ドキュメントにあるサンプルコードを転用します。

実装のポイントは以下となります。

・POST メソッドで受け付ける
・認証していない場合はエラーとする
・指定されたチャンネル名に認証ユーザーがアクセスできない場合はエラーとする
・エラーの場合は 403 ステータスを返す
class PusherController < ApplicationController

  def auth
    # 認証チェック
    if current_user
      # 本来は、ここでログインユーザーが指定の channel にアクセスして良いかチェックする

      response = pusher_client.authenticate(params[:channel_name], params[:socket_id], {
        user_id: current_user.id, # => required
        user_info: { # => optional - for example
          name: current_user.name,
          email: current_user.email
        }
      })
      render json: response
    else
      render text: 'Forbidden', status: '403'
    end
  end
end

次に trigger を実行する Channel の名前のプレフィックスに private- をつけます。これで Private Channel と認識されるようです。

pusher_client.trigger("private-channel", 'my-event', {
  message: 'hello world'
})

フロントエンド

フロント側では、認証に必要な以下の設定を加える必要があります。

・authEndpoint サーバーサイド側に追加した認証 API の URL
・authTransport ajax or jsonp を選択
・auth.headers 認証 API 実行時に付与されるヘッダを定義
const pusher = new Pusher('xxx', {
  authEndpoint: `/path-to-path/push-auth`,
  authTransport: 'ajax',
  auth: {
    headers: {
      'X-Requested-With': 'XMLHttpRequest'
    }
  },
  cluster: 'ap3',
  forceTLS: true
});

const channel = pusher.subscribe(`private-channel`);

また、API 側で Cookie による認証が必要な場合に、XMLHttpRequest.withCredentials = true を指定したいケースがありますが、現状 Pusher 側に対応する設定が無いようです。その場合は、以下のように XHR を生成するメソッドを書き換えることで対応できます。

Pusher.Runtime.createXHR = () => {
  const xhr = new XMLHttpRequest();
  xhr.withCredentials = true;
  return xhr;
};

これで、Private Channel に接続することができました。

接続上限を超えているかの確認

Pusher では契約プラン毎に接続可能な上限が決まっています。この上限を超えた場合、登録メールアドレスにメールが飛んできますので、一応気づくことはできます。

また、フロントエンド側で上限オーバー時のハンドリングを行うことも可能です。

  pusher.connection.bind('error', (err) => {
    if (err.error.data.code === 4004) {
      alert('Over limit!');
    }
  });

エラーコードの詳細は以下のドキュメントに記載されています。

サーバーサイドでは、以下のようなコードでチャンネル毎の subscription_count を取得することができます。

pusher_client.channel_info("private-channel", info: :subscription_count)

ただし、事前に subscription_count が取得できるよう設定をしておく必要があります。

これらの情報を使って、接続上限のチェックを仕込んでおくことができそうです。

まとめ

ポーリングを解決するといった簡単な用途であれば、自前で WebSocket 環境を構築・運用するより大分楽になりそうです。プラン毎の接続数の上限が結構シビアなので、採用できるかはその辺りの要件次第かな、と感じましたが、今後本番での採用も検討していきたいと思います。


<まちいろではエンジニアを絶賛募集中です!>


この記事が気に入ったらサポートをしてみませんか?