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.
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:
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.
After parsing, uploaded files are available via the FormFile
method:
- 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.
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)
}
http.MaxBytesReader
.filepath.Base()
or similar functions to strip potentially malicious path components.Here's a simple HTML form to test file uploads. This would typically be served by a separate handler.