Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run functions outside websocket loop in python (tornado)

I'm trying to set up a small example of a public Twitter stream over websockets. This is my websocket.py, and it's working.

What I'm wondering is: how can I interact with the websocket from 'outside' the class WSHandler (ie. not only answer when receiving a message from websocket.js)? Say I want to run some other function within this same script that would post "hello!" every five seconds and send that to the websocket (browser) without any interaction from client-side. How could I do that?

So it's kind of a fundamental beginner's question, I suppose, about how to deal with classes as those below. Any pointers in any direction would be greatly appreciated!

import os.path
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

# websocket
class FaviconHandler(tornado.web.RequestHandler):
    def get(self):
        self.redirect('/static/favicon.ico')

class WebHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("websockets.html")

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'new connection'
        self.write_message("Hi, client: connection is made ...")

    def on_message(self, message):
        print 'message received: \"%s\"' % message
        self.write_message("Echo: \"" + message + "\"")
        if (message == "green"):
            self.write_message("green!")

    def on_close(self):
        print 'connection closed'



handlers = [
    (r"/favicon.ico", FaviconHandler),
    (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': 'static'}),
    (r'/', WebHandler),
    (r'/ws', WSHandler),
]

settings = dict(
    template_path=os.path.join(os.path.dirname(__file__), "static"),
)

application = tornado.web.Application(handlers, **settings)

if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
like image 992
knutole Avatar asked Sep 18 '12 14:09

knutole


3 Answers

You could call a

IOLoop.add_timeout(deadline, callback)

that calls the callback at specified deadline timeout (one shot, but you can reschedule), or use the

tornado.ioloop.PeriodicCallback if you have a more periodic task.

See: http://www.tornadoweb.org/en/stable/ioloop.html#tornado.ioloop.IOLoop.add_timeout

Update: some example

import datetime

def test():
    print "scheduled event fired"
...

if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    main_loop = tornado.ioloop.IOLoop.instance()
    # Schedule event (5 seconds from now)
    main_loop.add_timeout(datetime.timedelta(seconds=5), test)
    # Start main loop
    main_loop.start()

it calls test() after 5 seconds.

Update 2:

import os.path
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web

# websocket
class FaviconHandler(tornado.web.RequestHandler):
    def get(self):
        self.redirect('/static/favicon.ico')

class WebHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("websockets.html")

class WSHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'new connection'
        self.write_message("Hi, client: connection is made ...")
        tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=5), self.test)

    def on_message(self, message):
        print 'message received: \"%s\"' % message
        self.write_message("Echo: \"" + message + "\"")
        if (message == "green"):
            self.write_message("green!")

    def on_close(self):
        print 'connection closed'

    def test(self):
        self.write_message("scheduled!")

handlers = [
    (r"/favicon.ico", FaviconHandler),
    (r'/static/(.*)', tornado.web.StaticFileHandler, {'path': 'static'}),
    (r'/', WebHandler),
    (r'/ws', WSHandler),
]

settings = dict(
    template_path=os.path.join(os.path.dirname(__file__), "static"),
)

application = tornado.web.Application(handlers, **settings)

import datetime

if __name__ == "__main__":
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
like image 182
pr0gg3d Avatar answered Nov 10 '22 09:11

pr0gg3d


I stumbled upon similar problem. Here is my solution. Hope this will be helpful to someone out there

wss = []
class wsHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        print 'Online'
        if self not in wss:
            wss.append(self)

    def on_close(self):
        print 'Offline'
        if self in wss:
            wss.remove(self)

def wsSend(message):
    for ws in wss:
        ws.write_message(message)

To send message to your websockets simply use this:

wsSend(message)

wsSend update

I've been getting exceptions with wsSend once in a while. In order to fix it I've modified code a bit to following:

def wsSend(message):
    for ws in wss:
        if not ws.ws_connection.stream.socket:
            print "Web socket does not exist anymore!!!"
            wss.remove(ws)
        else:
            ws.write_message(message)
like image 24
Barmaley Avatar answered Nov 10 '22 09:11

Barmaley


One way to also do this is to use a pub-sub module.

Meaning you have your connections subscribe to it, and rather than setting timeouts for every single connection, you just set one timeout to publish after said period of time.

Probably one of the most implemented is redis. There are also some modules specifically for tornado: toredis or brükva for example.

Of course this might not be necessary for just a simple page, but scales really well and is also very nice to maintain/extend once you've set it up.

like image 3
Levite Avatar answered Nov 10 '22 08:11

Levite