Created
October 11, 2024 10:46
-
-
Save Jeffrey04/63d0e675bd001634b083d7ad444d3052 to your computer and use it in GitHub Desktop.
ASGI - Websocket Chatroom
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<link | |
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" | |
rel="stylesheet" | |
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" | |
crossorigin="anonymous" | |
/> | |
<title>Test Chatroom</title> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="d-flex flex-column vh-100"> | |
<h1 class="flex-shrink-0">Test chatroom</h1> | |
<div id="messages" class="flex-grow-1 overflow-y-scroll"></div> | |
<form id="send" class="flex-shrink-0"> | |
<input | |
class="form-control form-control-lg" | |
type="text" | |
aria-label=".form-control-lg example" | |
/> | |
</form> | |
</div> | |
</div> | |
<script src=" https://cdn.jsdelivr.net/npm/[email protected]/chance.min.js "></script> | |
<script | |
type="text/javascript" | |
src="https://code.jquery.com/jquery-3.7.1.js" | |
></script> | |
<script src=" https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js "></script> | |
<script | |
src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" | |
integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" | |
crossorigin="anonymous" | |
></script> | |
<script | |
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" | |
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" | |
crossorigin="anonymous" | |
></script> | |
<script id="message" type="text/template"> | |
<div> | |
<p><strong><%= sender %>:</strong> <%= message %></p> | |
</div> | |
</script> | |
<script async type="text/javascript"> | |
document.addEventListener("DOMContentLoaded", () => { | |
const ws = new WebSocket("/chat"); | |
const sender = chance.name(); | |
ws.addEventListener("message", (e) => { | |
document | |
.getElementById("messages") | |
.dispatchEvent( | |
new CustomEvent("update", { detail: JSON.parse(e.data) }) | |
); | |
}); | |
const elem = document.querySelector("#send input"); | |
elem.setAttribute("placeholder", sender.concat(":")); | |
elem.focus(); | |
document.getElementById("send").addEventListener("submit", (e) => { | |
e.preventDefault(); | |
ws.send( | |
JSON.stringify({ | |
sender, | |
message: $("input", e.target).val(), | |
}) | |
); | |
e.target.querySelector("input").value = ""; | |
}); | |
document.getElementById("messages").addEventListener("update", (e) => { | |
const elem = new DOMParser().parseFromString( | |
_.template(document.getElementById("message").innerHTML)({ | |
sender: e.detail.sender, | |
message: e.detail.message, | |
}), | |
"text/html" | |
).body.firstChild; | |
e.target.appendChild(elem); | |
e.target.lastElementChild.scrollIntoView(false); | |
jQuery(elem).hide().fadeIn(); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import asyncio | |
import asyncio.selector_events | |
import contextlib | |
import json | |
import logging | |
import multiprocessing | |
import multiprocessing.managers | |
import random | |
import string | |
from functools import partial | |
from pathlib import Path | |
import uvicorn | |
from structlog import get_logger | |
queue = multiprocessing.Manager().Queue() | |
subscribers = {} | |
HTTP_REQUEST = "http.request" | |
HTTP_DISCONNECT = "http.disconnect" | |
WEBSOCKET_CONNECT = "websocket.connect" | |
WEBSOCKET_ACCEPT = "websocket.accept" | |
WEBSOCKET_RECEIVE = "websocket.receive" | |
WEBSOCKET_SEND = "websocket.send" | |
WEBSOCKET_DISCONNECT = "websocket.disconnect" | |
logger = get_logger() | |
logging.basicConfig() | |
def get_path(scope): | |
return scope["path"].lstrip(scope["root_path"]) | |
async def application(scope, receive, send): | |
subscriber = "".join(random.choices(string.ascii_letters, k=7)) | |
while event := await receive(): | |
logger.info("Incoming event", **event, path=get_path(scope)) | |
if event["type"] == HTTP_REQUEST and get_path(scope) == "/": | |
await index(send) | |
elif event["type"] == WEBSOCKET_CONNECT and get_path(scope) == "/chat": | |
subscribers[subscriber] = send | |
await accept(send) | |
elif event["type"] == WEBSOCKET_RECEIVE and get_path(scope) == "/chat": | |
asyncio.create_task(asyncio.to_thread(partial(queue.put, event["text"]))) | |
elif event["type"] == HTTP_DISCONNECT: | |
break | |
elif event["type"] == WEBSOCKET_DISCONNECT: | |
del subscribers[subscriber] | |
break | |
async def consume(): | |
while message := await asyncio.to_thread(partial(queue.get)): | |
for send in subscribers.values(): | |
asyncio.create_task( | |
send( | |
{ | |
"type": WEBSOCKET_SEND, | |
"text": message, | |
} | |
) | |
) | |
async def accept(send): | |
await send({"type": WEBSOCKET_ACCEPT}) | |
await send( | |
{ | |
"type": WEBSOCKET_SEND, | |
"text": json.dumps({"sender": "server", "message": "You are connected"}), | |
} | |
) | |
async def index(send): | |
with open(Path(__file__).parent / "index.html", "rb") as template: | |
await send( | |
{ | |
"type": "http.response.start", | |
"status": 200, | |
} | |
) | |
await send({"type": "http.response.body", "body": template.read()}) | |
async def main(): | |
asyncio.create_task(consume()) | |
server = uvicorn.Server( | |
uvicorn.Config( | |
app=application, | |
workers=4, | |
host="0.0.0.0", | |
port=8080, | |
) | |
) | |
await server.serve() | |
if __name__ == "__main__": | |
asyncio.run(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment