I am new to Tornado server in python and trying to a ping-pong at regular interval to the clients connected.
I saw something called websocket_ping_interval in tornado documentation but couldn't find any example as to how/when to use it.
I did the below using ioloop.PeriodicCallback but doesn't seem to be doing any ping.
import tornado.web
from tornado import ioloop
from terminado import TermSocket, SingleTermManager
from tornado import websocket
# BaseWebSocketHandler removed, because we need to track all opened
# sockets in the class. You could change this later.
class MeterInfo(websocket.WebSocketHandler):
"""Establish an websocket connection and send meter readings."""
opened_sockets = []
previous_meter_reading = 0
def open(self):
self.write_message('Connection Established.')
MeterInfo.opened_sockets.append(self)
def on_close(self):
"""Close the connection."""
self.write_message('bye')
MeterInfo.opened_sockets.remove(self)
#classmethod
def try_send_new_reading(cls):
"""Send new reading to all connected clients"""
new_reading = "text"
if new_reading == cls.previous_meter_reading:
return
cls.previous_meter_reading = new_reading
for socket in cls.opened_sockets:
socket.write_message({'A': new_reading})
if __name__ == '__main__':
term_manager = SingleTermManager(shell_command=['bash'])
handlers = [
(r"/websocket", TermSocket, {'term_manager': term_manager}),
(r"/()", tornado.web.StaticFileHandler, {'path': 'index.html'}),
(r"/(.*)", tornado.web.StaticFileHandler, {'path': '.'}),
]
app = tornado.web.Application(handlers)
app.listen(8010)
METER_CHECK_INTERVAL = 100 # ms
ioloop.PeriodicCallback(MeterInfo.try_send_new_reading,METER_CHECK_INTERVAL).start()
ioloop.IOLoop.instance().start()
All I need to do is to keep pinging the clients connected with something.
websocket_ping_interval is an application setting, so you pass it to the Application constructor:
app = tornado.web.Application(handlers, websocket_ping_interval=10)
Related
I am a Tornado and also Websocket newbie. There are many resources how to implement a websocket server application with Tornado. However, I haven't found a complete example that contains a websocket client application built on top of Tornado.
Server Application (server.py)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import tornado.web
import tornado.websocket
import tornado.ioloop
import tornado.options
from tornado.options import define, options
define("port", default=3000, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [(r"/", MainHandler)]
settings = dict(debug=True)
tornado.web.Application.__init__(self, handlers, **settings)
class MainHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
def open(self):
logging.info("A client connected.")
def on_close(self):
logging.info("A device disconnected")
def main():
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
Client Application (client.py):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tornado.ioloop import IOLoop
from tornado.websocket import websocket_connect
if __name__ == "__main__":
ws = websocket_connect("ws://localhost:3000")
IOLoop.instance().start()
Problems & Questions
I don't have any problems with server application. However, I can't say same thing for client application. Why?
If server goes down, client application doesn't react. It does not say "Hey, server is down buddy.". I write my own WebSocketClientConnection class (inherited by the original) and also websocket_connect function (same function, but this one uses my WebSocketClientConnection class). I override on_connection_close method in my WebSocketClientConnection class, so I can catch "server down on middle of the connection" failure. However, I cannot make it reconnect. How do I make it reconnect to server?
If server is already down before the connection, client application does not raise anything and seems perfectly. I don't know how to catch that failure. How do I catch this connection failure?
My WebSocketClientConnection class:
class MyWebSocketClientConnection(tornado.websocket.WebSocketClientConnection):
def on_connection_close(self):
super(MyWebSocketClientConnection, self).on_connection_close()
print "connection closed"
Thanks.
By the way, after I figured out, I did write an example Tornado WebSocket client/server pair to demonstrate how to do it.
https://github.com/ilkerkesen/tornado-websocket-client-example
I hope it helps someone.
websocket_connect returns a Future; to see whether it succeeded or failed you must examine the Future (probably by yielding it in a coroutine). Furthermore, after establishing the connection you should go into a read_message loop. Even if you don't expect the client to send any messages, you should still call read_message: this is how you will connections that are closed after being established (read_message will return None).
#gen.coroutine
def my_websocket_client(url):
ws = yield websocket_connect(url)
while True:
msg = yield ws.read_message()
if msg is None: break
# do stuff with msg
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)
I want to run both a websocket and a flash policy file server on port 80 using Tornado. The reason for not wanting to run a server on the default port 843 is that it's often closed in corporate networks. Is it possible to do this and if so, how should I do this?
I tried the following structure, which does not seem to work: the websocket connection works, but the policy file request is not routed to the TCPHandler.
#!/usr/bin/python
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket
import tornado.gen
from tornado.options import define, options
from tornado.tcpserver import TCPServer
define("port", default=80, help="run on the given port", type=int)
class FlashPolicyServer(TCPServer):
def handle_stream(self, stream, address):
self._stream = stream
self._read_line()
def _read_line(self):
self._stream.read_until('\n', self._handle_read)
def _handle_read(self, data):
policyFile = ""
self._stream.write(policyFile)
self._read_line()
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
pass
def on_message(self, message):
pass
def on_close(self):
pass
def main():
tornado.options.parse_command_line()
mainLoop = tornado.ioloop.IOLoop.instance()
app = tornado.web.Application(
handlers=[
(r"/websocket", WebSocketHandler),
(r"/", FlashPolicyServer)
], main_loop=mainLoop
)
httpServer = tornado.httpserver.HTTPServer(app)
httpServer.listen(options.port)
mainLoop.start()
if __name__ == "__main__":
main()
Any ideas? If this is not possible, would another idea be to serve the policy file via port 443?
There are two problems with your approach:
TCPServer is used like this:
server = TCPServer()
server.listen(6666)
IOLoop.instance().start()
That would do the trick, after you remove the (r"/", FlashPolicyServer) line.
But you want to use port 80.
However, you can't do that, because you need to have another HTTP server at that port.
So what you can do is to use nginx and set up port 80 as reverse proxying for /websocket, serving a Flash policy file otherwise.
I solved this by monkey-patching IOStream and HTTPServer:
def prepend_to_buffer(self, chunk):
self._read_buffer.appendleft(chunk)
self._read_buffer_size += len(chunk)
if self._read_buffer_size >= self.max_buffer_size:
logging.error("Reached maximum read buffer size")
self.close()
raise IOError("Reached maximum read buffer size")
return len(chunk)
def first_data_handler(self, data):
if data == '<policy-file-request/>\0':
self.stream.write(policy_file + '\0')
else:
self.stream.prepend_to_buffer(data)
tornado.httpserver.HTTPConnection(self.stream, self.address, self.request_callback,self.no_keep_alive, self.xheaders)
def handle_stream(self, stream, address):
self.stream = stream
self.address = address
self.stream.read_bytes(23, self.first_data_handler)
tornado.iostream.IOStream.prepend_to_buffer = prepend_to_buffer
tornado.httpserver.HTTPServer.first_data_handler = first_data_handler
tornado.httpserver.HTTPServer.handle_stream = handle_stream
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.
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()