Update app.py
Browse files
app.py
CHANGED
@@ -1,23 +1,22 @@
|
|
1 |
import asyncio
|
2 |
-
|
3 |
-
from fastapi
|
4 |
from fastapi.staticfiles import StaticFiles
|
5 |
from fastapi.middleware.cors import CORSMiddleware
|
6 |
from pydantic import BaseModel
|
7 |
-
from typing import List, Dict
|
8 |
import uvicorn
|
9 |
|
10 |
# Initialiser l'application FastAPI
|
11 |
app = FastAPI()
|
12 |
|
13 |
# Configurer CORS pour autoriser toutes les origines
|
14 |
-
# Permet à n'importe quel site web d'appeler votre API
|
15 |
app.add_middleware(
|
16 |
CORSMiddleware,
|
17 |
-
allow_origins=["*"],
|
18 |
allow_credentials=True,
|
19 |
-
allow_methods=["*"],
|
20 |
-
allow_headers=["*"],
|
21 |
)
|
22 |
|
23 |
# Monter un répertoire statique pour servir le fichier index.html
|
@@ -28,114 +27,103 @@ class MockRequest(BaseModel):
|
|
28 |
parameter: str
|
29 |
|
30 |
class ConnectionManager:
|
31 |
-
"""Gère les connexions WebSocket actives."""
|
32 |
def __init__(self):
|
33 |
self.active_connections: List[WebSocket] = []
|
34 |
-
#
|
35 |
self.response_futures: Dict[str, asyncio.Future] = {}
|
36 |
|
37 |
async def connect(self, websocket: WebSocket):
|
38 |
-
"""Accepte une nouvelle connexion WebSocket."""
|
39 |
await websocket.accept()
|
40 |
self.active_connections.append(websocket)
|
41 |
print(f"Nouvelle connexion WebSocket. Total: {len(self.active_connections)}")
|
42 |
|
43 |
def disconnect(self, websocket: WebSocket):
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
45 |
self.active_connections.remove(websocket)
|
46 |
print(f"Déconnexion WebSocket. Total: {len(self.active_connections)}")
|
47 |
|
48 |
-
async def
|
49 |
-
"""Envoie
|
50 |
-
|
51 |
-
|
52 |
-
websocket = self.active_connections[0]
|
53 |
-
await websocket.send_text(message)
|
54 |
-
# Créer un Future pour attendre la réponse
|
55 |
-
future = asyncio.get_event_loop().create_future()
|
56 |
-
# Utilise l'identifiant du client comme clé, bien que simple, c'est plus robuste
|
57 |
-
client_id = str(id(websocket))
|
58 |
-
self.response_futures[client_id] = future
|
59 |
-
return future
|
60 |
-
return None
|
61 |
-
|
62 |
-
manager = ConnectionManager()
|
63 |
|
|
|
|
|
64 |
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
raise HTTPException(status_code=404, detail="index.html not found")
|
73 |
|
|
|
74 |
|
75 |
-
|
76 |
-
|
77 |
-
"""
|
78 |
-
Endpoint API qui prend un string, le transmet via WebSocket,
|
79 |
-
attend une réponse et la retourne.
|
80 |
-
"""
|
81 |
try:
|
82 |
-
# Récupérer les données JSON du corps de la requête
|
83 |
-
# data = await request.json()
|
84 |
input_string = payload.parameter
|
85 |
-
|
86 |
-
if input_string is None:
|
87 |
-
return {"error": "Le paramètre 'parameter' est manquant."}
|
88 |
-
|
89 |
-
print(f"Endpoint /v1/mock appelé avec: '{input_string}'")
|
90 |
|
91 |
if not manager.active_connections:
|
92 |
return {"error": "Aucun client WebSocket n'est connecté."}
|
93 |
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
return {"error": "Échec de la diffusion du message."}
|
100 |
|
101 |
-
|
102 |
-
|
103 |
-
websocket_response = await asyncio.wait_for(response_future, timeout=10.0)
|
104 |
-
print(f"Réponse reçue du WebSocket: '{websocket_response}'")
|
105 |
-
return {"response_from_client": websocket_response}
|
106 |
-
|
107 |
-
except asyncio.TimeoutError:
|
108 |
-
print("Timeout: Aucune réponse du client WebSocket.")
|
109 |
-
return {"error": "Timeout: Le client n'a pas répondu à temps."}
|
110 |
|
|
|
|
|
|
|
111 |
except Exception as e:
|
112 |
-
print(f"Erreur dans
|
113 |
return {"error": f"Une erreur interne est survenue: {str(e)}"}
|
114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
@app.websocket("/ws")
|
117 |
async def websocket_endpoint(websocket: WebSocket):
|
118 |
-
"""Gère la communication WebSocket avec le client."""
|
119 |
await manager.connect(websocket)
|
120 |
try:
|
121 |
while True:
|
122 |
-
#
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
manager.response_futures[
|
130 |
-
del manager.response_futures[
|
|
|
|
|
131 |
|
132 |
except WebSocketDisconnect:
|
133 |
manager.disconnect(websocket)
|
134 |
-
print("Client déconnecté.")
|
135 |
except Exception as e:
|
136 |
print(f"Erreur dans le WebSocket: {e}")
|
137 |
manager.disconnect(websocket)
|
138 |
|
139 |
-
|
140 |
if __name__ == "__main__":
|
141 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
1 |
import asyncio
|
2 |
+
import uuid
|
3 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
4 |
from fastapi.staticfiles import StaticFiles
|
5 |
from fastapi.middleware.cors import CORSMiddleware
|
6 |
from pydantic import BaseModel
|
7 |
+
from typing import List, Dict, Any
|
8 |
import uvicorn
|
9 |
|
10 |
# Initialiser l'application FastAPI
|
11 |
app = FastAPI()
|
12 |
|
13 |
# Configurer CORS pour autoriser toutes les origines
|
|
|
14 |
app.add_middleware(
|
15 |
CORSMiddleware,
|
16 |
+
allow_origins=["*"],
|
17 |
allow_credentials=True,
|
18 |
+
allow_methods=["*"],
|
19 |
+
allow_headers=["*"],
|
20 |
)
|
21 |
|
22 |
# Monter un répertoire statique pour servir le fichier index.html
|
|
|
27 |
parameter: str
|
28 |
|
29 |
class ConnectionManager:
|
30 |
+
"""Gère les connexions WebSocket actives et les réponses en attente."""
|
31 |
def __init__(self):
|
32 |
self.active_connections: List[WebSocket] = []
|
33 |
+
# Utilise un ID de requête pour lier les requêtes aux réponses
|
34 |
self.response_futures: Dict[str, asyncio.Future] = {}
|
35 |
|
36 |
async def connect(self, websocket: WebSocket):
|
|
|
37 |
await websocket.accept()
|
38 |
self.active_connections.append(websocket)
|
39 |
print(f"Nouvelle connexion WebSocket. Total: {len(self.active_connections)}")
|
40 |
|
41 |
def disconnect(self, websocket: WebSocket):
|
42 |
+
# Annuler tous les futures en attente pour ce client déconnecté
|
43 |
+
keys_to_remove = [key for key, (ws, _) in self.response_futures.items() if ws == websocket]
|
44 |
+
for key in keys_to_remove:
|
45 |
+
self.response_futures[key][1].cancel()
|
46 |
+
del self.response_futures[key]
|
47 |
+
|
48 |
self.active_connections.remove(websocket)
|
49 |
print(f"Déconnexion WebSocket. Total: {len(self.active_connections)}")
|
50 |
|
51 |
+
async def send_action_and_wait(self, action: str, data: Any):
|
52 |
+
"""Envoie une action JSON au client et attend une réponse."""
|
53 |
+
if not self.active_connections:
|
54 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
|
56 |
+
request_id = str(uuid.uuid4())
|
57 |
+
websocket = self.active_connections[0] # Simplification : on envoie au premier client
|
58 |
|
59 |
+
future = asyncio.get_event_loop().create_future()
|
60 |
+
self.response_futures[request_id] = future
|
61 |
+
|
62 |
+
message_to_send = {"request_id": request_id, "action": action, "data": data}
|
63 |
+
await websocket.send_json(message_to_send)
|
64 |
+
|
65 |
+
return await future
|
|
|
66 |
|
67 |
+
manager = ConnectionManager()
|
68 |
|
69 |
+
async def handle_api_request(action: str, payload: MockRequest):
|
70 |
+
"""Factorise la logique commune aux endpoints API."""
|
|
|
|
|
|
|
|
|
71 |
try:
|
|
|
|
|
72 |
input_string = payload.parameter
|
73 |
+
print(f"Endpoint pour l'action '{action}' appelé avec: '{input_string}'")
|
|
|
|
|
|
|
|
|
74 |
|
75 |
if not manager.active_connections:
|
76 |
return {"error": "Aucun client WebSocket n'est connecté."}
|
77 |
|
78 |
+
print(f"Envoi de l'action '{action}' au client WebSocket...")
|
79 |
+
websocket_response = await manager.send_action_and_wait(action, input_string)
|
80 |
+
|
81 |
+
if websocket_response is None:
|
82 |
+
return {"error": "Échec de la communication avec le client."}
|
|
|
83 |
|
84 |
+
print(f"Réponse reçue du WebSocket pour l'action '{action}': '{websocket_response}'")
|
85 |
+
return {"response_from_client": websocket_response}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
+
except asyncio.CancelledError:
|
88 |
+
print("La tâche de réponse a été annulée (probablement déconnexion du client).")
|
89 |
+
return {"error": "La requête a été annulée car le client s'est déconnecté."}
|
90 |
except Exception as e:
|
91 |
+
print(f"Erreur dans handle_api_request: {e}")
|
92 |
return {"error": f"Une erreur interne est survenue: {str(e)}"}
|
93 |
|
94 |
+
@app.post("/v1/mock")
|
95 |
+
async def mock_endpoint(payload: MockRequest):
|
96 |
+
"""Demande au client une phrase prédéfinie."""
|
97 |
+
return await handle_api_request("get_sentence", payload)
|
98 |
+
|
99 |
+
@app.post("/v1/reverse")
|
100 |
+
async def reverse_endpoint(payload: MockRequest):
|
101 |
+
"""Demande au client d'inverser une chaîne de caractères."""
|
102 |
+
return await handle_api_request("reverse_string", payload)
|
103 |
+
|
104 |
|
105 |
@app.websocket("/ws")
|
106 |
async def websocket_endpoint(websocket: WebSocket):
|
|
|
107 |
await manager.connect(websocket)
|
108 |
try:
|
109 |
while True:
|
110 |
+
# Attend une réponse JSON du client
|
111 |
+
response_data = await websocket.receive_json()
|
112 |
+
request_id = response_data.get("request_id")
|
113 |
+
response_payload = response_data.get("response")
|
114 |
+
|
115 |
+
if request_id and request_id in manager.response_futures:
|
116 |
+
# Marque le future comme terminé avec le résultat
|
117 |
+
manager.response_futures[request_id].set_result(response_payload)
|
118 |
+
del manager.response_futures[request_id]
|
119 |
+
else:
|
120 |
+
print(f"Réponse reçue avec un ID inconnu ou manquant: {request_id}")
|
121 |
|
122 |
except WebSocketDisconnect:
|
123 |
manager.disconnect(websocket)
|
|
|
124 |
except Exception as e:
|
125 |
print(f"Erreur dans le WebSocket: {e}")
|
126 |
manager.disconnect(websocket)
|
127 |
|
|
|
128 |
if __name__ == "__main__":
|
129 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|