mime, multipart, expvar, and runtime/debug

Updated

September 8, 2026

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 vars

Useful 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 goroutine

Build 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.Deps

Handy 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

  1. Accept a multipart upload and reject bodies over 1 MiB.
  2. Register an expvar counter and curl /debug/vars.
  3. Print debug.ReadBuildInfo() from your binary’s main.