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

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よりご応募ください。


【中途採用】
サービスリードエンジニア
テックリード(アーキテクト)
フロントエンドエンジニア
サーバーサイドエンジニア
業務エンジニア(社内システム基盤・基幹システム)

【インターン・学生バイト】
19新卒対象サマーインターン
エンジニアインターン

その他採用情報

関連記事

マラソン
ハーフマラソンをアジャイル開発っぽく走ってみて目標達成した話

こんにちは、エンジニアのshinです。 アジャイル開発を良いマラソンランナーに例えるなど、開発をマラソンに例えることはよくあります。 私自身も以前から、開発とマラソンには共通点があると感じており、 今回は、ランサーズで実施しているスクラム開発をハーフマラソンに …

SREチームの発足

SREチームの金澤です。 2018年度より、SREチームを発足しました。 その経緯をお話ししたいと思います。 インフラエンジニアとして 私は、2013年11月にランサーズに入社しました。 ランサーズ5年目にして、サービスが本格的に伸び始めた時期で、アプリエンジ …

Ruby Mechanize によるサービス監視のすすめ

こんにちわ。エンジニアの こじま です。 今回は,ランサーズのサービス品質を支える サービス監視システムのご紹介をします。 弊社プラットフォームは,複数の監視システムを導入して運用を実施しています。 システム 用途 (自社製)サービス監視システム サービス稼働 …