import Flask from flask import request, render_template_string, jsonify, redirect, url_for import requests import threading import uuid # Pour générer des identifiants uniques pour chaque tâche import time import copy # Pour copier le payload pour chaque requête app = Flask(__name__) # --- Configuration --- TARGET_URL = "https://hook.us1.make.com/zal5qn0ggbewmvtsbo2uenfno8tz3n56" BASE_PAYLOAD = { "name": "Testeur Auto ", "email": "yoo+auto@example.com", # Ajout de '+auto' pour distinguer "company": "aragon Inc.", "message": "Ceci est un test automatisé via Flask.", "date": "2023-10-27T10:30:00Z", # Tu pourrais rendre cette date dynamique si besoin "source": "http://simulateur-bsbs-flask.com" } # Structure pour stocker l'état des tâches (jobs) en mémoire # Format: { 'job_id': {'status': 'running'/'completed'/'failed', 'total': N, 'completed_count': M, 'error_count': E, 'errors': [...] } } jobs = {} jobs_lock = threading.Lock() # Pour éviter les problèmes d'accès concurrents au dict jobs # --- Templates HTML --- # Page d'accueil pour démarrer les requêtes HTML_INDEX = """ Lanceur de Requêtes

Envoyer des Requêtes POST en Masse

{% if error %}

{{ error }}

{% endif %}

Tâches en cours / terminées :

""" # Page pour suivre la progression d'une tâche spécifique HTML_STATUS = """ Statut Tâche {{ job_id }}

Statut de la Tâche : {{ job_id }}

Chargement...
0%

Requêtes complétées : 0 / ?

Erreurs : 0

Retour à l'accueil

""" # --- Fonctions Logiques --- def send_single_request(target_url, payload, job_id, request_index): """Fonction pour envoyer UNE requête POST.""" # Crée une copie pour éviter de modifier l'original et pour ajouter un identifiant current_payload = copy.deepcopy(payload) current_payload['message'] += f" (Requête {request_index + 1})" # Ajoute un numéro à chaque message current_payload['request_uuid'] = str(uuid.uuid4()) # Ajoute un id unique par requête try: response = requests.post(target_url, json=current_payload, timeout=30) # Timeout de 30s response.raise_for_status() # Lève une exception pour les codes d'erreur HTTP (4xx, 5xx) return True, None # Succès except requests.exceptions.RequestException as e: print(f"Erreur requête {request_index + 1} pour job {job_id}: {e}") return False, f"Req {request_index + 1}: {str(e)}" # Échec avec message d'erreur def background_task(job_id, num_requests, target_url, base_payload): """Fonction exécutée dans un thread séparé pour envoyer les requêtes.""" print(f"Tâche {job_id}: Démarrage de {num_requests} requêtes vers {target_url}") completed_count = 0 error_count = 0 error_messages = [] # Initialiser le statut (on le fait déjà dans /start mais re-vérifier est ok) with jobs_lock: if job_id not in jobs: jobs[job_id] = { 'status': 'running', 'total': num_requests, 'completed_count': 0, 'error_count': 0, 'errors': [] } for i in range(num_requests): success, error_msg = send_single_request(target_url, base_payload, job_id, i) completed_count += 1 if not success: error_count += 1 error_messages.append({'index': i + 1, 'error': error_msg}) # Mettre à jour la progression dans le dictionnaire partagé (avec verrou) with jobs_lock: jobs[job_id]['completed_count'] = completed_count jobs[job_id]['error_count'] = error_count # Gardons seulement les X dernières erreurs pour éviter de saturer la mémoire jobs[job_id]['errors'] = error_messages[-50:] # Garde les 50 dernières erreurs # Petite pause optionnelle pour ne pas submerger la cible (ex: 0.1 seconde) # time.sleep(0.1) # Marquer la tâche comme terminée final_status = 'failed' if error_count == num_requests else ('completed' if error_count == 0 else 'completed_with_errors') with jobs_lock: jobs[job_id]['status'] = final_status print(f"Tâche {job_id}: Terminé. {completed_count - error_count} succès, {error_count} erreurs.") # --- Routes Flask --- @app.route('/', methods=['GET']) def index(): """Affiche la page d'accueil avec le formulaire.""" # On passe une copie triée des jobs récents au template with jobs_lock: # Trie par exemple par clé (qui approxime l'ordre de création ici) # ou ajoute un timestamp à la création du job pour trier par date. sorted_jobs = dict(sorted(jobs.items(), reverse=True)) return render_template_string(HTML_INDEX, jobs_list=sorted_jobs) @app.route('/start', methods=['POST']) def start_requests(): """Reçoit le nombre de requêtes et lance la tâche en arrière-plan.""" try: num_requests = int(request.form.get('num_requests')) if num_requests <= 0: raise ValueError("Le nombre de requêtes doit être positif.") except (TypeError, ValueError) as e: with jobs_lock: sorted_jobs = dict(sorted(jobs.items(), reverse=True)) return render_template_string(HTML_INDEX, error=f"Nombre invalide: {e}", jobs_list=sorted_jobs), 400 job_id = str(uuid.uuid4())[:8] # ID de tâche court et unique # Initialiser l'état de la tâche avant de démarrer le thread with jobs_lock: jobs[job_id] = { 'status': 'starting', # ou 'queued' 'total': num_requests, 'completed_count': 0, 'error_count': 0, 'errors': [] } # Créer et démarrer le thread thread = threading.Thread(target=background_task, args=(job_id, num_requests, TARGET_URL, BASE_PAYLOAD)) thread.daemon = True # Permet au programme principal de quitter même si des threads tournent thread.start() print(f"Nouvelle tâche démarrée avec ID: {job_id}") # Rediriger vers la page de statut de cette tâche return redirect(url_for('job_status', job_id=job_id)) @app.route('/status/', methods=['GET']) def job_status(job_id): """Affiche la page HTML de suivi pour une tâche spécifique.""" with jobs_lock: if job_id not in jobs: return "Tâche non trouvée", 404 # La page HTML utilisera l'API /api/status pour les mises à jour dynamiques return render_template_string(HTML_STATUS, job_id=job_id) @app.route('/api/status/', methods=['GET']) def api_job_status(job_id): """Fournit l'état actuel d'une tâche au format JSON (pour le JavaScript).""" with jobs_lock: job_info = jobs.get(job_id) if job_info: # Renvoyer une copie pour éviter les modifs concurrentes pendant la sérialisation JSON return jsonify(copy.deepcopy(job_info)) else: # Renvoyer une réponse JSON même pour une erreur 404 return jsonify({"error": "Tâche non trouvée"}), 404 # --- Démarrage de l'application --- if __name__ == '__main__': # Utilise host='0.0.0.0' pour rendre accessible depuis d'autres machines sur le réseau # Attention: debug=True ne doit PAS être utilisé en production app.run(host='0.0.0.0', port=5000, debug=True)