Tornado server stuck in loop, not accepting clients - python

I've written a code in tornado that connects to a server that is pushing an infinite data stream, processes the data stream and sends it out on a websocket server.
The problem is that the way I implemented it the server became blocked on a particular function and doesn't accept any more clients since it never exits the function serving the data to the websocket. I want the connection to the server and the data retrieved from it processed only once but send the processed data to all the clients that connect to my tornado server. Could someone please help me, I can't figure out a way to do it. Here's my code with processing of data removed:
import socket
import ssl
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
websockets = []
class WSHandler(tornado.websocket.WebSocketHandler):
def readData(self):
while True:
line = self.ssl_sock.read()
#PROCESS THE READ LINE AND CONVERT INTO RESULTING DATA
if(toSend):
self.write_message(result)
def makeConnection(self):
self.ssl_sock.connect(self.address)
self.readData()
def open(self):
print 'New connection was opened'
self.s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ssl_sock=ssl.wrap_socket(self.s, cert_reqs=ssl.CERT_NONE)
self.address=('SERVER_ADDRESS',5000)
self.nodes=[]
self.edges=[]
if self not in websockets:
print ('added')
websockets.append(self)
if(len(websockets)==1):
print('executing make conn')
self.makeConnection()
else:
self.readData()
print('executing read data')
def on_message(self, message):
print 'Incoming message:', message
self.write_message("You said: " + message)
def on_close(self):
print 'Connection was 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()

Tornado is an asynchronous framework, that is, all your IO must be run within its event loop, otherwise the whole server gets stuck.
Try having a look at Tornado Async Client.

Related

python - Multiple tornado clients simultaneously connecting to tornado server

I have my Tornado client continuously listening to my Tornado server in a loop, as it is mentioned here - http://tornadoweb.org/en/stable/websocket.html#client-side-support. It looks like this:
import tornado.websocket
from tornado import gen
#gen.coroutine
def test():
client = yield tornado.websocket.websocket_connect("ws://localhost:9999/ws")
client.write_message("Hello")
while True:
msg = yield client.read_message()
if msg is None:
break
print msg
client.close()
if __name__ == "__main__":
tornado.ioloop.IOLoop.instance().run_sync(test)
I'm not able to get multiple instances of clients to connect to the server. The second client always waits for the first client process to end before it connects to the server. The server is set up as follows, with reference from Websockets with Tornado: Get access from the "outside" to send messages to clients and Tornado - Listen to multiple clients simultaneously over websockets.
class WSHandler(tornado.websocket.WebSocketHandler):
clients = set()
def open(self):
print 'new connection'
WSHandler.clients.add(self)
def on_message(self, message):
print 'message received %s' % message
# process received message
# pass it to a thread which updates a variable
while True:
output = updated_variable
self.write_message(output)
def on_close(self):
print 'connection closed'
WSHandler.clients.remove(self)
application = tornado.web.Application([(r'/ws', WSHandler),])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(9999)
tornado.ioloop.IOLoop.instance().start()
But this has not worked - for some reason even after I have made a successful first connection, the second connection just fails to connect i.e. it does not even get added to the clients set.
I initially thought the while True would not block the server from receiving and handling more clients, but it does as without it multiple clients are able to connect. How can I send back continuously updated information from my internal thread without using the while True?
Any help would be greatly appreciated!
To write messages to client in a while loop, you can use the yield None inside the loop. This will pause the while loop and then Tornado's IOLoop will be free to accept new connections.
Here's an example:
#gen.coroutine
def on_message(self):
while True:
self.write_message("Hello")
yield None
Thanks for your answer #xyres! I was able to get it to work by starting a thread in the on_message method that handed processing and the while True to a function outside the WSHandler class. I believe this allowed for the method to run outside of Tornado's IOLoop, unblocking new connections.
This is how my server looks now:
def on_message(self, message):
print 'message received %s' % message
sendThread = threading.Thread(target=send, args=(self, message))
sendThread.start()
def send(client, msg):
# process received msg
# pass it to a thread which updates a variable
while True:
output = updated_variable
client.write_message(output)
Where send is a function defined outside the class which does the required computation for me and writes back to client inside thewhile True.

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'.

Implement SSL in tornadoweb websockets server

Good Day to all,
I have a websocket server written in python that serves clients changes done to a postgresql database table. Presently it uses the standard ws protocol. I would Like to implement SSL wss but there seems to be no documentation on the tornadoweb site. Any help appreciated!
Code as follows:
import tornado.web
import tornado.websocket
import tornado.ioloop
import tornado.httpserver
import threading
import select
import psycopg2
import psycopg2.extensions
# This is a global variable to store all connected clients
websockets = []
# Connect to DB to listen for notifications
conn = psycopg2.connect("host=xxx.xxx.xxx.xxx dbname=mydb user=xxx port=yyy")
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
curs = conn.cursor()
curs.execute("LISTEN notifier;")
# Create the websocket
class WebSocketHandler(tornado.websocket.WebSocketHandler):
#tornado.web.asynchronous
# Add to global variable the connected client
def open(self):
self.set_nodelay(True)
# on disconnect remove client
def on_close(self):
# Search for it in the list. If it matches with us (the client) then remove it as he has quit the con
try:
for websocket in websockets:
if websocket[0] == self:
websockets.remove(websocket)
except ValueError:
print ValueError
def on_message(self, message):
if self not in websockets:
websockets.append([self,message])
def on_pong(self, data):
print data
# This is the function that polls the database for changes, then it sends change to clients through websockets
def db_listener():
while 1:
if select.select([conn],[],[],5) == ([],[],[]):
for websocket in websockets:
# ping the client regularly to avoid disconnect
websocket[0].ping("ping")
else:
conn.poll()
while conn.notifies:
notify = conn.notifies.pop()
details = notify.payload.split(",")
if len(details) > 1:
for websocket in websockets:
if details[34] in websocket[1]:
websocket[0].write_message(notify.payload)
application = tornado.web.Application([
(r"/websocket", WebSocketHandler),
])
if __name__ == "__main__":
# Start a separate thread for every client so that we do not block the main websockets program!
threading.Thread(target=db_listener).start()
application.listen(5252)
tornado.ioloop.IOLoop.instance().start()
Any suggestions?
Thanks to all!!
try this:
server = tornado.httpserver.HTTPServer(application, ssl_options = {
"certfile": "path-to-crt-file",
"keyfile": "path-to-key-file",
})
server.listen(5252)

Attaching ZMQStream with existing tornado ioloop

I have an application where every websocket connection (within tornado open callback) creates a zmq.SUB socket to an existing zmq.FORWARDER device. Idea is to receive data from zmq as callbacks, which can then be relayed to frontend clients over websocket connection.
https://gist.github.com/abhinavsingh/6378134
ws.py
import zmq
from zmq.eventloop import ioloop
from zmq.eventloop.zmqstream import ZMQStream
ioloop.install()
from tornado.websocket import WebSocketHandler
from tornado.web import Application
from tornado.ioloop import IOLoop
ioloop = IOLoop.instance()
class ZMQPubSub(object):
def __init__(self, callback):
self.callback = callback
def connect(self):
self.context = zmq.Context()
self.socket = self.context.socket(zmq.SUB)
self.socket.connect('tcp://127.0.0.1:5560')
self.stream = ZMQStream(self.socket)
self.stream.on_recv(self.callback)
def subscribe(self, channel_id):
self.socket.setsockopt(zmq.SUBSCRIBE, channel_id)
class MyWebSocket(WebSocketHandler):
def open(self):
self.pubsub = ZMQPubSub(self.on_data)
self.pubsub.connect()
self.pubsub.subscribe("session_id")
print 'ws opened'
def on_message(self, message):
print message
def on_close(self):
print 'ws closed'
def on_data(self, data):
print data
def main():
application = Application([(r'/channel', MyWebSocket)])
application.listen(10001)
print 'starting ws on port 10001'
ioloop.start()
if __name__ == '__main__':
main()
forwarder.py
import zmq
def main():
try:
context = zmq.Context(1)
frontend = context.socket(zmq.SUB)
frontend.bind('tcp://*:5559')
frontend.setsockopt(zmq.SUBSCRIBE, '')
backend = context.socket(zmq.PUB)
backend.bind('tcp://*:5560')
print 'starting zmq forwarder'
zmq.device(zmq.FORWARDER, frontend, backend)
except KeyboardInterrupt:
pass
except Exception as e:
logger.exception(e)
finally:
frontend.close()
backend.close()
context.term()
if __name__ == '__main__':
main()
publish.py
import zmq
if __name__ == '__main__':
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.connect('tcp://127.0.0.1:5559')
socket.send('session_id helloworld')
print 'sent data for channel session_id'
However, my ZMQPubSub class doesn't seem like is receiving any data at all.
I further experimented and realized that I need to call ioloop.IOLoop.instance().start() after registering on_recv callback within ZMQPubSub. But, that will just block the execution.
I also tried passing main.ioloop instance to ZMQStream constructor but doesn't help either.
Is there a way by which I can bind ZMQStream to existing main.ioloop instance without blocking flow within MyWebSocket.open?
In your now complete example, simply change frontend in your forwarder to a PULL socket and your publisher socket to PUSH, and it should behave as you expect.
The general principles of socket choice that are relevant here:
use PUB/SUB when you want to send a message to everyone who is ready to receive it (may be no one)
use PUSH/PULL when you want to send a message to exactly one peer, waiting for them to be ready
it may appear initially that you just want PUB-SUB, but once you start looking at each socket pair, you realize that they are very different. The frontend-websocket connection is definitely PUB-SUB - you may have zero-to-many receivers, and you just want to send messages to everyone who happens to be available when a message comes through. But the backend side is different - there is only one receiver, and it definitely wants every message from the publishers.
So there you have it - backend should be PULL and frontend PUB. All your sockets:
PUSH -> [PULL-PUB] -> SUB
publisher.py: socket is PUSH, connected to backend in device.py
forwarder.py: backend is PULL, frontend is PUB
ws.py: SUB connects and subscribes to forwarder.frontend.
The relevant behavior that makes PUB/SUB fail on the backend in your case is the slow joiner syndrome, which is described in The Guide. Essentially, subscribers take a finite time to tell publishers about there subscriptions, so if you send a message immediately after opening a PUB socket, the odds are it hasn't been told that it has any subscribers yet, so it's just discarding messages.
ZeroMq subscribers have to subscribe on what messages they wish to receive; I don't see that in your code. I believe the Python way is this:
self.socket.setsockopt(zmq.SUBSCRIBE, "")

Sockjs-tornado and rpc

I'm trying to add a sockjs-tornado server to my site, and all worked fine until I decided to connect it to my other apps via MsgPack (using msgpack-rpc-python). And now works sockjs server either RPC server. Depending on wich of them start there loop first.
I think that I need to use one tornado.ioloop for both of them. But do not know how to achieve it. Or may be there is another way to add rpc to a tornado server?
Here is a sockjs-tornado sample code with msgpack-rpc-python:
import tornado.ioloop
import tornado.web
import sockjs.tornado
import msgpackrpc
class RPCServer(object):
def sum(self, x, y):
return x + y
class IndexHandler(tornado.web.RequestHandler):
"""Regular HTTP handler to serve the chatroom page"""
def get(self):
self.render('index.html')
class ChatConnection(sockjs.tornado.SockJSConnection):
"""Chat connection implementation"""
# Class level variable
participants = set()
def on_open(self, info):
# Send that someone joined
self.broadcast(self.participants, "Someone joined.")
# Add client to the clients list
self.participants.add(self)
def on_message(self, message):
# Broadcast message
self.broadcast(self.participants, message)
def on_close(self):
# Remove client from the clients list and broadcast leave message
self.participants.remove(self)
self.broadcast(self.participants, "Someone left.")
if __name__ == "__main__":
# 1. Create chat router
ChatRouter = sockjs.tornado.SockJSRouter(ChatConnection, '/chat')
# 1.5 Create MsgPack RPC Server
rpc = msgpackrpc.Server(RPCServer())
# 2. Create Tornado application
app = tornado.web.Application(
[(r"/", IndexHandler)] + ChatRouter.urls
)
# 3. Make Tornado app listen on port 5000
app.listen(5000)
# 3.5 Make MsgPack RPC Server listen on port 5001
rpc.listen(msgpackrpc.Address('localhost', 5001))
# 4. Start IOLoop
tornado.ioloop.IOLoop.instance().start()
# 5. Never executed
rpc.start()
`
Any suggestions or examples are welcome!
This happens because both start() calls start Tornado IOLoop and they won't exit until IOLoop stopped.
Yes, you have to use one IOLoop. As msgpackrpc.Server accepts Loop class instance and Loop incapsulates IOLoop, try this:
if __name__ == '__main__':
io_loop = tornado.ioloop.IOLoop.instance()
loop = msgpackrpc.Loop(io_loop)
rpc = msgpackrpc.Server(RPCServer(), loop=loop)
# ... sockjs-tornado initialisation. No need to call rpc.start()
io_loop.start()

Categories