""" 简化的SSE实现,专门解决uvicorn无法退出的问题 """ import json import time import threading from collections import defaultdict from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt # 全局状态 _clients = {} # 存储客户端连接的状态 _client_counter = 0 _shutdown_flag = threading.Event() _lock = threading.Lock() def get_next_client_id(): """获取下一个客户端ID""" global _client_counter with _lock: _client_counter += 1 return _client_counter def add_client(client_id): """添加客户端""" with _lock: _clients[client_id] = { 'active': True, 'messages': [], 'last_heartbeat': time.time() } def remove_client(client_id): """移除客户端""" with _lock: _clients.pop(client_id, None) def broadcast_message(message): """广播消息给所有客户端""" with _lock: for client_id, client_data in _clients.items(): if client_data['active']: client_data['messages'].append(message) def get_client_messages(client_id): """获取客户端的消息""" with _lock: if client_id in _clients: messages = _clients[client_id]['messages'][:] _clients[client_id]['messages'].clear() _clients[client_id]['last_heartbeat'] = time.time() return messages return [] def cleanup_clients(): """清理所有客户端""" global _shutdown_flag _shutdown_flag.set() with _lock: for client_data in _clients.values(): client_data['active'] = False _clients.clear() @csrf_exempt def simple_sse_view(request): """简化的SSE视图""" if request.method == 'OPTIONS': response = HttpResponse() origin = request.META.get('HTTP_ORIGIN') if origin: response['Access-Control-Allow-Origin'] = origin response['Access-Control-Allow-Methods'] = 'GET, OPTIONS' response['Access-Control-Allow-Headers'] = 'authorization, content-type, cache-control, accept' response['Access-Control-Allow-Credentials'] = 'true' response['Access-Control-Max-Age'] = '86400' return response def event_generator(): client_id = get_next_client_id() add_client(client_id) try: # 发送连接成功消息 yield f"data: {json.dumps({'type': 'connected', 'client_id': client_id})}\n\n" # 主循环 - 使用更短的检查间隔 iterations = 0 while not _shutdown_flag.is_set(): # 每5次迭代检查一次消息(约0.5秒) if iterations % 5 == 0: messages = get_client_messages(client_id) for message in messages: yield f"data: {json.dumps(message)}\n\n" # 每50次迭代发送心跳(约5秒) if iterations % 50 == 0: yield f": heartbeat {time.time()}\n\n" # 短暂睡眠,让出CPU并允许快速响应关闭信号 time.sleep(0.1) iterations += 1 # 如果关闭标志被设置,退出循环 if _shutdown_flag.is_set(): break # 发送关闭消息 yield f"data: {json.dumps({'type': 'shutdown', 'message': 'Server shutting down'})}\n\n" except Exception as e: print(f"SSE error for client {client_id}: {e}") finally: remove_client(client_id) print(f"Client {client_id} disconnected") response = HttpResponse(event_generator(), content_type='text/event-stream') response['Cache-Control'] = 'no-cache' response['X-Accel-Buffering'] = 'no' # CORS headers origin = request.META.get('HTTP_ORIGIN') if origin: response['Access-Control-Allow-Origin'] = origin response['Access-Control-Allow-Credentials'] = 'true' return response def broadcast_test_message(): """广播测试消息""" message = { 'type': 'test', 'message': 'Test message', 'timestamp': time.time() } broadcast_message(message) return len(_clients) def get_status(): """获取状态""" with _lock: return { 'clients': len(_clients), 'shutdown': _shutdown_flag.is_set() }