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

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新卒対象サマーインターン
エンジニアインターン

その他採用情報

関連記事

究極のPDCA!その名も「PD鮨A」でプロダクト開発を超速化!

ランサーズのtsuyoshiです。 今回も社内コミュニケーションを活性化させ、プロダクトをよりよくするような【ネタ】です。 サービスやプロダクト開発のPDCAにおいて、日々カイゼン要望を吸収し、高速なPDCAを回すことが必要不可欠です。 ですが、よくよく考える …

thumbnail
データ調査を効率化!SQLとシェルを使って、データを自由に操作する方法

はじめまして。 4月からランサーズのエンジニアチームにジョインしました大平です。 好きな言語はPerl, ShellScript, JavaScriptです。 プロンプトはbashですが、個人的なShellScriptはzshです。 データ調査をしている時に、 …

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

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