Tornado - Send message to client on demand - python

I'm using Tornado to serve a html file and, at the same time, I want my Python program to dynamically change a value in the same page through websockets. The purpose is that the value can be seen changing, without the need to refresh on the client side.
In this case, I want to send a message with a variable that changes from "1234" to "4321" every 4 seconds.
I have a thread doing the value toggling and I want that same thread to send that value to the websocket, so I can handle it with a script using the onmessage function in the client side.
The following code will give me the following error: 'Application' object has no attribute 'write_message'
import tornado.ioloop
import tornado.web
import tornado.websocket
import threading
import time
flag = True
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("mypage.html")
class wshandler(tornado.websocket.WebSocketHandler):
def open(self):
self.write_message('connected')
print "WebSocket opened"
def on_message(self, message):
self.write_message("You have sent: " + message)
def on_close(self):
print "WebSocket closed"
def write_message(self, message):
print ("writing message", message)
self.write_message(message)
application = tornado.web.Application(
handlers=[
(r"/", MainHandler),
(r"/ws", wshandler),
])
def toggle():
global flag
while True:
if flag==True:
print 'now on 1234'
flag = False
application.write_message('1234')
time.sleep(4)
elif flag==False:
flag = True
application.write_message('4321')
print 'Now on 4321'
time.sleep(4)
def main():
global response
valueThread=threading.Thread(target=toggle, name="Toggle Value Thread")
valueThread.setDaemon(True)
valueThread.start()
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
My problem is that every single tutorial/guide I've seen about Tornado only uses the function write_message within the open and onmessage function within the websocket class.. so I don't really get why this won't work.

You should only use the function write_message within the open and onmessage function within the websocket class. You are using application, which is a tornado.web.Application. It certainly won't have the method of tornado.websocket.WebSocketHandler. They are not the same class. Hence the error.
I would do something like:
class wshandler(tornado.websocket.WebSocketHandler):
def toggle(self):
flag = True
while True:
if flag==True:
print 'now on 1234'
flag = False
self.write_message('1234')
time.sleep(4)
elif flag==False:
flag = True
self.write_message('4321')
print 'Now on 4321'
time.sleep(4)
def on_message(self, message):
if message == 'toggle':
self.toggle()

Related

Python Tornado: Sending websocket messages from another class

Have an app that is using (Python 3.6) Tkinter & Tornado. Would like it send a websocket message when a button is pressed.
The sendSocket is in my class that handles the interface. I am able to open my sockets ok, and can send data into the socket handler ok. Additionally, it serves up my html file ok from my RequestHandler.
I can see that my code hits the sendSocketMessage line ok. However, I never get the print from within the SocketHandler.send_message def. There are no errors in the console.
def sendSocketMessage(self, data = "whatever"):
print("sending")
#WebSocketeer.send_message(data)
ioloop.IOLoop.current().add_callback(WebSocketeer.send_message, data)
class WebSocketeer(websocket.WebSocketHandler):
def open(self):
print("WebSocket opened")
def on_message(self, message):
print("got message: " + message)
def on_close(self):
print("WebSocket closed")
#classmethod
def send_message(self, message):
print("sending message: " + message)
for session_id, session in self.session.server._sessions._items.iteritems():
session.conn.emit(event, message)
Code based off of these SO responses
Send a websocket message:
How do I send a websocket message in Tornado at will?
Send to all clients:
Is it possible to send a message to all active WebSocket connections? Using either node.js or python tornado websockets
Found a way to make it work here: How to run functions outside websocket loop in python (tornado)
But am still wondering why the add_callback doesn't work - as, from what I've read, this is the recommended way.
This is what I've got working, taken from: https://github.com/tornadoweb/tornado/issues/2802
clients = [];
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print('connection opened...')
clients.append(self);
def on_message(self, message):
self.write_message("The server says: " + message + " back at you")
print('received:', message)
def on_close(self):
clients.remove(self);
print('connection closed...')
#classmethod
def send_message(self, message):
print("sending message: " + message)
for client in clients:
client.write_message(message);
#for session_id, session in self.session.server._sessions._items.iteritems():
# session.conn.emit(event, message);
return True;
def sendRandom():
global thread, data;
try:
print("sendRandom()");
time.sleep(0.125);
n = random.randint(0,1000);
data = str(n);
data = {"msg":"data","data":data};
if eventLoop is not None:
#If response needed
#sendData(eventLoop,WSHandler.send_message,json.dumps(data));
#else
eventLoop.add_callback(WSHandler.send_message,json.dumps(data));
except:
print("Err");
traceback.print_exc();
clients = [];
def sendData(loop,f,*a,**kw):
print("%s %s" % (type(loop),type(f)));
concurrent_future = concurrent.futures.Future();
async def wrapper():
try:
rslt = f(*a,**kw);
except Exception as e:
concurrent_future.set_exception(e);
else:
concurrent_future.set_result(rslt);
loop.add_callback(wrapper);
return concurrent_future.result();
eventLoop = None;
application = tornado.web.Application([
(r'/data', WSHandler),
])
def startServer():
global eventLoop;
try:
print("Starting server #%s:%d" %("localhost",9090));
asyncio.set_event_loop(asyncio.new_event_loop());
eventLoop = tornado.ioloop.IOLoop();
application.listen(9090)
eventLoop.start();
except KeyboardInterrupt:
print("^C");
except:
print("ERR");
traceback.print_exc();
if __name__ == "__main__":
thread = Thread(target=startServer,);
thread.setDaemon(True);
thread.start();
time.sleep(5);
while True:
sendRandom();

How can i send forever data to clients in tornado websocket?

I'm trying to run a server websocket using tornado, and i want to send messages to clients when i want in a loop not when i want and not onmessage.
This is my code for now :
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'new connection'
def on_message(self, message):
print 'message received: %s' % message
if message == "data":
self.write_message("message")
# here i want when i receive data from the client, to continue sending data for it until the connection is closed, and in the some time keep accepting other connections
def on_close(self):
print 'connection closed'
def check_origin(self, origin):
return True
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
myIP = socket.gethostbyname(socket.gethostname())
print '*** Websocket Server Started at %s***' % myIP
tornado.ioloop.IOLoop.instance().start()
Here's one way to do it, with a loop that sends a message every second until closed. The trickiest part of this is cancelling the loop when the connection is closed. This version uses the timeout parameter of Event.wait; other alternatives include gen.with_timeout and gen.sleep.
def open(self):
self.close_event = tornado.locks.Event()
IOLoop.current().spawn_callback(self.loop)
def on_close(self):
self.close_event.set()
#gen.coroutine
def loop(self):
while True:
if (yield self.close_event.wait(1.0)):
# yield event.wait returns true if the event has
# been set, or false if the timeout has been reached.
return
self.write_message("abc")

In a no-coroutine function, how do you get a return value from coroutine function of Tornado TCP?

I wrote 3 sections of code with Tornado TCP. I've encountered some difficulties.
My code is following:
client.py
'''tcp client'''
from socket import socket, AF_INET, SOCK_STREAM
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost', 20000))
resp = s.recv(8192)
print('Response:', resp)
s.send(b'Hello\n')
s.close()
server.py
'''tcp server'''
#! /usr/bin/env python
#coding=utf-8
from tornado.tcpserver import TCPServer
from tornado.ioloop import IOLoop
from tornado.gen import *
clientDict=dict() #save infomation of client
class TcpConnection(object):
def __init__(self,stream,address):
self._stream=stream
self._address=address
self._stream.set_close_callback(self.on_close)
#coroutine
def send_messages(self):
yield self.send_message(b'world \n')
response = yield self.read_message()
return response
def read_message(self):
return self._stream.read_until(b'\n')
def send_message(self,data):
return self._stream.write(data)
def on_close(self):
global clientDict
clientDict.pop(self._address)
print("the monitored %d has left",self._address)
class MonitorServer(TCPServer):
#coroutine
def handle_stream(self,stream,address):
global clientDict
print("new connection",address,stream)
clientDict.setdefault(address, TcpConnection(stream,address))
if __name__=='__main__':
print('server start .....')
server=MonitorServer()
server.listen(20000)
IOLoop.instance().start()
main.py
import time
from threading import Thread
import copy
from server import *
def watchClient():
'''call the "send" function when a new client connect''
global clientDict
print('start watch')
lastClientList=list()
while True:
currentClientList=copy.deepcopy([key for key in clientDict.keys()])
difference=list(set(currentClientList).difference(set(lastClientList)))
if len(difference)>0:
send(difference)
lastClientList=copy.deepcopy(currentClientList)
time.sleep(5)
else:
time.sleep(5)
continue
def send(addressList):
'''send message to a new client and get response'''
global clientDict
for address in addressList:
response=clientDict[address].send_messages()
print(address," response :",response)
def listen():
server=MonitorServer()
server.listen(20000)
IOLoop.instance().start()
if __name__=='__main__':
listenThread=Thread(target=listen)
watchThead=Thread(target=watchClient)
watchThead.start()
listenThread.start()
I want to get the "print information" when the main.py is run---address,response:b'hello\n'
But in fact I get the "print information" as ----
('127.0.0.1', 41233) response :<tornado.concurrent.Future object at 0x7f2894d30518>
It can't return the b'hello\n'.
Then I guess it can't get the response reasonably in the no-coroutine function(def send(addressList)) from the coroutine function( #coroutine def send_messages(self)).
How to solve this?
By the way, I want to know how to make the clientDict to be a property of the class MonitorServer,not a global property.
Please help me! Thank you.
In general, anything that calls a coroutine should be a coroutine itself. Mixing threads and coroutines can be very tricky; most coroutine-based code is deliberately not thread-safe.
The correct way to call a coroutine from a non-coroutine function on a different thread is something like this:
def call_coro_from_thread(f, *args, **kwargs):
q = queue.Queue()
def wrapper():
fut = f(*args, **kwargs)
fut.add_done_callback(q.put)
IOLoop.instance().add_callback(wrapper)
fut = q.get()
return fut.result()
IOLoop.add_callback is necessary to safely transfer control to the IOLoop thread, and then the Queue is used to transfer the result back.

Websockets with Tornado: Get access from the "outside" to send messages to clients

I'm starting to get into WebSockets as way to push data from a server to connected clients. Since I use python to program any kind of logic, I looked at Tornado so far. The snippet below shows the most basic example one can find everywhere on the Web:
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'new connection'
self.write_message("Hello World")
def on_message(self, message):
print 'message received %s' % message
self.write_message('ECHO: ' + message)
def on_close(self):
print 'connection closed'
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
As it is, this works as intended. However, I can't get my head around how can get this "integrated" into the rest of my application. In the example above, the WebSocket only sends something to the clients as a reply to a client's message. How can I access the WebSocket from the "outside"? For example, to notify all currently connected clients that some kind event has occured -- and this event is NOT any kind of message from a client. Ideally, I would like to write somewhere in my code something like:
websocket_server.send_to_all_clients("Good news everyone...")
How can I do this? Or do I have a complete misundersanding on how WebSockets (or Tornado) are supposed to work. Thanks!
You need to keep track of all the clients that connect. So:
clients = []
def send_to_all_clients(message):
for client in clients:
client.write_message(message)
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
send_to_all_clients("new client")
clients.append(self)
def on_close(self):
clients.remove(self)
send_to_all_clients("removing client")
def on_message(self, message):
for client in clients:
if client != self:
client.write_message('ECHO: ' + message)
This is building on Hans Then's example. Hopefully it helps you understand how you can have your server initiate communication with your clients without the clients triggering the interaction.
Here's the server:
#!/usr/bin/python
import datetime
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
clients = []
def open(self):
print 'new connection'
self.write_message("Hello World")
WSHandler.clients.append(self)
def on_message(self, message):
print 'message received %s' % message
self.write_message('ECHO: ' + message)
def on_close(self):
print 'connection closed'
WSHandler.clients.remove(self)
#classmethod
def write_to_clients(cls):
print "Writing to clients"
for client in cls.clients:
client.write_message("Hi there!")
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=15), WSHandler.write_to_clients)
tornado.ioloop.IOLoop.instance().start()
I made the client list a class variable, rather than global. I actually wouldn't mind using a global variable for this, but since you were concerned about it, here's an alternative approach.
And here's a sample client:
#!/usr/bin/python
import tornado.websocket
from tornado import gen
#gen.coroutine
def test_ws():
client = yield tornado.websocket.websocket_connect("ws://localhost:8888/ws")
client.write_message("Testing from client")
msg = yield client.read_message()
print("msg is %s" % msg)
msg = yield client.read_message()
print("msg is %s" % msg)
msg = yield client.read_message()
print("msg is %s" % msg)
client.close()
if __name__ == "__main__":
tornado.ioloop.IOLoop.instance().run_sync(test_ws)
You can then run the server, and have two instances of the test client connect. When you do, the server prints this:
bennu#daveadmin:~$ ./torn.py
new connection
message received Testing from client
new connection
message received Testing from client
<15 second delay>
Writing to clients
connection closed
connection closed
The first client prints this:
bennu#daveadmin:~$ ./web_client.py
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 0
And the second prints this:
bennu#daveadmin:~$ ./web_client.py
msg is Hello World
msg is ECHO: Testing from client
< 15 second delay>
msg is Hi there! 1
For the purposes of the example, I just had the server send the message to the clients on a 15 second delay, but it could be triggered by whatever you want.
my solution for this: first add "if __name__ == '__main__':" - to the main.py. then import main.py into the websocket module. e.g. (import main as MainApp) . it is now possible to call a function in 'main.py' from within the ws.py/WebSocketHandler-function. - inside the Handler pass the message like so:
MainApp.function(message)
i dunno if this is the opposite of elegant but it works for me.
..plus create and import a custom 'config.py' (thats looks like: someVar = int(0) ) into the 'mainApp.py' .. like so: import config as cfg --> now you can alter variables with cfg.someVar = newValue from inside the function in 'main.py' that once was called by the Handler from 'ws.py'.

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()
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()
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)
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.

Categories