#!/usr/bin/env python3 from http.server import HTTPServer, BaseHTTPRequestHandler, ThreadingHTTPServer import json import sys import os import random import threading import time from datetime import datetime, timedelta sessions = {} clients = {} class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): def _add_cors_headers(self): self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') self.send_header('Access-Control-Allow-Headers', 'Content-Type') def do_OPTIONS(self): self.send_response(200) self._add_cors_headers() self.end_headers() def do_GET(self): if self.path == '/': self.send_response(200) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'message': 'API Server is running!', 'status': 'ok'} self.wfile.write(json.dumps(response).encode()) elif self.path == '/health': self.send_response(200) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'status': 'healthy', 'active_sessions': len(sessions)} self.wfile.write(json.dumps(response).encode()) elif self.path.startswith('/api/session'): self.handle_session_get() elif self.path.startswith('/api/events/'): self.handle_sse_connection() else: self.send_response(404) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'error': 'Endpoint not found'} self.wfile.write(json.dumps(response).encode()) def do_POST(self): if self.path == '/api/session': self.handle_session_post() elif self.path == '/api/message': self.handle_message_post() elif self.path.startswith('/api/session/'): pass else: content_length = int(self.headers['Content-Length']) if self.headers.get('Content-Length') else 0 post_data = self.rfile.read(content_length) if content_length > 0 else b'' try: data = json.loads(post_data.decode()) if post_data else {} except json.JSONDecodeError: self.send_response(400) self.send_header('Content-type', 'application/json') self.end_headers() response = {'error': 'Invalid JSON'} self.wfile.write(json.dumps(response).encode()) return self.send_response(200) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'message': 'Data received', 'data': data} self.wfile.write(json.dumps(response).encode()) def handle_session_get(self): path_parts = self.path.split('/') if len(path_parts) >= 4 and path_parts[3]: session_code = path_parts[3].upper() if session_code in sessions: self.send_response(200) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'exists': True, 'session_code': session_code} self.wfile.write(json.dumps(response).encode()) else: self.send_response(404) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'exists': False, 'session_code': session_code} self.wfile.write(json.dumps(response).encode()) else: self.send_response(400) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'error': 'Missing session code'} self.wfile.write(json.dumps(response).encode()) def handle_session_post(self): content_length = int(self.headers['Content-Length']) if self.headers.get('Content-Length') else 0 post_data = self.rfile.read(content_length) if content_length > 0 else b'' try: data = json.loads(post_data.decode()) if post_data else {} except json.JSONDecodeError: self.send_response(400) self.send_header('Content-type', 'application/json') self.end_headers() response = {'error': 'Invalid JSON'} self.wfile.write(json.dumps(response).encode()) return session_code = None attempts = 0 while session_code is None and attempts < 100: code = ''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=4)) if code not in sessions: session_code = code attempts += 1 if session_code is None: self.send_response(500) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'error': 'Could not generate unique session code'} self.wfile.write(json.dumps(response).encode()) return sessions[session_code] = { 'clients': [], 'created_at': datetime.now(), 'host': data.get('client_id', 'unknown') } self.send_response(201) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'session_code': session_code, 'created': True} self.wfile.write(json.dumps(response).encode()) def handle_sse_connection(self): path_parts = self.path.split('/') if len(path_parts) >= 5 and path_parts[3] and path_parts[4]: session_code = path_parts[3].upper() client_id = path_parts[4] if session_code in sessions and client_id: if client_id not in sessions[session_code]['clients']: sessions[session_code]['clients'].append(client_id) clients[client_id] = { 'session': session_code, 'connection': self } self.send_response(200) self.send_header('Content-Type', 'text/event-stream') self.send_header('Cache-Control', 'no-cache') self.send_header('Connection', 'keep-alive') self._add_cors_headers() self.end_headers() try: while True: time.sleep(30) self.wfile.write(b': keepalive\n\n') self.wfile.flush() except: if client_id in clients: if client_id in sessions[session_code]['clients']: sessions[session_code]['clients'].remove(client_id) del clients[client_id] else: self.send_response(404) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'error': 'Session not found or missing client ID'} self.wfile.write(json.dumps(response).encode()) else: self.send_response(400) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'error': 'Missing session code or client ID'} self.wfile.write(json.dumps(response).encode()) def handle_message_post(self): content_length = int(self.headers['Content-Length']) if self.headers.get('Content-Length') else 0 post_data = self.rfile.read(content_length) if content_length > 0 else b'' try: data = json.loads(post_data.decode()) except json.JSONDecodeError: self.send_response(400) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'error': 'Invalid JSON'} self.wfile.write(json.dumps(response).encode()) return client_id = data.get('client_id') session_code = data.get('session_code') if session_code and session_code in sessions: broadcast_message = { 'type': 'message', 'client_id': client_id, 'username': data.get('username', f'User-{client_id[:8]}'), 'content': data['content'], 'timestamp': datetime.now().isoformat(), 'session_code': session_code } if 'replyTo' in data: broadcast_message['replyTo'] = data['replyTo'] for cid in sessions[session_code]['clients']: if cid in clients and cid != client_id: try: conn = clients[cid]['connection'] event_data = f"data: {json.dumps(broadcast_message)}\n\n" conn.wfile.write(event_data.encode()) conn.wfile.flush() except: pass self.send_response(200) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'sent': True} self.wfile.write(json.dumps(response).encode()) else: self.send_response(404) self.send_header('Content-type', 'application/json') self._add_cors_headers() self.end_headers() response = {'error': 'Session not found'} self.wfile.write(json.dumps(response).encode()) def log_message(self, format, *args): pass def cleanup_sessions(): current_time = datetime.now() expired_sessions = [] for session_code, session_data in sessions.items(): if current_time - session_data['created_at'] > timedelta(minutes=15): expired_sessions.append(session_code) for session_code in expired_sessions: if session_code in sessions: for client_id in sessions[session_code]['clients']: if client_id in clients: try: conn = clients[client_id]['connection'] event_data = f"data: {json.dumps({'type': 'session_expired', 'session_code': session_code})}\n\n" conn.wfile.write(event_data.encode()) conn.wfile.flush() except: pass del sessions[session_code] if expired_sessions: print(f"Cleaned up {len(expired_sessions)} expired sessions") def run_server(port=8000, host='0.0.0.0'): server_address = (host, port) httpd = ThreadingHTTPServer(server_address, SimpleHTTPRequestHandler) print(f"Server running on http://{host}:{port}") print("Press Ctrl+C to stop") def cleanup_timer(): while True: time.sleep(300) cleanup_sessions() cleanup_thread = threading.Thread(target=cleanup_timer, daemon=True) cleanup_thread.start() try: httpd.serve_forever() except KeyboardInterrupt: print("\nServer stopped") httpd.server_close() if __name__ == '__main__': port = int(sys.argv[1]) if len(sys.argv) > 1 else 7860 run_server(port)