Multipart Streaming File Upload Handler

Stream multipart uploads in a Go handler with a memory threshold, per-file size caps, and non-blocking cleanup of partial files.

GoFile Upload

Code

snippet
package main

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

const (
	maxUploadSize = 25 << 20 // 25 MiB
	memThreshold  = 1 << 20  // 1 MiB 以下暂存内存
)

func uploadHandler(uploadDir string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "post only", http.StatusMethodNotAllowed)
			return
		}

		r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize+1024)

		reader, err := r.MultipartReader()
		if err != nil {
			http.Error(w, "expected multipart", http.StatusBadRequest)
			return
		}

		saved := []string{}
		for {
			part, err := reader.NextPart()
			if err == io.EOF {
				break
			}
			if err != nil {
				http.Error(w, "read part failed", http.StatusBadRequest)
				return
			}
			if part.FileName() == "" {
				io.Copy(io.Discard, part) // 普通字段:必须消费掉
				continue
			}

			safe := filepath.Base(part.FileName()) // 剥离路径防穿越
			dst, err := os.CreateTemp(uploadDir, safe+".part-")
			if err != nil {
				http.Error(w, "storage error", http.StatusInternalServerError)
				return
			}

			written, copyErr := io.Copy(dst, part) // stream to disk without buffering
			closeErr := dst.Close()
			if copyErr != nil || closeErr != nil {
				os.Remove(dst.Name()) // 失败清理半成品
				http.Error(w, "write failed", http.StatusInternalServerError)
				return
			}
			if written > maxUploadSize {
				os.Remove(dst.Name())
				http.Error(w, "file too large", http.StatusRequestEntityTooLarge)
				return
			}

			final := filepath.Join(uploadDir, safe)
			if err := os.Rename(dst.Name(), final); err != nil {
				os.Remove(dst.Name())
				http.Error(w, "finalize failed", http.StatusInternalServerError)
				return
			}
			saved = append(saved, fmt.Sprintf("%s(%d)", safe, written))
		}

		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, "saved: %v\n", saved)
	}
}

func main() {
	http.HandleFunc("/upload", uploadHandler("./uploads"))
	http.ListenAndServe(":8080", nil)
}