18
18
import tornado .auth
19
19
import tornado .escape
20
20
import tornado .ioloop
21
- import tornado .options
22
21
import tornado .web
23
22
import os .path
24
23
import uuid
25
24
26
- from tornado .options import define , options
25
+ from tornado import gen
26
+ from tornado .options import define , options , parse_command_line
27
27
28
28
define ("port" , default = 8888 , help = "run on the given port" , type = int )
29
29
30
30
31
- class Application ( tornado . web . Application ):
31
+ class MessageBuffer ( object ):
32
32
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
67
36
68
37
def wait_for_messages (self , callback , cursor = None ):
69
- cls = MessageMixin
70
38
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 :] )
78
46
return
79
- cls .waiters .add (callback )
47
+ self .waiters .add (callback )
80
48
81
49
def cancel_wait (self , callback ):
82
- cls = MessageMixin
83
- cls .waiters .remove (callback )
50
+ self .waiters .remove (callback )
84
51
85
52
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 :
89
55
try :
90
56
callback (messages )
91
57
except :
92
58
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
+
97
64
65
+ # Making this a non-singleton is left as an exercise for the reader.
66
+ global_message_buffer = MessageBuffer ()
98
67
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 ):
100
83
@tornado .web .authenticated
101
84
def post (self ):
102
85
message = {
103
86
"id" : str (uuid .uuid4 ()),
104
87
"from" : self .current_user ["first_name" ],
105
88
"body" : self .get_argument ("body" ),
106
89
}
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 ))
108
94
if self .get_argument ("next" , None ):
109
95
self .redirect (self .get_argument ("next" ))
110
96
else :
111
97
self .write (message )
112
- self .new_messages ([message ])
98
+ global_message_buffer .new_messages ([message ])
113
99
114
100
115
- class MessageUpdatesHandler (BaseHandler , MessageMixin ):
101
+ class MessageUpdatesHandler (BaseHandler ):
116
102
@tornado .web .authenticated
117
103
@tornado .web .asynchronous
118
104
def post (self ):
119
105
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 )
122
108
123
109
def on_new_messages (self , messages ):
124
110
# Closed client connection
@@ -127,23 +113,21 @@ def on_new_messages(self, messages):
127
113
self .finish (dict (messages = messages ))
128
114
129
115
def on_connection_close (self ):
130
- self .cancel_wait (self .on_new_messages )
116
+ global_message_buffer .cancel_wait (self .on_new_messages )
131
117
132
118
133
119
class AuthLoginHandler (BaseHandler , tornado .auth .GoogleMixin ):
134
120
@tornado .web .asynchronous
121
+ @gen .coroutine
135
122
def get (self ):
136
123
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 ("/" )
138
128
return
139
129
self .authenticate_redirect (ax_attrs = ["name" ])
140
130
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
-
147
131
148
132
class AuthLogoutHandler (BaseHandler ):
149
133
def get (self ):
@@ -152,8 +136,21 @@ def get(self):
152
136
153
137
154
138
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
+ )
157
154
app .listen (options .port )
158
155
tornado .ioloop .IOLoop .instance ().start ()
159
156
0 commit comments