非常に基本的なチャット/エコー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:リクエストが閉じられましたが、ヘルプが見つかりませんでした。