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 環境を構築・運用するより大分楽になりそうです。プラン毎の接続数の上限が結構シビアなので、採用できるかはその辺りの要件次第かな、と感じましたが、今後本番での採用も検討していきたいと思います。
<まちいろではエンジニアを絶賛募集中です!>