Python websockets wait for a response - python

I'm trying to do a client/server with websockets and I want that when the client sends a message, it needs to wait to receive a response from the server. So the client can never send two messages in a row without a previous response.
I tried using Lock from asyncio but it doesn't work. Here's server.py:
async def runServer(self):
server = await websockets.serve(
self.onConnect,
"localhost",
port=8765
)
print("Server started listening to new connections...")
await server.wait_closed()
async def onConnect(self, ws):
try:
while True:
message = await ws.recv()
if message == "...":
...
And here is client.py
async def connect(self):
try:
lock = asyncio.Lock()
async with websockets.connect(
"ws://localhost:8765",
) as ws:
print("Connected to the switch.")
async with lock:
await ws.send("First message")
await ws.send("Second message")
while True:
message = await ws.recv()
print(f"Message received : {message}")
except Exception as e:
print(e)
And I would want the client to wait for a response from the first message and after that to send the second message.

Try this example:
server.py (server waits for msg1 and msg2 and responds accordingly):
async def runServer():
server = await websockets.serve(onConnect, "localhost", port=8765)
print("Server started listening to new connections...")
await server.wait_closed()
async def onConnect(ws):
try:
while True:
message = await ws.recv()
if message == "msg1":
await ws.send("response from server 1")
elif message == "msg2":
await ws.send("response from server 2")
except websockets.exceptions.ConnectionClosedOK:
print("Client closed...")
if __name__ == "__main__":
asyncio.run(runServer())
client.py (first sends msg1, prints response and afterwards sends msg2 and prints response):
async def connect():
async with websockets.connect(
"ws://localhost:8765",
) as ws:
print("Connected to the switch.")
await ws.send("msg1")
response = await ws.recv()
print("Response from the server:", response)
await ws.send("msg2")
response = await ws.recv()
print("Response from the server:", response)
if __name__ == "__main__":
asyncio.run(connect())
The clients prints:
Connected to the switch.
Response from the server: response from server 1
Response from the server: response from server 2

Related

Python asyncio http server

I try to build a base http server with the following code.
async def handle_client(client, address):
print('connection start')
data = await loop.sock_recv(client, 1024)
resp = b'HTTP/1.1 404 NOT FOUND\r\n\r\n<h1>404 NOT FOUND</h1>'
await loop.sock_sendall(client, resp)
client.close()
async def run_server():
while True:
client, address = await loop.sock_accept(server)
print('start')
loop.create_task(handle_client(client,address))
print(client)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 3006))
server.listen(8)
print(1)
loop = asyncio.get_event_loop()
loop.run_until_complete(run_server())
The output I expect to get is
1
start
connection start
But the actual result of running is
1
start
start
start
It seems that the function in loop.create_task() is not being run, so now I got confuesed., what is the correct way to use loop.create_task()?
You need to await the task that is created via loop.create_task(), otherwise run_server() will schedule the task and then just exit before the result has been returned.
Try changing run_server() to the following:
async def run_server():
while True:
client, address = await loop.sock_accept(server)
print('start')
await loop.create_task(handle_client(client,address))
print(client)

How to properly retry websockets connection in python?

This code tries to connect for 20 seconds. If the connection is established and lost then it tries to reconnect for 10 seconds. If the client can't connect or reconnect then it prints "Failure" and exits. If the server responds with "exit" then the client exits.
import asyncio
import websockets
class Client:
async def connect_with_retries(self, uri: str):
while True:
try:
return await websockets.connect(uri)
except (ConnectionError, websockets.exceptions.WebSocketException):
await asyncio.sleep(5)
async def loop(self, uri: str):
connection_timeout = 20
try:
while True:
websocket = await asyncio.wait_for(self.connect_with_retries(uri), connection_timeout)
connection_timeout = 10
try:
while True:
await websocket.send("req")
resp = await websocket.recv()
if resp == "exit":
return
await asyncio.sleep(1)
except (ConnectionError, websockets.exceptions.WebSocketException) as exc:
pass
finally:
await websocket.close()
except asyncio.TimeoutError:
print("Failure")
client = Client()
asyncio.run(client.loop("ws://localhost:8000"))
I don't like the explicit websocket.close(). How to use asyncio.wait_for with the context manager of the websocket?

Why asyncio.sleep freezes whole Task (which has websocket inside) in running state when used with aiohttp websockets?

Today I found very strange problem with asyncio or aiohttp.
I wrote very simple server and client which use Websockets. When server gets connection from client, it creates two tasks, one task listens to data from client, another one send data to client.
If client decides to finish session, it sends close to server, listen_on_socket (server) Task finishes fine, but send_to_socket (server) Task became frozen if it contains asyncio.sleep inside of the Task. I can not even cancel the frozen task.
What's the reason of the problem and how can I handle it?
I have the following aiohttp server code as example:
from aiohttp import web, WSMsgType
import asyncio
async def send_to_socket(ws: web.WebSocketResponse):
"""helper func which send messages to socket"""
for i in range(10):
try:
if ws.closed:
break
else:
await ws.send_str(f"I am super socket server-{i} !!!")
except Exception as ex:
print(ex)
break
# remove await asyncio.sleep(0.5) and it works !
print("| send_to_socket | St sleeping")
await asyncio.sleep(0.5)
print("| send_to_socket | Stopped sleeping") # you will not get the message
if not ws.closed:
await ws.send_str("close")
print("| send_to_socket | Finished sending")
async def listen_on_socket(ws: web.WebSocketResponse, send_task: asyncio.Task):
"""helper func which Listen messages to socket"""
async for msg in ws:
if msg.type == WSMsgType.TEXT:
if msg.data == "close":
await ws.close()
send_task.cancel()
print(send_task.cancelled(), send_task.done(), send_task)
break
elif msg.type == WSMsgType.ERROR:
print(f'ws connection closed with exception {ws.exception()}')
print("* listen_on_socket * Finished listening")
async def websocket_handler(req: web.Request) -> web.WebSocketResponse:
"""Socket aiohttp handler"""
ws = web.WebSocketResponse()
print(f"Handler | Started websocket: {id(ws)}")
await ws.prepare(req)
t = asyncio.create_task(send_to_socket(ws))
await asyncio.gather(listen_on_socket(ws, t), t)
print("Handler | websocket connection closed")
return ws
if __name__ == '__main__':
app = web.Application()
app.router.add_get("/socket", websocket_handler)
web.run_app(app, host="0.0.0.0", port=9999)
I have the following aiohttp client code as example:
from aiohttp import ClientSession
import aiohttp
import asyncio
async def client():
n = 3
async with ClientSession() as session:
async with session.ws_connect('http://localhost:9999/socket') as ws:
async for msg in ws:
if n == 0:
await ws.send_str("close")
break
if msg.type == aiohttp.WSMsgType.TEXT:
if msg.data == "close":
await ws.close()
break
else:
print(msg.data)
n -= 1
elif msg.type == aiohttp.WSMsgType.ERROR:
break
print("Client stopped")
if __name__ == '__main__':
asyncio.run(client())
It isn't freezes, just your cancellation and logging a bit incorrect, you should await for cancelled task
async def listen_on_socket(ws: web.WebSocketResponse, send_task: asyncio.Task):
"""helper func which Listen messages to socket"""
async for msg in ws:
if msg.type == WSMsgType.TEXT:
if msg.data == "close":
await ws.close()
send_task.cancel()
try:
await send_task
except asyncio.CancelledError:
print("send task cancelled")
print(send_task.cancelled(), send_task.done(), send_task)
break
elif msg.type == WSMsgType.ERROR:
print(f'ws connection closed with exception {ws.exception()}')
print("* listen_on_socket * Finished listening")
Also there should be set return_exceptions=True in the gather call inside the websocket_handler to prevent exception propagation.
You could just wrap all the function body with try-finally block and ensure it finishes fine (sure just for debugging, not in final implementation).
From aiohttp documentation: Reading from the WebSocket (await ws.receive()) must only be done inside the request handler task; however, writing (ws.send_str(...)) to the WebSocket, closing (await ws.close()) and canceling the handler task may be delegated to other tasks.
Hereby the mistake was that I created reading from ws task in listen_on_socket.
Solution. Changes only in server, client is the same:
from aiohttp import web, WSMsgType
import asyncio
async def send_to_socket(ws: web.WebSocketResponse):
"""helper func which send messages to socket"""
for i in range(4):
try:
if ws.closed:
break
else:
await ws.send_str(f"I am super socket server-{i} !!!")
except Exception as ex:
print(ex)
break
await asyncio.sleep(1.5)
if not ws.closed:
await ws.send_str("close")
print(f"| send_to_socket | Finished sending {id(ws)}")
async def websocket_handler(req: web.Request) -> web.WebSocketResponse:
"""Socket aiohttp handler"""
ws = web.WebSocketResponse()
print(f"Handler | Started websocket: {id(ws)}")
await ws.prepare(req)
# create another task for writing
asyncio.create_task(send_to_socket(ws))
async for msg in ws:
if msg.type == WSMsgType.TEXT:
if msg.data == "close":
await ws.close()
break
elif msg.type == WSMsgType.ERROR:
print(f'ws connection closed with exception {ws.exception()}')
print(f"Connection {id(ws)} is finished")
return ws
if __name__ == '__main__':
app = web.Application()
app.router.add_get("/socket", websocket_handler)
web.run_app(app, host="0.0.0.0", port=9999)

chat app using websocket python+django+angular

I want to make one real-time chat application using websockets and the frontend is angular5.
So, I create websocket in purepython and backend is Django and frontend is angular5.
Myquestion is when i create websocket service in python. So, do i have to make websockets services in angular too?
this is my python websocket service
async def consumer_handler(websocket):
global glob_message
while True:
message = await websocket.recv()
await glob_message.put(message)
print("this went in glob_message: {}".format(message))
async def producer_handler(websocket):
global glob_message
while True:
message = await glob_message.get()
await websocket.send(message)
async def handler(websocket, path):
producer_task = asyncio.ensure_future(producer_handler(websocket))
consumer_task = asyncio.ensure_future(consumer_handler(websocket))
done, pending = await asyncio.wait(
[consumer_task, producer_task],
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
if __name__ == '__main__':
glob_message = asyncio.Queue()
start_server = websockets.serve(
handler,
'127.0.0.1', 8788)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
So, i want to create two user can send and receive messages.so must be design in angular ..
so i am asking that i already create one service in python so do i have to create websocket service(Observable subscribers) in angular too?
In your angular side, you should open a connection indeed.
This is how you can use Observables to communicate with your server :
Client side
// open the socket connection
const ws = new WebSocket('ws://127.0.0.1:8788')
// when opened, print all messages
ws.onopen = open => {
Observable.fromEvent(ws, 'message')
.subscribe(message => console.log(message))
}
To send a message, simply use :
ws.send('test')
Server Side
You can use the demo in https://pypi.org/project/websocket-client/ to build your websocket server.
to import WebSocket :
pip install websocket-client
Then :
import websocket
try:
import thread
except ImportError:
import _thread as thread
import time
def on_message(ws, message):
print(message)
def on_error(ws, error):
print(error)
def on_close(ws):
print("### closed ###")
def on_open(ws):
def run(*args):
for i in range(3):
time.sleep(1)
ws.send("Hello %d" % i)
time.sleep(1)
ws.close()
print("thread terminating...")
thread.start_new_thread(run, ())
if __name__ == "__main__":
websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://127.0.0.1:8788",
on_message = on_message,
on_error = on_error,
on_close = on_close)
ws.on_open = on_open
ws.run_forever()

Python Websockets : infinite lock on client recv

I am learning how to use the websockets package for python 3.6 with asyncio.
Using the Websockets Getting Started example, here are my server and client code (both running in two separate console using python <script>)
wsserver.py
import asyncio
import websockets
msg_queue = asyncio.Queue()
async def consumer_handler(websocket):
global msg_queue
while True:
message = await websocket.recv()
print("Received message {}".format(message))
await msg_queue.put("Hello {}".format(message))
print("Message queued")
async def producer_handler(websocket):
global msg_queue
while True:
print("Waiting for message in queue")
message = await msg_queue.get()
print("Poped message {}".format(message))
websocket.send(message)
print("Message '{}' sent".format(message))
async def handler(websocket, path):
print("Got a new connection...")
consumer_task = asyncio.ensure_future(consumer_handler(websocket))
producer_task = asyncio.ensure_future(producer_handler(websocket))
done, pending = await asyncio.wait([consumer_task, producer_task]
, return_when=asyncio.FIRST_COMPLETED)
print("Connection closed, canceling pending tasks")
for task in pending:
task.cancel()
start_server = websockets.serve(handler, 'localhost', 5555)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
wsclient.py
import asyncio
import websockets
async def repl():
async with websockets.connect('ws://localhost:5555') as websocket:
while True:
name = input("\nWhat's your name? ")
await websocket.send(name)
print("Message sent! Waiting for server answer")
greeting = await websocket.recv()
# never goes here
print("> {}".format(greeting))
asyncio.get_event_loop().run_until_complete(repl())
During the execution, the server is doing what is expected of him :
Wait for a client message
Queue 'Hello $message'
Dequeue it
Send the dequeued message back to the sender
The client does work up to the waiting of the server response :
Wait for a user input
Send it to the server
Wait answer from the server <-- Holds on indefinitely
Print it & loop
Here are the console outputs of the execution :
Server
Got a new connection...
Waiting for message in queue
Received message TestName
Message queued
Poped message Hello TestName
Message 'Hello TestName' sent
Waiting for message in queue
Client
What's your name? TestName
Message sent! Waiting for server answer
_
What am I missing?
Server-side, you're missing an await on the websocket.send(message) line.
To find those kind of bugs, start your program with the PYTHONASYNCIODEBUG environment variable, like: PYTHONASYNCIODEBUG=1 python3 wsserver.py which prints:
<CoroWrapper WebSocketCommonProtocol.send() running at […]/site-packages/websockets/protocol.py:301, created at wsserver.py:23> was never yielded from

Categories

Resources