8.4 KiB
Upload I/O Asynchrone — Documentation
Date: 2025-01-27
Status: ✅ IMPLEMENTED - MOD-P2-008
Vue d'ensemble
L'upload de fichiers audio utilise maintenant une sémantique asynchrone avec réponse 202 Accepted. La copie fichier (io.Copy) se fait en arrière-plan dans une goroutine suivie, permettant au handler HTTP de répondre immédiatement.
Sémantique HTTP
Endpoint: POST /api/v1/tracks
Réponse: 202 Accepted
Headers:
Location: /api/v1/tracks/{track_id}/status
Body:
{
"success": true,
"data": {
"track_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "uploading",
"status_url": "/api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status",
"message": "Upload initiated, file is being saved in background"
}
}
Flux d'Exécution
1. Handler (UploadTrack)
- Validation du fichier (ClamAV, format, quota)
- Création du Track en DB avec
Status=Uploadingimmédiatement - Lancement de la copie fichier en goroutine (
copyFileAsync) - Réponse 202 Accepted avec
track_id
2. Goroutine Asynchrone (copyFileAsync)
- Création d'un contexte avec timeout (5 minutes)
- Ouverture du fichier source (
fileHeader.Open()) - Création du fichier destination (
os.Create) - Copie avec
io.Copy - Mise à jour du Status:
Processingsi succèsFailedsi erreur
- Nettoyage automatique en cas d'échec (
os.Remove)
Suivi de Progression
Endpoint: GET /api/v1/tracks/{id}/status
Réponse: 200 OK
Body:
{
"success": true,
"data": {
"track_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"progress": 100,
"message": "File uploaded, processing..."
}
}
Status possibles:
uploading: Fichier en cours de copieprocessing: Fichier copié, traitement en courscompleted: Track prêtfailed: Échec (upload ou traitement)
Gestion d'Erreurs
Erreurs Synchrones (avant goroutine)
- Validation échouée:
400 Bad Request - Quota dépassé:
403 Forbidden - ClamAV unavailable:
503 Service Unavailable - Virus détecté:
422 Unprocessable Entity
Erreurs Asynchrones (dans goroutine)
- Erreur de copie: Status →
Failed, fichier nettoyé - Timeout (5 min): Status →
Failed, fichier nettoyé - Contexte annulé: Status →
Failed, fichier nettoyé
Nettoyage automatique: Le fichier est supprimé (os.Remove) en cas d'échec.
Traçabilité
Logs
Tous les logs incluent:
track_id: UUID du trackuser_id: UUID de l'utilisateurrequest_id: ID de requête (si disponible via context)
Exemples:
INFO Track upload initiated (async) track_id=... user_id=... filename=...
INFO Track status updated track_id=... status=processing message=...
INFO Track file copied successfully (async) track_id=... bytes_written=...
Request ID
Le request_id est propagé via le contexte:
ctx := c.Request.Context() // Contient request_id du middleware
track, err := service.UploadTrack(ctx, userID, fileHeader)
Exemples cURL
1. Upload d'un fichier
curl -X POST http://localhost:8080/api/v1/tracks \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@audio.mp3" \
-v
Réponse:
HTTP/1.1 202 Accepted
Location: /api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status
{
"success": true,
"data": {
"track_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "uploading",
"status_url": "/api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status",
"message": "Upload initiated, file is being saved in background"
}
}
2. Vérifier le statut
curl -X GET http://localhost:8080/api/v1/tracks/550e8400-e29b-41d4-a716-446655440000/status \
-H "Authorization: Bearer YOUR_TOKEN"
Réponse (pendant upload):
{
"success": true,
"data": {
"track_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "uploading",
"progress": 0,
"message": "Upload started"
}
}
Réponse (après copie):
{
"success": true,
"data": {
"track_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"progress": 100,
"message": "File uploaded, processing..."
}
}
Réponse (échec):
{
"success": true,
"data": {
"track_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"progress": 0,
"message": "Failed to save file: ..."
}
}
Polling Recommandé
Stratégie Simple
async function uploadAndWait(trackId) {
const maxAttempts = 60; // 5 minutes max (5s * 60)
const interval = 5000; // 5 secondes
for (let i = 0; i < maxAttempts; i++) {
const response = await fetch(`/api/v1/tracks/${trackId}/status`);
const data = await response.json();
if (data.data.status === 'completed') {
return data.data;
}
if (data.data.status === 'failed') {
throw new Error(data.data.message);
}
await sleep(interval);
}
throw new Error('Upload timeout');
}
Stratégie avec Exponential Backoff
async function uploadAndWaitWithBackoff(trackId) {
let interval = 1000; // 1 seconde initial
const maxInterval = 30000; // 30 secondes max
const maxAttempts = 120; // ~10 minutes max
for (let i = 0; i < maxAttempts; i++) {
const response = await fetch(`/api/v1/tracks/${trackId}/status`);
const data = await response.json();
if (data.data.status === 'completed') {
return data.data;
}
if (data.data.status === 'failed') {
throw new Error(data.data.message);
}
await sleep(interval);
interval = Math.min(interval * 1.5, maxInterval); // Exponential backoff
}
throw new Error('Upload timeout');
}
Configuration
Timeout de Copie
Valeur par défaut: 5 minutes
Modification: internal/core/track/service.go
copyCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
Répertoire d'Upload
Variable d'environnement: UPLOAD_DIR (optionnel)
Valeur par défaut: uploads/tracks
Tests
Tests Unitaires
go test ./internal/core/track -v -run TestUploadTrack_Async
Tests inclus:
TestUploadTrack_Async_Success: Upload réussi, vérification StatusTestUploadTrack_Async_Interruption: Gestion interruption (contexte)TestUploadTrack_Async_ErrorHandling: Gestion erreursTestCopyFileAsync_ContextCancellation: Annulation directe
Limitations et Notes
Limitations Actuelles
- Pas de progression détaillée: Le
progressdansGetUploadStatusn'est pas mis à jour pendant la copie (reste à 0 jusqu'à 100) - Timeout fixe: 5 minutes (non configurable via env)
- Pas de retry automatique: Si la copie échoue, le Track reste en
Failed
Améliorations Futures (Optionnel)
- Progression détaillée: Utiliser
io.TeeReaderpour suivre les bytes copiés - Retry automatique: Relancer la copie en cas d'erreur réseau
- Webhooks: Notifier le client quand l'upload est terminé
- Chunked upload: Pour très gros fichiers (>100MB)
Cohérence avec l'Architecture
Avantages
- ✅ Cohérent avec
GetUploadStatusexistant - ✅ Cohérent avec
Track.Status(Uploading, Processing, Completed, Failed) - ✅ Traçabilité complète (logs + request_id)
- ✅ Nettoyage automatique en cas d'échec
- ✅ Support cancellation (context)
Intégration
- ✅ Utilise le système de Status existant
- ✅ Compatible avec le traitement asynchrone (streaming, metadata)
- ✅ Pas de changement breaking pour les clients (juste nouveau status code)
Dépannage
Upload reste en "uploading"
Cause: Goroutine bloquée ou timeout non atteint
Solution: Vérifier les logs, attendre 5 minutes max
Fichier créé mais Status=Failed
Cause: Erreur après copie (validation, DB, etc.)
Solution: Vérifier les logs pour le message d'erreur
Status=Failed immédiatement
Cause: Erreur lors de l'ouverture du fichier source
Solution: Vérifier que le fichier est valide et accessible
Références
Dernière mise à jour: 2025-01-27
Maintenu par: Veza Backend Team