4

非常に基本的なチャット/エコーWebページを実装しようとしています。

クライアントが/notify:8000にアクセスすると、単純なサイトが読み込まれ、クライアント側でリスナーを確立するための要求が開始され、バックエンドでクラウドカウントが更新され、既存のすべてのクライアントに送信されます。

ユーザーがテキストボックスに何かを入力するたびに、POSTがバックエンドに対して行われ、他のすべてのクライアントはそのテキストで更新を受け取ります。

これがフロントエンドテンプレートです

<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
</head>
<body>
    <p>session: <span id="session">{{ session }}</span><br/>
    client count: <span id="clientCount">{{ clientCount }}</span><br/>
    <span id="welcome">...registering with notify...</span></p>

    <div style="width: 600px; height: 100px; border: 1px solid black; overflow:auto; padding: 4px;" id="chatText">

    </div>

    <div>
    <span>enter text below:</span><br />
    <input type="text" id="textInput"></textarea>
    <div>

<script>

$(document).ready(function() {
    document.session = $('#session').html();
    setTimeout(initializeListener, 100);


    $('#textInput').keypress(function(e) {
    //    e.preventDefault(); 
        if(e.keyCode == 13 && !e.shiftKey) {
            var text = $('#textInput').val();
            submitChatText(text);
            return false;
        }
    });
});

var logon = '1';

function initializeListener() {
    console.log('initializeListener() called');
    jQuery.getJSON('//localhost/notify/listen', {logon: logon, session: document.session},
        function(data, status, xhr) {
            console.log('initializeListener() returned');
            if ('clientCount' in data) {
                $('#clientCount').html(data['clientCount']);
            }
            if ('chatText' in data) {
                text = $('#chatText').html()
                $('#chatText').html(data['chatText'] + "<br />\n" + text);
            }
            if (logon == '1') {
                $('#welcome').html('registered listener with notify!');
                logon = '0';
            }
            setTimeout(initializeListener, 0);
        })
        .error(function(XMLHttpRequest, textStatus, errorThrown) {
            console.log('error: '+textStatus+' ('+errorThrown+')');
            setTimeout(initializeListener, 100);
        });
}

function submitChatText(text) {
    console.log('submitChatText called with text: '+text)
    jQuery.ajax({
        url: '//localhost/notify/send',
        type: 'POST',
        data: {
            session: document.session,
            text: ''+text
        },
        dataType: 'json',
        //beforeSend: function(xhr, settings) {
        //    $(event.target).attr('disabled', 'disabled');
        //},
        success: function(data, status, xhr) {
            console.log('sent text message')
            $("#textInput").val('');
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
            console.log('error: '+textStatus+' ('+errorThrown+')');
        }
    });


}

</script>
</body>
</html>

サーバーコードは次のとおりです。

import tornado.ioloop
import tornado.web
import tornado.options
from uuid import uuid4
import json

class Client(object):
    callbacks = {}
    chat_text = ''

    def register(self, callback, session, logon=False):
        self.callbacks[session] = callback
        if logon == '1':
            self.notifyCallbacks()

    def notifyCallbacks(self):
        result = {}
        result['clientCount'] = self.getClientCount()
        if self.chat_text:
            result['chatText'] = self.chat_text

        for session, callback in self.callbacks.iteritems():
            callback(result)

        self.callbacks = {}

    def sendText(self, session, text):
        self.chat_text = text
        self.notifyCallbacks()
        self.chat_text = ''

    def getClientCount(self):
        return len(self.callbacks)

class Application(tornado.web.Application):
    def __init__(self):
        self.client = Client()
        handlers = [
            (r"/notify", MainHandler),
            (r"/notify/listen", ListenHandler),
            (r"/notify/send", SendHandler)
        ]
        settings = dict(
            cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
            template_path="templates/notify",
        )
        tornado.web.Application.__init__(self, handlers, **settings)

class ListenHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        logon = self.get_argument('logon')
        session = self.get_argument('session')
        self.application.client.register(self.on_message, session, logon)

    def on_message(self, result):
        json_result = json.dumps(result)
        self.write(json_result)
        self.finish()

class SendHandler(tornado.web.RequestHandler):
    def post(self):
        text = self.get_argument('text')
        session = self.get_argument('session')
        self.application.client.sendText(session, text)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        session = uuid4()
        client_count= self.application.client.getClientCount()
        self.render("testpage.html", session=session, clientCount=client_count)


if __name__ == '__main__':
    application = Application()
    application.listen(8000)
    tornado.ioloop.IOLoop.instance().start()

次に、あるタブを閉じて別のタブからブロードキャストしようとすると、サーバーがエラーをスローすることがあります。エラーは次のようになります。

    ERROR:root:Uncaught exception POST /notify/send (127.0.0.1)
HTTPRequest(protocol='http', host='localhost', method='POST', uri='/notify/send', version='HTTP/1.1', remote_ip='127.0.0.1', body='session=e5608630-e2c7-4e1a-baa7-0d74bc0ec9fc&text=swff', headers={'Origin': 'http://localhost', 'Content-Length': '54', 'Accept-Language': 'en-US,en;q=0.8', 'Accept-Encoding': 'gzip,deflate,sdch', 'X-Forwarded-For': '127.0.0.1', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'User-Agent': 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', 'Host': 'localhost', 'X-Requested-With': 'XMLHttpRequest', 'X-Real-Ip': '127.0.0.1', 'Referer': 'http://localhost/notify', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'})
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/tornado/web.py", line 1021, in _execute
    getattr(self, self.request.method.lower())(*args, **kwargs)
  File "test.py", line 66, in post
    self.application.client.sendText(session, text)
  File "test.py", line 30, in sendText
    self.notifyCallbacks()
  File "test.py", line 24, in notifyCallbacks
    callback(result)
  File "test.py", line 60, in on_message
    self.finish()
  File "/usr/lib/python2.7/site-packages/tornado/web.py", line 701, in finish
    self.request.finish()
  File "/usr/lib/python2.7/site-packages/tornado/httpserver.py", line 433, in finish
    self.connection.finish()
  File "/usr/lib/python2.7/site-packages/tornado/httpserver.py", line 187, in finish
    self._finish_request()
  File "/usr/lib/python2.7/site-packages/tornado/httpserver.py", line 223, in _finish_request
    self.stream.read_until(b("\r\n\r\n"), self._header_callback)
  File "/usr/lib/python2.7/site-packages/tornado/iostream.py", line 153, in read_until
    self._try_inline_read()
  File "/usr/lib/python2.7/site-packages/tornado/iostream.py", line 381, in _try_inline_read
    self._check_closed()
  File "/usr/lib/python2.7/site-packages/tornado/iostream.py", line 564, in _check_closed
    raise IOError("Stream is closed")
IOError: Stream is closed

明らかに、これはタブが閉じられたためにクライアントがリッスンしなくなったためですが、これは通常のことですが、どうすればその状況をより適切に処理できますか?

このエラーについて私が見つけた唯一のことは、別のスタックオーバーフローの投稿でした。提案は、finish()メソッドを呼び出す前に接続が終了したかどうかを確認することでした。

if not self._finished:
    self.finish()

しかし、それを試しても、同じエラーが発生するのを助けていないようでした。または、別のエラーが発生しました。AssertionError:リクエストが閉じられましたが、ヘルプが見つかりませんでした。

4

1 に答える 1

4

いくつかの古いコードを調べてみると、非同期コードで同様の問題が発生し、クライアントが応答する前に消えていたことがわかりました。私はon_connection_closeを使用して次のように処理しました(例としてコードを使用)。

def on_message(self, result):
    json_result = json.dumps(result)
    if not self.connection_closed:
        try:
            self.write(json_result)
            self.finish()
        except:
            # Catch all, as the client could go away while we're replying.
            self.connection_closed = True

def on_connection_close(self):
    # The client has given up and gone home.
    self.connection_closed = True
于 2012-06-27T14:32:11.460 に答える