Skip to content

Commit 974c229

Browse files
committed
Update chat demo to be more exemplary.
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.
1 parent 53fc8fd commit 974c229

File tree

1 file changed

+68
-71
lines changed

1 file changed

+68
-71
lines changed

demos/chat/chatdemo.py

Lines changed: 68 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,107 +18,93 @@
1818
import tornado.auth
1919
import tornado.escape
2020
import tornado.ioloop
21-
import tornado.options
2221
import tornado.web
2322
import os.path
2423
import uuid
2524

26-
from tornado.options import define, options
25+
from tornado import gen
26+
from tornado.options import define, options, parse_command_line
2727

2828
define("port", default=8888, help="run on the given port", type=int)
2929

3030

31-
class Application(tornado.web.Application):
31+
class MessageBuffer(object):
3232
def __init__(self):
33-
handlers = [
34-
(r"/", MainHandler),
35-
(r"/auth/login", AuthLoginHandler),
36-
(r"/auth/logout", AuthLogoutHandler),
37-
(r"/a/message/new", MessageNewHandler),
38-
(r"/a/message/updates", MessageUpdatesHandler),
39-
]
40-
settings = dict(
41-
cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
42-
login_url="/auth/login",
43-
template_path=os.path.join(os.path.dirname(__file__), "templates"),
44-
static_path=os.path.join(os.path.dirname(__file__), "static"),
45-
xsrf_cookies=True,
46-
)
47-
tornado.web.Application.__init__(self, handlers, **settings)
48-
49-
50-
class BaseHandler(tornado.web.RequestHandler):
51-
def get_current_user(self):
52-
user_json = self.get_secure_cookie("chatdemo_user")
53-
if not user_json: return None
54-
return tornado.escape.json_decode(user_json)
55-
56-
57-
class MainHandler(BaseHandler):
58-
@tornado.web.authenticated
59-
def get(self):
60-
self.render("index.html", messages=MessageMixin.cache)
61-
62-
63-
class MessageMixin(object):
64-
waiters = set()
65-
cache = []
66-
cache_size = 200
33+
self.waiters = set()
34+
self.cache = []
35+
self.cache_size = 200
6736

6837
def wait_for_messages(self, callback, cursor=None):
69-
cls = MessageMixin
7038
if cursor:
71-
index = 0
72-
for i in xrange(len(cls.cache)):
73-
index = len(cls.cache) - i - 1
74-
if cls.cache[index]["id"] == cursor: break
75-
recent = cls.cache[index + 1:]
76-
if recent:
77-
callback(recent)
39+
new_count = 0
40+
for msg in reversed(self.cache):
41+
if msg["id"] == cursor:
42+
break
43+
new_count += 1
44+
if new_count:
45+
callback(self.cache[-new_count:])
7846
return
79-
cls.waiters.add(callback)
47+
self.waiters.add(callback)
8048

8149
def cancel_wait(self, callback):
82-
cls = MessageMixin
83-
cls.waiters.remove(callback)
50+
self.waiters.remove(callback)
8451

8552
def new_messages(self, messages):
86-
cls = MessageMixin
87-
logging.info("Sending new message to %r listeners", len(cls.waiters))
88-
for callback in cls.waiters:
53+
logging.info("Sending new message to %r listeners", len(self.waiters))
54+
for callback in self.waiters:
8955
try:
9056
callback(messages)
9157
except:
9258
logging.error("Error in waiter callback", exc_info=True)
93-
cls.waiters = set()
94-
cls.cache.extend(messages)
95-
if len(cls.cache) > self.cache_size:
96-
cls.cache = cls.cache[-self.cache_size:]
59+
self.waiters = set()
60+
self.cache.extend(messages)
61+
if len(self.cache) > self.cache_size:
62+
self.cache = self.cache[-self.cache_size:]
63+
9764

65+
# Making this a non-singleton is left as an exercise for the reader.
66+
global_message_buffer = MessageBuffer()
9867

99-
class MessageNewHandler(BaseHandler, MessageMixin):
68+
69+
class BaseHandler(tornado.web.RequestHandler):
70+
def get_current_user(self):
71+
user_json = self.get_secure_cookie("chatdemo_user")
72+
if not user_json: return None
73+
return tornado.escape.json_decode(user_json)
74+
75+
76+
class MainHandler(BaseHandler):
77+
@tornado.web.authenticated
78+
def get(self):
79+
self.render("index.html", messages=global_message_buffer.cache)
80+
81+
82+
class MessageNewHandler(BaseHandler):
10083
@tornado.web.authenticated
10184
def post(self):
10285
message = {
10386
"id": str(uuid.uuid4()),
10487
"from": self.current_user["first_name"],
10588
"body": self.get_argument("body"),
10689
}
107-
message["html"] = self.render_string("message.html", message=message)
90+
# to_basestring is necessary for Python 3's json encoder,
91+
# which doesn't accept byte strings.
92+
message["html"] = tornado.escape.to_basestring(
93+
self.render_string("message.html", message=message))
10894
if self.get_argument("next", None):
10995
self.redirect(self.get_argument("next"))
11096
else:
11197
self.write(message)
112-
self.new_messages([message])
98+
global_message_buffer.new_messages([message])
11399

114100

115-
class MessageUpdatesHandler(BaseHandler, MessageMixin):
101+
class MessageUpdatesHandler(BaseHandler):
116102
@tornado.web.authenticated
117103
@tornado.web.asynchronous
118104
def post(self):
119105
cursor = self.get_argument("cursor", None)
120-
self.wait_for_messages(self.on_new_messages,
121-
cursor=cursor)
106+
global_message_buffer.wait_for_messages(self.on_new_messages,
107+
cursor=cursor)
122108

123109
def on_new_messages(self, messages):
124110
# Closed client connection
@@ -127,23 +113,21 @@ def on_new_messages(self, messages):
127113
self.finish(dict(messages=messages))
128114

129115
def on_connection_close(self):
130-
self.cancel_wait(self.on_new_messages)
116+
global_message_buffer.cancel_wait(self.on_new_messages)
131117

132118

133119
class AuthLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
134120
@tornado.web.asynchronous
121+
@gen.coroutine
135122
def get(self):
136123
if self.get_argument("openid.mode", None):
137-
self.get_authenticated_user(self.async_callback(self._on_auth))
124+
user = yield self.get_authenticated_user()
125+
self.set_secure_cookie("chatdemo_user",
126+
tornado.escape.json_encode(user))
127+
self.redirect("/")
138128
return
139129
self.authenticate_redirect(ax_attrs=["name"])
140130

141-
def _on_auth(self, user):
142-
if not user:
143-
raise tornado.web.HTTPError(500, "Google auth failed")
144-
self.set_secure_cookie("chatdemo_user", tornado.escape.json_encode(user))
145-
self.redirect("/")
146-
147131

148132
class AuthLogoutHandler(BaseHandler):
149133
def get(self):
@@ -152,8 +136,21 @@ def get(self):
152136

153137

154138
def main():
155-
tornado.options.parse_command_line()
156-
app = Application()
139+
parse_command_line()
140+
app = tornado.web.Application(
141+
[
142+
(r"/", MainHandler),
143+
(r"/auth/login", AuthLoginHandler),
144+
(r"/auth/logout", AuthLogoutHandler),
145+
(r"/a/message/new", MessageNewHandler),
146+
(r"/a/message/updates", MessageUpdatesHandler),
147+
],
148+
cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
149+
login_url="/auth/login",
150+
template_path=os.path.join(os.path.dirname(__file__), "templates"),
151+
static_path=os.path.join(os.path.dirname(__file__), "static"),
152+
xsrf_cookies=True,
153+
)
157154
app.listen(options.port)
158155
tornado.ioloop.IOLoop.instance().start()
159156

0 commit comments

Comments
 (0)