How to receive data from multiple WebSockets asynchronously in Python? - python

I am currently trying to use the websockets library. If another library is better suited for this purpose, let me know.
Given these functions:
def handle_message(msg):
# do something
async def consumer_handler(websocket, path):
async for message in websocket:
handle_message(message)
How can I (indefinitely) connect to multiple websockets? Would the below code work?
import asyncio
import websockets
connections = set()
connections.add(websockets.connect(consumer_handler, 'wss://api.foo.com', 8765))
connections.add(websockets.connect(consumer_handler, 'wss://api.bar.com', 8765))
connections.add(websockets.connect(consumer_handler, 'wss://api.foobar.com', 8765))
async def handler():
await asyncio.wait([ws for ws in connections])
asyncio.get_event_loop().run_until_complete(handler())

For anyone who finds this, I found an answer. Only works in > Python 3.6.1 I believe.
import asyncio
import websockets
connections = set()
connections.add('wss://api.foo.com:8765')
connections.add('wss://api.bar.com:8765'))
connections.add('wss://api.foobar.com:8765'))
async def handle_socket(uri, ):
async with websockets.connect(uri) as websocket:
async for message in websocket:
print(message)
async def handler():
await asyncio.wait([handle_socket(uri) for uri in connections])
asyncio.get_event_loop().run_until_complete(handler())

Instead of:
connections = set()
I would list it:
connections = []
connections = ["wss://api.foo.com:8765"]
connections.append ("wss://api.bar.com:8765")
connections.append ("wss://api.foobar.com:8765")

Related

How to implement recv and send at one time correctly

I am trying to messing up with Websockets module and after checking the main page:
https://websockets.readthedocs.io/en/stable/intro.html
I did following:
SERVER
# SERVER
import asyncio
import websockets
import nest_asyncio
USERS = {}
async def set_online(websocket, user_name):
USERS[user_name] = websocket
await notify()
async def set_offline(websocket, user_name):
USERS.pop(user_name, None)
await notify()
async def notify():
if USERS:
message = "Online users: {}\n".format(len(USERS))
print (message)
#await asyncio.wait([user.send(message) for user in USERS])
else:
message = "Online users: 0\n"
print (message)
async def server(websocket, path):
user_name = await websocket.recv()
await set_online(websocket, user_name)
try:
async for message in websocket:
for user_name, user_ws in USERS.items():
if websocket == user_ws:
print (f"{user_name}: {message}")
finally:
await set_offline(websocket, user_name)
start_server = websockets.serve(server, "localhost", 3000,
ping_interval=None)
nest_asyncio.apply()
loop = asyncio.get_event_loop()
loop.run_until_complete(start_server)
loop.run_forever()
and also:
CLIENT
# CLIENT
import asyncio
import websockets
import nest_asyncio
async def client(localhost, port):
uri = "ws://{0}:{1}".format(localhost, str(port))
async with websockets.connect(uri) as websocket:
user_name = input("set your name: ")
await websocket.send(f"{user_name}")
while True:
message = input("> ")
if message == "/quit":
break
else:
await websocket.send(message)
host = "localhost"
port = 3000
nest_asyncio.apply()
loop = asyncio.get_event_loop()
loop.run_until_complete(client(host, port))
so all works as expected but I would like to achieve that each user can receive the answer as well from other users.
I found there is a conflict when I want to use websocket.send(message) in for loop async for message in websocket: on SERVER side
The link which I paste above, I think has a solution but I am struggling to figure out how to use it properly in my script.
I believe I need to create two tasks (send and recv) which will work in parallel.
Like:
async def handler(websocket, path):
consumer_task = asyncio.ensure_future(consumer_handler(websocket, path))
producer_task = asyncio.ensure_future(producer_handler(websocket, path))
done, pending = await asyncio.wait([consumer_task, producer_task],return_when=asyncio.FIRST_COMPLETED)
for task in pending:
task.cancel()
the following is displayed on the website which I provided above, just one thing needs to be changed from asyncio.ensure_future to asyncio.create_task. I implemented function handler, producer, consumer, producer_handler and consumer_handler to make it works but no luck.
Could someone provide an example or how this should be set up correctly?
I believe asyncio.create_task should be used on both (SERVER and CLIENT) so they both receive and send at one time.
This is pretty long but I hope someone can help me with it and also maybe my part of script will be handy for someone as well!

How to use only one session for all the lifecycle of my application with Aiohttp?

I'm making a python module for interacting with an API. I'd like it to be fast, so I chose to use asyncio and Aiohttp. I'm quite new to async programming and I'm not quite sure how to reuse the same session for every request. Also, I'd like to spare my end-users the hassle of creating the loop etc. I came up with this class for my base client:
import asyncio
import aiohttp
class BaseClient:
API_BASE_URL = "dummyURL"
API_VERSION = 3
async def __aenter__(self):
self._session = aiohttp.ClientSession(raise_for_status=True)
return self
async def __aexit__(self, exc_type, exc, tb):
await self._session.close()
#remove the next line when aiohttp 4.0 is released
await asyncio.sleep(0.250)
async def _get(self, endpoint: str) -> None:
url = f"{self.API_BASE_URL}/{endpoint}/?v={self.API_VERSION}"
async with self._session.get(url) as resp:
json_body = await resp.json()
return json_body
async def list_forums(self):
endpoint = "forums"
return await self._get(endpoint)
async def main():
async with BaseClient() as client:
forums = await client.list_forums()
print(forums)
asyncio.run(main())
Is that the right way to reuse the same session? Is it possible to refactor BaseClient in such a way my end-users would only have to dothe following:
client = BaseClient()
forums = client.list_forums()
Thanks for your help.

Python Websocket only receives once from client

I want to send values from a for loop from the client-server but the server only receives the first value and the connection is cut shortly
Client
import asyncio
import websockets
import time
async def message():
async with websockets.connect("ws://-------:5051") as socket:
for i in range(20):
await socket.send(f"{i}")
print(i)
time.sleep(4)
asyncio.get_event_loop().run_until_complete(message())
Server
import asyncio
import websockets
async def consumer_handler(websocket,path):
client_type = await websocket.recv()
print(client_type)
start_server = websockets.serve(consumer_handler,"ws://-------:5051", 5051)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
So, your consumer_handler receives message once and finishes.
You need to add loop.
Try something like this:
async def consumer_handler(websocket, path):
async for msg in websocket:
print(msg)

Simple way to test websocket availability in python

I am using the following code to test that a local websocket server is running:
import asyncio
import websockets
async def hello():
async with websockets.connect('ws://127.0.0.1:8000/ws/registration/123') as websocket:
await websocket.send(json_data)
asyncio.get_event_loop().run_until_complete(hello())
Is there a simpler way to do this without using asyncio? Something such as:
import asyncio
import websockets
conn = websockets.connect('ws://127.0.0.1:8000/ws/registration/123')
conn.send('hello')
Basically, I'm just trying to find the simplest way to test to see if my websocket server is listening and receiving messages at a particular url.
Doesn't async_to_sync make this more complex? Why not just create a normal test_url function:
def test_url(url, data=""):
async def inner():
async with websockets.connect(url) as websocket:
await websocket.send(data)
return asyncio.get_event_loop().run_until_complete(inner())
test_url("ws://127.0.0.1:8000/ws/registration/123")
You can do the above by using async_to_sync, for example:
from asgiref.sync import async_to_sync
import websockets
def test_url(url, data=""):
conn = async_to_sync(websockets.connect)(url)
async_to_sync(conn.send)(data)
test_url("ws://127.0.0.1:8000/ws/registration/123")
Note that the "handshake" will probably not complete here because it needs to be accepted both ways, but the above should enable you to test to make sure that the urls are being routed properly, etc.

listen to multiple socket with websockets and asyncio

I am trying to create a script in python that listens to multiple sockets using websockets and asyncio, the problem is that no matter what I do it only listen to the first socket I call.
I think its the infinite loop, what are my option to solve this? using threads for each sockets?
async def start_socket(self, event):
payload = json.dumps(event)
loop = asyncio.get_event_loop()
self.tasks.append(loop.create_task(
self.subscribe(event)))
# this should not block the rest of the code
await asyncio.gather(*tasks)
def test(self):
# I want to be able to add corotines at a different time
self.start_socket(event1)
# some code
self.start_socket(event2)
this is what I did eventually, that way its not blocking the main thread and all subscriptions are working in parallel.
def subscribe(self, payload):
ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
ws.connect(url)
ws.send(payload)
while True:
result = ws.recv()
print("Received '%s'" % result)
def start_thread(self, loop):
asyncio.set_event_loop(loop)
loop.run_forever()
def start_socket(self, **kwargs):
worker_loop = asyncio.new_event_loop()
worker = Thread(target=self.start_thread, args=(worker_loop,))
worker.start()
worker_loop.call_soon_threadsafe(self.subscribe, payload)
def listen(self):
self.start_socket(payload1)
# code
self.start_socket(payload2)
# code
self.start_socket(payload3)
Your code appears incomplete, but what you've shown has two issues. One is that run_until_complete accepts a coroutine object (or other kind of future), not a coroutine function. So it should be:
# note parentheses after your_async_function()
asyncio.get_event_loop().run_until_complete(your_async_function())
the problem is that no matter what I do it only listen to the first socket I call. I think its the infinite loop, what are my option to solve this? using threads for each sockets?
The infinite loop is not the problem, asyncio is designed to support such "infinite loops". The problem is that you are trying to do everything in one coroutine, whereas you should be creating one coroutine per websocket. This is not a problem, as coroutines are very lightweight.
For example (untested):
async def subscribe_all(self, payload):
loop = asyncio.get_event_loop()
# create a task for each URL
for url in url_list:
tasks.append(loop.create_task(self.subscribe_one(url, payload)))
# run all tasks in parallel
await asyncio.gather(*tasks)
async def subsribe_one(self, url, payload):
async with websockets.connect(url) as websocket:
await websocket.send(payload)
while True:
msg = await websocket.recv()
print(msg)
One way to efficiently listen to multiple websocket connections from a websocket server is to keep a list of connected clients and essentially juggle multiple conversations in parallel.
E.g. A simple server that sends random # to each connected client every few secs:
import os
import asyncio
import websockets
import random
websocket_clients = set()
async def handle_socket_connection(websocket, path):
"""Handles the whole lifecycle of each client's websocket connection."""
websocket_clients.add(websocket)
print(f'New connection from: {websocket.remote_address} ({len(websocket_clients)} total)')
try:
# This loop will keep listening on the socket until its closed.
async for raw_message in websocket:
print(f'Got: [{raw_message}] from socket [{id(websocket)}]')
except websockets.exceptions.ConnectionClosedError as cce:
pass
finally:
print(f'Disconnected from socket [{id(websocket)}]...')
websocket_clients.remove(websocket)
async def broadcast_random_number(loop):
"""Keeps sending a random # to each connected websocket client"""
while True:
for c in websocket_clients:
num = str(random.randint(10, 99))
print(f'Sending [{num}] to socket [{id(c)}]')
await c.send(num)
await asyncio.sleep(2)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
try:
socket_server = websockets.serve(handle_socket_connection, 'localhost', 6789)
print(f'Started socket server: {socket_server} ...')
loop.run_until_complete(socket_server)
loop.run_until_complete(broadcast_random_number(loop))
loop.run_forever()
finally:
loop.close()
print(f"Successfully shutdown [{loop}].")
A simple client that connects to the server and listens for the numbers:
import asyncio
import random
import websockets
async def handle_message():
uri = "ws://localhost:6789"
async with websockets.connect(uri) as websocket:
msg = 'Please send me a number...'
print(f'Sending [{msg}] to [{websocket}]')
await websocket.send(msg)
while True:
got_back = await websocket.recv()
print(f"Got: {got_back}")
asyncio.get_event_loop().run_until_complete(handle_message())
Mixing up threads and asyncio is more trouble than its worth and you still have code that will block on the most wasteful steps like network IO (which is the essential benefit of using asyncio).
You need to run each coroutine asynchronously in an event loop, call any blocking calls with await and define each method that interacts with any awaitable interactions with an async
See a working e.g.: https://github.com/adnantium/websocket_client_server

Categories