Tornado websocket async background task - python

I try to create a tornado websocket, which is in the background.
to handle events of input from a another device.
Usecase: Control the raspberry pi with a websocket connection from android app.
i am able to create a connection with the following implemtation:
main.py:
import sys
from sys import stdin
from websocket import connection
def main(args):
print "start application"
port = 7004
if args[0] != port:
port = args[0]
connection.WebSocketConnection(port)
core()
def core():
run = True
while run:
userinput = stdin.readline()
print userinput
if userinput == 'quit':
print "quit app"
sys.exit()
if __name__ == "__main__":
main(sys.argv)
connection handler:
import tornado.web
import message.MessageHandler
from message.messageConstructor import MessageConstructor
from tornado.web import asynchronous
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'new connection'
self.send(message.MessageHandler.serialize(MessageConstructor('Engine1', '100')))
def on_message(self, rawMessage):
print rawMessage
obj = message.MessageHandler.deserialize(rawMessage)
print(obj.target)
def on_close(self):
print 'connection closed'
def check_origin(self, origin):
return True
def send(self, message):
self.write_message(message)
connection:
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import socket
from websocket import connectionHandler
class WebSocketConnection():
def __init__(self, port):
self.socketHandler = connectionHandler.WebSocketHandler
application = tornado.web.Application([
(r'/', self.socketHandler),
])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(7004)
myIP = socket.gethostbyname(socket.gethostname())
print '*** Websocket Server Started at %s***' % myIP
tornado.ioloop.IOLoop.instance().start()
def send(self, message):
self.socketHandler.send(message)
in the main.py file is function after the websocket creation and this function is never called. I guess it's a problem with the async chain of tornado.
Any suggestions?
Thanks in advance.

i figured out a way to do that, but i am not sure if this is the right way. Anyway in this set up it works:
main.py:
import sys
from sys import stdin
from websocket import connection
from tornado.ioloop import IOLoop
def main(args):
print "start application"
port = 7004
if args[0] != port:
port = args[0]
connection.WebSocketConnection(port)
core()
IOLoop.instance().start()
def core():
run = True
while run:
userinput = stdin.readline()
print userinput
if userinput == 'quit':
print "quit app"
sys.exit()
if __name__ == "__main__":
main(sys.argv)
connection.py:
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import socket
from websocket import connectionHandler
class WebSocketConnection():
def __init__(self, port):
self.socketHandler = connectionHandler.WebSocketHandler
application = tornado.web.Application([
(r'/', self.socketHandler),
])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(7004)
myIP = socket.gethostbyname(socket.gethostname())
print '*** Websocket Server Started at %s***' % myIP
def send(self, message):
self.socketHandler.send(message)
as you can see i start the loop just in the main file after calling the core function. In this way it works.
but as i said before i am not sure if this i really correct.

Related

How create a remote websoket sever python on Raspi

I've got Raspberry Pi and I want to create a websocket server in python to which I could access remotely from a website.
I've got a few ideas to how I can make it, with the python libraries Tornado or WebSocket. In this way I've successfully created a python websocket server to which I access from a website... but locally.
So now, how can I make this server distant? I've seen that I need a ws:// or wss:// address, but I don't how to implement it in my first version's server.
server.py :
import os.path
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
#Tornado Folder Paths
settings = dict(
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "static")
)
# Server port
PORT = 70
class MainHandler(tornado.web.RequestHandler):
def get(self):
print ("[HTTP](MainHandler) User Connected.")
self.render("index.html")
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print ('[WS] Connection was opened.')
def on_message(self, message):
print ('[WS] Incoming message: ' + message)
def on_close(self):
print ('[WS] Connection was closed.')
application = tornado.web.Application([
(r'/', MainHandler),
(r'/ws', WSHandler),
], **settings)
if __name__ == "__main__":
try:
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(PORT)
main_loop = tornado.ioloop.IOLoop.instance()
print ("Tornado Server started")
main_loop.start()
except:
print ("Exception triggered - Tornado Server stopped.")

Tornado-eventsource server not getting messages

I am working on Rabbitmq and Server sent events for this I use tornado-eventsource (https://github.com/guilhermef/tornado-eventsource/tree/master/tornado_eventsource) from this code I tried to make my connection of EventSource. When my server receives messages it suooise to send it to client but it gives this error message and messages are not watched by server to send
But I guess due to a change in versions as I use python3 I get an error.
File "C:\Users\Admin\AppData\Local\Programs\Python\Python37\lib\site-packages\tornado_eventsource\handler.py", line 71, in write_message
if isinstance(msg, str) or isinstance(msg, unicode):
NameError: name 'unicode' is not defined
Please guide me regards this.
Server side code here:
import _thread
import asyncio
import time
import os
import tornado.ioloop
import tornado.web
from DateTime import DateTime
import pika
from tornado.web import Application, RequestHandler
from threading import Thread
import logging
import six
#from tornadose.handlers
import tornado_eventsource.handler
from tornado_eventsource.handler import EventSourceHandler
logging.basicConfig(level=logging.INFO)
# web socket clients connected.
clients = []
connection = pika.BlockingConnection()
logging.info('Connected:localhost')
channel = connection.channel()
print("Connection Time: ", time.time()*1000)
print("Connection Time: ", DateTime())
def get_connection():
credentials = pika.PlainCredentials('guest', 'guest')
conn = pika.BlockingConnection(pika.ConnectionParameters(host="localhost", port=5672,
virtual_host="/",
credentials=credentials))
return conn
def callback(ch, method, properties, body):
print(" [x] Received %r", fetch_DateTime(), ':', body)
time.sleep(body.count(b'.'))
#print(fetch_DateTime())
for itm in clients:
itm.write_message(body)
def start_consumers():
asyncio.set_event_loop(asyncio.new_event_loop())
channel = get_connection().channel()
channel.queue_declare(queue="my_queue")
channel.basic_consume(
queue="my_queue",
on_message_callback=callback,
auto_ack=True)
channel.start_consuming()
def fetch_DateTime():
time_milliseconds = time.time() * 1000
return(time_milliseconds)
def disconnect_to_rabbitmq():
channel.stop_consuming()
connection.close()
logging.info('Disconnected from Rabbitmq')
class EventHandler(tornado_eventsource.handler.EventSourceHandler):
def open(self):
logging.info('Server-Sent Events opened')
clients.append(self)
def on_close(self):
logging.info('Server-Sent Events closed')
clients.remove(self)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("SSE_client.html")
def make_app():
return tornado.web.Application([
(r'/', EventHandler,),
(r"/", MainHandler),
])
class WebServer(tornado.web.Application):
def __init__(self):
handlers = [ (r'/', EventHandler,),
(r"/", MainHandler), ]
settings = {'debug': True}
super().__init__(handlers, **settings)
def run(self, port=8888):
self.listen(port)
tornado.ioloop.IOLoop.instance().start()
class TestHandler(tornado.web.RequestHandler):
def get(self):
self.write("test success")
ws = WebServer()
def start_server():
asyncio.set_event_loop(asyncio.new_event_loop())
ws.run()
if __name__ == "__main__":
logging.info('Starting thread Tornado')
threadC = Thread(target=start_consumers)
threadC.start()
from threading import Thread
t = Thread(target=start_s
erver, args=())
t.daemon = True
t.start()
t.join()
try:
input("Server ready. Press enter to stop\n")
except SyntaxError:
pass
try:
logging.info('Disconnecting from RabbitMQ..')
disconnect_to_rabbitmq()
except Exception:
pass
stopTornado();
logging.info('See you...')
Support to Python 3.6+ was added on the version 2.0.0rc2

Tornado websocket callback on_message

i achieved to have two threads working in my python application.
Now i want to add a callback function to the on_message to handle the receiving message in another thread.
here is my code:
import tornado.websocket
import tornado.web
import message.MessageHandler
from message.messageConstructor import MessageConstructor
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'new connection'
self.send(message.MessageHandler.serialize(MessageConstructor('Engine1', '100')))
self.send("CONNECT!")
def on_message(self, rawMessage):
print rawMessage
obj = message.MessageHandler.deserialize(rawMessage)
callback(message)
def on_close(self):
print 'connection closed'
def check_origin(self, origin):
return True
def send(self, message):
self.write_message(message)
and the creation:
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import socket
from websocket import connectionHandler
class WebSocketConnection():
def __init__(self, port, callback):
self.socketHandler = connectionHandler.WebSocketHandler
application = tornado.web.Application([
(r'/', self.socketHandler),
])
callback("World")
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(7004)
myIP = socket.gethostbyname(socket.gethostname())
print '*** Websocket Server Started at %s***' % myIP
tornado.ioloop.IOLoop.instance().start()
def send(self, message):
self.socketHandler.send(message)
how can i provide a callback to the not instantiated instance of the WebsocketHandler?
You can use RequestHandler.initialize to pass configuration through the Application to the handlers:
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def initialize(self, callback):
self.callback = callback
app = Application([('/', WebSocketHandler, dict(callback=callback))])

How do I run pyzmq and a webserver in one ioloop?

I want to write a single threaded program that hosts a webserver using Tornado and also receive messages on a ZMQ socket (using PyZMQ Tornado event loop: http://learning-0mq-with-pyzmq.readthedocs.org/en/latest/pyzmq/multisocket/tornadoeventloop.html), but I'm not sure how to structure it. Should I be using
from zmq.eventloop import ioloop
or
from tornado.ioloop import IOLoop
or both?
Before all Tornado imports you need import zmq.eventloop.ioloop and call zmq.eventloop.ioloop.install function. Then you may import Tornado ioloop and use it.
See:
http://zeromq.github.io/pyzmq/eventloop.html
Here is an example with Tornado HTTP server with zeroMQ PUB SUB sockets.
#!/usr/bin/env python
import json
import tornado
import tornado.web
import zmq
from tornado import httpserver
from zmq.eventloop import ioloop
from zmq.eventloop.zmqstream import ZMQStream
ioloop.install()
tornado.ioloop = ioloop
import sys
def ping_remote():
"""callback to keep the connection with remote server alive while we wait
Network routers between raspberry pie and cloud server will close the socket
if there is no data exchanged for long time.
"""
pub_inst.send_json_data(msg="Ping", req_id="##")
sys.stdout.write('.')
sys.stdout.flush()
pending_requests = {}
class ZMQSub(object):
def __init__(self, callback):
self.callback = callback
context = zmq.Context()
socket = context.socket(zmq.SUB)
# socket.connect('tcp://127.0.0.1:5559')
socket.bind('tcp://*:8081')
self.stream = ZMQStream(socket)
self.stream.on_recv(self.callback)
socket.setsockopt(zmq.SUBSCRIBE, "")
def shutdown_zmq_sub(self):
self.stream.close()
class ZMQPub(object):
def __init__(self):
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind('tcp://*:8082')
self.publish_stream = ZMQStream(socket)
def send_json_data(self, msg, req_id):
topic = str(req_id)
self.publish_stream.send_multipart([topic, msg])
def shutdown_zmq_sub(self):
self.publish_stream.close()
def SensorCb(msg):
# decode message from raspberry pie and the channel ID.
key, msg = (i for i in msg)
if not key == "##":
msg = json.loads(msg)
if key in pending_requests.keys():
req_inst = pending_requests[key]
req_inst.write(msg)
req_inst.finish()
del pending_requests[key]
else:
print "no such request"
print pending_requests
else:
print "received ping"
class Handler(tornado.web.RequestHandler):
def __init__(self, *args, **kwargs):
super(Handler, self).__init__(*args, **kwargs)
# get the unique req id
self.req_id = str(self.application.req_id) + "#"
self.application.req_id += 1
# set headers
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header("Access-Control-Allow-Headers", "x-requested-with")
self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT')
#tornado.web.asynchronous
def get(self):
print self.request
if self.req_id not in pending_requests.keys():
pending_requests[self.req_id] = self
else:
print "WTF"
pub_inst.send_json_data(msg=json.dumps({"op": "ServiceCall"}), req_id=self.req_id)
if __name__ == "__main__":
pub_inst = ZMQPub()
sub_inst = ZMQSub(callback=SensorCb)
application = tornado.web.Application(
[(r'/get_sensor_data', Handler), (r'/(.*)')])
application.req_id = 0
server = httpserver.HTTPServer(application, )
port = 8080
server.listen(port)
print "Sensor server ready on port: ", port
ping = ioloop.PeriodicCallback(ping_remote, 3000)
ping.start()
tornado.ioloop.IOLoop.instance().start()

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