---
id: 8wel7k12347894gsdjkfnru23kl78989
author: Yunjin Lee
title: 'Minska svarsfördröjningen vid filuppladdning i Go HTTP: implementering av en asynkron arbetskö baserad på kanaler'
description: Strukturen separerar mottagandet av tillfälliga filer och överföringen till Incus-containrar i en net/http-filuppladdningshanterare genom en buffrad kanal och en arbetspool av goroutiner, vilket minskar svarsfördröjningen för endpointen.
language: sv
date: 2025-07-14T11:51:27.614782351Z
path: /blog/posts/go-handle-file-upload-with-concurrency-s844ce238
category: concurrency
---

# Översikt

När man först lär sig Go, gör man det ofta genom att implementera en backend-server. Låt oss till exempel betrakta ett scenario där man skapar ett exempel som tar emot en filström från ett RestAPI eller liknande och laddar upp den till servern.
Go-språkets net/http-server hanterar som standard flera förfrågningar samtidigt, så det är inga problem med samtidiga uppladdningar i sig.
Om man däremot hanterar alla åtgärder efter strömmottagningen på ett synkront sätt, fördröjs slutpunktens svar.
Låt oss undersöka en teknik för att förhindra denna situation.

# Orsak

Det tar vanligtvis lång tid att ta emot strömmen, och för stora filer kan en enda förfrågan bearbetas under flera minuter.
I sådana fall är det viktigt att hantera åtgärderna efter mottagningen så snabbt som möjligt, även om det bara rör sig om en liten förbättring.
Detta exempelscenario innebär att strömmen tas emot, sparas som en temporär fil och sedan skickas till en container.
Om den delen som skickar den temporära filen till containern hanteras av en worker pool, kan svarsfördröjningen reduceras.

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

Om du tittar på den del som är markerad som THIS PART här, har du antagligen märkt att uppgifter läggs till i en kö.

Låt oss nu se hur arbetskören fungerar.

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

Arbetskören som tillhandahålls som exempel är enkelt implementerad. Denna arbetskö har en enkel struktur där uppgifter som ligger i kö tas bort från kanalen.

Nedan följer worker-metoden för att skicka till containern efter filuppladdningen.
Den nuvarande metoden använder en oändlig loop för god responsivitet och enkel implementering, men beroende på användningsområde går det bra att lägga till ytterligare algoritmer.

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

För det första körs en kontinuerlig loop av denna funktion för att försöka hämta uppgifter från kön. Därefter, inom det tillåtna intervallet för återförsök, görs ett försök med uppladdningsfasen från den temporära filen till containern, i stället för direkt från strömmen till den temporära filen.

# Fördelar

Fördelen med detta tillvägagångssätt är att, **förutsatt att strömuppladdningen är normal**, kan väntetiden för efterföljande åtgärder reduceras, och resursutmattning till följd av samtidiga containeroperationer kan förhindras. Som framgår av den aktuella koden begränsas antalet containeroperationer som kan utföras samtidigt av kanalens storlek. På detta sätt har vi utforskat ett exempel där Go-språkets parallellitet kan användas på ett praktiskt sätt. Om du vill se fler exempel, vänligen besök länkarna nedan.
[Modul med exempel](https://github.com/gg582/linux_virt_unit)
[Projekt som använder exemplen](https://github.com/gg582/incuspeed)
Eftersom själva projektet innehåller många kringkomponenter kan du snabbt hoppa över det och fokusera på hur initieringsfunktionen för workern anropas i main.go för att lära dig om workern i sig. Modulen innehåller även andra typer av workers, så ta gärna en titt på dem också.
