2012-06-22 3 views
4

Я пытаюсь реализовать очень базовую веб-страницу чата/эха.Tornado server throws error Поток закрыт

Когда клиент посещает/уведомляет: 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 пост, предложение было проверить, если соединение было закончено перед вызовом() метод отделки:

if not self._finished: 
    self.finish() 

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

+0

Это не отвечает на ваш вопрос, но есть пример чата с источником Tornado, который может помочь. https://github.com/facebook/tornado/tree/master/demos/chat –

+0

Это полный код сервера? Кажется, вам не хватает обработчиков, которые дают серверу маршруты, чтобы знать, что выполнять для какого пути ... – gabeio

+0

@gabeDel Да, это полный код сервера. Обработчики устанавливаются там, в классе Application. –

ответ

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 
+0

спасибо стержень! это сделало трюк –

+0

Я немного озадачен этим ответом. Кажется, что никто не вызывает on_connection_close, поэтому self.connection_closed никогда не изменяется. Единственный соответствующий код здесь - это 'try', но я думаю, что' write' или 'finish' могут выйти из строя из-за других исключений. Я что-то упускаю? – Ofir

+0

Поздний ответ на ваше сообщение, но on_connection_close будет установлен с использованием stream.set_close_callback (on_connection_close), когда поток обрабатывается торнадо. – TappCandy

Смежные вопросы