8000 Implemented event acknowledgment with optional data to be sent over t… · pythonanywhere/tornadio2@e425706 · GitHub
[go: up one dir, main page]

Skip to content

Commit e425706

Browse files
committed
Implemented event acknowledgment with optional data to be sent over the wire.
1 parent de4ba3d commit e425706

File tree

10 files changed

+192
-18
lines changed

10 files changed

+192
-18
lines changed

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ For more information, check `TornadIO2 documentation <http://readthedocs.org/doc
7777
Examples
7878
~~~~~~~~
7979

80+
Acknowledgment
81+
^^^^^^^^^^^^^^
82+
83+
Ping sample which shows how to use events to work in request-response mode. It is in the ``examples/ackping`` directory.
84+
8085
Cross site
8186
^^^^^^^^^^
8287

doc/acknowledgments.rst

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,49 @@ Acknowledgments
33

44
New feature of the socket.io 0.7+. When you send message to the client,
55
you now have way to get notified when client receives the message. To use this, pass a
6-
callback function when sending a message:
7-
::
6+
callback function when sending a message::
87

98
class MyConnection(SocketConnection):
109
def on_message(self, msg):
1110
self.send(msg, self.my_callback)
1211

13-
def my_callback(self, msg):
12+
def my_callback(self, msg, ack_data):
1413
print 'Got ack for my message: %s' % message
1514

15+
``ack_data`` contains acknowledgment data sent from the client, if any.
1616

17-
To send event with acknowledgement, use ``SocketConnection.emit_ack`` method:
18-
::
17+
To send event with acknowledgement, use ``SocketConnection.emit_ack`` method::
1918

2019
class MyConnection(SocketConnection):
2120
def on_message(self, msg):
2221
self.emit_ack(self.my_callback, 'hello')
2322

24-
def my_callback(self, event):
23+
def my_callback(self, event, ack_data):
2524
print 'Got ack for my message: %s' % msg
25+
26+
If you want to send reverse confirmation with a message, just return value you want to send
27+
from your event handler::
28+
29+
class MyConnection(SocketConnection):
30+
@event('test')
31+
def test(self):
32+
return 'Joes'
33+
34+
and then, in your javascript code, you can do something like::
35+
36+
sock.emit('test', function(data) {
37+
console.log(data); // data will be 'Joes'
38+
});
39+
40+
If you want to return multiple arguments, return them as tuple::
41+
42+
class MyConnection(SocketConnection):
43+
@event('test')
44+
def test(self):
45+
return 'Joes', 'Mike', 'Mary'
46+
47+
On client-side, you can access them by doing something like::
48+
49+
sock.emit('test', function(name1, name2, name3) {
50+
console.log(name1, name2, name3); // data will be 'Joes'
51+
});

examples/ackping/ackping.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from os import path as op
2+
3+
import datetime
4+
5+
from tornado import web
6+
7+
from tornadio2 import SocketConnection, TornadioRouter, SocketServer, event
8+
9+
10+
ROOT = op.normpath(op.dirname(__file__))
11+
12+
13+
class IndexHandler(web.RequestHandler):
14+
"""Regular HTTP handler to serve the chatroom page"""
15+
def get(self):
16+
self.render('index.html')
17+
18+
19+
class SocketIOHandler(web.RequestHandler):
20+
def get(self):
21+
self.render('../socket.io.js')
22+
23+
24+
class PingConnection(SocketConnection):
25+
@event('ping')
26+
def ping(self, client):
27+
now = datetime.datetime.now()
28+
return client, [now.hour, now.minute, now.second, now.microsecond / 1000]
29+
30+
# Create tornadio router
31+
PingRouter = TornadioRouter(PingConnection)
32+
33+
# Create socket application
34+
application = web.Application(
35+
PingRouter.apply_routes([(r"/", IndexHandler),
36+
(r"/socket.io.js", SocketIOHandler)]),
37+
flash_policy_port = 843,
38+
flash_policy_file = op.join(ROOT, 'flashpolicy.xml'),
39+
socket_io_port = 8001
40+
)
41+
42+
if __name__ == "__main__":
43+
import logging
44+
logging.getLogger().setLevel(logging.DEBUG)
45+
46+
# Create and start tornadio server
47+
SocketServer(application)

examples/ackping/flashpolicy.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
3+
<cross-domain-policy>
4+
<allow-access-from domain="*" to-ports="*" />
5+
</cross-domain-policy>
6+

examples/ackping/index.html

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
5+
<script src="socket.io.js"></script>
6+
<script>
7+
$(function() {
8+
var ping = new io.connect('http://' + window.location.host);
9+
10+
// Establish event handlers
11+
ping.on('disconnect', function() {
12+
ping.socket.reconnect();
13+
});
14+
15+
function getPrintableDate(date) {
16+
return date.getFullYear().toString() + '/' +
17+
(date.getMonth()+1).toString() + '/' +
18+
date.getDate().toString() + ' ' +
19+
date.getHours().toString() + ':' +
20+
date.getMinutes().toString() + ':' +
21+
date.getSeconds().toString() + '.' +
22+
date.getMilliseconds().toString();
23+
}
24+
25+
function encodeDate(date)
26+
{
27+
return [date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()];
28+
}
29+
30+
function decodeDate(data)
31+
{
32+
var date = new Date();
33+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(),
34+
data[0], data[1], data[2], data[3]);
35+
}
36+
37+
function sendPing()
38+
{
39+
ping.emit('ping', encodeDate(new Date()), function(clientDate, serverDate) {
40+
var client = decodeDate(clientDate);
41+
var server = decodeDate(serverDate);
42+
var now = new Date();
43+
44+
$('#ping').html('Ping: ' + (now.getTime() - client.getTime()).toString() + ' ms.<br/>' +
45+
'C2S: ' + (server.getTime() - client.getTime()).toString() + ' ms.<br/>'
46+
);
47+
});
48+
49+
setTimeout(sendPing, 5000);
50+
}
51+
sendPing();
52+
});
53+
</script>
54+
</head>
55+
<body>
56+
<h3>Ping!</h3>
57+
<div id="ping"></div>
58+
</body>
59+
</html>

tests/session_test.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def on_message(self, message):
7878
def on_event(self, name, *args, **kwargs):
7979
self.events.append((name, kwargs))
8080
self.emit(name, **kwargs)
81+
return name
8182

8283
def on_close(self):
8384
self.is_open = False
@@ -304,8 +305,9 @@ def test_ack():
304305
eq_(transport.pop_outgoing(), '6:::1')
305306

306307
# Send with ACK
307-
def handler(message):
308-
eq_(message, 'abc')
308+
def handler(msg, data):
309+
eq_(msg, 'abc')
310+
eq_(data, None)
309311

310312
conn.send('yes')
311313

@@ -319,3 +321,10 @@ def handler(message):
319321
# Check if handler was called
320322
eq_(transport.pop_outgoing(), '3:::yes')
321323

324+
# Test ack with event
325+
# Send event with multiple parameters
326+
transport.recv(proto.event(None, 'test', 1, a=10, b=20))
327+
328+
# Check outgoing
329+
eq_(transport.pop_outgoing(), proto.event(None, 'test', None, a=10, b=20))
330+
eq_(transport.pop_outgoing(), proto.ack(None, 1, 'test'))

tornadio2/conn.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,12 @@ def queue_ack(self, callback, message):
271271

272272
return ack_id
273273

274-
def deque_ack(self, msg_id):
274+
def deque_ack(self, msg_id, ack_data):
275275
"""Dequeue acknowledgment callback"""
276276
if msg_id in self.ack_queue:
277277
time_stamp, callback, message = self.ack_queue.pop(msg_id)
278278

279-
callback(message)
279+
callback(message, ack_data)
280280
else:
281281
logging.error('Received invalid msg_id for ACK: %s' % msg_id)
282282

tornadio2/gen.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ def run(args, kwargs):
8383
if isinstance(gen, types.GeneratorType):
8484
data.runner = SyncRunner(gen, finished)
8585
data.runner.run()
86+
else:
87+
return gen
8688

8789
# Completion callback
8890
def finished():

tornadio2/proto.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,28 @@ def event(endpoint, name, message_id, *args, **kwargs):
144144
)
145145

146146

147-
def ack(endpoint, message_id):
147+
def ack(endpoint, message_id, ack_response=None):
148148
"""Generate ACK packet.
149149
150150
`endpoint`
151151
Optional endpoint name
152152
`message_id`
153153
Message id to acknowledge
154+
`ack_response`
155+
Acknowledgment response data (will be json serialized)
154156
"""
155-
return u'6::%s:%s' % (endpoint or '',
156-
message_id)
157+
if ack_response is not None:
158+
if not isinstance(ack_response, tuple):
159+
ack_response = (ack_response,)
160+
161+
data = json_dumps(ack_response)
162+
163+
return u'6::%s:%s+%s' % (endpoint or '',
164+
message_id,
165+
data)
166+
else:
167+
return u'6::%s:%s' % (endpoint or '',
168+
message_id)
157169

158170

159171
def error(endpoint, reason, advice=None):

tornadio2/session.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,25 +378,33 @@ def raw_message(self, msg):
378378

379379
args = event['args']
380380

381+
ack_response = None
382+
381383
# It is kind of magic - if there's only one parameter
382384
# and it is dict, unpack dictionary. Otherwise, pass
383385
# in args
384386
if len(args) == 1 and isinstance(args[0], dict):
385387
# Fix for the http://bugs.python.org/issue4978 for older Python versions
386388
str_args = dict((str(x), y) for x, y in args[0].iteritems())
387389

388-
conn.on_event(event['name'], **str_args)
390+
ack_response = conn.on_event(event['name'], **str_args)
389391
else:
390-
conn.on_event(event['name'], *args)
392+
ack_response = conn.on_event(event['name'], *args)
391393

392394
if msg_id:
393-
self.send_message(proto.ack(msg_endpoint, msg_id))
395+
if msg_id.endswith('+'):
396+
msg_id = msg_id[:-1]
397+
398+
self.send_message(proto.ack(msg_endpoint, msg_id, ack_response))
394399
elif msg_type == proto.ACK:
395400
# Handle ACK
396401
ack_data = msg_data.split('+', 2)
397402

398-
# TODO: Support custom data sent from the server
399-
conn.deque_ack(int(ack_data[0]))
403+
data = None
404+
if len(ack_data) > 1:
405+
data = proto.json_load(ack_data[1])
406+
407+
conn.deque_ack(int(ack_data[0]), data)
400408
elif msg_type == proto.ERROR:
401409
# TODO: Pass it to handler?
402410
logging.error('Incoming error: %s' % msg_data)

0 commit comments

Comments
 (0)
0