ランサーズ等のサービスを開発・運用する中で得た知識やノウハウを紹介しています。

thumbnail

Labels:  DevOps 投稿者:s.t

pythonを使ってsocket.io-redisへイベントをemitする

pythonを使ってsocket.io-redisへイベントをemitする

はじめに

AJAX+ポーリングで実装していたチャットサーバなどを、後付けでwebsocketを使ってリアルタイム通信にしようとすると、図1のような構成になることもあると思います。

python-websocket-1

まず、端末Aと端末Bがそれぞれsocket.ioライブラリを使ってwebsocketコネクションを確立
1. AJAXでメッセージをPOST
2. メッセージの内容をDBに保存
3. 端末Bに新しいメッセージが作成されたことを伝えるためにsocket.io-php-emitterライブラリを使ってイベントをemit(パブリッシュ)する
4. node.jsが3のイベントをsocket.io-redis
ライブラリを使ってサブスクライブする
5. node.jsは、端末B, C, D…に対して新しいメッセージの内容とともに画面を更新するためのイベントをemitする

図1の構成のシステムがすでにある状態で、websocketで繋がっている端末A, B, C…にPythonでイベントをemitしたい場合の方法を紹介します。基本的には図1のステップ3で行なっていることと同じことをPythonで実行するだけです。構成としては図2のようになります。

python-websocket-2

  1. socket.io-redis
    形式に則ったイベントをemit(publish)
  2. 1のイベントをサブスクライブ
  3. 1のイベントに応じた処理を行うためのイベントをクライアントに対してemit
    クライアントはそのイベントを受け取り、処理を行う

PythonにもPHPのようにsocket.io-php-emitterのようなライブラリがあれば、わざわざブログで紹介するほどのことでもないのですが、Pythonにはsocket.io-emitterを実現するライブラリがありません。
socket.io-redisのProtocolについて言及している項目にPython用のsocket.io-redisが紹介されているのですが、名前空間を指定するOfとルーム名を指定するInが機能していないので使えません。
socket.io-redisはRedisのPublishコマンドを発行しているに過ぎないので、自分でpublishコマンドを構築します。

チャネル名

チャネル名はsocket.io-redisによると次の通りに指定します。

特定のnamespace全体にイベントをemitしたい場合のチャネル名は以下の通り

prefix + '#' + namespace + '#'

特定のnamespaceのroomにイベントをemitしたい場合のチャネル名は以下の通り

prefix + '#' + namespace + '#' + room + '#'

socket.ioの場合はprefixのデフォルトはsocket.ioになります。
namespaceを/とし、room名をroom_777とする場合は、例えば以下のようにchannel_nameを準備します。

prefix = 'socket.io'
namespace_name = '/'
room_name = 'room_777'

channel_name = '{0}#{1}#{2}#'.format(prefix, namespace_name, room_name)

emitするデータの形式

socket.ioのemitで送信されるデータはMessagePackと呼ばれる形式でシリアライズされています。
socket.ioで送信する際のデータの形式は以下の通りです。

data = [
    'emitter',
    {
        # socket.ioはv1からバイナリ形式のデータ(画像・動画)も送信できるようになりました。
        # 通常のテキストデータの送信と区別するためにtypeキーが用意されています。
        # テキストデータの場合は2をセットし、バイナリデータの場合はは5をセットします。
        'type': 2,
        'data': [
            # emitしたいイベント名をセットします
            'my_event_name',
            # 送信したいデータを辞書形式でセットします
            {
                'id': 'message_id',
                'created_at': '2017-12-19 14:00:00',
                'body': 'message_body'
            }
        ],
        # namespaceをセットします
        'nsp': namespace_name
    },
    {
        # room_nameをセットします
        'rooms': [room_name],
        'flags': []
    }
]

上記データをmsgpackを使ってシリアライズします。

import msgpack

data_packed = msgpack.packb(data)

print(data_packed)
b'\x93\xa7emitter\x83\xa4type\x02\xa4data\x92\xadmy_event_name\x83\xa2id\xaamessage_id\xaacreated_at\xb32017-12-19 14:00:00\xa4body\xacmessage_body\xa3nsp\xa1/\x82\xa5rooms\x91\xa8room_777\xa5flags\x90'

Publish

node.js側に事前にセットしておいたイベント名でpublishすればsocket.ioのイベントがemitされます。

pythonにおけるRedisクライアントはこちら
がおすすめです。

import redis

r = redis.StrictRedis()
r.publish(channel_name, data_packed)

redisで次のようにコマンドが発行されていれば正常に動作しています。

redis-cli monitor | grep PUBLISH

1513649216.0000000 [0 127.0.0.1:50283] "PUBLISH" "socket.io#/#room_777#" "\x93\xa7emitter\x83\xa4type\x02\xa4data\x92\xadmy_event_name\x83\xa2id\xaamessage_id\xaacreated_at\xb32017-12-19 14:00:00\xa4body\xacmessage_body\xa3nsp\xa1/\x82\xa5rooms\x91\xa8room_777\xa5flags\x90"

Posted by 高田茂臣(takada.shigeomi@lancers.co.jp)

ランサーズではサービスを成長させてくれるエンジニア、デザイナーを募集しています!
ご興味がある方は、以下URLよりご応募ください。


【中途採用】
フロントエンドエンジニア
サーバーサイドエンジニア
コーポレートエンジニア
SREエンジニア

【新卒採用・その他】
21・22新卒
その他採用情報

関連記事

thumbnail
プロジェクトにおける7つの問いを元にアジャイル開発しているおはなし。

こんにちは。 ランサーズエンジニアの@sayaです。 ふだんはランサーズの機能追加や改善を担当しています。   いきなり本題で恐縮ですが、ランサーズには、先日弊社CTOが提唱した『プロジェクトにおける7つの問い』があります。 # 詳細に関してはこちら …

ハイパフォーマンスな開発合宿をする3つの秘訣

  おはこんにちばんは!ランサーズエンジニアの satoshi です。 今回は開発合宿のすすめ的記事を共有させていただきます。 はじめに ランサーズ開発チームでは年に一度、日頃の業務から身を置き、技術やサービスに向き合うために、都会の喧騒から離れ、自 …

React Redux を用いた SPA 新規サービスを運用して得た知見と実装例

@tsuyoshi です。先日行われた React Redux を用いた SPA 新規サービスを運用して得た知見と実装例 と言うイベントで使ったスライドをアップロードしました。 https://pook.life/ と言うサービスをReact Redux の …