ハウツー:WebSocket(socket.io、sockjs)でDjangoを友達にする方法

バージョン:0.2



他のユーザー( herbalifeチャットではない)のアクションに応じて、特定の数のユーザーのページをリアルタイムでアトミックに更新する必要がありました。 もちろん 、あなたはすべてをゴミ箱に捨てて、 tornado / twisted.webで最初からそれをギャッシュできますが、すべてが必要なとき、それは明らかに最も生産的な方法ではありません(そして、私は一度も良くありません)-それはすでにDjangoで動作し、ほんの少し...当然、ここではWebSocketが要求されます。 そして、すべてはDjango WSGIアプリケーションに他ならないでしょう。そしてこの標準は、(今のところ)そのようなトリックを意味しません。 インターネットのグーグル検索は、よく知られたパイソンの達人kmikeの仕事につながりました (これは皮肉なことではありません。彼の仕事が私を何度も助けてくれたからです。



したがって、 socket.ioまたはsockjs jsライブラリを使用してDjangoプロジェクトをwebsocketとクロスさせたい場合は、お気軽に!



更新について



この記事の最初のバージョンは、Socket.ioを使用したバージョンのみに当てられました。 その過程で、ライブラリが失敗した接続を常に判断するとは限らないという奇妙な感覚が生じました。 つまり ソケットを長時間ダンプするとき、彼女はしがみつきませんでした。 私自身が何か間違ったことをしたことを完全に認めます。 記事の最初のバージョンのコメントで、人々はsockjsを推奨しました (最終的に切り替えたので、自分のイニシアチブで)、私はこのオプションをチェックすることにしました。 その結果、libはすべての歯との接続にしがみついていますが、サーバー側が応答を停止すると、奇妙な状況が発生しました(これもおそらく私たちのせいですが、理由はまだ解明中です)。 いずれにせよ、写真を完成させるために記事を補足することにしました(Googleからの友人の緊急の要請がない限りではありません)。 一般的に、実装の選択はあなたに任せます。



エントリー



長い間、非同期の何かを試してみたかったのですが、それでも正当な理由はありませんでした。必要があり、どこから始めるべきかは完全に不明でした。 実際、ここで最も関連性の高いものを作成しようとします(私自身は上記のドキュメントを出発点として取りましたが、かなり古く、いくつかの改善がすでに現れています)。 Djangoのおなじみの小島があり、そこに新鮮な風を吹き込む方法を示します...



ちなみに、 kmikeの仕事から、いくつかの機能が変更なしで使用されました。著者が気にしないことを願っています。



何を得る



その結果、メインdjangoサイトの隣でスピンし、ユーザーがどのdjangoをリクエストを送信/受信するかを知る非同期サービスを取得し、[サービス]はdjangoからコマンドを受信し、ユーザーのブラウザーでアクションを実行します。





たとえば、仮想の交換を考えてみましょう。 彼女にはモデレーターとクライアントがいます。 すべてがうまく機能しました。ここでは、モデレーターにリアルタイムで取引所のポジションの変化を見る機会を与える必要がありました。 同時に、モデレーターはエクスチェンジのポジションで何らかの形で操作でき、ページをリロードすることはできません。



それ以前は、全員がF5のソーセージでした...そして、一般的に、 highload自体は 、特に興味がありません。



ツール



仕事には、次のものが必要です。

pip install redis tornado-redis  pip install tornadio2  pip install sockjs-tornado     .
      
      





また、 socket.ioまたはsockjsライブラリ



理論



tornadio2ライブラリを使用して、 sockjsのsocket.ioおよびsockjs-tornadoを操作します。これらは、自然にtornado非同期フレームワークに基づいています。 このケースは、djangoの管理チーム(hello スーパーバイザー )として開始されます。 トルナディオがない場合のジャングルのプレイには特に問題はありませんが、その代わりに、 PubSubRedis機能で解決する小さなギャグがあります(要するに、これらはパブリッシャーのプッシュメッセージとサブスクライバーが受信するチャネルまたはメッセージキューです)。



言い訳



プレイが進むにつれて、注意深い読者は、本質的に同期であるジャンゴ機能の使用などの矛盾に気付くかもしれませんが、これは急速な発展のための小さな犠牲です。 さらに、最初はハイロイドの話はありません。これは包括的なソリューションではなく、出発点です。 ですから、実装の微妙な違いやコードのボトルネックを楽しんでもらいたいと思います。



また、私がいつも参照しているドキュメントのkmikeの言い訳も参照してください。



練習する



ソースのコメントには多くの説明があるため、実践は実用的です。



service.py


実際、ブラウザへの接続をサポートするサービス自体は、djangoからコマンドを受信し、それらをクライアントに送信します(逆の方向でも同様)。



on_message



メソッドon_message



実装に必要ですが、上記の例では必要ありません。 すべてがnewfangledイベントモデル(socket.io用)に実装されています。



socket.ioの実装


 # -*- coding: utf-8 -*- import tornado import tornadoredis from tornadio2 import SocketConnection from tornadio2.conn import event import django from django.utils.importlib import import_module from django.conf import settings from django.utils import simplejson # start of kmike's sources _engine = import_module(settings.SESSION_ENGINE) def get_session(session_key): return _engine.SessionStore(session_key) def get_user(session): class Dummy(object): pass django_request = Dummy() django_request.session = session return django.contrib.auth.get_user(django_request) # end of kmike's sources #     redis     django ORDERS_REDIS_HOST = getattr(settings, 'ORDERS_REDIS_HOST', 'localhost') ORDERS_REDIS_PORT = getattr(settings, 'ORDERS_REDIS_PORT', 6379) ORDERS_REDIS_PASSWORD = getattr(settings, 'ORDERS_REDIS_PASSWORD', None) ORDERS_REDIS_DB = getattr(settings, 'ORDERS_REDIS_DB', None) #   unjson = simplejson.loads json = simplejson.dumps class Connection(SocketConnection): def __init__(self, *args, **kwargs): super(Connection, self).__init__(*args, **kwargs) self.listen_redis() @tornado.gen.engine def listen_redis(self): """     . """ self.redis_client = tornadoredis.Client( host=ORDERS_REDIS_HOST, port=ORDERS_REDIS_PORT, password=ORDERS_REDIS_PASSWORD, selected_db=ORDERS_REDIS_DB ) self.redis_client.connect() yield tornado.gen.Task(self.redis_client.subscribe, [ 'order_lock', 'order_done' ]) self.redis_client.listen(self.on_redis_queue) #    #  self.on_redis_queue def on_open(self, info): """   django. """ self.django_session = get_session(info.get_cookie('sessionid').value) @event # ,    def login(self): """      """ #      ,      on_open self.user = get_user(self.django_session) self.is_client = self.user.has_perm('order.lock') self.is_moder = self.user.has_perm('order.delete') def on_message(self): """  . """ pass def on_redis_queue(self, message): """     """ if message.kind == 'message': #      , #  ,     message_body = unjson(message.body) #  ,   #      JSON #        if message.channel == 'order_lock': self.on_lock(message_body) if message.channel == 'order_done: self.on_done(message_body) def on_lock(self, message): """   """ if message['user'] != self.user.pk: # -       self.emit('lock', message) def on_done(self, message): """   """ if message['user'] != self.user.pk: if self.is_client: message['action'] = 'hide' else: message['action'] = 'highlight' self.emit('done', message) def on_close(self): """       """ self.redis_client.unsubscribe([ 'order_lock', 'order_done' ]) self.redis_client.disconnect()
      
      





sockjsの実装


 # -*- coding: utf-8 -*- import tornado import tornadoredis from sockjs.tornado import SockJSConnection import django from django.utils.importlib import import_module from django.conf import settings from django.utils import simplejson # start of kmike's sources _engine = import_module(settings.SESSION_ENGINE) def get_session(session_key): return _engine.SessionStore(session_key) def get_user(session): class Dummy(object): pass django_request = Dummy() django_request.session = session return django.contrib.auth.get_user(django_request) # end of kmike's sources #     redis     django ORDERS_REDIS_HOST = getattr(settings, 'ORDERS_REDIS_HOST', 'localhost') ORDERS_REDIS_PORT = getattr(settings, 'ORDERS_REDIS_PORT', 6379) ORDERS_REDIS_PASSWORD = getattr(settings, 'ORDERS_REDIS_PASSWORD', None) ORDERS_REDIS_DB = getattr(settings, 'ORDERS_REDIS_DB', None) #   unjson = simplejson.loads json = simplejson.dumps class Connection(SocketConnection): def __init__(self, *args, **kwargs): super(Connection, self).__init__(*args, **kwargs) self.listen_redis() @tornado.gen.engine def listen_redis(self): """     . """ self.redis_client = tornadoredis.Client( host=ORDERS_REDIS_HOST, port=ORDERS_REDIS_PORT, password=ORDERS_REDIS_PASSWORD, selected_db=ORDERS_REDIS_DB ) self.redis_client.connect() yield tornado.gen.Task(self.redis_client.subscribe, [ 'order_lock', 'order_done' ]) self.redis_client.listen(self.on_redis_queue) #    #  self.on_redis_queue def send(self, msg_type, message): """  . """ return super(Connection, self).send({ 'type': msg_type, 'data': message, }) def on_open(self, info): """   django. """ self.django_session = get_session(info.get_cookie('sessionid').value) self.user = get_user(self.django_session) self.is_client = self.user.has_perm('order.lock') self.is_moder = self.user.has_perm('order.delete') def on_message(self): """  . """ pass def on_redis_queue(self, message): """     """ if message.kind == 'message': #      , #  ,     message_body = unjson(message.body) #  ,   #      JSON #        if message.channel == 'order_lock': self.on_lock(message_body) if message.channel == 'order_done: self.on_done(message_body) def on_lock(self, message): """   """ if message['user'] != self.user.pk: # -       self.send('lock', message) def on_done(self, message): """   """ if message['user'] != self.user.pk: if self.is_client: message['action'] = 'hide' else: message['action'] = 'highlight' self.send('done', message) def on_close(self): """       """ self.redis_client.unsubscribe([ 'order_lock', 'order_done' ]) self.redis_client.disconnect()
      
      





models.py


変更のソース。 モデルにしましょう。



 # -*- coding: utf-8 -*- import redis from django.conf import settings from django.db import models ORDERS_FREE_LOCK_TIME = getattr(settings, 'ORDERS_FREE_LOCK_TIME', 0) ORDERS_REDIS_HOST = getattr(settings, 'ORDERS_REDIS_HOST', 'localhost') ORDERS_REDIS_PORT = getattr(settings, 'ORDERS_REDIS_PORT', 6379) ORDERS_REDIS_PASSWORD = getattr(settings, 'ORDERS_REDIS_PASSWORD', None) ORDERS_REDIS_DB = getattr(settings, 'ORDERS_REDIS_DB', 0) #   service_queue = redis.StrictRedis( host=ORDERS_REDIS_HOST, port=ORDERS_REDIS_PORT, db=ORDERS_REDIS_DB, password=ORDERS_REDIS_PASSWORD ).publish json = simplejson.dumps class Order(models.Model) … def lock(self): """   """ … service_queue('order_lock', json({ 'user': self.client.pk, 'order': self.pk, })) def done(self): """   """ … service_queue('order_done', json({ 'user': self.client.pk, 'order': self.pk, }))
      
      





実際、ここでlock



done



メソッドは、ある種のビジネスロジックを実行した後、必要な情報を含むメッセージを送信します。 この情報は上記のサービスによって受信され、処理されてクライアントのブラウザーに送信されます。



つまり アクションは標準スキームに従ってユーザーによって実行されました:彼はリンクをクリック/ボタンを押し、djangoは必要なアクションを実行し、websocket経由で配信するためにチャンネルに通知を送信し、ユーザーに古典的な応答を返しました。



client.js


選択に応じて、htmlにsocket.io.jsまたはsockjs.jsを読み込むことを忘れないでください(記事の冒頭のリンク)。



実際には、このすべてのアクションの評価-クライアント側で動作します。



socket.ioの実装


  var socket = io.connect('http://' + window.location.host + ':8989'); //      //     login,       socket.on('connect', function(){ socket.emit('login'); }); //   -    socket.on('disconnect', function() { setTimeout(socket.socket.reconnect, 5000); }); //    "lock"  "ws_order_lock"       socket.on('lock', function(msg){ ws_order_lock(msg); }); socket.on('done', function(msg){ ws_order_done(msg); }); function ws_order_lock(msg){ if (msg.action == 'highlight'){ $('.id_order_row__' + msg.order).addClass('order-row_is_locked'); }else{ $('.id_info_renew_orders').addClass('hidden'); } } …
      
      





sockjsの実装


 socket_connect(); function socket_connect() { socket = new SockJS('http://' + window.location.host + ':8989/orders'); //      //     login,       socket.onmessage = function(msg){ window['ws_order_' + msg.data.type](msg.data.data); // ,      } socket.onclose = function(e){ setTimeout(socket_connect, 5000); }; } function ws_order_lock(msg){ if (msg.action == 'highlight'){ $('.id_order_row__' + msg.order).addClass('order-row_is_locked'); }else{ $('.id_info_renew_orders').addClass('hidden'); } } …
      
      





async_server.py


これは管理コマンドです。ファイルはmyProject/orderApp/management/commands



フォルダーに入れる必要があります。各サブフォルダーのファイルは__init__.py



です。



socket.ioの実装


 # -*- coding: utf-8 -*- import tornado import tornadio2 as tornadio from django.core.management.base import NoArgsCommand from myProject.order.tornado.service import Connection class Command(NoArgsCommand): def handle_noargs(self, **options): router = tornadio.TornadioRouter(Connection) app = tornado.web.Application(router.urls, socket_io_port=8989) #      tornadio.SocketServer(app)
      
      





sockjsの実装


 # -*- coding: utf-8 -*- import tornado import tornadio2 as tornadio from django.core.management.base import NoArgsCommand from myProject.order.tornado.service import Connection class Command(NoArgsCommand): def handle_noargs(self, **options): router = SockJSRouter(Connection, '/orders') # sockjs      :( app = tornado.web.Application(router.urls) app.listen(8989) tornado.ioloop.IOLoop.instance().start()
      
      







これで、 python manage.py async_server



開始できpython manage.py async_server






All Articles