In the previous tutorial, we built a boilerplate to serve HTML. In this tutorial, we are going to actually put javascript-based web socket calls from frontend to backend. Let's proceed with the backend first.
class ConnectionManager:
def __init__(self) -> None:
self.active_connections: list[str,WebSocket]= {}
print("Creating a list to hold active connections",self.active_connections)
async def connect(self, room_id: str, websocket: WebSocket):
await websocket.accept()
if not self.active_connections.get(room_id):
self.active_connections[room_id] = []
self.active_connections[room_id].append(websocket)
print("New Active connections are ",self.active_connections)
async def disconnect(self, room_id: str, websocket: WebSocket):
self.active_connections[room_id].remove(websocket)
print("After disconnect active connections are: ",self.active_connections)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
print("Sent a personal msg to , ",websocket)
async def broadcast(self, message: str, room_id: str, websocket: WebSocket):
for connection in self.active_connections[room_id]:
if connection != websocket:
await connection.send_text(message)
print("In broadcast: sent msg to ",connection)
manager = ConnectionManager()
@app.websocket("/{room_id}")
async def websocket_chat(websocket: WebSocket, room_id: str):
await manager.connect(room_id, websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"You wrote: {data}",websocket)
await manager.broadcast(f"A client says: {data}", room_id, websocket)
except Exception as e:
print("Got an exception ",e)
await manager.disconnect(room_id, websocket)
The ConnectionManager
class acts as a central hub for handling WebSocket connections. It has methods to connect, disconnect, send personal messages, and broadcast messages to connected clients. When an instance ConnectionManager
is created, it initializes an empty dictionary called active_connections
to keep track of connected clients.
The connect
method of the ConnectionManager
class is called when a new WebSocket connection is established. It takes a room_id
and a websocket
instance as parameters. This method accepts the new connection by sending an acceptance signal to the client using the await websocket.accept()
call. It then adds the new WebSocket instance to the list of active connections for the specified room_id
. If the room_id
does not exist in the active_connections
dictionary, a new entry is created.
Conversely, the disconnect
method is responsible for handling WebSocket disconnections. It takes a room_id
and a websocket
instance as parameters and removes the WebSocket instance from the list of active connections for the specified room_id
.
The send_personal_message
method allows sending personalized messages to a specific client. It takes a message
and a websocket
instance as parameters and uses the WebSocket instance to send the message to that specific client.
The broadcast
method is designed to send a message to all clients in a given room except the sender. It accepts a message
, a room_id
, and the sender's websocket
instance. It iterates through the active connections for the specified room_id
and sends the message to each connected client except the sender.
The code would be crystal clear if you type it out. Let's move forward to the WebSockets part now. and add the below code in templates/chatroom.html after the ul tag.
<!DOCTYPE html>
<html>
<body>
<!-- previous blog content here -->
<ul id='messages'>
</ul>
<script>
roomId = document.getElementById('room-id').textContent
var ws = new WebSocket(`ws://localhost:8000/${roomId}`);
console.log("Websocket endpoint is ",ws)
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>
The form is set to trigger the JavaScript function sendMessage(event)
when it is submitted. The JavaScript script section follows, responsible for establishing and managing the WebSocket connection. The script starts by extracting the room_id
from the hidden <div>
using JavaScript's document.getElementById('room-id').textContent
. This value, representing the specific chat room, is then used to construct a WebSocket URL in the format ws://localhost:8000/${roomId}
.
subsequently, a new WebSocket object (ws
) is created using the constructed URL. This WebSocket connection acts as a conduit for real-time communication between the client (the user's web browser) and the server.
The script includes an event handler ws.onmessage
, which is triggered whenever a new message is received over the WebSocket connection. Within this handler, the code dynamically creates new <li>
elements to represent each incoming message. The content of the message is encapsulated within a text node, and this node is appended to the newly created <li>
. Ultimately, the completed <li>
element is added to the previously mentioned <ul>
with the id
"messages"
Lastly, the sendMessage
function is defined. This function is called when the user submits a message through the form. It sends the entered message to the WebSocket server using the ws.send(input.value)
method. Following the sending of the message, the function clears the input field to prepare for the next message, and it prevents the default form submission behavior using event.preventDefault()
. This ensures that the form submission does not result in a full page reload, which is crucial for maintaining a seamless chat experience.
Brige the gap between Tutorial hell and Industry. We want to bring in the culture of Clean Code, Test Driven Development.
We know, we might make it hard for you but definitely worth the efforts.
© Copyright 2022-23 Team FastAPITutorial