Update server.py
Browse files
server.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
-
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect, status
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
-
from fastapi.responses import HTMLResponse
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
from fastapi.templating import Jinja2Templates
|
| 6 |
import json
|
| 7 |
import asyncio
|
| 8 |
import uvicorn
|
| 9 |
-
from typing import Dict, Set
|
| 10 |
import logging
|
| 11 |
|
| 12 |
# Logging konfigurieren
|
|
@@ -49,11 +49,23 @@ async def get_map_by_normalized_name(request: Request, normalized_name: str):
|
|
| 49 |
return templates.TemplateResponse("map.html", {"request": request, "maps": filtered_maps})
|
| 50 |
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
class ConnectionManager:
|
| 53 |
def __init__(self):
|
| 54 |
self.groups: Dict[str, Set[WebSocket]] = {}
|
| 55 |
self.connections: Dict[WebSocket, str] = {}
|
|
|
|
| 56 |
self.message_stats = {"total_sent": 0, "total_received": 0}
|
|
|
|
| 57 |
|
| 58 |
async def connect(self, websocket: WebSocket):
|
| 59 |
await websocket.accept()
|
|
@@ -65,7 +77,15 @@ class ConnectionManager:
|
|
| 65 |
self.remove_from_group(websocket, group_name)
|
| 66 |
logger.info(f"Client disconnected from group '{group_name}'")
|
| 67 |
|
| 68 |
-
async def join_group(self, websocket: WebSocket, group_name: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
# Remove from previous group if exists
|
| 70 |
if websocket in self.connections:
|
| 71 |
old_group = self.connections[websocket]
|
|
@@ -74,24 +94,53 @@ class ConnectionManager:
|
|
| 74 |
# Add to new group
|
| 75 |
if group_name not in self.groups:
|
| 76 |
self.groups[group_name] = set()
|
|
|
|
|
|
|
|
|
|
| 77 |
self.groups[group_name].add(websocket)
|
| 78 |
self.connections[websocket] = group_name
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
def remove_from_group(self, websocket: WebSocket, group_name: str):
|
| 82 |
if group_name in self.groups and websocket in self.groups[group_name]:
|
| 83 |
self.groups[group_name].remove(websocket)
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
del self.groups[group_name]
|
|
|
|
|
|
|
| 86 |
logger.info(f"Group '{group_name}' deleted (no members)")
|
| 87 |
|
| 88 |
if websocket in self.connections:
|
| 89 |
del self.connections[websocket]
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
async def broadcast_to_group(self, message: str, group_name: str):
|
| 92 |
if group_name in self.groups:
|
| 93 |
disconnected = set()
|
| 94 |
for connection in self.groups[group_name].copy(): # Kopie für sichere Iteration
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
try:
|
| 96 |
await connection.send_text(message)
|
| 97 |
self.message_stats["total_sent"] += 1
|
|
@@ -104,7 +153,31 @@ class ConnectionManager:
|
|
| 104 |
self.disconnect(conn)
|
| 105 |
|
| 106 |
def get_stats(self):
|
| 107 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
|
| 110 |
manager = ConnectionManager()
|
|
@@ -124,10 +197,24 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
| 124 |
# Handle join messages
|
| 125 |
if message.get("type") == "join":
|
| 126 |
group_name = message.get("group")
|
|
|
|
| 127 |
if group_name:
|
| 128 |
-
await manager.join_group(websocket, group_name)
|
| 129 |
# Bestätigungsmessage
|
| 130 |
-
await websocket.send_text(json.dumps(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
else:
|
| 132 |
error_msg = {"type": "error", "message": "Missing group name in join request"}
|
| 133 |
await websocket.send_text(json.dumps(error_msg))
|
|
@@ -165,5 +252,15 @@ async def health_check():
|
|
| 165 |
return {"status": "healthy", "websocket_stats": manager.get_stats()}
|
| 166 |
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
if __name__ == "__main__":
|
| 169 |
-
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect, status, HTTPException
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
from fastapi.templating import Jinja2Templates
|
| 6 |
import json
|
| 7 |
import asyncio
|
| 8 |
import uvicorn
|
| 9 |
+
from typing import Dict, Set, Optional, Any
|
| 10 |
import logging
|
| 11 |
|
| 12 |
# Logging konfigurieren
|
|
|
|
| 49 |
return templates.TemplateResponse("map.html", {"request": request, "maps": filtered_maps})
|
| 50 |
|
| 51 |
|
| 52 |
+
# Neuer API-Endpunkt für Map-Daten
|
| 53 |
+
@app.get("/api/map/{map_name}")
|
| 54 |
+
async def get_map_data(map_name: str):
|
| 55 |
+
"""API-Endpunkt zum Abrufen von Kartendaten"""
|
| 56 |
+
map_entry = maps_data.get(map_name)
|
| 57 |
+
if not map_entry:
|
| 58 |
+
raise HTTPException(status_code=404, detail="Map not found")
|
| 59 |
+
return JSONResponse(content=map_entry)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
class ConnectionManager:
|
| 63 |
def __init__(self):
|
| 64 |
self.groups: Dict[str, Set[WebSocket]] = {}
|
| 65 |
self.connections: Dict[WebSocket, str] = {}
|
| 66 |
+
self.client_types: Dict[WebSocket, str] = {} # Neue Speicherung für Client-Typen
|
| 67 |
self.message_stats = {"total_sent": 0, "total_received": 0}
|
| 68 |
+
self.group_leaders: Dict[str, WebSocket] = {} # Speichert den ersten Client jeder Gruppe
|
| 69 |
|
| 70 |
async def connect(self, websocket: WebSocket):
|
| 71 |
await websocket.accept()
|
|
|
|
| 77 |
self.remove_from_group(websocket, group_name)
|
| 78 |
logger.info(f"Client disconnected from group '{group_name}'")
|
| 79 |
|
| 80 |
+
async def join_group(self, websocket: WebSocket, group_name: str, client_type: str = "web"):
|
| 81 |
+
"""
|
| 82 |
+
Client einer Gruppe hinzufügen
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
websocket: WebSocket-Verbindung
|
| 86 |
+
group_name: Name der Gruppe
|
| 87 |
+
client_type: Typ des Clients ("monitor" oder "web")
|
| 88 |
+
"""
|
| 89 |
# Remove from previous group if exists
|
| 90 |
if websocket in self.connections:
|
| 91 |
old_group = self.connections[websocket]
|
|
|
|
| 94 |
# Add to new group
|
| 95 |
if group_name not in self.groups:
|
| 96 |
self.groups[group_name] = set()
|
| 97 |
+
self.group_leaders[group_name] = websocket # Erster Client ist der Gruppenleiter
|
| 98 |
+
logger.info(f"Neue Gruppe '{group_name}' erstellt von {client_type}-Client")
|
| 99 |
+
|
| 100 |
self.groups[group_name].add(websocket)
|
| 101 |
self.connections[websocket] = group_name
|
| 102 |
+
self.client_types[websocket] = client_type # Speichere den Client-Typ
|
| 103 |
+
|
| 104 |
+
# Prüfe ob dies der erste Client in der Gruppe ist
|
| 105 |
+
is_first_client = (self.group_leaders.get(group_name) == websocket)
|
| 106 |
+
client_info = {
|
| 107 |
+
"type": "joined",
|
| 108 |
+
"group": group_name,
|
| 109 |
+
"client_type": client_type,
|
| 110 |
+
"is_first": is_first_client,
|
| 111 |
+
"message": f"Erfolgreich Gruppe '{group_name}' beigetreten als {client_type}-Client"
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
logger.info(f"Client ({client_type}) joined group '{group_name}' (First: {is_first_client})")
|
| 115 |
+
return client_info
|
| 116 |
|
| 117 |
def remove_from_group(self, websocket: WebSocket, group_name: str):
|
| 118 |
if group_name in self.groups and websocket in self.groups[group_name]:
|
| 119 |
self.groups[group_name].remove(websocket)
|
| 120 |
+
# Wenn der Gruppenleiter sich trennt, wähle einen neuen
|
| 121 |
+
if self.group_leaders.get(group_name) == websocket and self.groups[group_name]:
|
| 122 |
+
# Wähle den nächsten Client als neuen Leiter
|
| 123 |
+
self.group_leaders[group_name] = next(iter(self.groups[group_name]))
|
| 124 |
+
logger.info(f"Neuer Gruppenleiter für '{group_name}' gewählt")
|
| 125 |
+
elif not self.groups[group_name]: # Leere Gruppen entfernen
|
| 126 |
del self.groups[group_name]
|
| 127 |
+
if group_name in self.group_leaders:
|
| 128 |
+
del self.group_leaders[group_name]
|
| 129 |
logger.info(f"Group '{group_name}' deleted (no members)")
|
| 130 |
|
| 131 |
if websocket in self.connections:
|
| 132 |
del self.connections[websocket]
|
| 133 |
+
if websocket in self.client_types:
|
| 134 |
+
del self.client_types[websocket]
|
| 135 |
|
| 136 |
+
async def broadcast_to_group(self, message: str, group_name: str, exclude: Optional[WebSocket] = None):
|
| 137 |
if group_name in self.groups:
|
| 138 |
disconnected = set()
|
| 139 |
for connection in self.groups[group_name].copy(): # Kopie für sichere Iteration
|
| 140 |
+
# Optional: Nachricht an bestimmten Client ausschließen
|
| 141 |
+
if connection == exclude:
|
| 142 |
+
continue
|
| 143 |
+
|
| 144 |
try:
|
| 145 |
await connection.send_text(message)
|
| 146 |
self.message_stats["total_sent"] += 1
|
|
|
|
| 153 |
self.disconnect(conn)
|
| 154 |
|
| 155 |
def get_stats(self):
|
| 156 |
+
return {
|
| 157 |
+
"active_groups": len(self.groups),
|
| 158 |
+
"total_connections": len(self.connections),
|
| 159 |
+
**self.message_stats
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
def get_group_info(self, group_name: str) -> Dict[str, Any]:
|
| 163 |
+
"""Gibt Informationen über eine Gruppe zurück"""
|
| 164 |
+
if group_name not in self.groups:
|
| 165 |
+
return {"group": group_name, "members": 0, "clients": []}
|
| 166 |
+
|
| 167 |
+
members = []
|
| 168 |
+
for ws in self.groups[group_name]:
|
| 169 |
+
client_type = self.client_types.get(ws, "unknown")
|
| 170 |
+
is_leader = (self.group_leaders.get(group_name) == ws)
|
| 171 |
+
members.append({
|
| 172 |
+
"client_type": client_type,
|
| 173 |
+
"is_leader": is_leader
|
| 174 |
+
})
|
| 175 |
+
|
| 176 |
+
return {
|
| 177 |
+
"group": group_name,
|
| 178 |
+
"members": len(members),
|
| 179 |
+
"clients": members
|
| 180 |
+
}
|
| 181 |
|
| 182 |
|
| 183 |
manager = ConnectionManager()
|
|
|
|
| 197 |
# Handle join messages
|
| 198 |
if message.get("type") == "join":
|
| 199 |
group_name = message.get("group")
|
| 200 |
+
client_type = message.get("client_type", "web") # Standard: web
|
| 201 |
if group_name:
|
| 202 |
+
join_info = await manager.join_group(websocket, group_name, client_type)
|
| 203 |
# Bestätigungsmessage
|
| 204 |
+
await websocket.send_text(json.dumps(join_info))
|
| 205 |
+
|
| 206 |
+
# Benachrichtige andere Gruppenmitglieder über neuen Client
|
| 207 |
+
notification = {
|
| 208 |
+
"type": "client_joined",
|
| 209 |
+
"group": group_name,
|
| 210 |
+
"client_type": client_type,
|
| 211 |
+
"message": f"Neuer {client_type}-Client ist der Gruppe beigetreten"
|
| 212 |
+
}
|
| 213 |
+
await manager.broadcast_to_group(
|
| 214 |
+
json.dumps(notification),
|
| 215 |
+
group_name,
|
| 216 |
+
exclude=websocket # Sende nicht an den neu verbundenen Client
|
| 217 |
+
)
|
| 218 |
else:
|
| 219 |
error_msg = {"type": "error", "message": "Missing group name in join request"}
|
| 220 |
await websocket.send_text(json.dumps(error_msg))
|
|
|
|
| 252 |
return {"status": "healthy", "websocket_stats": manager.get_stats()}
|
| 253 |
|
| 254 |
|
| 255 |
+
# Neuer Endpoint für Gruppeninformationen
|
| 256 |
+
@app.get("/api/group/{group_name}")
|
| 257 |
+
async def get_group_info(group_name: str):
|
| 258 |
+
"""API-Endpunkt zum Abrufen von Gruppeninformationen"""
|
| 259 |
+
group_info = manager.get_group_info(group_name)
|
| 260 |
+
if group_info["members"] == 0:
|
| 261 |
+
raise HTTPException(status_code=404, detail="Group not found")
|
| 262 |
+
return JSONResponse(content=group_info)
|
| 263 |
+
|
| 264 |
+
|
| 265 |
if __name__ == "__main__":
|
| 266 |
+
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
|