Iris 表单提交需严格匹配 Content-Type:x-www-form-urlencoded 用 ctx.FormValue,JSON 用 ctx.ReadJSON(需 json tag),URL 参数用 ctx.URLParam;文件上传必须先 ctx.UploadForm() 再 ctx.FormFile,且需预建目录、设内存限制。

表单提交必须区分 Content-Type 再选对读取方法
前端用 form 提交(enctype="application/x-www-form-urlencoded")和用 fetch 发 JSON 是两套完全不同的解析路径。Iris 不会自动判断你传的是什么,ctx.FormValue("name") 在 JSON 请求里永远返回空字符串,且不报错——这是最隐蔽的坑。
实操建议:
- 确认前端请求头:
Content-Type: application/x-www-form-urlencoded→ 用ctx.FormValue("key")或ctx.PostValue("key") -
Content-Type: application/json→ 必须用ctx.ReadJSON(&v),且结构体字段要加json:"field_name"tag - URL 查询参数(如
/search?q=go)统一用ctx.URLParam("q"),它和表单字段共用解析结果,但优先级低于 POST body - 所有读取后必须检查 error:
if err != nil { ctx.StatusCode(400); return },Iris 不会帮你拦截解析失败
文件上传要用 ctx.UploadForm,不是 ctx.ReadJSON
很多人误以为 ctx.ReadJSON 能读 multipart 文件——不能。文件上传必须走 ctx.UploadForm(),它内部调用 r.ParseMultipartForm 并校验 Content-Type: multipart/form-data。漏掉这步,ctx.FormFile("file") 会直接 panic 或返回 nil。
实操建议:
- 先调
err := ctx.UploadForm(),失败就立即返回 400;不要跳过这一步直接调ctx.FormFile - 文件名来自
handler.Filename,不是handler.Header.Get("filename"),后者不可靠 - 保存路径需提前创建目录:
os.MkdirAll("./uploads", 0755),否则os.OpenFile会报no such file or directory - 限制大小:在
app.Run()里加配置iris.WithPostMaxMemory(32 (32MB),否则默认只允许 32MB,超限直接 400
表单 + 文件混合提交时,ctx.FormValue 和 ctx.FormFile 可同时用
一个表单既传文本字段又传文件(比如上传头像时带用户 ID 和昵称),Iris 允许你在同一个 handler 里混用 ctx.FormValue("id") 和 ctx.FormFile("avatar"),前提是已成功调用过 ctx.UploadForm()。
常见错误现象:能读到文件,但 ctx.FormValue("user_id") 返回空。原因通常是前端没把文本字段写进 FormData,或用了 fetch 却没设 Content-Type: multipart/form-data(其实 fetch 不能手动设这个 header,必须让浏览器自动生成)。
实操建议:
- 前端用原生
FormData:const fd = new FormData(); fd.append("user_id", "123"); fd.append("avatar", fileInput.files[0]); fetch("/upload", { method: "POST", body: fd }) - 后端顺序固定:
ctx.UploadForm()→ctx.FormValue("xxx")→ctx.FormFile("xxx") - 注意
ctx.FormFile返回的file是io.ReadCloser,记得defer file.Close(),否则句柄泄漏
别用 net/http 原生 handler 混搭 Iris 路由
看到网上有些示例直接用 http.HandleFunc 注册上传 handler,再和 Iris 的 app.Get 混用——这会导致中间件失效、日志丢失、上下文不一致。Iris 的 ctx 对象封装了完整的生命周期,绕过它等于放弃框架所有优势。
正确做法是全部走 Iris 路由:
app.Post("/upload", func(ctx iris.Context) {
err := ctx.UploadForm()
if err != nil {
ctx.StatusCode(400)
ctx.WriteString("bad request")
return
}
id := ctx.FormValue("id")
file, info, err := ctx.FormFile("file")
if err != nil {
ctx.StatusCode(400)
ctx.WriteString("no file received")
return
}
defer file.Close()
dst, err := os.OpenFile("./uploads/"+info.Filename, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
ctx.StatusCode(500)
return
}
defer dst.Close()
io.Copy(dst, file)
ctx.JSON(map[string]string{"status": "ok", "filename": info.Filename})
})
关键点在于:所有逻辑都在 ctx 上操作,不碰 http.Request 或 http.ResponseWriter 原生对象。一旦混用,ctx.Stop()、ctx.Next()、中间件链就全失效了。


















