---
id: 8wel7k12347894gsdjkfnru23kl78989
author: Yunjin Lee
title: 'Réduction du délai de réponse lors du téléversement de fichiers en Go HTTP : implémentation d''une file d''attente de workers asynchrones basée sur les canaux'
description: Cette structure sépare, dans le gestionnaire d'envoi de fichiers net/http, la réception des fichiers temporaires et les opérations de transfert vers les conteneurs Incus en un canal tamponné et un pool de workers basés sur des goroutines, afin de réduire le délai de réponse de l'endpoint.
language: fr
date: 2025-07-14T11:51:27.614782351Z
path: /blog/posts/go-handle-file-upload-with-concurrency-s844ce238
category: concurrency
---

# Introduction

Lors de l'apprentissage initial de Go, il est fréquent de l'appréhender en implémentant des serveurs backend. Considérons par exemple le cas où l'on crée un exemple de réception d'un flux de fichiers via une RestAPI pour l'importer sur un serveur.
Le serveur net/http du langage Go gère par défaut plusieurs requêtes de manière simultanée, de sorte que les importations simultanées en elles-mêmes ne posent aucun problème.
Cependant, si toutes les opérations consécutives à la réception du flux sont traitées de manière synchrone, la réponse du point de terminaison subit un retard.
Examinons les techniques permettant de prévenir cette situation.

# Cause

La réception du flux elle-même prend généralement beaucoup de temps, et dans le cas de fichiers volumineux, une requête unique peut nécessiter plusieurs minutes de traitement.
Dans de telles situations, il est primordial de traiter les opérations post-réception avec un minimum de rapidité.
Le scénario d'exemple présent consiste à recevoir un flux, à l'enregistrer sous forme de fichier temporaire, puis à l'envoyer vers un conteneur.
Si la partie consistant à envoyer le fichier temporaire vers le conteneur est gérée par un pool de workers, il est alors possible de réduire le délai de réponse.

```go
package file_upload

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

const uploadTempDir = "/tmp/incus_uploads" // Host temporary directory

// UploadTask holds data for asynchronous file push.
type UploadTask struct {
	HostTempFilePath         string
	ContainerName            string
    HostFilename             string
	ContainerDestinationPath string
}

// UploadHandler processes file uploads. Saves to temp file, then queues for Incus push.
func UploadHandler(wr http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodPost {
		http.Error(wr, "POST method required.", http.StatusMethodNotAllowed)
		return
	}
	originalFilePath := req.Header.Get("X-File-Path")
    originalFilename := filepath.Base(req.Header.Get("X-Host-Path"))
	containerName := req.Header.Get("X-Container-Name")
	if originalFilePath == "" || containerName == "" {
		http.Error(wr, "Missing X-File-Path or X-Container-Name header.", http.StatusBadRequest)
		return
	}

	cleanContainerDestPath := filepath.Clean(originalFilePath)
	if !filepath.IsAbs(cleanContainerDestPath) {
		http.Error(wr, "File path must be absolute.", http.StatusBadRequest)
		return
	}

	// Create unique temporary file path on host
	tempFileName := fmt.Sprintf("%d-%s", time.Now().UnixNano(), filepath.Base(originalFilePath))
	hostTempFilePath := filepath.Join(uploadTempDir, tempFileName)

	if err := os.MkdirAll(uploadTempDir, 0755); err != nil {
		log.Printf("ERROR: Failed to create temp upload directory: %v", err)
		http.Error(wr, "Server error.", http.StatusInternalServerError)
		return
	}

	// Create and copy request body to temporary file (synchronous)
	outFile, err := os.Create(hostTempFilePath)
	if err != nil {
		log.Printf("ERROR: Failed to create temporary file: %v", err)
		http.Error(wr, "Server error.", http.StatusInternalServerError)
		return
	}
	defer outFile.Close()

	bytesWritten, err := io.Copy(outFile, req.Body)
	if err != nil {
		outFile.Close()
		os.Remove(hostTempFilePath)
		log.Printf("ERROR: Failed to copy request body to temp file: %v", err)
		http.Error(wr, "File transfer failed.", http.StatusInternalServerError)
		return
	}
	log.Printf("Upload Info: Received %d bytes, saved to %s.", bytesWritten, hostTempFilePath)

	// Enqueue task for asynchronous Incus push
	task := UploadTask{
		HostTempFilePath:         hostTempFilePath,
		ContainerName:            containerName,
        HostFilename :            originalFilename,
		ContainerDestinationPath: cleanContainerDestPath,
	}
	EnqueueTask(task) //THIS PART
	log.Printf("Upload Info: Task enqueued for %s to %s.", originalFilePath, containerName)

	// Send immediate 202 Accepted response
	wr.WriteHeader(http.StatusAccepted)
	fmt.Fprintf(wr, "File '%s' queued for processing on container '%s'.\n", originalFilename, containerName)
}
```

En observant la section marquée par THIS PART, vous aurez sans doute remarqué que les tâches sont insérées dans une file d'attente.

Voyons maintenant comment fonctionne cette file d'attente de tâches.

```go
package file_upload

import (
	"log"
	"sync"
)

var taskQueue chan UploadTask
var once sync.Once

// InitWorkQueue initializes the in-memory task queue.
func InitWorkQueue() {
	once.Do(func() {
		taskQueue = make(chan UploadTask, 100)
		log.Println("Upload Info: Work queue initialized.")
	})
}

// EnqueueTask adds an UploadTask to the queue.
func EnqueueTask(task UploadTask) {
	if taskQueue == nil {
		log.Fatal("ERROR: Task queue not initialized.")
	}
	taskQueue <- task
	log.Printf("Upload Info: Queue: Task enqueued. Size: %d", len(taskQueue))
}

// DequeueTask retrieves an UploadTask from the queue, blocking if empty.
func DequeueTask() UploadTask {
	if taskQueue == nil {
		log.Fatal("ERROR: Task queue not initialized.")
	}
	task := <-taskQueue
	log.Printf("Upload Info: Queue: Task dequeued. Size: %d", len(taskQueue))
	return task
}

// GetQueueLength returns current queue size.
func GetQueueLength() int {
	if taskQueue == nil {
		return 0
	}
	return len(taskQueue)
}
```

La file d'attente de tâches fournie à titre d'exemple possède une implémentation simple. Cette structure se contente de retirer les tâches accumulées du canal.

Voici ci-dessous la méthode worker destinée à effectuer l'envoi vers le conteneur après le téléchargement du fichier.
Actuellement, cette méthode utilise une boucle infinie afin de garantir une bonne réactivité et une mise en œuvre aisée, mais il est tout à fait possible d'ajouter des algorithmes selon l'usage visé.

```go
func StartFilePushWorker() {
	for {
		task := DequeueTask()
		log.Printf("Upload Info: Worker processing task for %s from %s.", task.ContainerName, task.HostFilename)

		// Defer cleanup of the temporary file
		defer func(filePath string) {
			if err := os.Remove(filePath); err != nil {
				log.Printf("ERROR: Worker: Failed to remove temp file '%s': %v", filePath, err)
			} else {
				log.Printf("Upload Info: Worker: Cleaned up temp file: %s", filePath)
			}
		}(task.HostTempFilePath)

		// Process task with retries for transient Incus errors
		for i := 0; i <= MaxRetries; i++ {
			err := processUploadTask(task) //separate upload task
			if err == nil {
				log.Printf("SUCCESS: Worker: Task completed for %s.", task.ContainerName)
				break
			}

			isTransient := true
			if err.Error() == "incus: container not found" { // Example permanent error
				isTransient = false
			}

			if isTransient && i < MaxRetries {
				log.Printf("WARNING: Worker: Task failed for %s (attempt %d/%d): %v. Retrying.",
					task.ContainerName, i+1, MaxRetries, err)
				time.Sleep(RetryDelay)
			} else {
				log.Printf("ERROR: Worker: Task permanently failed for %s after %d attempts: %v.",
					task.ContainerName, i+1, err)
				break
			}
		}
	}
}
```

Premièrement, cette fonction exécute une boucle continue pour tenter de récupérer des tâches depuis la file d'attente. Ensuite, dans la limite des tentatives autorisées, elle tente d'exécuter l'étape d'importation non plus du flux vers le fichier temporaire, mais du fichier temporaire vers le conteneur.

# Avantages

L'avantage de cette méthode de traitement réside dans le fait que, **à condition que le téléchargement du flux se déroule correctement**, il est possible de réduire la latence des opérations traitées ultérieurement, tout en prévenant l'épuisement des ressources causé par des opérations simultanées sur les conteneurs. Comme l'illustre le code actuel, le nombre d'opérations sur les conteneurs pouvant s'exécuter en parallèle est limité par la capacité du canal. Nous avons ainsi examiné un exemple pratique d'utilisation de la concurrence en Go. Si vous souhaitez consulter davantage d'exemples, veuillez visiter les liens ci-dessous.
[Module contenant des exemples](https://github.com/gg582/linux_virt_unit)
[Projet utilisant les exemples](https://github.com/gg582/incuspeed)
Le projet lui-même comportant de nombreux composants annexes, vous pouvez vous contenter d'observer brièvement comment la fonction d'initialisation du worker est appelée dans main.go pour assimiler le fonctionnement du worker. Veuillez noter que le module comprend également d'autres types de workers.
