Working broadcasting library

This commit is contained in:
2023-11-20 22:58:01 +01:00
parent 026b1a314a
commit 9d9124e00a
5 changed files with 271 additions and 5 deletions

6
flake.lock generated
View File

@@ -58,11 +58,11 @@
}, },
"nixpkgs-for-iosl": { "nixpkgs-for-iosl": {
"locked": { "locked": {
"lastModified": 1700505490, "lastModified": 1700515317,
"narHash": "sha256-MLF5dkExensQoByZCmsR/kdcwZoaY/j6/ctSvmQHBJc=", "narHash": "sha256-DSnKT3glZCKE0/Rc6ainSJUbUGC248HNKVRSbo9kxkM=",
"owner": "Luis-Hebendanz", "owner": "Luis-Hebendanz",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5f9d94794badee6fcb3230ccea75628a802baeab", "rev": "bcd6be7a5ab22c94c775945fb16a61b5867f15d2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -4,11 +4,13 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.routing import APIRoute from fastapi.routing import APIRoute
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager
from typing import Any
from ..errors import ClanError from ..errors import ClanError
from .assets import asset_path from .assets import asset_path
from .error_handlers import clan_error_handler from .error_handlers import clan_error_handler
from .routers import health, root, socket_manager from .routers import health, root, socket_manager, socket_manager2
origins = [ origins = [
"http://localhost:3000", "http://localhost:3000",
@@ -17,8 +19,15 @@ origins = [
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI) -> Any:
await socket_manager2.brd.connect()
yield
await socket_manager2.brd.disconnect()
def setup_app() -> FastAPI: def setup_app() -> FastAPI:
app = FastAPI() app = FastAPI(lifespan=lifespan)
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=origins, allow_origins=origins,
@@ -30,6 +39,7 @@ def setup_app() -> FastAPI:
app.include_router(health.router) app.include_router(health.router)
app.include_router(socket_manager.router) app.include_router(socket_manager.router)
app.include_router(socket_manager2.router)
# Needs to be last in register. Because of wildcard route # Needs to be last in register. Because of wildcard route
app.include_router(root.router) app.include_router(root.router)

View File

@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html>
<head>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'></script>
<script>
$(document).ready(function(){
if (typeof WebSocket != 'undefined') {
$('#ask').show();
} else {
$('#error').show();
}
// join on enter
$('#ask input').keydown(function(event) {
if (event.keyCode == 13) {
$('#ask a').click();
}
})
// join on click
$('#ask a').click(function() {
join($('#ask input').val());
$('#ask').hide();
$('#channel').show();
$('input#message').focus();
});
function join(name) {
var host = window.location.host.split(':')[0];
var ws = new WebSocket("ws://localhost:2979/ws2");
var container = $('div#msgs');
ws.onmessage = function(evt) {
var obj = JSON.parse(evt.data);
if (typeof obj != 'object') return;
console.log("Received: " + obj);
var action = obj['action'];
var struct = container.find('li.' + action + ':first');
if (struct.length < 1) {
console.log("Could not handle: " + evt.data);
return;
}
var msg = struct.clone();
msg.find('.time').text((new Date()).toString("HH:mm:ss"));
if (action == 'message') {
var matches;
if (matches = obj['message'].match(/^\s*[\/\\]me\s(.*)/)) {
msg.find('.user').text(obj['user'] + ' ' + matches[1]);
msg.find('.user').css('font-weight', 'bold');
} else {
msg.find('.user').text(obj['user']);
msg.find('.message').text(': ' + obj['message']);
}
} else if (action == 'control') {
msg.find('.user').text(obj['user']);
msg.find('.message').text(obj['message']);
msg.addClass('control');
}
if (obj['user'] == name) msg.find('.user').addClass('self');
container.find('ul').append(msg.show());
container.scrollTop(container.find('ul').innerHeight());
}
$('#channel form').submit(function(event) {
event.preventDefault();
var input = $(this).find(':input');
var msg = input.val();
if (msg) {
ws.send(JSON.stringify({ action: 'message', user: name, message: msg }));
}
input.val('');
});
}
});
</script>
<style type="text/css" media="screen">
* {
font-family: Georgia;
}
a {
color: #000;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
div.bordered {
margin: 0 auto;
margin-top: 100px;
width: 600px;
padding: 20px;
text-align: center;
border: 10px solid #ddd;
-webkit-border-radius: 20px;
}
#error {
background-color: #BA0000;
color: #fff;
font-weight: bold;
}
#ask {
font-size: 20pt;
}
#ask input {
font-size: 20pt;
padding: 10px;
margin: 0 10px;
}
#ask span.join {
padding: 10px;
background-color: #ddd;
-webkit-border-radius: 10px;
}
#channel {
margin-top: 100px;
height: 480px;
position: relative;
}
#channel div#descr {
position: absolute;
left: -10px;
top: -190px;
font-size: 13px;
text-align: left;
line-height: 20px;
padding: 5px;
width: 630px;
}
div#msgs {
overflow-y: scroll;
height: 400px;
}
div#msgs ul {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
div#msgs li {
line-height: 20px;
}
div#msgs li span.user {
color: #ff9900;
}
div#msgs li span.user.self {
color: #aa2211;
}
div#msgs li span.time {
float: right;
margin-right: 5px;
color: #aaa;
font-family: "Courier New";
font-size: 12px;
}
div#msgs li.control {
text-align: center;
}
div#msgs li.control span.message {
color: #aaa;
}
div#input {
text-align: left;
margin-top: 20px;
}
div#input #message {
width: 600px;
border: 5px solid #bbb;
-webkit-border-radius: 3px;
font-size: 30pt;
}
</style>
</head>
<body>
<div id="error" class="bordered" style="display: none;">
This browser has no native WebSocket support.<br/>
Use a WebKit nightly or Google Chrome.
</div>
<div id="ask" class="bordered" style="display: none;">
Name: <input type="text" id="name" /> <a href="#"><span class="join">Join!</span></a>
</div>
<div id="channel" class="bordered" style="display: none;">
<div id="descr" class="bordered">
<strong>Tip:</strong> Open up another browser window to chat.
</div>
<div id="msgs">
<ul>
<li class="message" style="display: none">
<span class="user"></span><span class="message"></span>
<span class="time"></span>
</li>
</ul>
</div>
<div id="input">
<form><input type="text" id="message" /></form>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,54 @@
# Requires: `starlette`, `uvicorn`, `jinja2`
# Run with `uvicorn example:app`
import anyio
from broadcaster import Broadcast
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from fastapi import APIRouter, Response
import os
import logging
import asyncio
log = logging.getLogger(__name__)
router = APIRouter()
brd = Broadcast("memory://")
@router.get("/ws2_example")
async def get() -> HTMLResponse:
html = open(f"{os.getcwd()}/webui/routers/messenger.html").read()
return HTMLResponse(html)
@router.websocket("/ws2")
async def chatroom_ws(websocket: WebSocket) -> None:
await websocket.accept()
async with anyio.create_task_group() as task_group:
# run until first is complete
async def run_chatroom_ws_receiver() -> None:
await chatroom_ws_receiver(websocket=websocket)
task_group.cancel_scope.cancel()
task_group.start_soon(run_chatroom_ws_receiver)
log.warning("Started chatroom_ws_sender")
await chatroom_ws_sender(websocket)
async def chatroom_ws_receiver(websocket: WebSocket) -> None:
async for message in websocket.iter_text():
log.warning(f"Received message: {message}")
await brd.publish(channel="chatroom", message=message)
async def chatroom_ws_sender(websocket: WebSocket) -> None:
async with brd.subscribe(channel="chatroom") as subscriber:
log.warning("====>Subscribed to chatroom channel")
async for event in subscriber:
log.warning(f"Sending message: {event.message}")
await websocket.send_text(event.message)