@event decorator limitation? #17

greg-hellings opened this Issue Jan 9, 2012 · 16 comments


None yet

4 participants


I have a set of classes like this:

class DistributeHandler(SocketConnection):
def join(self, args, *kargs):
# Join a channel based on kargs
print "Joining %s" % (kargs['group'], )

on_message(self, msg):

class MainRouter(SocketConnection):
endpoints : { '/realtime': DistributeHandler, '/other': OtherHandler}
on_open(self, *args):
print "Connected"

On the client side, I have this:
var socket = io.connect("ws://mysite.com/realtime");
socket.emit('join', {group: 5}, function() { console.log('joined'); });

However, the server informs me that 'join' is an invalid event. Is this because the @event decorator is on the endpoint SocketConnection class and not on the MainRouter object? Is @event supposed to be that limited or this a bug? I'd love to be able to put the @event decorator on the endpoint classes, as that would make my system far more flexible. I just wanted to make sure I understood what is going on.


There's no limitation for the @event decorator, as events are stored (and handled) on SocketConnection level. Maybe you have some name collision (event defined elsewhere, etc)?

I checked with ping sample - added test event for PingConnection and it worked as expected.


There is no occurrences of the identifier 'event' in my file except for where I 'from tornadio2 import event' and where I use it as the decorator. There is one extra layer of abstraction that might be a cause of it, but I'm not sure if it affects this. The actual code is as follows:

listeners = {}
names = {}
tokens = {}
hmac_key = False    # Will be set down lower

class PostHandler(web.RequestHandler):
    only authorized parties can post messages
    def post(self):
        if hmac_key and not 'signature' in self.request.arguments: return 'false'
        if 'message' in self.request.arguments:
            message = self.request.arguments['message'][0]
            group = self.request.arguments.get('group',['default'])[0]
            print '%s:MESSAGE to %s:%s' % (time.time(), group, message)
            if hmac_key:
                signature = self.request.arguments['signature'][0]
                if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false'
            for client in listeners.get(group,[]): client.send(message)
            return 'true'
        return 'false'

class TokenHandler(web.RequestHandler):
    if running with -t post a token to allow a client to join using the token
    the message here is the token (any uuid)
    allows only authorized parties to joins, for example, a chat
    def post(self):
        if hmac_key and not 'message' in self.request.arguments: return 'false'
        if 'message' in self.request.arguments:
            message = self.request.arguments['message'][0]
            if hmac_key:
                signature = self.request.arguments['signature'][0]
                if not hmac.new(hmac_key,message).hexdigest()==signature: return 'false'
            tokens[message] = None
            return 'true'
        return 'false'

class DistributeHandler(SocketConnection):
    def on_open(self, conn):
        # only authorized parties can join
        self.group = 'default'
        print '%s:CONNECT to %s' % (time.time(), self.group)

    def join(self, *args, **kargs):
        group = kargs['group'] # The new group to join
        only  = kargs['only']  # Remove me from all the other groups?
        token = kargs['token'] # Retrieve the token
        print 'JOINING GROUP: %d' % (group,)
        # Storage for later
        self.group = group
        self.token = token
        # Check that the token is valid
        if DistributeHandler.tokens:
            if not self.token in tokens or not token[self.token]==None:
                tokens[self.token] = self
        # Add ourself to the listening group
            # Try to take us out of the group we're in
            if only:
                for gg in listeners.keys():
            # No need to handle it, we'll just add ourselves to the new channel
    def on_message(self, message):
    def on_close(self):
        if self.group in listeners: listeners[self.group].remove(self)
        # notify clients that a member has left the groups
        for client in listeners.get(self.group,[]): client.send('-'+self.name)
        print '%s:DISCONNECT from %s' % (time.time(), self.group)

class TornadioHandler(object):
    """This should be the only class you need to worry about if you are trying to extend or
    customize the behavior of this file."""
    def __init__(self, *points, **kargs):
        """Instantiate a new handler for SocketIO 0.7+ which will operate as a distribution center for web2py
        applications on this site.

        Positional arguments are tuples of the form ('/chat', MyHandler) where the first argument is the URL path
        that should be picked up by the handler and the second argument is a class which extends tornadio2.SocketConnection.

        Optional keyword arguments are regarded as follows:
        port : The port that SocketIO connections should listen on (defaults 8888)
        flash_port : The port that the Flash policy file should be served on (defaults 843)
        flash_policy_file : An absolute path to the flash policy file on the file system
        hmac_key : An authorization key (string) which must be passed when making a broadcast post from the localhost
        tokens : A boolean value which requires the client files to possess identifier tokens in order to be allowed
                 to join a channel (default False)
        debug : A boolean value which enables or disables debugging output (default True)"""

        global hmac_key

        # First, construct the endpoints that need to be available
        endpoints = self.make_endpoints(points)

        # Next, parse the options
        self.port               = 'port' in kargs.keys() and kargs['port'] or 8888
        self.flash_port         = 'flash_port' in kargs.keys() and kargs['flash_port'] or 843
        self.flash_policy_file  = 'flash_policy_file' in kargs.keys() and kargs['flash_policy_file'] or op.join(ROOT, 'tornadio2', 'examples', 'multiplexed', 'flashpolicy.xml')
        self.hmac_key           = 'hmac_key' in kargs.keys() and kargs['hmac_key'] or False
        self.tokens             = 'tokens' in kargs.keys() and kargs['tokens'] or False
        self.debug              = 'debug' in kargs.keys() and kargs['debug'] or True

        # A few of these need to be globalized
        hmac_key = self.hmac_key
        DistributeHandler.tokens = self.tokens

        # Now, construct the router class
        def __make_class__(mypoints):
            class DisposableRouter(SocketConnection):
                __endpoints__ = mypoints

                def on_open(self, *args):
                    print "Connection received: %s" % (repr(args),)

            return DisposableRouter
        my_class = __make_class__(endpoints)
        if self.debug: repr(my_class.__endpoints__)
        my_router = TornadioRouter(my_class)

        # Create the application
        self.application = web.Application(
            my_router.apply_routes([(r'/', PostHandler),
                                    (r'/token', TokenHandler)]),
            flash_policy_port = self.flash_port,
            flash_policy_file = self.flash_policy_file,
            socket_io_port    = self.port


    def make_endpoints(self, points):
        """Constructs the endpoints that are necessary for this distribution handler."""
        endpoints = { '/realtime/' : DistributeHandler }
        for point in points:
            endpoints[point[0]] = point[1]

        return endpoints```

As you see, I have an inner method def __make_class__ defined inside of the constructor of TornadioHandler and that class creates the class DisposableRouter within its closure using the set of endpoints that are passed to __make_class__. Thus, the actual handler is not a basic class but something hidden away inside several levels of closure. I'm unsure how to track if that is my issue or if something else is causing this, but I get the message
> Connection received: (<tornadio2.session.ConnectionInfo object at 0xb715c44c>,)
> Connection received: (<tornadio2.session.ConnectionInfo object at 0xb715cb0c>,)
> 1326149167.33:CONNECT to default
> ERROR:root:Invalid event name: join

when the client attempts to connect.

Any advice on what's going on here?

In that case, am I able to change the values of the endpoint map after I've instantiated the main class router? When I glanced through the code I didn't see a reason that wouldn't be possible, but if I can do that, it would remove the need for the extra layer of abstraction.


OK, so I've greatly simplified my code so it just contains the following, and it's still not working for me. https://gist.github.com/1596892

The first two classes are just static URL handlers. DistributeHandler is for my '/realtime/' endpoint and BaseRouter encapsulates that. TornadioHandler is instantiated by a main body portion I omitted for simplicity's sake. BaseRouter properly detects a client joining and it gets passed to DistributeHandler afterwards. I have experimented with the @event decorator and join method on both the BaseRouter and DistributeHandler. Either place it gives me the same error message that 'join' is not a valid event name.

I'm fresh out of ideas on what to do next to get events working.


Hi Greg, did you figured out?
I'm trying to code the old web2py tornadio adapter(http://greg.thehellings.com/2011/05/web2py-websockets-and-socket-io-part-iii-socket-io/) to tornadio2 but haven't succeed yet.
I've tried you configuration and I can connect, but I can't change channel.. do you already know how its done?


I'll try to explain how to add @event support for non-stadard use cases:
1. This is a on_event handler, which gets called when tornadio2 receives event from the socket.io client: https://github.com/mrjoes/tornadio2/blob/master/tornadio2/conn.py#L187

As you can see, it attempts to get event handler by its name from self._events. If handler is not there, will log error message and ignore event.

  1. There's @event decorator, which works together with metaclass to feed marked methods from into the self._events.

So, if your class is not derived from SockConnection, if metaclass did not kick in, etc - self._events won't be populated and it won't work.

Which options do you have:
1. Override on_event and handle events way you want
2. Feed handlers to the self._events in constructor, in on_open, etc

Hope it helps.



I abandoned using socket.io for anything for several reasons:
1) Socket.IO is not readily available outside of Node.js
2) Alternate language implementations tend to lag the main implementation in features, activity, or timeliness of releases
3) To use Node.js for only my websocket work would have required a significant amount of duplication of code between Python and JavaScript which would be a maintenance nightmare
4) I have found Node.js and Socket.IO to be terribly documented for the novice.

I wish you luck trying to get anything working but I abandoned my attempts to get web2py + websocket working with any version of Socket.IO after the v0.6-v0.7 migration.



Well, tornadio2 supports all of the socket.io features, it's just socket.io is not really evolving.

On a side note, I would not go with socket.io into high-loaded production, as there are some problems with socket.io stability and scalability. If you don't care about high level abstractions, pick SockJS https://github.com/sockjs/sockjs-client

And there's sockjs-tornado, which is fully compliant with SockJS protocol, enforced through tests.


Hi mrjoes,
my main issue here is the channels. I can establish the connection in the "default" but I can't change channel.
On the tornadio1 (socket.io v0.6) I would change via the resource attr but now I don't know how to do that.


You're looking for http://tornadio2.readthedocs.org/en/latest/multiplexed/ or if you don't need multiplexed connections, just pass "namespace" parameter to the TornadioRouter constructor and change namespace when you create js socket.io object.


But how do I use it when the namespaces are dynamically specified on the client side?


Well, I'm not sure why you want to have dynamic namespaces, but you can override http://tornadio2.readthedocs.org/en/latest/mod_conn/#tornadio2.conn.SocketConnection.get_endpoint and do whatever you want.


@mrjoes - can you elaborate on socket.io stability in production? I'm taking tornadio2 into production pretty soon, and will definitely switch to sockjs beforehand if I can read up some more on problems with socket.io.


Well, long story short..

I'll try to provide some information why SockJS is better choice:
1. It is actively maintained. By "actively" I mean that all tickets are getting reviewed in matter of a day and you get meaningful response.
2. SockJS enforces certain behavior patterns for all server implementations. Tests cover everything - from protocol to proper error handing. So, it is easy to know if your server implementation works according to the spec or not.
3. Because of the previous point, SockJS is more predictable and just works better. There's test suite for client-side library as well: http://sockjs.popcnt.org/
4. SockJS is designed to be horizontally scalable. Have capacity problems? Throw-in more nodes, add nodes to load balancer and you're set. There's no need to use cookie-based sticky sessions - all information is already in the URL.
5. SockJS really works for all browsers, even Opera, even in cross-domain scenario. Socket.io client is more picky about where it works.
6. I benchmarked sockjs-tornado and expect that tornadio2 will be ~20% slower than sockjs-tornado due to more complex socket.io protocol. You can find benchmark here: http://mrjoes.github.com/2011/12/15/sockjs-bench.html

Only thing that SockJS is missing, in comparison to socket.io, is events. But it is not very hard to implement them yourself.

I know few guys who were very active in socket.io bugtracker asking for help, but then gave up and switched to SockJS.

As for the socket.io problems:
1. Development focus

Right now, socket.io devs are focused on the engine.io and current socket.io version looks abandoned. Yes, there were 5 minor releases in last month, which fixed some of the critical issues (like this one - socketio/socket.io#438 which was open for 8 months), but other than that - no significant progress.

If you'll open socket.io bugtracker, you'll see like 20+ pull requests and 200+ open defects. That's not very good sign, unfortunately.

  1. Stability

Protocol and behavior patterns are poorly documented. No unit tests. No protocol tests. No client-side library tests.

Client still does not know how to close multiplexed connections, can't properly fallback to polling protocols if something screwed up native websocket connection, etc.

Client has lots of places where race-condition can happen, which either kill your server (like issue #438 mentioned above) or you will lose data without knowing it. For example, for polling transports, if client sees disconnect, it thinks that it was intentional disconnect and will try to reconnect to get more data, which might lead to data loss in some cases. And so on.

To sum it up: just go with SockJS, at least until Engine.io will be as mature as SockJS.

@mrjoes mrjoes closed this Jul 31, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment