---
id: 8wel7k12347894gsdjkfnru23kl78989
author: Yunjin Lee
title: 'Verkorting van de responstijd bij HTTP-bestandsuploads in Go: implementatie van een asynchrone workerwachtrij op basis van channels'
description: Deze structuur verlaagt de responsvertraging van het eindpunt door het ontvangen van tijdelijke bestanden en de overdracht naar Incus-containers te scheiden in een upload-handler van net/http, met behulp van een gebufferd kanaal en een pool van goroutine-workers.
language: nl
date: 2025-07-14T11:51:27.614782351Z
path: /blog/posts/go-handle-file-upload-with-concurrency-s844ce238
category: concurrency
---

# Overzicht

Wanneer men Go voor het eerst leert, gebeurt het vaak dat men dit doet door een backendserver te implementeren. Laten we als voorbeeld een geval nemen waarin een RestAPI of vergelijkbaar systeem een bestandsstroom ontvangt en deze naar de server uploadt.
De net/http-server van de Go-taal verwerkt standaard meerdere verzoeken tegelijkertijd, dus op het uploaden van gelijktijdig veel bestanden zelf is geen probleem.
Als men echter alle bewerkingen na het ontvangen van de stroom synchroon uitvoert, wordt de respons van het eindpunt vertraagd.
Laten we een techniek bekijken om deze situatie te voorkomen.

# Oorzaak

Het ontvangen van een stroom kost over het algemeen ook veel tijd, en bij grote bestanden kan een enkel verzoek enkele minuten in beslag nemen.
In zulke gevallen is het belangrijk om de bewerkingen na ontvangst zo snel mogelijk af te handelen.
Het scenario van dit voorbeeld is: de stroom ontvangen, opslaan als tijdelijk bestand en vervolgens naar een container pushen.
Als het pushen van het tijdelijke bestand naar de container hierbij wordt afgehandeld met een worker pool, kan de responsvertraging worden verkort.

```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)
}
```

Bij het gedeelte dat is aangeduid met THIS PART, is het waarschijnlijk opgevallen dat hier een taak in de wachtrij wordt geplaatst.

Laten we nu eens kijken hoe de taakwachtrij werkt.

```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)
}
```

De als voorbeeld gegeven taakwachtrij is eenvoudig geïmplementeerd. Deze wachtrij heeft een eenvoudige structuur waarbij de taken in de wachtrij uit het kanaal worden gehaald.

Hieronder volgt de workermethode voor het pushen naar de container na het uploaden van het bestand.
Voor een goede responsiviteit en eenvoudige implementatie is de huidige methode een oneindige lus, maar afhankelijk van het doel mag men er een algoritme aan toevoegen.

```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
			}
		}
	}
}
```

Allereerst blijft deze functie in een lus draaien en probeert zij steeds taken uit de wachtrij op te halen. Vervolgens probeert zij binnen het bereik van de herpogingen de stap van het uploaden van het tijdelijke bestand naar de container uit te voeren, en niet de stap van stroom naar tijdelijk bestand.

# Voordelen

Het voordeel van deze verwerkingswijze is dat, zolang de **streamupload normaal verloopt**, de vertraging van de daaropvolgende bewerkingen kan worden verminderd, en dat uitputting van bronnen door gelijktijdige containerbewerkingen kan worden voorkomen. Zoals in de huidige code te zien is, wordt het aantal containerbewerkingen dat tegelijk kan worden uitgevoerd beperkt door het aantal kanalen. Op deze manier hebben we een voorbeeld besproken dat op praktische wijze gebruik maakt van de parallelle verwerking van Go. Wilt u meer voorbeelden zien, dan kunt u de onderstaande links bezoeken.
[Module met voorbeelden](https://github.com/gg582/linux_virt_unit)
[Project dat de voorbeelden gebruikt](https://github.com/gg582/incuspeed)
Het project zelf bevat veel bijkomende componenten, dus voor het leren over de worker zelf volstaat het om kort te bekijken hoe de worker-init-functie vanuit main.go wordt aangeroepen en daarna verder te gaan. De module bevat ook andere vormen van workers, dus raadpleeg die gerust.
