Quart is a Python web framework which re-implements the Flask API on top of the asyncio coroutine system of Python. In my particular case, I have a Quart websocket endpoint which is supposed to have not just one source of incoming events, but two possible sources of events which are supposed to continue the asynchronous loop.
An example with one event source:
from quart import Quart, websocket
app = Quart(__name__)
#app.websocket("/echo")
def echo():
while True:
incoming_message = await websocket.receive()
await websocket.send(incoming_message)
Taken from https://pgjones.gitlab.io/quart/
This example has one source: the incoming message stream. But what is the correct pattern if I had two possible sources, one being await websocket.receive() and another one being something along the lines of await system.get_next_external_notification() .
If either of them arrives, I'd like to send a websocket message.
I think I'll have to use asyncio.wait(..., return_when=FIRST_COMPLETED), but how do I make sure that I miss no data (i.e. for the race condition that websocket.receive() and system.get_next_external_notification() both finish almost exactly at the same time) ? What's the correct pattern in this case?
An idea you could use is a Queue to join the events together from different sources, then have an async function listening in the background to that queue for requests. Something like this might get you started:
import asyncio
from quart import Quart, websocket
app = Quart(__name__)
#app.before_serving
async def startup():
print(f'starting')
app.q = asyncio.Queue(1)
asyncio.ensure_future(listener(app.q))
async def listener(q):
while True:
returnq, msg = await q.get()
print(msg)
await returnq.put(f'hi: {msg}')
#app.route("/echo/<message>")
async def echo(message):
while True:
returnq = asyncio.Queue(1)
await app.q.put((returnq, message))
response = await returnq.get()
return response
#app.route("/echo2/<message>")
async def echo2(message):
while True:
returnq = asyncio.Queue(1)
await app.q.put((returnq, message))
response = await returnq.get()
return response
Related
I want to send two different messages to a websocket server but with different time intervalls.
For example:
The first message should be send every 2 seconds.
The second message should send every 5 seconds.
async def send_first_message(websocket):
while True:
await websocket.send("FIRST MESSAGE")
response = await websocket.recv()
await asyncio.sleep(2)
async def send_second_message():
while True:
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
asyncio.create_task(send_first_message(websocket))
while True:
await websocket.send("SECOND MESSAGE")
response = await websocket.recv()
await asyncio.sleep(5)
asyncio.run(send_second_message())
If I run the code like this I get:
"RuntimeError: cannot call recv while another coroutine is already waiting for the next message"
If I comment out one of the "await websocket.recv()" it works fine for a few seconds and then it throws:
"RuntimeError no close frame received or sent"
There's a bit of a disconnect between what you are trying to do in the tasks (synchronous request-response interaction) and what the protocol and the library expects you to do (asynchronous messages).
When writing asynchronous code, you need to look at what the library/protocol/service expects to be an atomic operation that can happen asynchronously to everything else, and what you want to be a synchronous series of operations. Then you need to find the primitive in the library that will support that. In the case of websockets, the atomic operation is a message being sent in either direction. So you can't expect websockets to synchronize flow over two messages.
Or to put it another way, you are expecting synchronous responses for each send message, but websockets are not designed to handle interleaved synchronous requests. You've sent a message to the websocket server, and you want to get a response to that message. But you've also sent another message on the same websocket and want a response to that too. Your client websocket library can't differentiate between a message intended for the first request and a message intended for the second request (because from the websocket protocol layer, that is a meaningless concept - so the library enforces this by limiting the recv operations on a websocket that can be blocking to one).
So ...
Option 1 - multiple tasks on separate sockets
From the fact the library limits a websocket to one blocking recv, a primitive in the protocol that meets the requirement is the websocket itself. If these are separate requests that you need separate blocking responses to (so only continue in the requesting task once those responses are available) then you could have separate websocket connections and block for the response in each.
client1.py
async def send_first_message():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
while True:
await websocket.send("FIRST MESSAGE")
response = await websocket.recv()
print(response)
await asyncio.sleep(2)
async def send_second_message():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
while True:
await websocket.send("SECOND MESSAGE")
response = await websocket.recv()
print(response)
await asyncio.sleep(5)
async def main():
asyncio.create_task(send_first_message())
asyncio.create_task(send_second_message())
await asyncio.Future()
asyncio.run(main())
Option 1 is however not really the websocket or asynchronous way.
Option 2 - embrace the asynchronous
To do this on a single websocket, you will need to receive the response asynchronous to both sending tasks.
If you don't actually care that the send_* functions get the response, you can do this easily...
client2.py
async def send_first_message(websocket):
while True:
await websocket.send("FIRST MESSAGE")
await asyncio.sleep(2)
async def send_second_message(websocket):
while True:
await websocket.send("SECOND MESSAGE")
await asyncio.sleep(5)
async def receive_message(websocket):
while True:
response = await websocket.recv()
print(response)
async def main():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
asyncio.create_task(send_first_message(websocket))
asyncio.create_task(send_second_message(websocket))
asyncio.create_task(receive_message(websocket))
await asyncio.Future()
asyncio.run(main())
Option 3
But what if you want to line up responses to requests and keep on a single websocket? You need some way of knowing which request any particular response is for. Most web services that need this sort of interaction will have you send an ID in the message to the server, and it will respond once a response is ready using the ID as a reference.
There's also a way of getting your message tasks to block and wait for the response with the right ID by queuing up the responses and checking them periodically.
client3.py
unhandled_responses = {}
async def send_first_message(websocket):
while True:
req_id = random.randint(0,65535)
message = json.dumps({'id': req_id, 'message': 'FIRST MESSAGE'})
await websocket.send(message)
response = await block_for_response(req_id)
print(response)
await asyncio.sleep(2)
async def send_second_message(websocket):
while True:
req_id = random.randint(0,65535)
message = json.dumps({'id': req_id, 'message': 'SECOND MESSAGE'})
await websocket.send(message)
response = await block_for_response(req_id)
print(response)
await asyncio.sleep(5)
async def block_for_response(id):
while True:
response = unhandled_responses.pop(id, None)
if response:
return response
await asyncio.sleep(0.1)
async def receive_message(websocket):
while True:
response = json.loads(await websocket.recv())
unhandled_responses[response['id']] = response
async def main():
async with websockets.connect(f"ws://{IP}:{PORT}") as websocket:
asyncio.create_task(send_first_message(websocket))
asyncio.create_task(send_second_message(websocket))
asyncio.create_task(receive_message(websocket))
await asyncio.Future()
asyncio.run(main())
For completeness, the server code the clients were talking to in my tests.
server.py
import asyncio
import websockets
async def server_endpoint(websocket):
try:
while True:
recv_msg = await websocket.recv()
response = recv_msg
await websocket.send(response)
except Exception as ex:
print(str(ex))
async def main():
async with websockets.serve(server_endpoint, "localhost", 8765):
await asyncio.Future() # run forever
if __name__ == "__main__":
asyncio.run(main())
Using Quart I am trying to receive data from one client via a websocket, then have the Quart websocket server send it to a different client via websocket.
The two clients will be alone sharing the same url, other pairs of clients will have their own urls. This echo test works for both clients individually:
#copilot_ext.websocket('/ws/<unique_id>')
async def ws(unique_id):
while True:
data = await websocket.receive()
await websocket.send(f"echo {data}")
I have tried broadcasting using the example here https://pgjones.gitlab.io/quart/tutorials/websocket_tutorial.html#broadcasting although I can catch and print the different websockets, have not had much luck sending data from one client to the other :(
connected_websockets = set()
def collect_websocket(func):
#wraps(func)
async def wrapper(*args, **kwargs):
global connected_websockets
send_channel, receive_channel = trio.open_memory_channel(2)
connected_websockets.add(send_channel)
try:
return await func(send_channel, *args, **kwargs)
finally:
connected_websockets.remove(send_channel)
return wrapper
#copilot_ext.websocket('/ws/<unique_id>')
#collect_websocket
async def ws(que, unique_id):
while True:
data = await websocket.receive()
for send_channel in connected_websockets:
await send_channel.send(f"message {data}")
print(send_channel)
Just storing the websocket object and iterating through them doesn't work either
connected_websockets = set()
#copilot_ext.websocket('/ws/<unique_id>')
async def ws(unique_id):
global connected_websockets
while True:
data = await websocket.receive()
connected_websockets.add(websocket)
for websockett in connected_websockets:
await websockett.send(f"message {data}")
print(type(websockett))
I think this snippet can form the basis of what you want to achieve. The idea is that rooms are a collection of queues keyed by the room id. Then each connected client has a queue in the room which any other clients put messages to. The send_task then runs in the background to send any messages to the client that are on its queue. I hope this makes sense,
import asyncio
from collections import defaultdict
from quart import Quart, websocket
app = Quart(__name__)
websocket_rooms = defaultdict(set)
async def send_task(ws, queue):
while True:
message = await queue.get()
await ws.send(message)
#app.websocket("/ws/<id>/")
async def ws(id):
global websocket_rooms
queue = asyncio.Queue()
websocket_rooms[id].add(queue)
try:
task = asyncio.ensure_future(send_task(websocket._get_current_object(), queue))
while True:
message = await websocket.receive()
for other in websocket_rooms[id]:
if other is not queue:
await other.put(message)
finally:
task.cancel()
await task
websocket_rooms[id].remove(queue)
I've tried setting up a WebSocket server, and an API to access it via browser
I'm a week new into Python, and I hope this somewhat makes sense.
I have a Server class
**Server.py**
# WS server that sends messages at random intervals
import asyncio
import datetime
import random
import websockets
async def time(websocket, path):
while True:
now = datetime.datetime.utcnow().isoformat() + "Z"
await websocket.send(now)
await asyncio.sleep(random.random() * 3)
async def echo(websocket, path):
async for message in websocket:
await websocket.send(message)
async def router(websocket, path):
if path == "/get":
await time(websocket)
elif path == "/post":
await echo(websocket)
asyncio.get_event_loop().run_until_complete(
websockets.serve(router, "127.0.0.1", 8081, ping_interval=None))
asyncio.get_event_loop().run_forever()
and an API class
**API.py**
from quart import Quart
import json
import websockets
app = Quart(__name__)
#app.route('/get',methods=['GET'])
async def foo1():
uri = "ws://localhost:8081"
async with websockets.connect(uri) as websocket:
try:
receivedMessage = await websocket.recv()
print(f"< {receivedMessage}")
except:
print('Reconnecting')
websocket = await websockets.connect(uri)
return ' '
#app.route('/post', methods=['POST'])
async def foo2():
uri = "ws://localhost:8081"
async with websockets.connect(uri) as websocket:
await websocket.send("Hello world!")
try:
await websocket.recv()
except:
print('Reconnecting')
websocket = await websockets.connect(uri)
if __name__ == '__main__':
app.run()
My aim was to be able to access different functions from the Server, based on the API's Path which was being accessed (seen it here: https://github.com/aaugustin/websockets/issues/547)
The problem I'm getting is that "Reconnecting" appears constantly, and nothing actually happens when I make use of /get or /post (i.e. it seems to be closing after .recv() occurs - I know this because I had seen ConnectionClosedOk - no reason 1000 earlier.
I really don't understand what I'm doing right now lol, it is very confusing. I did get things to work when Server only had 1 function (constantly spitting out timestamps), and the API only had a GET function (to display the current time of the request).. things now don't really work after I implemented the router function.
Any help would be greatly, greatly appreciated. My Python knowledge is incredibly limited, so as verbose an answer as possible would really help me out.
I'm assuming that the POST method is going to result in "Hello world!" appearing on the console for the Server, but I'm probably wrong about that :( I just seen it in some tutorial documentation.
EDIT: Also, I'm not able to access localhost:(port)/post; it says Specified method is invalid for this resource; why is that?
I am running a program that makes three different requests from a rest api. data, indicator, request functions all fetch data from BitMEX's api using a wrapper i've made.
I have used asyncio to try to speed up the process so that while i am waiting on a response from previous request, it can begin to make another one.
However, my asynchronous version is not running any quicker for some reason. The code works and as far as I know, I have set everything up correctly. But there could be something wrong with how I am setting up the coroutines?
Here is the asynchronous version:
import time
import asyncio
from bordemwrapper import BitMEXData, BitMEXFunctions
'''
asynchronous I/O
'''
async def data():
data = BitMEXData().get_ohlcv(symbol='XBTUSD', timeframe='1h',
instances=25)
await asyncio.sleep(0)
return data
async def indicator():
indicator = BitMEXData().get_indicator(symbol='XBTUSD',
timeframe='1h', indicator='RSI', period=20, source='close',
instances=25)
await asyncio.sleep(0)
return indicator
async def request():
request = BitMEXFunctions().get_price()
await asyncio.sleep(0)
return request
async def chain():
data_ = await data()
indicator_ = await indicator()
request_ = await request()
return data_, indicator_, request_
async def main():
await asyncio.gather(chain())
if __name__ == '__main__':
start = time.perf_counter()
asyncio.run(main())
end = time.perf_counter()
print('process finished in {} seconds'.format(end - start))
Unfortunately, asyncio isn't magic. Although you've put them in async functions, the BitMEXData().get_<foo> functions are not themselves async (i.e. you can't await them), and therefore block while they run. The concurrency in asyncio can only occur while awaiting something.
You'll need a library which makes the actual HTTP requests asynchronously, like aiohttp. It sounds like you wrote bordemwrapper yourself - you should rewrite the get_<foo> functions to use asynchronous HTTP requests. Feel free to submit a separate question if you need help with that.
http://websockets.readthedocs.io/en/stable/intro.html#consumer contains the following example:
async def consumer_handler(websocket, path):
while True:
message = await websocket.recv()
await consumer(message)
and http://websockets.readthedocs.io/en/stable/intro.html#producer
async def producer_handler(websocket, path):
while True:
message = await producer()
await websocket.send(message)
But there is no example for consumer() and producer() implementation or any explanation. Can somebody provide any simple example for that?
In the first example, consumer_handler listens for the messages from a websocket connection. It then passes the messages to a consumer. In its simplest form, a consumer can look like this:
async def consumer(message):
# do something with the message
In the second example, producer_handler receives a message from a producer and sends it to the websocket connection. A producer can look like this:
async def producer():
message = "Hello, World!"
await asyncio.sleep(5) # sleep for 5 seconds before returning message
return message