From 4e2d3781455d5d8a577d8b13376d849d9b463985 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 2 Mar 2022 21:15:16 +0800 Subject: [PATCH] improve file uploading using strict route feature --- net/ghttp/ghttp_request_param_file.go | 6 +-- net/ghttp/ghttp_request_param_request.go | 18 +++++-- .../ghttp_z_unit_feature_request_file_test.go | 48 +++++++++++++++++++ protocol/goai/goai.go | 1 + protocol/goai/goai_parameter.go | 3 +- protocol/goai/goai_path.go | 10 +++- 6 files changed, 75 insertions(+), 11 deletions(-) diff --git a/net/ghttp/ghttp_request_param_file.go b/net/ghttp/ghttp_request_param_file.go index 0b624cb76..84952e274 100644 --- a/net/ghttp/ghttp_request_param_file.go +++ b/net/ghttp/ghttp_request_param_file.go @@ -23,8 +23,8 @@ import ( // UploadFile wraps the multipart uploading file with more and convenient features. type UploadFile struct { - *multipart.FileHeader - ctx context.Context + *multipart.FileHeader `json:"-"` + ctx context.Context } // UploadFiles is array type for *UploadFile. @@ -32,7 +32,7 @@ type UploadFiles []*UploadFile // Save saves the single uploading file to directory path and returns the saved file name. // -// The parameter `dirPath` should be a directory path or it returns error. +// The parameter `dirPath` should be a directory path, or it returns error. // // Note that it will OVERWRITE the target file if there's already a same name file exist. func (f *UploadFile) Save(dirPath string, randomlyRename ...bool) (filename string, err error) { diff --git a/net/ghttp/ghttp_request_param_request.go b/net/ghttp/ghttp_request_param_request.go index 035ba2c72..8874fa79c 100644 --- a/net/ghttp/ghttp_request_param_request.go +++ b/net/ghttp/ghttp_request_param_request.go @@ -66,14 +66,10 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte var ( ok, filter bool ) - var length int if len(kvMap) > 0 && kvMap[0] != nil { - length = len(kvMap[0]) filter = true - } else { - length = len(r.routerMap) + len(r.queryMap) + len(r.formMap) + len(r.bodyMap) + len(r.paramsMap) } - m := make(map[string]interface{}, length) + m := make(map[string]interface{}) for k, v := range r.routerMap { if filter { if _, ok = kvMap[0][k]; !ok { @@ -114,6 +110,16 @@ func (r *Request) GetRequestMap(kvMap ...map[string]interface{}) map[string]inte } m[k] = v } + // File uploading. + if r.MultipartForm != nil { + for name := range r.MultipartForm.File { + if uploadFiles := r.GetUploadFiles(name); len(uploadFiles) == 1 { + m[name] = uploadFiles[0] + } else { + m[name] = uploadFiles + } + } + } // Check none exist parameters and assign it with default value. if filter { for k, v := range kvMap[0] { @@ -171,9 +177,11 @@ func (r *Request) doGetRequestStruct(pointer interface{}, mapping ...map[string] if data == nil { data = map[string]interface{}{} } + // Default struct values. if err = r.mergeDefaultStructValue(data, pointer); err != nil { return data, nil } + return data, gconv.Struct(data, pointer, mapping...) } diff --git a/net/ghttp/ghttp_z_unit_feature_request_file_test.go b/net/ghttp/ghttp_z_unit_feature_request_file_test.go index bf273f62a..509a01897 100644 --- a/net/ghttp/ghttp_z_unit_feature_request_file_test.go +++ b/net/ghttp/ghttp_z_unit_feature_request_file_test.go @@ -7,6 +7,7 @@ package ghttp_test import ( + "context" "fmt" "testing" "time" @@ -18,6 +19,7 @@ import ( "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/test/gtest" "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gmeta" "github.com/gogf/gf/v2/util/guid" ) @@ -169,3 +171,49 @@ func Test_Params_File_Batch(t *testing.T) { t.Assert(gfile.GetContents(dstPath2), gfile.GetContents(srcPath2)) }) } + +func Test_Params_Strict_Route_File_Single(t *testing.T) { + type Req struct { + gmeta.Meta `method:"post" mime:"multipart/form-data"` + File *ghttp.UploadFile `type:"file"` + } + type Res struct{} + + dstDirPath := gfile.Temp(gtime.TimestampNanoStr()) + s := g.Server(guid.S()) + s.BindHandler("/upload/single", func(ctx context.Context, req *Req) (res *Res, err error) { + var ( + r = g.RequestFromCtx(ctx) + file = req.File + ) + if file == nil { + r.Response.WriteExit("upload file cannot be empty") + } + name, err := file.Save(dstDirPath) + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit(name) + return + }) + s.SetDumpRouterMap(false) + s.Start() + defer s.Shutdown() + time.Sleep(100 * time.Millisecond) + // normal name + gtest.C(t, func(t *gtest.T) { + client := g.Client() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort())) + + srcPath := gdebug.TestDataPath("upload", "file1.txt") + dstPath := gfile.Join(dstDirPath, "file1.txt") + content := client.PostContent(ctx, "/upload/single", g.Map{ + "file": "@file:" + srcPath, + }) + t.AssertNE(content, "") + t.AssertNE(content, "upload file cannot be empty") + t.AssertNE(content, "upload failed") + t.Assert(content, "file1.txt") + t.Assert(gfile.GetContents(dstPath), gfile.GetContents(srcPath)) + }) +} diff --git a/protocol/goai/goai.go b/protocol/goai/goai.go index 85409e922..90c4bf020 100644 --- a/protocol/goai/goai.go +++ b/protocol/goai/goai.go @@ -82,6 +82,7 @@ const ( TagNamePath = `path` TagNameMethod = `method` TagNameMime = `mime` + TagNameConsumes = `consumes` TagNameType = `type` TagNameDomain = `domain` TagNameValidate = `v` diff --git a/protocol/goai/goai_parameter.go b/protocol/goai/goai_parameter.go index 298b75821..082bd60e4 100644 --- a/protocol/goai/goai_parameter.go +++ b/protocol/goai/goai_parameter.go @@ -54,7 +54,8 @@ func (oai *OpenApiV3) newParameterRefWithStructMethod(field gstructs.Field, path parameter.Name = field.Name() } if len(tagMap) > 0 { - if err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter); err != nil { + err := gconv.Struct(oai.fileMapWithShortTags(tagMap), parameter) + if err != nil { return nil, gerror.Wrap(err, `mapping struct tags to Parameter failed`) } } diff --git a/protocol/goai/goai_path.go b/protocol/goai/goai_path.go index d60b912e4..62f29f142 100644 --- a/protocol/goai/goai_path.go +++ b/protocol/goai/goai_path.go @@ -80,6 +80,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { } var ( + mime string path = Path{} inputMetaMap = gmeta.Data(inputObject.Interface()) outputMetaMap = gmeta.Data(outputObject.Interface()) @@ -126,12 +127,17 @@ func (oai *OpenApiV3) addPath(in addPathInput) error { } if len(inputMetaMap) > 0 { - if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &path); err != nil { + inputMetaMap = oai.fileMapWithShortTags(inputMetaMap) + if err := gconv.Struct(inputMetaMap, &path); err != nil { return gerror.Wrap(err, `mapping struct tags to Path failed`) } - if err := gconv.Struct(oai.fileMapWithShortTags(inputMetaMap), &operation); err != nil { + if err := gconv.Struct(inputMetaMap, &operation); err != nil { return gerror.Wrap(err, `mapping struct tags to Operation failed`) } + // Allowed request mime. + if mime = inputMetaMap[TagNameMime]; mime == "" { + mime = inputMetaMap[TagNameConsumes] + } } // =================================================================================================================