一般的做法
err := r.ParseMultipartForm(32 << 20) // 32Mb
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
问题
请注意,32Mb是分配给请求体的字节存储在内存中,而不是请求体的限制,当满(33Mb)时,它将写入临时目录。
查看源码
src/net/http/request.go中
func (r *Request) ParseMultipartForm(maxMemory int64) error {
...
f, err := mr.ReadForm(maxMemory)
...
}
func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
return r.readForm(maxMemory)
}
func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
...
n, err := io.CopyN(&b, p, maxMemory+1)
...
if n > maxMemory {
可以看到这里把剩余的内容写入到临时文件了
file, err := ioutil.TempFile("", "multipart-")
size, err := io.Copy(file, io.MultiReader(&b, p))
...
}
...
}
解决方案一
直接限制客户端上传大小,但是有时候这个数字不通用而且不合适,而且文件类型的检查也没法做
r.Body = http.MaxBytesReader(w, r.Body, 32<<20+512)
解决方案二
自己逐步解析
r.Body = http.MaxBytesReader(w, r.Body, 32<<20+1024)
reader, err := r.MultipartReader()
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// parse text field
text := make([]byte, 512)
p, err := reader.NextPart()
// one more field to parse, EOF is considered as failure here
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if p.FormName() != "text_field" {
http.Error(w, "text_field is expected", http.StatusBadRequest)
return
}
_, err = p.Read(text)
if err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// parse file field
p, err = reader.NextPart()
if err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if p.FormName() != "file_field" {
http.Error(w, "file_field is expected", http.StatusBadRequest)
return
}
buf := bufio.NewReader(p)
sniff, _ := buf.Peek(512)
contentType := http.DetectContentType(sniff)
if contentType != "application/zip" {
http.Error(w, "file type not allowed", http.StatusBadRequest)
return
}
f, err := ioutil.TempFile("", "")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer f.Close()
var maxSize int64 = 32 << 20
lmt := io.MultiReader(buf, io.LimitReader(p, maxSize - 511))
written, err := io.Copy(f, lmt)
if err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if written > maxSize {
os.Remove(f.Name())
http.Error(w, "file size over limit", http.StatusBadRequest)
return
}
提示:
只在POST主体中获取前两个部分,处理程序将期望text_field然后file_field
使用bufio.Reader包装part reader(先查看512字节)断言文件类型
使用io.LimitReader限制文件大小(使用1个字节的偏移量来查看part reader是否还有一些数据)