---
id: 8wel7k12347894gsdjkfnru23kl78989
author: Yunjin Lee
title: 'Forkortelse av responstid for filopplasting i Go HTTP: implementering av en kanalbasert asynkron arbeidskø'
description: Strukturen skiller mottak av midlertidige filer og overføring til Incus-containere i en filopplastingshandler i net/http, ved hjelp av en bufret kanal og en arbeiderpool av goroutiner, for å redusere responsforsinkelsen i endepunktet.
language: "no"
date: 2025-07-14T11:51:27.614782351Z
path: /blog/posts/go-handle-file-upload-with-concurrency-s844ce238
category: concurrency
---

# Oversikt

Når man lærer Go for første gang, lærer man ofte ved å implementere en backend-server. La oss som eksempel lage et program som tar imot en filstrøm via en RestAPI og laster den opp til serveren.
Go-språkets net/http-server håndterer som standard flere forespørsler samtidig, så samtidige opplastinger i seg selv er ikke noe problem.
Hvis alle operasjoner etter mottak av strømmen utføres synkront, vil imidlertid responsen fra endepunktet bli forsinket.
La oss se på en teknikk for å unngå denne situasjonen.

# Årsak

Det tar vanligvis lang tid å motta en stream, og for store filer kan en enkelt forespørsel ta flere minutter å behandle.
I slike tilfeller er det viktig å behandle operasjonene etter mottak så raskt som mulig.
Scenariet i dette eksempelet er å motta en stream, lagre den som midlertidig fil og deretter pushe den til en container.
Hvis delen som pusher den midlertidige filen til containeren håndteres med en worker pool, kan responsforsinkelsen reduseres.

```go
package file_upload

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

const uploadTempDir = "/tmp/incus_uploads" // Midlertidig katalog på verten

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

I koden ovenfor, der THIS PART er markert, ser du at oppgaven settes inn i køen.

La oss nå se på hvordan oppgavekøen fungerer.

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

Oppgavekøen i eksemplet er enkelt implementert. Den har en enkel struktur som henter ut oppgaver som ligger i køen fra kanalen.

Nedenfor er worker-metoden som pusher filen til containeren etter opplasting.
Metoden bruker en uendelig løkke for god responsivitet og enkel implementasjon, men det står fritt å legge til algoritmer avhengig av formålet.

```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ørst går denne funksjonen kontinuerlig i en løkke og forsøker å hente oppgaver fra køen. Deretter forsøker den, innenfor rammen av retries, å utføre opplastingen fra midlertidig fil til container, og ikke fra stream til midlertidig fil.

# Fordeler

Fordelen med denne behandlingsmåten er at **så lenge opplastingen av stream fungerer**, kan forsinkelsen for de etterfølgende operasjonene reduseres, og utmattelse av ressurser på grunn av samtidige containeroperasjoner kan forebygges. Som koden viser, er antallet containeroperasjoner som kan kjøres samtidig begrenset av kanalens kapasitet. På denne måten har vi sett et praktisk eksempel på hvordan Gos parallelle prosessering kan brukes. For flere eksempler kan du besøke lenkene nedenfor.
[Modul med eksempler](https://github.com/gg582/linux_virt_unit)
[Prosjekt som bruker eksemplet](https://github.com/gg582/incuspeed)
Prosjektet i seg selv har mange tilleggskomponenter, så for å lære om selve workeren er det nok å se kort på hvordan worker-init-funksjonen kalles fra main.go, og deretter gå videre. Modulen inneholder også andre typer workere, så ta gjerne en titt.
