I'm trying to implement a websocket client in python using websockets
and the apparently mandatory asyncio
which I never used before (and I have a hard time to understand...).
I've read a lot on the subject and saw (too) many examples here and everywhere, but I can't find a way to properly make a websocket client with a persistent connection.
Here's what I have right now, based on examples I found here and there (with some obfuscated data) :
import json
import asyncio
from websockets import connect
URL = 'wss://server.com/endpoint'
class Websocket:
async def __aenter__(self):
self._conn = connect(URL)
self.websocket = await self._conn.__aenter__()
return self
async def __aexit__(self, *args, **kwargs):
await self._conn.__aexit__(*args, **kwargs)
async def send(self, message):
await self.websocket.send(message)
async def receive(self):
return await self.websocket.recv()
class Handler:
def __init__(self):
self.wws = Websocket()
self.loop = asyncio.get_event_loop()
def command(self, cmd):
return self.loop.run_until_complete(self.__async__command(cmd))
async def __async__command(self, cmd):
async with self.wws as echo:
await echo.send(json.dumps(cmd))
return await echo.receive()
def main():
handler = Handler()
foo = handler.command('authentication command')
print('auth: ', foo)
bar = handler.command('another command to run depending on the first authentication')
print('command: ', bar)
if __name__ == '__main__':
main()
Basically right now I get these answers (simplified and obfuscated) :
auth: Ok, authenticated
command: Command refused, not authenticated
I suppose my problem is that the block async with self.wws as echo:
kind of create the connection, runs its code then drop it instead of keeping the connection alive. Since we are not using a usual __init__
here but some asyncio
voodoo I don't understand, I'm kind of stuck.
I think your diagnosis is correct, the problem is that the async context manager it creating and closing a connection for each call of Handler.command
... really not want you want.
Instead you could just synchronously establish the websocket connection during the init of Handler
and then store the connection websocket (instance of type WebSocketClientProtocol
) as a class member for later use, as in this sample code:
import json
import asyncio
from websockets import connect
URL = 'ws://localhost:8000'
class Handler:
def __init__(self):
self.ws = None
self.loop = asyncio.get_event_loop()
# perform a synchronous connect
self.loop.run_until_complete(self.__async__connect())
async def __async__connect(self):
print("attempting connection to {}".format(URL))
# perform async connect, and store the connected WebSocketClientProtocol
# object, for later reuse for send & recv
self.ws = await connect(URL)
print("connected")
def command(self, cmd):
return self.loop.run_until_complete(self.__async__command(cmd))
async def __async__command(self, cmd):
await self.ws.send(json.dumps(cmd))
return await self.ws.recv()
def main():
handler = Handler()
foo = handler.command('authentication command')
print('auth: ', foo)
bar = handler.command('another command to run depending on the first authentication')
print('command: ', bar)
if __name__ == '__main__':
main()