Skip to content

Instantly share code, notes, and snippets.

@ansemjo
Created April 10, 2020 18:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ansemjo/eccdfb7a5c60b408d51afec0affe046e to your computer and use it in GitHub Desktop.
Save ansemjo/eccdfb7a5c60b408d51afec0affe046e to your computer and use it in GitHub Desktop.
an example of a vanilla WebSocket-based chat with Python's aiohttp server
from aiohttp import web
import os, binascii
import asyncio
import weakref
import datetime
app = web.Application()
app["ws"] = weakref.WeakSet()
routes = web.RouteTableDef()
@routes.get("/ws")
async def wshandler(request):
# open new connection
ws = web.WebSocketResponse()
await ws.prepare(request)
# send id and insert in set
i = binascii.hexlify(os.urandom(2)).decode()
ws["id"] = i
await ws.send_json(dict(type="id", id=i))
request.app["ws"].add(ws)
# handle messages
try:
async for msg in ws:
print(f"received from {i}: {msg.data}")
await broadcast(request.app, i, msg.data)
finally:
request.app["ws"].discard(ws)
return ws
@routes.get("/")
async def hello(request):
response = web.Response(content_type="text/html")
response.text = """
<!doctype html>
<html>
<head>
<title> SimpleSocketChat </title>
<style>
table { border-collapse: collapse; margin-top: 1em; width: 100%; }
th { background-color: skyblue; }
td, th { padding: 0.5em; }
</style>
</head>
<body>
<h1> SimpleSocketChat </h1>
<p>Current time: <code id="now">unknown</code></p>
<p><code id="status">unknown status</code></p>
<form id="chat" onsubmit="send(this); return false;">
<input type="text" id="message" style="width: 300px">
<input type="submit" value="Send">
</form>
<table border="1">
<tr>
<th>ID</th>
<th>Date</th>
<th>Message</th>
</tr>
<tbody id="messages">
</tbody>
</table>
<script>
ws = new WebSocket(`ws://${location.host}/ws`);
ws.addEventListener("message", function(event) {
let msg = JSON.parse(event.data);
switch(msg.type) {
case "id":
let st = document.getElementById("status");
st.textContent = `CONNECTED as ${msg.id}`;
break;
case "now":
let now = document.getElementById("now");
now.textContent = msg.now;
break;
case "message":
let messages = document.getElementById("messages");
let m = document.createElement("tr");
[msg.id, msg.date, msg.message].forEach(t => {
let td = document.createElement("td");
td.textContent = t;
m.append(td);
});
messages.prepend(m);
break;
};
});
ws.addEventListener("close", () => {
st = document.getElementById("status");
st.textContent = "CONNECTION CLOSED";
});
function send(el) {
message = el["message"].value;
ws.send(message);
el["message"].value = "";
};
</script>
</body>
</html>
"""
return response
async def currenttime(app):
while True:
now = datetime.datetime.utcnow().isoformat()
for ws in app["ws"]:
await ws.send_json(dict(type="now", now=now))
await asyncio.sleep(1)
async def broadcast(app, sender, message):
now = datetime.datetime.utcnow().isoformat()
for ws in app["ws"]:
await ws.send_json(dict(type="message", id=sender, date=now, message=message))
async def connectedclients(app):
clients = dict(type="clients", clients=[hex(ws["id"]) for ws in app["ws"]])
for ws in app["ws"]:
await ws.send_json(clients)
async def start_background_tasks(app):
app["timer"] = asyncio.create_task(currenttime(app))
app.add_routes(routes)
app.on_startup.append(start_background_tasks)
web.run_app(app)
@ansemjo
Copy link
Author

ansemjo commented Apr 10, 2020

Preview

Screenshot from 2020-04-10 20-34-52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment