I'm trying to ingest data from an external WebSocket using Websocket Client A and send those messages to WebSocket Server B for clients connected to B to view. (Reason being, I'm trying to connect multiple external WebSockets and feed their messages to a single point, if this is a bad design can someone suggest an alternative)
Running server B uvicorn server:app --reload then visit http://127.0.0.1:8000/
WebSocket Server B
from typing import List
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
app = FastAPI()
html = """
<!DOCTYPE html>
<html>
<head>
<title>Screener</title>
</head>
<body>
<h2>Your ID: <span id="ws-id"></span></h2>
<ul id='messages'>
</ul>
<script>
var client_id = Date.now()
document.querySelector("#ws-id").textContent = client_id;
var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
#app.get("/")
async def get():
return HTMLResponse(html)
#app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"You wrote: {data}", websocket)
await manager.broadcast(f"Client #{client_id} says: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"Client #{client_id} left the chat")
WebSocket ClientA
#!/usr/bin/env python
import websocket
import _thread
import time
import asyncio
import websockets
from concurrent.futures import ThreadPoolExecutor
_EXECUTOR_ = ThreadPoolExecutor(1)
async def main_loop():
async with websockets.connect("ws://localhost:8000/ws/1") as server_ws:
await server_ws.send("main_loop")
def send_message(message):
server_ws.send(message)
def ws_message(ws, message):
loop = asyncio.get_event_loop()
loop.run_in_executor(_EXECUTOR_, send_message, message)
print("WebSocket thread: %s" % message)
def ws_open(ws):
ws.send('{"event":"subscribe", "subscription":{"name":"trade"}, "pair":["XBT/USD","XRP/USD"]}')
def ws_thread(*args):
ws = websocket.WebSocketApp("wss://ws.kraken.com/", on_open = ws_open, on_message = ws_message)
ws.run_forever()
_thread.start_new_thread(ws_thread, ())
while True:
time.sleep(5)
print("Main thread: %d" % time.time())
asyncio.run(main_loop())
What is the question?
WS is kind of a multiplex connection that allows each side (server/client or whatever) to both send and receive messages in an asynchronous way. Ingesting data from one side of the WS (in your case Client A) and consuming some related data through another API is OK - it all depends on your needs.
This approach kind of reminds me of a classic Pub/Sub pattern. Where you have an API that you ingest data through and pass it to a Queue (RabbitMQ, Google Pub/Sub service/AWS SNS) the messages ingested into the queue are being consumed by another service that parses the data and populates some DB,
Then any other client can access a third service (API) to consume the relevant data.
Nice explanation about the Pub/Sub pattern Link1, Link2
Not sure what advantage WebSockets provide in your example
Related
I have the next code:
from fastapi import FastAPI, WebSocket, BackgroundTasks
import uvicorn
import time
app = FastAPI()
def run_model():
...
## code of the model
answer = [1, 2, 3]
...
results = {"message": "the model has been excuted succesfully!!", "results": answer}
return results
#app.post("/execute-model")
async def ping(background_tasks: BackgroundTasks):
background_tasks.add_task(run_model)
return {"message": "the model is executing"}
#app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
## Here I wnat the results of run_model
await websocket.send_text(1)
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8001)
I need to make a post fecth to /execute-model. This endpoint will excute a run_model function as a background task. I need return the answer to the front when run_model() finish and I thought in use websockets but I don't know how do it. Help please.
I had something similar. Here is how I did it (not saying it's the best or even a good solution, but it's working so far):
The route endpoint:
# client makes a post request, gets saved model immeditely, while a background task is started to process the image
#app.post("/analyse", response_model=schemas.ImageAnalysis , tags=["Image Analysis"])
async def create_image_analysis(
img: schemas.ImageAnalysisCreate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
):
saved = crud.create_analysis(db=db, img=img)
background_tasks.add_task(analyse_image,db=db, img=img)
#model includes a ws_token (some random string) that the client can connect to right away
return saved
The websocket endpoint:
#app.websocket("/ws/{ws_token}")
async def websocket_endpoint(websocket: WebSocket, ws_token: str):
#add the websocket to the connections dict (by ws_token)
await socket_connections.connect(websocket,ws_token=ws_token)
try:
while True:
print(socket_connections)
await websocket.receive_text() #not really necessary
except WebSocketDisconnect:
socket_connections.disconnect(websocket,ws_token=ws_token)
The analyse_image function:
#notice - the function is not async, as it does not work with background tasks otherwise!!
def analyse_image (db: Session, img: ImageAnalysis):
print('analyse_image started')
for index, round in enumerate(img.rounds):
# some heavy workload etc
# send update to user
socket_connections.send_message({
"status":EstimationStatus.RUNNING,
"current_step":index+1,
"total_steps":len(img.rounds)
}, ws_token=img.ws_token)
print("analysis finished")
The connection Manager:
import asyncio
from typing import Dict, List
from fastapi import WebSocket
#notice: active_connections is changed to a dict (key= ws_token), so we know which user listens to which model
class ConnectionManager:
def __init__(self):
self.active_connections: Dict[str, List[WebSocket]] = {}
async def connect(self, websocket: WebSocket, ws_token: str):
await websocket.accept()
if ws_token in self.active_connections:
self.active_connections.get(ws_token).append(websocket)
else:
self.active_connections.update({ws_token: [websocket]})
def disconnect(self, websocket: WebSocket, ws_token: str):
self.active_connections.get(ws_token).remove(websocket)
if(len(self.active_connections.get(ws_token))==0):
self.active_connections.pop(ws_token)
# notice: changed from async to sync as background tasks messes up with async functions
def send_message(self, data: dict,ws_token: str):
sockets = self.active_connections.get(ws_token)
if sockets:
#notice: socket send is originally async. We have to change it to syncronous code -
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
for socket in sockets:
socket.send_text
loop.run_until_complete(socket.send_json(data))
socket_connections = ConnectionManager()
I want to make a websocket server using Fastapi then get a message from a manager then broadcast it to all clients.
The messages are just text.
Here is the process:
The manager connects to server.
The clients connect to server.
The manager send a message to server and server receive the message(could send OK status or response for error handling)
The server broadcast the message to all clients(no matter to manager or not)
The manager and clients are preferred to be MQL4/5 Experts.
A simple topology of the system:
The server is using fastapi sockets
FastAPI websocket
The simple server code like The manager of system:
from typing import List
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
app = FastAPI()
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
#app.get("/")
async def get():
return (f"Hi!")
#app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"You wrote: {data}", websocket)
await manager.broadcast(f"Client #{client_id} says: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"Hello everyone")
The connection manager in the above code can broadcast a message to all client(in that case, it is used for disconnection handling, but it is not my case).
Also, a sample piece of code for the client in MQL5:
int socket=SocketCreate();
if(socket!=INVALID_HANDLE)
{
if(SocketConnect(socket,"127.0.0.1",9091,1000)) {
Print("Connected to "," 127.0.0.1",":",9091);
string tosend = "heartbeat";
string received = socksend(socket, tosend) ? socketreceive(socket, 10) : "";
Print(received);
string sep="_"; // A separator as a character
ushort u_sep; // The code of the separator character
string result[]; // An array to get strings
u_sep=StringGetCharacter(sep,0);
//--- Split the string to substrings
int k=StringSplit(received,u_sep,result);
Print(result[0]);
Print(result[1]);
Print(result[2]);
}
}
//---
bool socksend(int sock,string request) {
char req[];
int len=StringToCharArray(request,req)-1;
if(len<0) return(false);
return(SocketSend(sock,req,len)==len);
}
//---
string socketreceive(int sock,int timeout) {
char rsp[];
string result="";
uint len;
uint timeout_check=GetTickCount()+timeout;
do {
len=SocketIsReadable(sock);
if(len) {
int rsp_len;
rsp_len=SocketRead(sock,rsp,len,timeout);
if(rsp_len>0) {
result+=CharArrayToString(rsp,0,rsp_len);
}
}
} while((GetTickCount()<timeout_check) && !IsStopped());
return result;
}
I want to know how can I connect the manager to system the get it's message and broadcast it.
I want to know how can I make the system.
The websockets code example from https://fastapi.tiangolo.com/advanced/websockets/ in section Handling disconnections and multiple clients basically works, but piles up memory even when websockets are closed. This can be observed with the help of the windows task manager.
The similar application on a Linux server shows the same problem and leads to an out of memory exception.
I am using python 3.6, fastapi 0.70.0, uvicorn 0.16. The code below is a direct copy from the Fastapi documentation.
from typing import List
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import uvicorn
app = FastAPI()
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<h2>Your ID: <span id="ws-id"></span></h2>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var client_id = Date.now()
document.querySelector("#ws-id").textContent = client_id;
var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
#app.get("/")
async def get():
return HTMLResponse(html)
#app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"You wrote: {data}", websocket)
await manager.broadcast(f"Client #{client_id} says: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"Client #{client_id} left the chat")
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8000)
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'm trying to read messages from Azure ServiceBus Topics using async/await and then forward the content to another application via HTTP. My code is simple:
import asyncio
from aiohttp import ClientSession
from azure.servicebus.aio.async_client import ServiceBusService
bus_service = ServiceBusService(service_namespace=..., shared_access_key_name=..., shared_access_key_value=...)
async def watch(topic_name, subscription_name):
print('{} started'.format(topic_name))
message = bus_service.receive_subscription_message(topic_name, subscription_name, peek_lock=False, timeout=1)
if message.body is not None:
async with ClientSession() as session:
await session.post('ip:port/endpoint',
headers={'Content-type': 'application/x-www-form-urlencoded'},
data={'data': message.body.decode()})
async def do():
while True:
for topic in ['topic1', 'topic2', 'topic3']:
await watch(topic, 'watcher')
if __name__ == "__main__":
asyncio.run(do())
I want to look for messages (forever) from various topics and when a message arrives send the POST. I import the aio package from azure which should work in an async way. After many attempts, the only solution I got is this with while True and setting the timeout=1. This is not what I wanted, I'm doing it sequentially.
azure-servicebus version 0.50.3.
This is my first time with async/await probably I'm missing something...
Any solution/suggestions?
Here's how you'll do it with the latest major version v7 of servicebus
Please take a look a the async samples to send and receive subscription messages
https://github.com/Azure/azure-sdk-for-python/blob/04290863fa8963ec525a0b2f4079595287e15d93/sdk/servicebus/azure-servicebus/samples/async_samples/sample_code_servicebus_async.py
import os
import asyncio
from aiohttp import ClientSession
from azure.servicebus.aio import ServiceBusClient
connstr = os.environ['SERVICE_BUS_CONNECTION_STR']
topic_name = os.environ['SERVICE_BUS_TOPIC_NAME']
subscription_name = os.environ['SERVICE_BUS_SUBSCRIPTION_NAME']
async def watch(topic_name, subscription_name):
async with ServiceBusClient.from_connection_string(conn_str=servicebus_connection_str) as servicebus_client:
subscription_receiver = servicebus_client.get_subscription_receiver(
topic_name=topic_name,
subscription_name=subscription_name,
)
async with subscription_receiver:
message = await subscription_receiver.receive_messages(max_wait_time=1)
if message.body is not None:
async with ClientSession() as session:
await session.post('ip:port/endpoint',
headers={'Content-type': 'application/x-www-form-urlencoded'},
data={'data': message.body.decode()})
async def do():
while True:
for topic in ['topic1', 'topic2', 'topic3']:
await watch(topic, 'watcher')
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(do())
You will have to use the package : azure.servicebus.aio
They have the below modules for async :
We will have to use the Receive handler class - it can instantiated with get_receiver() method. With this object you will be able to iterate through the message Asynchronously. Spun up a sample script which does that you could further optimise it :
from azure.servicebus.aio import SubscriptionClient
import asyncio
import nest_asyncio
nest_asyncio.apply()
Receiving = True
#Topic 1 receiver :
conn_str= "<>"
name="Allmessages1"
SubsClient = SubscriptionClient.from_connection_string(conn_str, name)
receiver = SubsClient.get_receiver()
#Topic 2 receiver :
conn_str2= "<>"
name2="Allmessages2"
SubsClient2 = SubscriptionClient.from_connection_string(conn_str2, name2)
receiver2 = SubsClient2.get_receiver()
#obj= SubscriptionClient("svijayservicebus","mytopic1", shared_access_key_name="RootManageSharedAccessKey", shared_access_key_value="ySr+maBCmIRDK4I1aGgkoBl5sNNxJt4HTwINo0FQ/tc=")
async def receive_message_from1():
await receiver.open()
print("Opening the Receiver for Topic1")
async with receiver:
while(Receiving):
msgs = await receiver.fetch_next()
for m in msgs:
print("Received the message from topic 1.....")
print(str(m))
await m.complete()
async def receive_message_from2():
await receiver2.open()
print("Opening the Receiver for Topic2")
async with receiver2:
while(Receiving):
msgs = await receiver2.fetch_next()
for m in msgs:
print("Received the message from topic 2.....")
print(str(m))
await m.complete()
loop = asyncio.get_event_loop()
topic1receiver = loop.create_task(receive_message_from1())
topic2receiver = loop.create_task(receive_message_from2())
I have created two tasks to facilitate the concurrency. You could refer this post to get more clarity on them.
Output :