How do I get the dynamic url passed by js in my websocket, do I need it to pass by parameter to the pc.connect() class that today does not receive anything but in the future will it?
webosocket:
class MyWebSocketHandler(websocket.WebSocketHandler):
def allow_draft76(self):
# for iOS 5.0 Safari
return True
def check_origin(self, origin):
return True
def open(self, *args, **kwargs):
self.application.pc.add_event_listener(self)
print ("WebSocket opened")
def on_close(self):
print ("WebSocket closed")
self.application.pc.remove_event_listener(self)
def main():
parse_command_line()
aplication = tornado.web.Application([
(r'.*', MyWebSocketHandler)
])
pc = PikaClient(io_loop)
aplication.pc = pc
aplication.pc.connect()
aplication.listen(8081)
io_loop.start()
if __name__ == '__main__':
main()
html:
const websocket = new WebSocket('ws://192.168.99.100:8081/${this.$route.params.uri}')
In the application, define the route like this:
(r'/(.*)', MyWebSocketHandler),
The parentheses define a capturing group which will be passed to open() as args[0].
Also note that the allow_draft76 method was removed in Tornado 4.0, so there is no longer any reason to include this method in your handler.
Related
Longtime didn't use tornado. I would like to have a websocket which get's updates from a serial device of a host where tornado runs. So I tried multiprocessing with tornado but the process has no access to the tornado websocket. I tried to incorporate it as coroutine but that seems to not not spawn.
class WebApplication(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', IndexPageHandler),
(r"/config", ConfigHandler),
(r"/shutdown", ShutdownHandler),
(r'/websocket', WebSocketHandler),
(r'/(.*)', tornado.web.StaticFileHandler, {'path': resourcesWeb})
]
settings = {
'debug': debug,
'static_path': resourcesWeb,
'template_path': 'templates'
}
tornado.web.Application.__init__(self, handlers, **settings)
#gen.coroutine
def serial_reader(self):
log('serial_reader: start')
done = False
while not done:
sh.read()
serial_data_from = str(sh.data)
if len(serial_data_from) > 0:
if debug:
log('serial read:' + serial_data_from)
yield [con.write_message(serial_data_from) for con in WebSocketHandler.connections]
yield gen.sleep(0.3)
log('serial_reader: exit')
Python 3.8.5, Tornad 6.1
how would I properly and constantly update a websocket with data from outside the the tornado app
Since sh.read is blocking, you'll need to run it in an executor. To then notify clients in the main thread, you'll need to use IOLoop.add_callback (safe to call from any thread). This also means the reader method becomes a regular sync method.
Example:
from concurrent.futures import ThreadPoolExecutor
import functools
from tornado import web, websocket, ioloop
log = print
class IndexHandler(web.RequestHandler):
def get(self):
self.write("""<html>
<textarea cols="30" rows="10" id="output">%s</textarea><br />
start<br />
stop<br />
<iframe name="f" width="100" height="30"></iframe>
<script>
ws = new WebSocket("ws://localhost:8888/stream");
out_el = document.getElementById("output");
function log(data) {out_el.value = data + "\\n" + out_el.value;}
ws.onmessage = function (ev) {log(ev.data);}
</script>""" % "\n".join(map(str, reversed(self.application.read_data))))
class StartHandler(web.RequestHandler):
def get(self):
self.application.start_reader()
self.write("Started")
class StopHandler(web.RequestHandler):
def get(self):
self.application.stop_reader()
self.write("Stopped")
class WebSocketHandler(websocket.WebSocketHandler):
connections = set()
def open(self):
WebSocketHandler.connections.add(self)
def on_close(self):
if self in WebSocketHandler.connections:
WebSocketHandler.connections.remove(self)
class WebApplication(web.Application):
def __init__(self, autostart=False):
handlers = [
(r"/", IndexHandler),
(r"/start", StartHandler),
(r"/stop", StopHandler),
(r'/stream', WebSocketHandler),
]
web.Application.__init__(self, handlers)
self._reader_executor = ThreadPoolExecutor(1)
self._keep_reading = None
self.read_data = []
if autostart:
self.start_reader()
def start_reader(self):
if not self._keep_reading:
self._keep_reading = True
loop = ioloop.IOLoop.current()
self._reader_future = loop.run_in_executor(self._reader_executor, functools.partial(self.reader, loop))
def stop_reader(self):
if self._keep_reading:
self._keep_reading = False
self._reader_future.cancel()
def notify_clients(self, data=None):
for con in WebSocketHandler.connections:
try:
con.write_message("{}".format(data))
except Exception as ex:
log("error sending to {}".format(con))
def reader(self, main_loop):
import random
import time
while self._keep_reading:
time.sleep(1 + random.random()) # simulate read - block for some time
data = random.getrandbits(32)
print("reader: data={}".format(data))
if data:
main_loop.add_callback(self.notify_clients, data)
self.read_data.append(data)
time.sleep(0.1)
if __name__ == "__main__":
app = WebApplication(True)
app.listen(8888)
loop = ioloop.IOLoop.current()
try:
loop.start()
except KeyboardInterrupt as ex:
app.stop_reader()
for con in WebSocketHandler.connections:
con.close()
loop.stop()
In this case I'm working with the websockets module.
A typical server implementation is this:
import websockets
start_server = websockets.serve(counter, "localhost", 6789)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
But, I want to be able to stop and restart the server without stopping the event loop. It's not clear to me from the minimal documentation that I've seen about asyncio servers, how to do this. Nor do I know if websockets is implemented in quite the same way.
For instance, if I do something like this:
def counter():
start_server = websockets.serve(connection_handler, 'localhost', 6789)
this = loop.run_until_complete(start_server)
try:
loop.run_forever()
finally:
this.close()
loop.run_until_complete(this.wait_closed())
loop = asyncio.get_event_loop()
loop.create_task(anothertask)
startcounter = counter()
I can trigger the server to stop by calling loop.stop(). How do I stop the server without stopping the loop (and upsetting another task running on the loop)?
You can use asyncio.create_task to submit a task when the loop is already running. The run_until_complete() followed by run_forever() pattern is now deprecated because it is incompatible with asyncio.run, which is now the preferred way to run asyncio code.
The recommended approach is to use asyncio.run at top-level to start an async entry point (typically defined as async def main()), and do the rest from there. run_until_complete(x) then becomes simply await x, and run_forever() is not needed because you can await things like server.serve_forever() or an asyncio.Event of your choosing.
Since serve_forever doesn't appear to exist for websockets server, here is the variant with the event (untested):
async def connection_handler(...):
...
async def test(stop_request):
# occasionally stop the server to test it
while True:
await asyncio.sleep(1)
print('requesting stop')
stop_request.set()
async def main():
stop_request = asyncio.Event()
asyncio.create_task(test(stop_request))
while True:
print('starting the server')
server = await websockets.serve(connection_handler, 'localhost', 6789)
await stop_request.wait()
stop_request.clear()
print('stopping the server')
server.close()
await server.wait_closed()
asyncio.run(main())
This answer is based on the suggestions and answer provided by user4815162342 as well as borrowing a couple things from the websockets module documentation. The reason for my answer is largely to provide a more fleshed out example. I think that one of the hardest parts of working with asyncio is integrating it with other pieces of code without breaking it.
One thing I want to point out about this implementation is that the "server" object in the line of user4815162342's code:
async def somefunction(): # requires async function
server = await websockets.serve(handler, 'localhost', 6789)
is the same "server" object that would be created with this bit of code that you will see in the websockets documentation fairly often:
def somefunction(): # non-async function
start_server = websockets.serve(handler, 'localhost', 6789)
server = asyncio.run_until_complete(start_server)
And therefore it will respond to the method close().
Furthermore, the author of websockets suggests that the server can be started with:
async def somefunction(): # requires async function
async with websockets.serve(handler, 'localhost', 6789)
await something_that_says_stop
Note which of these can be called from a non-async function and which must occur "on the loop".
In my example (a somewhat fanciful bit of script) I integrate a tkinter GUI. This is accomplished with the aiotkinter module which, aside from it's pypi page, has hardly any other mention, let alone example code, on the interwebs. So, here's an example of how to use it with the websockets module in a scenario where the websockets server[s] can be started and stopped.
Notice that I do not call root.mainloop(). Instead, the "blocking line" in this is the asyncio.run() line. Also notice where the set_event_loop_policy line is placed.
This has been tested on mac with python 3.8.2 and TclTk 8.6. Thank you user4815162342 for your help. The language of asyncio is getting clearer and I think we need to get more example code and answers out there that uses the newer language/structures...
import sys
from functools import partial
import asyncio
import aiotkinter
from tkinter import *
from tkinter import messagebox
import json
import websockets
from netifaces import interfaces, ifaddresses, AF_INET
async def register(w, label2):
USERS.remove(w) if w in USERS else USERS.add(w)
label2.config(text=f"User Count = {len(USERS)}")
async def connection_handler(websocket, path, stop_request,
ticker, start_request, label, label2):
misterDict = {"Mr.White":"white", "Mr.Orange":"orange",
"Mr.Blonde":"khaki", "Mr.Pink":"pink",
"Mr.Blue":"blue", "Mr.Brown":"brown"}
await register(websocket, label2)
try:
message = json.dumps("welcome")
await websocket.send(message)
# ignore this -> while server.get():
async for message in websocket:
msg = json.loads(message)
if msg == "end":
return
elif msg == "stoptick":
ticker.set(False)
elif msg == "starttick":
ticker.set(True)
startTicker(ticker, label)
elif msg == "stopserver":
stop_request.set()
break
elif msg in misterDict.keys():
color = misterDict[msg]
changeColor(label, color)
# ignore this -> await asyncio.sleep(.05)
except Exception as E1:
print(f"Error! {E1}")
finally:
await register(websocket, label2)
async def main():
stop_request = asyncio.Event()
start_request = asyncio.Event()
# Some tkinter setup
root = Tk()
root.title("Color Server")
root.minsize(250, 250)
ticker = BooleanVar()
ticker.set(0)
server = BooleanVar()
server.set(0)
label = Label(master=root, height=4, width=20, bg="white",
bd=5)
label.pack()
buttonS = Checkbutton(master=root, text='Mr tick', variable=ticker,
height=3, command=partial(startTicker,
ticker, label))
buttonS.pack()
buttonW = Checkbutton(master=root, text='Websocket Server',
variable=server, height=3,
command=partial(startWeb, server,
label, stop_request, start_request))
buttonW.pack()
def on_closing():
if messagebox.askokcancel("Quit", "Do you want to quit?"):
QUITTER.add("yes")
if server.get():
stop_request.set()
else:
start_request.set()
buttonX = Button(master=root, text='Shut down everything', height=2,
command=on_closing)
buttonX.pack()
label2 = Label(master=root, text='User Count = 0',
height=2, bg="white", bd=2)
label2.pack()
root.protocol("WM_DELETE_WINDOW", on_closing)
# websocket server setup code.
serverlist = set()
iplist = [ifaddresses(face)[AF_INET][0]["addr"]
for face in interfaces() if AF_INET in ifaddresses(face)]
print(f"the interfaces found = {iplist}")
while True:
await start_request.wait()
start_request.clear()
if "yes" in QUITTER:
break
server.set(1)
for ipadd in iplist: # for each IP address in the system
# setup a websocket server
bound_handler = partial(connection_handler,
stop_request=stop_request, ticker=ticker,
start_request=start_request,
label=label, label2=label2)
sockserver = await websockets.serve(bound_handler, ipadd, 6789)
serverlist.add(sockserver)
await stop_request.wait()
stop_request.clear()
for sockserver in serverlist:
sockserver.close()
await sockserver.wait_closed()
serverlist.clear()
if "yes" in QUITTER:
break
server.set(0)
def startWeb(server, label, stop_request, start_request):
if not server.get():
stop_request.set()
return
else:
start_request.set()
def changeColor(label, color):
label.config(bg=color)
async def mRtick(ticker, label):
ticktock = [("tick", "w"), ("tock", "e")]
tocker = 0
while ticker.get():
a,b = ticktock[tocker]
label.config(text=a, anchor=b)
tocker = not tocker
await asyncio.sleep(0.5)
label.config(text="")
def startTicker(ticker, label):
if not ticker.get():
return
asyncio.create_task(mRtick(ticker, label))
QUITTER = set()
USERS = set()
asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy())
asyncio.run(main())
print("we've ended...")
sys.exit()
Please note that I discovered that I had an unnecessary while loop in the code. This has now been commented out.
My python program work so slow, because make socket reconnect for any request. I want make one connect and send request, with out reconnect
My functions in file send_by_socket.py, some other function and class call send_to_socket for send logmessage. Now it work, but very slow. Reason - make new connect for any message. I want single connection or poll for use it without reconnect. How make it, possibel have good example with sourcecode?
import asyncio
import websockets
from logging import StreamHandler
import json
async def async_send(message):
async with websockets.connect('wss://****.com/chat') as web_socket:
await web_socket.send(message)
class WebSocketHandler(StreamHandler):
def __init__(self):
StreamHandler.__init__(self)
def emit(self, record):
msg = json.dumps({'log': {'message': record.message, 'date': record.asctime, 'level': record.levelname}})
try:
asyncio.get_event_loop().run_until_complete(async_send(msg))
except ConnectionRefusedError:
pass
def send_to_socket(msg_dict):
msg = json.dumps(msg_dict)
try:
asyncio.get_event_loop().run_until_complete(async_send(msg))
except ConnectionRefusedError:
pass
Now program spend about 1 - 1.2 sec for request. I try
con = websockets.connect('wss://****.com/chat')
con.send('some thing')
but have error AttributeError: 'Connect' object has no attribute 'send'
python
import asyncio
import websockets
from logging import StreamHandler
import json
import time
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
#singleton
class SendToWebSocket:
"""
Send message in web-socket, use one connection for sending.
Try make new connection, if old is lost.
"""
__ws = None
__url = "wss://***.com/chat"
def __init__(self):
self.retryTime = 0
self.retryRepeat = 30
self.__create_connect()
#asyncio.coroutine
def __create_connect(self):
if (time.time() - self.retryTime) > self.retryRepeat:
try:
self.__ws = yield from websockets.connect(self.__url)
self.retryTime = 0
except ConnectionRefusedError:
self.retryTime = time.time()
def send(self, message):
t = type(message)
if t is dict:
msg = json.dumps(message)
elif t is str:
msg = message
else:
raise ValueError("Message must be str or dict. Received %s" % type(t))
if self.__ws is not None:
try:
asyncio.get_event_loop().run_until_complete(self.__async_send(msg))
# print('Send normal')
except ConnectionRefusedError:
# print("Can't send")
# try recreate connect
self.__create_connect()
else:
asyncio.get_event_loop().run_until_complete(self.__create_connect())
async def __async_send(self, message):
await self.__ws.send(message)
class WebSocketHandler(StreamHandler):
"""Custom handler for logging library"""
def __init__(self):
StreamHandler.__init__(self)
self.web_socket = SendToWebSocket()
def emit(self, record):
msg = json.dumps({'log': {'message': record.message, 'date': record.asctime, 'level': record.levelname}})
try:
self.web_socket.send(msg)
except ConnectionRefusedError:
pass
I want to send data through websockets as soon as a client is connected.
The Data is at an other place then the Websocket Handler. How can i get the data to the client ?
The server should hold the loop and the Handler. In the connector i connect to a tcp socket to get the data out of some hardware. I expect to have not more then 6 Websockets open once a time. The Data comes as a stream out of the TCP socket.
server.py
import os
from tornado import web, websocket
import asyncio
import connector
class StaticFileHandler(web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
def get(self):
self.render('index.html')
class WSHandler(websocket.WebSocketHandler):
def open(self):
print('new connection')
self.write_message("connected")
def on_message(self, message):
print('message received %s' % message)
self.write_message("pong")
def on_close(self):
print('connection closed')
public_root = 'web_src'
handlers = [
(r'/', StaticFileHandler),
(r'/ws', WSHandler),
]
settings = dict(
template_path = os.path.join(os.path.dirname(__file__), public_root),
static_path = os.path.join(os.path.dirname(__file__), public_root),
debug = True
)
app = web.Application(handlers, **settings)
sensorIP = "xxx.xxx.xxx.xxx"
if __name__ == "__main__":
app.listen(8888)
asyncio.ensure_future(connector.main_task(sensorIP))
asyncio.get_event_loop().run_forever()
connector.py
import yaml
import asyncio
class RAMReceiver:
def __init__(self, reader):
self.reader = reader
self.remote_data = None
self.initParams = None
async def work(self):
i = 0
while True:
data = await self.reader.readuntil(b"\0")
self.remote_data = yaml.load(data[:-1].decode("utf-8",
"backslashreplace"))
# here i want to emit some data
# send self.remote_data to websockets
if i == 0:
i += 1
self.initParams = self.remote_data
# here i want to emit some data after open event is
# triggered
# send self.initParams as soon as a client has connected
async def main_task(host):
tasks = []
(ram_reader,) = await asyncio.gather(asyncio.open_connection(host,
51000))
receiver = RAMReceiver(ram_reader[0])
tasks.append(receiver.work())
while True:
await asyncio.gather(*tasks)
You can use Tornado's add_callback function to call a method on your websocket handler to send the messages.
Here's an example:
1. Create an additional method on your websocket handler which will receive a message from connector.py and will send to connected clients:
# server.py
class WSHandler(websocket.WebSocketHandler):
# make it a classmethod so that
# it can be accessed directly
# from class without `self`
#classmethod
async def send_data(cls, data):
# write your code for sending data to client
2. Pass the currently running IOLoop and WSHandler.send_data to your connector.py:
# server.py
from tornado import ioloop
...
if __name__ == "__main__":
...
io_loop = ioloop.IOLoop.current() # current IOLoop
callback = WSHandler.send_data
# pass io_loop and callback to main_task
asyncio.ensure_future(connector.main_task(sensorIP, io_loop, callback))
...
3. Then modify main_task function in connector.py to receive io_loop and callback. Then pass io_loop and callback to RAMReceiver.
4. Finally, use io_loop.add_callback to call WSHandler.send_data:
class RAMReceiver:
def __init__(self, reader, io_loop, callback):
...
self.io_loop = io_loop
self.callback = callback
async def work(self):
...
data = "Some data"
self.io_loop.add_callback(self.callback, data)
...
The HandlerWebsockets does work fine and is just replying with what has been send at the moment throught messageToSockets(msg). However both tries to send messages to the websocket from the coroutine of the web application are not working. Looks like everything is blocked by these attempts...
class webApplication(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', HandlerIndexPage),
(r'/websocket', HandlerWebSocket, dict(msg='start')),
]
settings = {
'template_path': 'templates'
}
tornado.web.Application.__init__(self, handlers, **settings)
#gen.coroutine
def generateMessageToSockets(self):
while True:
msg = str(randint(0, 100))
print ('new messageToCon: ', msg)
yield [con.write_message(msg) for con in HandlerWebSocket.connections]
yield gen.sleep(1.0)
if __name__ == '__main__':
ws_app = webApplication()
server = tornado.httpserver.HTTPServer(ws_app)
port = 9090
print('Listening on port:' + str(port))
server.listen(port)
IOLoop.current().spawn_callback(webApplication.generateMessageToSockets)
IOLoop.current().set_blocking_log_threshold(0.5)
IOLoop.instance().start()
Here the WebSockets Handler
class HandlerWebSocket(tornado.websocket.WebSocketHandler):
connections = set()
def initialize(self, msg):
print('HWS:' + msg)
def messageToSockets(self, msg):
print ('return message: ', msg)
[con.write_message(msg) for con in self.connections]
def open(self):
self.connections.add(self)
print ('new connection was opened')
pass
def on_message(self, message):
print ('from WebSocket: ', message)
self.messageToSockets(message)
def on_close(self):
self.connections.remove(self)
print ('connection closed')
pass
I am a bit lost in the examples, questions here, documentation etc. So any hint how to properly start a continous calling websocket routine is greatly appreciated
generateMessageToSockets will loop endlessly, generating messages as fast as it can without waiting for those messages to be sent. Since it starts first and never yields, the HTTPServer will never actually be able to accept a connection.
If you really want to send messages as fast as you can, the minimal solution without blocking would be
yield [con.write_message(msg) for con in HandlerWebSocket.connections]
yield gen.moment
But it would probably be better to use gen.sleep to send messages at regular intervals, instead of "as fast as possible".
unfortunately all the gen.routines tries didn't work for me. Moved back to threads
def generateMessageToSockets():
while True:
msg = str(randint(0, 100))
print ('new messageToCon: ', msg)
[con.write_message(msg) for con in HandlerWebSocket.connections]
sleep(1.0)
class WebApplication(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', HandlerIndexPage),
(r'/websocket', HandlerWebSocket, dict(msg='start')),
]
settings = {
'template_path': 'templates'
}
tornado.web.Application.__init__(self, handlers, **settings)
if __name__ == '__main__':
tGenarate = threading.Thread(target=generateMessageToSockets)
tGenarate.start()
ws_app = WebApplication()
server = tornado.httpserver.HTTPServer(ws_app)
port = 9090
print('Listening on port:' + str(port))
server.listen(port)
ioloop.IOLoop.instance().start()
which works