Update app.py
Browse files
app.py
CHANGED
@@ -1,23 +1,23 @@
|
|
1 |
import asyncio
|
2 |
-
import
|
3 |
-
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
4 |
-
from fastapi.staticfiles import StaticFiles
|
5 |
from fastapi.responses import HTMLResponse
|
|
|
6 |
from fastapi.middleware.cors import CORSMiddleware
|
7 |
from pydantic import BaseModel
|
8 |
-
from typing import List, Dict
|
9 |
import uvicorn
|
10 |
|
11 |
# Initialiser l'application FastAPI
|
12 |
app = FastAPI()
|
13 |
|
14 |
# Configurer CORS pour autoriser toutes les origines
|
|
|
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,69 +28,39 @@ 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 |
await websocket.accept()
|
39 |
self.active_connections.append(websocket)
|
40 |
print(f"Nouvelle connexion WebSocket. Total: {len(self.active_connections)}")
|
41 |
|
42 |
def disconnect(self, websocket: WebSocket):
|
43 |
-
|
44 |
-
keys_to_remove = [key for key, (ws, _) in self.response_futures.items() if ws == websocket]
|
45 |
-
for key in keys_to_remove:
|
46 |
-
self.response_futures[key][1].cancel()
|
47 |
-
del self.response_futures[key]
|
48 |
-
|
49 |
self.active_connections.remove(websocket)
|
50 |
print(f"Déconnexion WebSocket. Total: {len(self.active_connections)}")
|
51 |
|
52 |
-
async def
|
53 |
-
"""Envoie
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
return await future
|
67 |
|
68 |
manager = ConnectionManager()
|
69 |
|
70 |
-
async def handle_api_request(action: str, payload: MockRequest):
|
71 |
-
"""Factorise la logique commune aux endpoints API."""
|
72 |
-
try:
|
73 |
-
input_string = payload.parameter
|
74 |
-
print(f"Endpoint pour l'action '{action}' appelé avec: '{input_string}'")
|
75 |
-
|
76 |
-
if not manager.active_connections:
|
77 |
-
return {"error": "Aucun client WebSocket n'est connecté."}
|
78 |
-
|
79 |
-
print(f"Envoi de l'action '{action}' au client WebSocket...")
|
80 |
-
websocket_response = await manager.send_action_and_wait(action, input_string)
|
81 |
-
|
82 |
-
if websocket_response is None:
|
83 |
-
return {"error": "Échec de la communication avec le client."}
|
84 |
-
|
85 |
-
print(f"Réponse reçue du WebSocket pour l'action '{action}': '{websocket_response}'")
|
86 |
-
return {"response_from_client": websocket_response}
|
87 |
-
|
88 |
-
except asyncio.CancelledError:
|
89 |
-
print("La tâche de réponse a été annulée (probablement déconnexion du client).")
|
90 |
-
return {"error": "La requête a été annulée car le client s'est déconnecté."}
|
91 |
-
except Exception as e:
|
92 |
-
print(f"Erreur dans handle_api_request: {e}")
|
93 |
-
return {"error": f"Une erreur interne est survenue: {str(e)}"}
|
94 |
|
95 |
@app.get("/", response_class=HTMLResponse)
|
96 |
async def root():
|
@@ -101,39 +71,71 @@ async def root():
|
|
101 |
except FileNotFoundError:
|
102 |
raise HTTPException(status_code=404, detail="index.html not found")
|
103 |
|
|
|
104 |
@app.post("/v1/mock")
|
105 |
async def mock_endpoint(payload: MockRequest):
|
106 |
-
"""
|
107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
|
114 |
|
115 |
@app.websocket("/ws")
|
116 |
async def websocket_endpoint(websocket: WebSocket):
|
|
|
117 |
await manager.connect(websocket)
|
118 |
try:
|
119 |
while True:
|
120 |
-
#
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
manager.response_futures[
|
128 |
-
del manager.response_futures[
|
129 |
-
else:
|
130 |
-
print(f"Réponse reçue avec un ID inconnu ou manquant: {request_id}")
|
131 |
|
132 |
except WebSocketDisconnect:
|
133 |
manager.disconnect(websocket)
|
|
|
134 |
except Exception as e:
|
135 |
print(f"Erreur dans le WebSocket: {e}")
|
136 |
manager.disconnect(websocket)
|
137 |
|
|
|
138 |
if __name__ == "__main__":
|
139 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
1 |
import asyncio
|
2 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request
|
|
|
|
|
3 |
from fastapi.responses import HTMLResponse
|
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=["*"], # Autorise toutes les origines
|
18 |
allow_credentials=True,
|
19 |
+
allow_methods=["*"], # Autorise toutes les méthodes (GET, POST, etc.)
|
20 |
+
allow_headers=["*"], # Autorise tous les en-têtes
|
21 |
)
|
22 |
|
23 |
# Monter un répertoire statique pour servir le fichier index.html
|
|
|
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 |
+
# Dictionnaire pour attendre les réponses des clients
|
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 |
+
"""Ferme une connexion WebSocket."""
|
|
|
|
|
|
|
|
|
|
|
45 |
self.active_connections.remove(websocket)
|
46 |
print(f"Déconnexion WebSocket. Total: {len(self.active_connections)}")
|
47 |
|
48 |
+
async def broadcast(self, message: str):
|
49 |
+
"""Envoie un message à tous les clients connectés."""
|
50 |
+
# Pour ce cas simple, nous n'envoyons qu'au premier client connecté
|
51 |
+
if self.active_connections:
|
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 |
@app.get("/", response_class=HTMLResponse)
|
66 |
async def root():
|
|
|
71 |
except FileNotFoundError:
|
72 |
raise HTTPException(status_code=404, detail="index.html not found")
|
73 |
|
74 |
+
|
75 |
@app.post("/v1/mock")
|
76 |
async def mock_endpoint(payload: MockRequest):
|
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 |
+
# Envoyer le message via WebSocket et obtenir un "future" pour la réponse
|
95 |
+
print("Envoi du message au client WebSocket...")
|
96 |
+
response_future = await manager.broadcast(input_string)
|
97 |
+
|
98 |
+
if response_future is None:
|
99 |
+
return {"error": "Échec de la diffusion du message."}
|
100 |
|
101 |
+
try:
|
102 |
+
# Attendre la réponse du client WebSocket avec un timeout de 10 secondes
|
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 /v1/mock: {e}")
|
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 |
+
# Attendre un message du client
|
123 |
+
data = await websocket.receive_text()
|
124 |
+
print(f"Message reçu du client: '{data}'")
|
125 |
+
|
126 |
+
# Trouver le "future" correspondant et y mettre le résultat
|
127 |
+
client_id = str(id(websocket))
|
128 |
+
if client_id in manager.response_futures:
|
129 |
+
manager.response_futures[client_id].set_result(data)
|
130 |
+
del manager.response_futures[client_id] # Nettoyer après utilisation
|
|
|
|
|
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)
|