title: Tornado server throws error Stream is closed
body: <p>I'm trying to implement a very basic chat/echo web page.</p>
  
  <p>When the client visits /notify:8000 a simple site loads, on the client side a request is initiated to establish a listener, on the back-end the cloud count is updated and sent to all existing clients. </p>
  
  <p>Whenever the user enters something in a text box, a POST is made to the back-end and all the other clients receive an update with that text.</p>
  
  <p>Here's the front-end template</p>
  
  <pre><code>&lt;html&gt;
  &lt;head&gt;
  &lt;script type="text/javascript" src="http://code.jquery.com/jquery.min.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
      &lt;p&gt;session: &lt;span id="session"&gt;{{ session }}&lt;/span&gt;&lt;br/&gt;
      client count: &lt;span id="clientCount"&gt;{{ clientCount }}&lt;/span&gt;&lt;br/&gt;
      &lt;span id="welcome"&gt;...registering with notify...&lt;/span&gt;&lt;/p&gt;
  
      &lt;div style="width: 600px; height: 100px; border: 1px solid black; overflow:auto; padding: 4px;" id="chatText"&gt;
  
      &lt;/div&gt;
  
      &lt;div&gt;
      &lt;span&gt;enter text below:&lt;/span&gt;&lt;br /&gt;
      &lt;input type="text" id="textInput"&gt;&lt;/textarea&gt;
      &lt;div&gt;
  
  &lt;script&gt;
  
  $(document).ready(function() {
      document.session = $('#session').html();
      setTimeout(initializeListener, 100);
  
  
      $('#textInput').keypress(function(e) {
      //    e.preventDefault(); 
          if(e.keyCode == 13 &amp;&amp; !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'] + "&lt;br /&gt;\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+')');
          }
      });
  
  
  }
  
  &lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>Here is the server code:</p>
  
  <pre><code>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()
  </code></pre>
  
  <p>Then sometimes the server throws errors when I close one tab and try to broadcast from another one. The error is like this:</p>
  
  <pre><code>    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&amp;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
  </code></pre>
  
  <p>Obviously this is because the tab was closed, so the client is no longer listening, but that should be a normal thing to expect, how can I handle that situation more gracefully?</p>
  
  <p>The only thing I could find about this error, was another stackoverflow post, the suggestion was to check if the connection was finished before calling the finish() method:</p>
  
  <pre><code>if not self._finished:
      self.finish()
  </code></pre>
  
  <p>However when I tried that, it didn't seem to help I still got the same error, OR I would get another error <strong>AssertionError: Request closed</strong> which I couldn't find any help on.</p>