Git Commit: websocket based code sharing
Note: The earlier websocket and HTTP endpoints will collide with these new endpoints, make sure you comment/remove them.
In this tutorial we are going to build a codesharing application, It lets us share code in real-time. It could be used for interviewing or for sharing a snippet with colleagues. Our earlier implementation of websocket is not scalable as everything is handled in memory, in a single dictionary, it will only work while the process is running, and will only work with a single process. Let's use the suggested broadcaster library for a more robust implementation.
Let's first install the basic requirements. Put these additional requirements in requirements.txt file.
broadcaster==0.2.0
asyncio-redis==0.16.0
Now, we can utilize broadcaster and its redis based pub-sub capabilities.
from broadcaster import Broadcast
from fastapi import FastAPI, WebSocket, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.concurrency import run_until_first_complete
broadcast = Broadcast("redis://redis:6379")
templates = Jinja2Templates(directory="templates")
app = FastAPI(on_startup=[broadcast.connect], on_shutdown=[broadcast.disconnect])
@app.get("/{room_id}/", response_class=HTMLResponse)
async def get(request: Request, room_id:str):
context = {"room_id":room_id,"request":request}
return templates.TemplateResponse("codeshare.html",context)
async def chatroom_ws_receiver(websocket: WebSocket, room_id: str):
async for message in websocket.iter_text():
await broadcast.publish(channel=f"chatroom_{room_id}", message=message)
async def chatroom_ws_sender(websocket: WebSocket, room_id: str):
async with broadcast.subscribe(channel=f"chatroom_{room_id}") as subscriber:
async for event in subscriber:
await websocket.send_text(event.message)
@app.websocket("/{room_id}")
async def websocket_chat(websocket: WebSocket, room_id: str):
await websocket.accept()
await run_until_first_complete(
(chatroom_ws_receiver, {"websocket": websocket, "room_id":room_id}),
(chatroom_ws_sender, {"websocket": websocket, "room_id":room_id}),
)
A Broadcast
instance is created to facilitate message broadcasting, configured to communicate with a Redis server at the address redis://redis:6379
as defined in our docker-compose service. The broadcast.connect
function is set to run during startup, ensuring that the broadcasting mechanism is properly established. Conversely, the broadcast.disconnect
function will be executed during shutdown, allowing for a graceful closure of the broadcasting infrastructure.
Broadcaster is utilizing Redis' pub-sub capabilities internally. The code employs the run_until_first_complete
function to concurrently execute the chatroom_ws_receiver
and chatroom_ws_sender
functions. This concurrent execution ensures that the chatroom can handle both incoming and outgoing messages simultaneously, enabling a seamless real-time chat experience for users.
Let's include the below HTML template for the UI. templates > codeshare.html
<!DOCTYPE html>
<html>
<head>
<title>CodeShare</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="room-id" style="display: none;">{{room_id}}</div>
<form>
<div class="w-full mb-4 border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-700 dark:border-gray-600">
<div class="flex items-center justify-between px-3 py-2 border-b dark:border-gray-600">
<div class="flex flex-wrap items-center divide-gray-200 sm:divide-x dark:divide-gray-600">
<div class="flex items-center space-x-1 sm:pr-4">
<h1 class="text-rose-500 font-bold mx-5">Algoholic</h1>
</div>
</div>
</div>
<div class="px-4 py-2 bg-white rounded-b-lg dark:bg-gray-800">
<label for="editor" class="sr-only">Publish post</label>
<textarea id="editor" rows="8" class="block w-full px-0 text-sm text-gray-800 bg-white border-0 dark:bg-gray-800 focus:ring-0 dark:text-white dark:placeholder-gray-400" placeholder="Paste/type your code..." required></textarea>
</div>
</div>
</form>
<script>
let editor = document.getElementById("editor")
room_id = document.getElementById('room-id').textContent
let ws = new WebSocket(`ws://localhost:8000/{{room_id}}`);
console.log("Websocket endpoint is",ws)
ws.onmessage = function(event) {
old_value = document.getElementById("editor").value
document.getElementById("editor").value = event.data
};
function sendMessage(event) {
let input = document.getElementById("editor").value
console.log("Text are value is: ",input)
ws.send(input)
}
editor = document.getElementById("editor")
editor.addEventListener("paste", sendMessage);
editor.addEventListener("input", sendMessage);
</script>
</body>
</html>
Let's see this in action :D