diff --git a/.example/net/ghttp/client/upload-batch/client.go b/.example/net/ghttp/client/upload-batch/client.go
new file mode 100644
index 000000000..492c5e421
--- /dev/null
+++ b/.example/net/ghttp/client/upload-batch/client.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/gogf/gf/net/ghttp"
+ "github.com/gogf/gf/os/glog"
+)
+
+func main() {
+ path := "/home/john/Workspace/Go/github.com/gogf/gf/version.go"
+ r, e := ghttp.Post("http://127.0.0.1:8199/upload", "name=john&age=18&upload-file=@file:"+path)
+ if e != nil {
+ glog.Error(e)
+ } else {
+ fmt.Println(string(r.ReadAll()))
+ r.Close()
+ }
+}
diff --git a/.example/net/ghttp/client/upload-batch/server.go b/.example/net/ghttp/client/upload-batch/server.go
new file mode 100644
index 000000000..8e9e03776
--- /dev/null
+++ b/.example/net/ghttp/client/upload-batch/server.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "github.com/gogf/gf/frame/g"
+ "github.com/gogf/gf/net/ghttp"
+ "github.com/gogf/gf/os/gfile"
+ "io"
+)
+
+// Upload uploads files to /tmp .
+func Upload(r *ghttp.Request) {
+ saveDir := "/tmp/"
+ for _, item := range r.GetMultiPartFiles("upload-file") {
+ file, err := item.Open()
+ if err != nil {
+ r.Response.Write(err)
+ return
+ }
+ defer file.Close()
+
+ f, err := gfile.Create(saveDir + gfile.Basename(item.Filename))
+ if err != nil {
+ r.Response.Write(err)
+ return
+ }
+ defer f.Close()
+
+ if _, err := io.Copy(f, file); err != nil {
+ r.Response.Write(err)
+ return
+ }
+ }
+ r.Response.Write("upload successfully")
+}
+
+// UploadShow shows uploading page.
+func UploadShow(r *ghttp.Request) {
+ r.Response.Write(`
+
+
+ GF UploadFile Demo
+
+
+
+
+
+ `)
+}
+
+func main() {
+ s := g.Server()
+ s.Group("/upload", func(g *ghttp.RouterGroup) {
+ g.ALL("/", Upload)
+ g.ALL("/show", UploadShow)
+ })
+ s.SetPort(8199)
+ s.Run()
+}
diff --git a/net/ghttp/ghttp_request.go b/net/ghttp/ghttp_request.go
index d3ee68248..c983cc18c 100644
--- a/net/ghttp/ghttp_request.go
+++ b/net/ghttp/ghttp_request.go
@@ -9,6 +9,7 @@ package ghttp
import (
"fmt"
"io/ioutil"
+ "mime/multipart"
"net/http"
"strings"
@@ -41,6 +42,7 @@ type Request struct {
parsedPost bool // POST参数是否已经解析
parsedDelete bool // DELETE参数是否已经解析
parsedRaw bool // 原始参数是否已经解析
+ parsedForm bool // 是否已调用r.ParseMultipartForm
getMap map[string]interface{} // GET解析参数
putMap map[string]interface{} // PUT解析参数
postMap map[string]interface{} // POST解析参数
@@ -208,6 +210,18 @@ func (r *Request) GetHost() string {
return r.parsedHost
}
+// 获取上传的文件列表
+func (r *Request) GetMultiPartFiles(name string) []*multipart.FileHeader {
+ if !r.parsedForm {
+ r.ParseMultipartForm(r.Server.config.FormParsingMemory)
+ r.parsedForm = true
+ }
+ if r.MultipartForm == nil {
+ return nil
+ }
+ return r.MultipartForm.File[name]
+}
+
// 判断是否为静态文件请求
func (r *Request) IsFileRequest() bool {
return r.isFileRequest
diff --git a/net/ghttp/ghttp_request_method_post.go b/net/ghttp/ghttp_request_method_post.go
index 46006ca4a..2923589fe 100644
--- a/net/ghttp/ghttp_request_method_post.go
+++ b/net/ghttp/ghttp_request_method_post.go
@@ -22,7 +22,10 @@ func (r *Request) initPost() {
r.parsedPost = true
if v := r.Header.Get("Content-Type"); v != "" && gstr.Contains(v, "multipart/") {
// multipart/form-data, multipart/mixed
- r.ParseMultipartForm(r.Server.config.FormParsingMemory)
+ if !r.parsedForm {
+ r.ParseMultipartForm(r.Server.config.FormParsingMemory)
+ r.parsedForm = true
+ }
if len(r.PostForm) > 0 {
// 重新组织数据格式,使用统一的数据Parse方式
params := ""