4

これは、私がこれまでに作成した最初のロング ポーリング アプリケーションであり、Twisted を使用した 2 番目のプロジェクトです。そのため、私のコード内の何かについて誰かがフィードバックをいただければ幸いです。

さまざまな例をまとめてみましたが、ほとんど機能していますが、データを Javascript に戻す方法が見つからないようです。私は Twisted で Django サイトを実行していますが、問題なく動作しているように見えるので、誰かが重要だと考えない限り、Django ビットを含めるつもりはありません。Django サイトが行う唯一のことは、チャットをホストすることです。元々は通常のポーリングを使用して設定していましたが、ロングポーリングに変更するように求められており、ほぼそこにいます (願っています)。

HTML/JS (long.html) は次のとおりです。

<div class="chat-messages" style="width:300px;height:400px;border:1px solid black;overflow:scroll;" id="messages">
  </div><br/>
  <form action="javascript:sendMessage();" >
    <input type="text" id="chat_nickname" name="author"/>
    <input type="text" id="chat_input" name="message" class="chat-new"/>
    <button class="submit">Submit</button>
  </form>
 </body>

 <script type="text/javascript">
    // keep track of the last time data wes received
    var last_update = 0;

    // call getData when the document has loaded
    $(document).ready(function(){
        getData(last_update);
    });

    // execute ajax call to chat_server.py
    var getData = function(last_update){
        $.ajax({
            type: "GET",
            url: "http://"+ window.location.hostname + ":8081?last_update=" + last_update + "&callback=?",
            dataType: 'json',
            async: true,
            cache:false,
            timeout: 300000,
            success: function(response){
                // append the new message to the message list
                var messages = response.data.messages;
                console.log(response);
                for (i in messages){
                    $('<p><span class="time">[' + messages[i].time +']</span> - <span class="message">' + messages[i].message + '</span></p>').appendTo('#messages');
                    if (messages[i].time > last_update){
                        last_update = messages[i].time;
                    }
                }
                console.log("Last_update: " + last_update);
                // Keep div scrolled to bottom
                $("#messages").scrollTop($("#messages")[0].scrollHeight);
                // Check again in a second
                setTimeout('getData(' + last_update + ');', 1000);
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                // Try again in 10 seconds
                setTimeout( "getData(" + last_update + ");", 10000);
            },
            failure: function(){ console.log('fail'); },
        });
    }
    // Add a contribution to the conversation
     function sendMessage(){
        var nickname = $('#chat_nickname').val();
        var message = $('#chat_input').val();
        $('#chat_input').val("");

        console.log( "nickname: " + nickname + "; message: " + message );

        $.ajax({
             type: 'POST',
             url: '/chat/post_message/',
             data: {
                nickname: nickname, 
                message:message
             },
             success: function(data, status, xml){
                console.log("Success! - " + status);
             },
             error: function(xml, status, error){
                console.log(error + " - Error! - " + status);
             },
             complete: function(xml, status){
                console.log("Complete! - " + status);
             }
        });

    }
</script> 

sendMessageフォームから Django にデータを渡すと、Django はそれをデータベースに入れます (そして時刻を追加します)。 getData:8081 に移動します。ここで Twisted は次のコード (chat_server.py)の### チャット サーバー部分 (後半) をリッスンします:

import datetime, json, sys, time, os, types
from twisted.web import client, resource, server, wsgi
from twisted.python import threadpool
from twisted.internet import defer, task, reactor
from twisted.application import internet, service
from twisted.enterprise import adbapi
from django.core.handlers.wsgi import WSGIHandler

## Django environment variables
sys.path.append("mydjangosite")
os.environ['DJANGO_SETTINGS_MODULE'] = 'mydjangosite.settings'

 ## Tying Django's WSGIHandler into Twisted
def wsgi_resource():
    pool = threadpool.ThreadPool()
    pool.start()

    # Allow Ctrl-C to get you out cleanly:
    reactor.addSystemEventTrigger('after', 'shutdown', pool.stop)

    wsgi_resource = wsgi.WSGIResource(reactor, pool, WSGIHandler())

    return wsgi_resource

## Twisted Application Framework
application = service.Application('twisted-django')

class Root(resource.Resource):
    def __init__(self, wsgi_resource = None):
        resource.Resource.__init__(self)
        if wsgi_resource != None:
            self.wsgi_resource = wsgi_resource

    def getChild(self, path, request):
        child_path = request.prepath.pop(0)
        request.postpath.insert(0, child_path)
        return self.wsgi_resource

    def render_GET(self, request):
        id = request.args.get('id', [""])[0]
        command = request.args.get('command', [""])[0]
        self.get_page(request, id)
        return server.NOT_DONE_YET

    @defer.inlineCallbacks
    def get_page(self, request, id):
        page = yield client.getPage("/chat/latest/%s" % id)
        request.write(page)
        request.finish()

## Create and attach the django site to the reactor
django_root = Root(wsgi_resource())
django_factory = server.Site(django_root)
reactor.listenTCP(8080, django_factory)





### Chat Server
class ChatServer(resource.Resource):
    isLeaf = True

    def __init__(self):
        # throttle in seconds
        self.throttle = 5
        # store client requests
        self.delayed_requests = []
        # setup a loop to process collected requests
        loopingCall = task.LoopingCall(self.processDelayedRequests)
        loopingCall.start(self.throttle, False)
        # Initialize
        resource.Resource.__init__(self)

    def render(self, request):
        """Handle a new request"""
        request.setHeader('Content-Type', 'applicaton/json')
        args = request.args
        # set jsonp callback handler name if it exists
        if 'callback' in args:
            request.jsonpcallback = args['callback'][0]
        # set last_update if it exists
        if 'last_update' in args:
            request.last_update = args ['last_update'][0]

        data = self.getData(request)
        if type(data) is not types.InstanceType and len(data) > 0:
            # send the requested messages back
            return self.__format_response(request, 1, data)
        else:
            # or put them in the delayed request list and keep the connection going
            self.delayed_requests.append(request)
            return server.NOT_DONE_YET

    def getData(self, request):
        data = {}
        dbpool = adbapi.ConnectionPool("sqlite3", database="/home/server/development/twisted_chat/twisted-wsgi-django/mydjangosite/site.db", check_same_thread=False)
        last_update = request.last_update
        print "LAST UPDATE: ", last_update
        new_messages = dbpool.runQuery("SELECT * FROM chat_message WHERE time > %r" % request.last_update )
        return new_messages.addCallback(self.gotRows, request )

    def gotRows(self, rows, request):
        if rows:
            data = {"messages": 
                                [{ 'author': row[1], 'message':row[2],'timestamp': row[3] } for row in rows] 
                        }
            print 'MESSAGES: ', data
            if  len(data) > 0:
                return self.__format_response(request, 1, data)
            return data

    def processDelayedRequests(self):
        for request in self.delayed_requests:
            data = self.getData(request)
            if type(data) is not types.InstanceType and len(data) > 0:
                try:
                    print "REQUEST DATA:", data
                    request.write(self.__format_response(request, 1, data))
                    request.finish()
                except:
                    print 'connection lost before complete.'
                finally:
                    self.delayed_requests.remove(request)

    def __format_response(self, request, status, data):
        response = json.dumps({ "status": status, "time": int(time.time()), "data": data })
        if hasattr(request, 'jsonpcallback'):
            return request.jsonpcallback + '(' + response + ')'
        else:
            return response

chat_server = ChatServer()
chat_factory = server.Site(chat_server)
reactor.listenTCP(8081, chat_factory)

ここで、 をrender試みgetDataます (そうではないかもしれません) self.delayed_requestsgetDataenterprise.adbapi を使用して Django のデータベースでクエリを実行し、Deferred インスタンスを返します。 processedDelayedRequestsは遅延リクエスト キューを通過し、クエリが完了すると、そのデータが に渡されgotRows、必要な形式に変換されて__format_response送信されます。 とにかくそれが理論です - 前の文は私が私の問題があると思う場所です.

print "LAST UPDATE: ", last_update常に "LAST_UPDATE: 0" を出力しますが、last_update は JS を介して更新されるため、これはエラーではありません。

print 'MESSAGES: ', data出力 "{'messages': [{'timestamp': u'2013-08-10 16:59:07.909350', 'message': u'chat message', 'author': u'test'}, {'timestamp ': u'2013-08-10 17:11:56.893340', 'message': u'hello', 'author': u'pardon'}]}" など、新しいメッセージがデータベースに追加されます。投稿が行われると新しいデータを取得し、それ以外の場合はかなりうまく機能するようです.

print "REQUEST DATA:", dataまったく起動しません...このメソッドは、これを機能させるための以前の試みから残っていると思います。

から正しい出力gotRowsが得られますが、その出力をクライアントに戻す方法がわかりません。私は Deferreds の理解に半分も自信がないので、そこに問題があると思いますが、ここから先に進むために何ができるかわかりません。どんな助けでも大歓迎です。

4

1 に答える 1

3

ねじれたアプリケーションの関数は、条件付きでデータを返したり、Deferred. そのような場合、データを取得したかどうかを確認することはできません。おそらくそうはならないでしょうし、遅延が発生した場合、いくら再チェックしてもそれは変わりません。そのような関数を を使用して常に実際の Deferred に変換maybeDeferredし、その結果にコールバックをアタッチする必要があります。

とはいえ、そのt.e.adbapi.ConnectionPool.runQuery()ような機能ではありません。常にdeferredを返します。そのデータを操作する唯一の方法は、コールバックをアタッチすることです。一般に、ねじれたアプリケーションでは、最初の呼び出しを行った同じ関数で非同期呼び出しの結果が表示されることはありません。

これは、長いポーリング リクエストごとにクエリを実行する必要があり、それらが無条件に非同期であるため (開始render()する前に関数から戻る必要がある)、常に次を返すことを意味します。render()NOT_DONE_YET

def render(self, request):
    """Handle a new request"""
    request.setHeader('Content-Type', 'applicaton/json')
    self.getData(request)
    return server.NOT_DONE_YET

getData ですべてが正しく行われる必要があります。結局のところ、deferred from の処理runQueryは問題ありません。しかし、SQL自体には かなり大きな問題があります。その理由を理解するために、巧妙なハッカーがアクセスしようとしたと想像してください。

http://yoursite?last_update=5+and+"secret"+in+(select+password+from+users)

修正は簡単ですが、文字列補間を行わず、バインド パラメータを使用してください。クエリのfor aと、関数呼び出し自体の%sfor aを切り替えます。ここまで来たら、をこのメソッドから に移動させてみましょう。各リクエストの再試行ごとにプール全体を必要としないか、必要としません。?%,ConnectionPool__init__

def getData(self, request):
    last_update = request.args['last_update']
    print "LAST UPDATE: ", last_update
    new_messages = self.dbpool.runQuery("SELECT *"
                                        " FROM chat_message"
                                        " WHERE time > ?", request.last_update)
    #                                                  ^ ^
    return new_messages.addCallback(self.gotRows, request)

によって返される deferred にアタッチされたコールバックrunQueryは、フォーマットされた結果を返します。しかし、それを返す人は誰もいません。すべての作業を自分で行う必要があります。幸いなことに、私たちはすでにそれをrequest使用できるようにしているので、それほど難しいことではありません。また、遅延リクエストのリストにデータを追加する相手がいないため、返すデータがなかった場合にも対処する必要があります。

def gotRows(self, rows, request):
    if rows:
        # we have data to send back to the client! actually finish the
        # request here.
        data = {"messages": [{'author': row[1], 'message': row[2], 'timestamp': row[3]} for
                             row in rows]}
        request.write(self.__format_response(request, 1, data))
        request.finish()

    else:
        self.delayed_requests.append(self)

processedDelayedRequests()最後に、 で行ったのと同様の変更を に加える必要がありrender()ます。クエリを起動することしかできません。結果がないため、結果に基づいて状態を更新することはできません。簡単にするために、リストから項目を削除します。

def processDelayedRequests(self):
    delayed_requests = self.delayed_requests
    self.delayed_requests = []
    while self.delayed_requests:
        # grab a request out of the "queue"
        request = self.delayed_requests.pop()

        # we can cause another attempt at getting data, but we'll never get
        # to see what hapened with it in this function.
        self.getData(request)
于 2013-08-14T18:25:44.190 に答える