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)
Related
I am trying to send a message to the websocket from an ulr and another url receives it to update some data, let's say to make a dasboard, currently both connections are open,I can to send the message to the backend but the other url does not receive it, I would like to do it in pure javascript to understand this well, what other libraries could I use? And what is the problem that you can identify in the code?
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.websockets import WebSocket
app = FastAPI()
#app.get("/send")
def index():
return HTMLResponse(content="""
<html>
<head>
<title>Send Message</title>
</head>
<body>
<h1 id="message">Mensaje</h1>
<button id="send-button">Enviar</button>
<script>
const socket = new WebSocket("ws://localhost:8000/ws");
socket.onopen = function(e) {
console.log("Connect");
};
const sendButton = document.getElementById("send-button");
sendButton.addEventListener("click", function() {
socket.send("Hola");
});
</script>
</body>
</html>
""")
#app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
message = await websocket.receive_text()
print(message)
await websocket.send_text(message)
#app.get("/receive")
def enviar():
return HTMLResponse(content="""
<html>
<head>
<title>Receive</title>
</head>
<body>
<h1 id="received-message">Mensaje recibido</h1>
<script>
const socket = new WebSocket("ws://localhost:8000/ws");
socket.onopen = function(e) {
console.log("Connect");
};
socket.onmessage = function(event) {
const receivedMessage = document.getElementById("received-message");
receivedMessage.innerHTML = event.data;
};
</script>
</body>
</html>""")
Why my code dont work, best approach to handle websocket
This is how I resolved
customers = []
#app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
customers.append(websocket)
await websocket.accept()
while True:
message = await websocket.receive_text()
for customer in customers:
await customer.send_text(message)
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'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
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.
I'm newbie in FastAPI. I have a websocket server in 127.0.0.1:8000/abcd and ConnectionManager Object.
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()
And the websocket server:
#router.websocket("/abcd")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
How can I get the IP Address of the Client Websocket Connection? And How can I change the active_connections to a Dictionary with the key is IP Address and value is the websocket? Thank you
Information from the initial HTTP request for creating the websocket is stored in the Websocket object and can be introspected. Starlette gives a few helper methods for retrieving this information.
#router.websocket("/abcd")
async def websocket_endpoint(websocket: WebSocket):
client_host = websocket.client.host
await manager.connect(websocket)
For more information you can see the Starlette Websocket source:
https://github.com/encode/starlette/blob/5ee04ef9b1bc11dc14d299e6c855c9a3f7d5ff16/starlette/websockets.py#L20
and the source for it's super class HTTPConnection:
https://github.com/encode/starlette/blob/5ee04ef9b1bc11dc14d299e6c855c9a3f7d5ff16/starlette/requests.py#L57