Вопрос по asynchronous, python, websocket, comet, tornado – Сервер Торнадо выдает ошибку Поток закрыт

4

Я пытаюсь реализовать очень простую страницу чата / эха.

Когда клиент посещает / уведомляет: 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

Очевидно, это связано с тем, что вкладка была закрыта, поэтому клиент больше не слушает, но этого следует ожидать, как я могу справиться с этой ситуацией более изящно?

Единственное, что я мог найти об этой ошибке, это еще одна публикация stackoverflow, предложение было проверить, завершилось ли соединение, перед вызовом метода finish ():

if not self._finished:
    self.finish()

Однако, когда я попробовал это, это не помогло, но я все еще получил ту же ошибку, ИЛИ я получил бы другую ошибку.AssertionError: Request closed по которой я не смог найти никакой помощи.

@NickJennings ааа пропустил это ... gabeio
@RodHyde - да, я знаю об этом демонстрационном приложении, дело в том, что я не вижу, что я делаю по-другому, чтобы вызвать эту ошибку. Nick Jennings
Это не отвечает на ваш вопрос, но есть пример чата с источником Tornado, который может помочь.github.com/facebook/tornado/tree/master/demos/chat Rod Hyde
@gabeDel Да, это полный код сервера. Обработчики установлены там, в классе Application. Nick Jennings
Это ваш полный код сервера? Кажется, вам не хватает обработчиков, которые дают серверу маршруты, чтобы знать, что выполнить для какого пути ... gabeio

Ваш Ответ

1   ответ
3

я обнаружил, что у меня была похожая проблема с асинхронным кодом, когда клиент уходил, прежде чем я смог ответить на него. Я справился с этим, используя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
спасибо род! это сделал трюк Nick Jennings
Поздний ответ на ваше сообщение, но on_connection_close будет установлен с использованием stream.set_close_callback (on_connection_close), когда поток обрабатывается торнадо.
Я немного озадачен этим ответом. Кажется, никто не вызывает on_connection_close, поэтому self.connection_closed никогда не изменяется. Единственный соответствующий фрагмент кода здесьtry, но я думаюwrite или жеfinish может произойти сбой из-за других исключений. Я что-то пропустил?

Похожие вопросы