]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Update chat demo to be more exemplary.
authorBen Darnell <ben@bendarnell.com>
Sat, 16 Mar 2013 04:52:04 +0000 (00:52 -0400)
committerBen Darnell <ben@bendarnell.com>
Sat, 16 Mar 2013 04:52:04 +0000 (00:52 -0400)
Use gen.coroutine in auth handler (but not in wait_for_messages
because it makes things less clear and we don't have good cancellation
support).  No more mixin.  Python 3 compatible.

demos/chat/chatdemo.py

index 4442fc391c75aaa70255458e77a6fd33e9aa4eb1..095f7784c16470788d8c93daa16d91e7b58975b3 100755 (executable)
@@ -18,85 +18,68 @@ import logging
 import tornado.auth
 import tornado.escape
 import tornado.ioloop
-import tornado.options
 import tornado.web
 import os.path
 import uuid
 
-from tornado.options import define, options
+from tornado import gen
+from tornado.options import define, options, parse_command_line
 
 define("port", default=8888, help="run on the given port", type=int)
 
 
-class Application(tornado.web.Application):
+class MessageBuffer(object):
     def __init__(self):
-        handlers = [
-            (r"/", MainHandler),
-            (r"/auth/login", AuthLoginHandler),
-            (r"/auth/logout", AuthLogoutHandler),
-            (r"/a/message/new", MessageNewHandler),
-            (r"/a/message/updates", MessageUpdatesHandler),
-        ]
-        settings = dict(
-            cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
-            login_url="/auth/login",
-            template_path=os.path.join(os.path.dirname(__file__), "templates"),
-            static_path=os.path.join(os.path.dirname(__file__), "static"),
-            xsrf_cookies=True,
-        )
-        tornado.web.Application.__init__(self, handlers, **settings)
-
-
-class BaseHandler(tornado.web.RequestHandler):
-    def get_current_user(self):
-        user_json = self.get_secure_cookie("chatdemo_user")
-        if not user_json: return None
-        return tornado.escape.json_decode(user_json)
-
-
-class MainHandler(BaseHandler):
-    @tornado.web.authenticated
-    def get(self):
-        self.render("index.html", messages=MessageMixin.cache)
-
-
-class MessageMixin(object):
-    waiters = set()
-    cache = []
-    cache_size = 200
+        self.waiters = set()
+        self.cache = []
+        self.cache_size = 200
 
     def wait_for_messages(self, callback, cursor=None):
-        cls = MessageMixin
         if cursor:
-            index = 0
-            for i in xrange(len(cls.cache)):
-                index = len(cls.cache) - i - 1
-                if cls.cache[index]["id"] == cursor: break
-            recent = cls.cache[index + 1:]
-            if recent:
-                callback(recent)
+            new_count = 0
+            for msg in reversed(self.cache):
+                if msg["id"] == cursor:
+                    break
+                new_count += 1
+            if new_count:
+                callback(self.cache[-new_count:])
                 return
-        cls.waiters.add(callback)
+        self.waiters.add(callback)
 
     def cancel_wait(self, callback):
-        cls = MessageMixin
-        cls.waiters.remove(callback)
+        self.waiters.remove(callback)
 
     def new_messages(self, messages):
-        cls = MessageMixin
-        logging.info("Sending new message to %r listeners", len(cls.waiters))
-        for callback in cls.waiters:
+        logging.info("Sending new message to %r listeners", len(self.waiters))
+        for callback in self.waiters:
             try:
                 callback(messages)
             except:
                 logging.error("Error in waiter callback", exc_info=True)
-        cls.waiters = set()
-        cls.cache.extend(messages)
-        if len(cls.cache) > self.cache_size:
-            cls.cache = cls.cache[-self.cache_size:]
+        self.waiters = set()
+        self.cache.extend(messages)
+        if len(self.cache) > self.cache_size:
+            self.cache = self.cache[-self.cache_size:]
+
 
+# Making this a non-singleton is left as an exercise for the reader.
+global_message_buffer = MessageBuffer()
 
-class MessageNewHandler(BaseHandler, MessageMixin):
+
+class BaseHandler(tornado.web.RequestHandler):
+    def get_current_user(self):
+        user_json = self.get_secure_cookie("chatdemo_user")
+        if not user_json: return None
+        return tornado.escape.json_decode(user_json)
+
+
+class MainHandler(BaseHandler):
+    @tornado.web.authenticated
+    def get(self):
+        self.render("index.html", messages=global_message_buffer.cache)
+
+
+class MessageNewHandler(BaseHandler):
     @tornado.web.authenticated
     def post(self):
         message = {
@@ -104,21 +87,24 @@ class MessageNewHandler(BaseHandler, MessageMixin):
             "from": self.current_user["first_name"],
             "body": self.get_argument("body"),
         }
-        message["html"] = self.render_string("message.html", message=message)
+        # to_basestring is necessary for Python 3's json encoder,
+        # which doesn't accept byte strings.
+        message["html"] = tornado.escape.to_basestring(
+            self.render_string("message.html", message=message))
         if self.get_argument("next", None):
             self.redirect(self.get_argument("next"))
         else:
             self.write(message)
-        self.new_messages([message])
+        global_message_buffer.new_messages([message])
 
 
-class MessageUpdatesHandler(BaseHandler, MessageMixin):
+class MessageUpdatesHandler(BaseHandler):
     @tornado.web.authenticated
     @tornado.web.asynchronous
     def post(self):
         cursor = self.get_argument("cursor", None)
-        self.wait_for_messages(self.on_new_messages,
-                               cursor=cursor)
+        global_message_buffer.wait_for_messages(self.on_new_messages,
+                                                cursor=cursor)
 
     def on_new_messages(self, messages):
         # Closed client connection
@@ -127,23 +113,21 @@ class MessageUpdatesHandler(BaseHandler, MessageMixin):
         self.finish(dict(messages=messages))
 
     def on_connection_close(self):
-        self.cancel_wait(self.on_new_messages)
+        global_message_buffer.cancel_wait(self.on_new_messages)
 
 
 class AuthLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
     @tornado.web.asynchronous
+    @gen.coroutine
     def get(self):
         if self.get_argument("openid.mode", None):
-            self.get_authenticated_user(self.async_callback(self._on_auth))
+            user = yield self.get_authenticated_user()
+            self.set_secure_cookie("chatdemo_user",
+                                   tornado.escape.json_encode(user))
+            self.redirect("/")
             return
         self.authenticate_redirect(ax_attrs=["name"])
 
-    def _on_auth(self, user):
-        if not user:
-            raise tornado.web.HTTPError(500, "Google auth failed")
-        self.set_secure_cookie("chatdemo_user", tornado.escape.json_encode(user))
-        self.redirect("/")
-
 
 class AuthLogoutHandler(BaseHandler):
     def get(self):
@@ -152,8 +136,21 @@ class AuthLogoutHandler(BaseHandler):
 
 
 def main():
-    tornado.options.parse_command_line()
-    app = Application()
+    parse_command_line()
+    app = tornado.web.Application(
+        [
+            (r"/", MainHandler),
+            (r"/auth/login", AuthLoginHandler),
+            (r"/auth/logout", AuthLogoutHandler),
+            (r"/a/message/new", MessageNewHandler),
+            (r"/a/message/updates", MessageUpdatesHandler),
+            ],
+        cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
+        login_url="/auth/login",
+        template_path=os.path.join(os.path.dirname(__file__), "templates"),
+        static_path=os.path.join(os.path.dirname(__file__), "static"),
+        xsrf_cookies=True,
+        )
     app.listen(options.port)
     tornado.ioloop.IOLoop.instance().start()