err := r.ParseMultipartForm(32 << 20) // 32Mb
if err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
}
multipart/form-datar.ParseMultipartFormr.FormFile

那 32Mb 是对文件上传大小的限制吗?不是,上传的文件们按顺序存入内存中,累加大小不得超出 32Mb ,最后累加超出的文件就存入系统的临时文件中。非文件字段部分不计入累加。所以这种情况,文件上传是没有任何限制的。

r.Body = http.MaxBytesReader(w, r.Body, 32<<20+512)
...
http: request body too large
r.ParseMultipartForm
  • 文件类型校验
  • 文件大小校验
  • 字段白名单
  • 一旦校验失败,立即停止解析
var filesMax int64 = 4 << 20
var valuesMax int64 = 512

// 整体限制 4.5Mb
r.Body = http.MaxBytesReader(w, r.Body, filesMax+valuesMax)
reader, err := r.MultipartReader()
if err != nil {
  http.Error(w, err.Error(), http.StatusBadRequest)
  return
}

// 白名单
files := map[string][]byte{"file": nil}
values := map[string]string{"text": ""}

for {
  part, err := reader.NextPart()

  if err == io.EOF {
    break
  }
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  var buf bytes.Buffer
  filename := part.FileName()
  name := part.FormName()

  // 非文件字段
  if filename == "" {
    if _, ok := values[name]; !ok {
      http.Error(w, name+" is not expected", http.StatusBadRequest)
      return
    }

    n, err := io.CopyN(&buf, part, valuesMax+1)
    if err != nil && err != io.EOF {
      http.Error(w, err.Error(), http.StatusInternalServerError)
      return
    }

    valuesMax -= n
    if valuesMax < 0 {
      http.Error(w, "multipart: message too large", http.StatusBadRequest)
      return
    }

    values[name] = buf.String()
    continue
  }

  // 文件字段
  if _, ok := files[name]; !ok {
    http.Error(w, name+" is not expected", http.StatusBadRequest)
    return
  }

  n, err := io.CopyN(&buf, part, filesMax+1)
  if err != nil && err != io.EOF {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }

  filesMax -= n
  if filesMax < 0 {
    http.Error(w, "file size over limit", http.StatusBadRequest)
    return
  }

  files[name] = buf.Bytes()
  contentType := http.DetectContentType(files[name])
  if contentType != "application/zip" {
    http.Error(w, "file type not allowed", http.StatusBadRequest)
    return
  }
}

上面代码主要做了这样几个事:

textfileapplication/zip