net/http: File Uploads

Handling Multipart Form Data

The net/http package in Go provides robust support for handling HTTP requests, including file uploads which are typically sent using the multipart/form-data content type. This document outlines the key components and patterns for implementing file upload functionality.

Parsing Multipart Requests

When a client sends a request with Content-Type: multipart/form-data, the server needs to parse this complex data structure. The http.Request type has a built-in method for this:

func (r *Request) ParseMultipartForm(maxMemory int64) error

This method parses the multipart form, storing up to maxMemory bytes of non-file data in memory. File data is written to a temporary file. It's crucial to call this before accessing form values or files.

Accessing Uploaded Files

After parsing, uploaded files are available via the FormFile method:

func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)

- key: The name of the form field that contains the file. - multipart.File: An io.Reader interface representing the uploaded file's content. - *multipart.FileHeader: Contains metadata about the uploaded file, such as its original filename, size, and content type.

Example: Basic File Upload Handler


package main

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

const maxUploadSize = 10 << 20 // 10 MB

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Limit the size of the incoming request
	r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)

	// Parse the multipart form
	err := r.ParseMultipartForm(maxUploadSize)
	if err != nil {
		http.Error(w, "File too large or invalid form", http.StatusBadRequest)
		return
	}

	// Get the file from the form field "myFile"
	file, handler, err := r.FormFile("myFile")
	if err != nil {
		fmt.Println("Error retrieving the file:", err)
		http.Error(w, "Error retrieving the file", http.StatusInternalServerError)
		return
	}
	defer file.Close()

	fmt.Printf("Uploaded File: %+v\n", handler.Filename)
	fmt.Printf("File Size: %v bytes\n", handler.Size)
	fmt.Printf("File Type: %v\n", handler.Header.Get("Content-Type"))

	// Create a new file on the server to save the uploaded file
	// Use a safe path construction
	dstPath := filepath.Join("./uploads", filepath.Base(handler.Filename))
	dst, err := os.Create(dstPath)
	if err != nil {
		fmt.Println("Error creating the destination file:", err)
		http.Error(w, "Error saving the file", http.StatusInternalServerError)
		return
	}
	defer dst.Close()

	// Copy the uploaded file to the destination
	_, err = io.Copy(dst, file)
	if err != nil {
		fmt.Println("Error copying file content:", err)
		http.Error(w, "Error saving the file", http.StatusInternalServerError)
		return
	}

	fmt.Fprintln(w, "File uploaded successfully!")
}

func main() {
	// Create an "uploads" directory if it doesn't exist
	os.MkdirAll("./uploads", os.ModePerm)

	http.HandleFunc("/upload", uploadHandler)
	fmt.Println("Server listening on :8080")
	http.ListenAndServe(":8080", nil)
}
            

Security Considerations

Interactive Upload Form

Here's a simple HTML form to test file uploads. This would typically be served by a separate handler.