From 242a2622c839bf883c415fd088d24fec8727fb23 Mon Sep 17 00:00:00 2001 From: Sai Date: Thu, 14 Mar 2019 17:26:51 +0900 Subject: [PATCH 1/5] Fix Japanese text hiragana -> kanji (#1812) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9c10b22..b46c5637 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ All documentation is available on the Gin website. - [English](https://gin-gonic.com/docs/) - [简体中文](https://gin-gonic.com/zh-cn/docs/) - [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [にほんご](https://gin-gonic.com/ja/docs/) +- [日本語](https://gin-gonic.com/ja/docs/) ## Examples From 05b5c3ba7495fb3cd737cad8b35e62e0862ed1c2 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 15 Mar 2019 15:39:34 +0800 Subject: [PATCH 2/5] Doc: fix gin example notice syntax (#1814) --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index b02deae4..bfebc6c0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,3 @@ # Gin Examples -⚠️ **NOTICE:** All gin examples has moved as alone repository to [here](https://github.com/gin-gonic/examples). +⚠️ **NOTICE:** All gin examples have been moved as standalone repository to [here](https://github.com/gin-gonic/examples). From bcf36ade9f763b875bd781ad26cdb0549349c5f8 Mon Sep 17 00:00:00 2001 From: sekky0905 <20237968+sekky0905@users.noreply.github.com> Date: Sat, 16 Mar 2019 17:09:10 +0900 Subject: [PATCH 3/5] Remove sudo setting from travis.yml (#1816) --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b38adcb1..2fd9c8a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: go -sudo: false matrix: fast_finish: true From c16bfa7949c6ca59c049d20df507d24b1f2ec629 Mon Sep 17 00:00:00 2001 From: Boyi Wu Date: Mon, 18 Mar 2019 10:16:34 +0800 Subject: [PATCH 4/5] update for supporting file binding (#1264) update for supporting multipart form and file binding example: ``` type PhoptUploadForm struct { imgData *multipart.FileHeader `form:"img_data" binding:"required"` ProjectID string `form:"project_id" binding:"required"` Description string `form:"description binding:"required"` } ``` ref: https://github.com/gin-gonic/gin/issues/1263 --- binding/binding.go | 4 +- binding/binding_test.go | 94 ++++++++++++++++++++++++++++++++++++++++- binding/form.go | 5 +++ binding/form_mapping.go | 29 +++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 26d71c9f..520c5109 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -98,7 +98,9 @@ func Default(method, contentType string) Binding { return MsgPack case MIMEYAML: return YAML - default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: return Form } } diff --git a/binding/binding_test.go b/binding/binding_test.go index b265af36..ee788225 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -8,9 +8,11 @@ import ( "bytes" "encoding/json" "errors" + "io" "io/ioutil" "mime/multipart" "net/http" + "os" "strconv" "strings" "testing" @@ -31,6 +33,18 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooBarFileStruct struct { + FooBarStruct + File *multipart.FileHeader `form:"file" binding:"required"` +} + +type FooBarFileFailStruct struct { + FooBarStruct + File *multipart.FileHeader `invalid_name:"file" binding:"required"` + // for unexport test + data *multipart.FileHeader `form:"data" binding:"required"` +} + type FooDefaultBarStruct struct { FooStruct Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` @@ -187,8 +201,8 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) - assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm)) - assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) @@ -536,6 +550,54 @@ func createFormPostRequestForMapFail(t *testing.T) *http.Request { return req } +func createFormFilesMultipartRequest(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file", "form.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + +func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) @@ -613,6 +675,34 @@ func TestBindingFormPostForMapFail(t *testing.T) { assert.Error(t, err) } +func TestBindingFormFilesMultipart(t *testing.T) { + req := createFormFilesMultipartRequest(t) + var obj FooBarFileStruct + FormMultipart.Bind(req, &obj) + + // file from os + f, _ := os.Open("form.go") + defer f.Close() + fileActual, _ := ioutil.ReadAll(f) + + // file from multipart + mf, _ := obj.File.Open() + defer mf.Close() + fileExpect, _ := ioutil.ReadAll(mf) + + assert.Equal(t, FormMultipart.Name(), "multipart/form-data") + assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, fileExpect, fileActual) +} + +func TestBindingFormFilesMultipartFail(t *testing.T) { + req := createFormFilesMultipartRequestFail(t) + var obj FooBarFileFailStruct + err := FormMultipart.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest(t) var obj FooBarStruct diff --git a/binding/form.go b/binding/form.go index 8955c95b..f1f89195 100644 --- a/binding/form.go +++ b/binding/form.go @@ -56,5 +56,10 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := mapForm(obj, req.MultipartForm.Value); err != nil { return err } + + if err := mapFiles(obj, req); err != nil { + return err + } + return validate(obj) } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index ba9d2c4f..fc33b1df 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "net/http" "reflect" "strconv" "strings" @@ -15,6 +16,34 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +func mapFiles(ptr interface{}, req *http.Request) error { + typ := reflect.TypeOf(ptr).Elem() + val := reflect.ValueOf(ptr).Elem() + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + structField := val.Field(i) + + t := fmt.Sprintf("%s", typeField.Type) + if string(t) != "*multipart.FileHeader" { + continue + } + + inputFieldName := typeField.Tag.Get("form") + if inputFieldName == "" { + inputFieldName = typeField.Name + } + + _, fileHeader, err := req.FormFile(inputFieldName) + if err != nil { + return err + } + + structField.Set(reflect.ValueOf(fileHeader)) + + } + return nil +} + var errUnknownType = errors.New("Unknown type") func mapUri(ptr interface{}, m map[string][]string) error { From b40d4c175c079fa41cda2669c308485175cf2eee Mon Sep 17 00:00:00 2001 From: Sai Date: Mon, 18 Mar 2019 12:12:30 +0900 Subject: [PATCH 5/5] IsTerm flag should not be affected by DisableConsoleColor method. (#1802) * IsTerm flag should not be affected by DisableConsoleColor method. * change public property to private --- logger.go | 49 +++++++++++++++++++++++++++++------------------- logger_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/logger.go b/logger.go index 2ecaed7d..198a0192 100644 --- a/logger.go +++ b/logger.go @@ -14,17 +14,24 @@ import ( "github.com/mattn/go-isatty" ) +type consoleColorModeValue int + +const ( + autoColor consoleColorModeValue = iota + disableColor + forceColor +) + var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) - disableColor = false - forceColor = false + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + reset = string([]byte{27, 91, 48, 109}) + consoleColorMode = autoColor ) // LoggerConfig defines the config for Logger middleware. @@ -62,8 +69,8 @@ type LogFormatterParams struct { Path string // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - // IsTerm shows whether does gin's output descriptor refers to a terminal. - IsTerm bool + // isTerm shows whether does gin's output descriptor refers to a terminal. + isTerm bool // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. @@ -115,10 +122,15 @@ func (p *LogFormatterParams) ResetColor() string { return reset } +// IsOutputColor indicates whether can colors be outputted to the log. +func (p *LogFormatterParams) IsOutputColor() bool { + return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) +} + // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { var statusColor, methodColor, resetColor string - if param.IsTerm { + if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() @@ -137,12 +149,12 @@ var defaultLogFormatter = func(param LogFormatterParams) string { // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { - disableColor = true + consoleColorMode = disableColor } // ForceConsoleColor force color output in the console. func ForceConsoleColor() { - forceColor = true + consoleColorMode = forceColor } // ErrorLogger returns a handlerfunc for any error type. @@ -199,9 +211,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); (!ok || - (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || - disableColor) && !forceColor { + if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { isTerm = false } @@ -228,7 +239,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { if _, ok := skip[path]; !ok { param := LogFormatterParams{ Request: c.Request, - IsTerm: isTerm, + isTerm: isTerm, Keys: c.Keys, } diff --git a/logger_test.go b/logger_test.go index 36231371..11a859e6 100644 --- a/logger_test.go +++ b/logger_test.go @@ -240,7 +240,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: false, + isTerm: false, } termTrueParam := LogFormatterParams{ @@ -251,7 +251,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: true, + isTerm: true, } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) @@ -296,6 +296,39 @@ func TestResetColor(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) } +func TestIsOutputColor(t *testing.T) { + // test with isTerm flag true. + p := LogFormatterParams{ + isTerm: true, + } + + consoleColorMode = autoColor + assert.Equal(t, true, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // test with isTerm flag false. + p = LogFormatterParams{ + isTerm: false, + } + + consoleColorMode = autoColor + assert.Equal(t, false, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // reset console color mode. + consoleColorMode = autoColor +} + func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) @@ -358,14 +391,20 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { func TestDisableConsoleColor(t *testing.T) { New() - assert.False(t, disableColor) + assert.Equal(t, autoColor, consoleColorMode) DisableConsoleColor() - assert.True(t, disableColor) + assert.Equal(t, disableColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor } func TestForceConsoleColor(t *testing.T) { New() - assert.False(t, forceColor) + assert.Equal(t, autoColor, consoleColorMode) ForceConsoleColor() - assert.True(t, forceColor) + assert.Equal(t, forceColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor }