mime, multipart, expvar, and runtime/debug
mime, multipart, expvar, and runtime/debug
Overview
This chapter groups edge-of-stdlib packages every serious tool eventually touches:
mime/mime/multipart— content types and file uploads
expvar— process variables for quick ops scrapes
net/http/pprof— profile endpoints (import for side effect)
runtime/debug/debug/buildinfo— GC knobs, stack dumps, embed build metadata
mime: content types
import "mime"
ctype := mime.TypeByExtension(".json") // application/json
exts, _ := mime.ExtensionsByType("image/png")
mediatype, params, err := mime.ParseMediaType(
`multipart/form-data; boundary=----abcd`)
// mediatype == "multipart/form-data"
// params["boundary"] == "----abcd"Set types explicitly on responses when you know them:
w.Header().Set("Content-Type", "application/json; charset=utf-8")multipart: form uploads
func upload(w http.ResponseWriter, r *http.Request) {
// Bound memory: 32 MiB in memory, rest to temp files
if err := r.ParseMultipartForm(32 << 20); err != nil {
http.Error(w, "bad multipart", http.StatusBadRequest)
return
}
file, hdr, err := r.FormFile("file")
if err != nil {
http.Error(w, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
// Optional: sniff type from head bytes
head := make([]byte, 512)
n, _ := file.Read(head)
ctype := http.DetectContentType(head[:n])
// re-open or use io.MultiReader if you need full stream again
dst, err := os.CreateTemp("", "upload-*")
if err != nil {
http.Error(w, "storage", http.StatusInternalServerError)
return
}
defer dst.Close()
if _, err := io.Copy(dst, io.MultiReader(bytes.NewReader(head[:n]), file)); err != nil {
http.Error(w, "write", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "saved %s (%s) as %s\n", hdr.Filename, ctype, dst.Name())
}| Habit | Why |
|---|---|
| Size limits | Avoid memory exhaustion |
Don’t trust Filename alone |
Path traversal / odd characters |
| Detect content type | Clients lie about MIME |
Writing multipart (client)
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
fw, _ := w.CreateFormFile("file", "notes.txt")
_, _ = io.WriteString(fw, "hello")
_ = w.Close()
req, _ := http.NewRequest(http.MethodPost, url, &buf)
req.Header.Set("Content-Type", w.FormDataContentType())expvar: export process vars
import "expvar"
var (
requests = expvar.NewInt("requests_total")
version = expvar.NewString("version")
)
func init() {
version.Set("1.2.3")
}
func handle(w http.ResponseWriter, r *http.Request) {
requests.Add(1)
// ...
}
// http.Handle("/debug/vars", expvar.Handler()) // JSON map of varsUseful for simple scrapes. For production metrics at scale, prefer Prometheus-style instrumentation (part 16)—but expvar is zero-dep and always there.
net/http/pprof
import _ "net/http/pprof"
// If you use DefaultServeMux:
go func() {
log.Println(http.ListenAndServe("127.0.0.1:6060", nil))
}()
// Then: go tool pprof http://127.0.0.1:6060/debug/pprof/heap
// go tool pprof http://127.0.0.1:6060/debug/pprof/goroutineleak (Go 1.27+)Bind pprof to localhost or protect it—profiles leak internals.
runtime/debug
import "runtime/debug"
debug.SetGCPercent(50) // GC target; trade CPU vs memory
debug.SetMemoryLimit(2 << 30) // soft memory limit (Go 1.19+)
debug.WriteHeapDump(fd) // heavy; support tooling
debug.PrintStack() // stderr stack of current goroutineBuild info at runtime:
if bi, ok := debug.ReadBuildInfo(); ok {
fmt.Println(bi.Main.Path, bi.Main.Version)
for _, s := range bi.Settings {
// vcs.revision, -ldflags, CGO_ENABLED, ...
}
}debug/buildinfo (binaries on disk)
import "debug/buildinfo"
bi, err := buildinfo.ReadFile("/path/to/binary")
// bi.GoVersion, bi.Path, bi.DepsHandy for “what version is deployed?” without a /version endpoint.
Rules of thumb
| Do | Don’t |
|---|---|
| Bound multipart memory | Parse unlimited uploads |
Lock down /debug/* |
Expose pprof on 0.0.0.0 publicly |
| Export a version string | Guess deploy version from timestamps |
| Prefer slog + metrics for ops | Rely only on fmt prints in prod |
Try next
- Accept a multipart upload and reject bodies over 1 MiB.
- Register an
expvarcounter and curl/debug/vars. - Print
debug.ReadBuildInfo()from your binary’smain.