From 8a6792d5162d449eb79743e372d5b651cc385561 Mon Sep 17 00:00:00 2001 From: Kevin Zhu Date: Tue, 23 Jan 2018 10:07:33 +0800 Subject: [PATCH 001/111] Fix README.md example code (#1231) r -> router --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63137883..2ad2fc11 100644 --- a/README.md +++ b/README.md @@ -435,7 +435,7 @@ func main() { c.String(200, "pong") }) - r.Run(":8080") +    router.Run(":8080") } ``` From 7a9a290b36bb803a18874aa5c5b108be2d7eb34d Mon Sep 17 00:00:00 2001 From: MW Lim Date: Tue, 23 Jan 2018 10:36:36 +0800 Subject: [PATCH 002/111] minor typo in README.md (#1219) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ad2fc11..6e421f02 100644 --- a/README.md +++ b/README.md @@ -1190,7 +1190,7 @@ func main() { ### Run multiple service using Gin -See the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling example: +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: [embedmd]:# (examples/multiple-service/main.go go) ```go From 2fbb97117cda046278ad34bd7bac35cbc38c68d5 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 24 Jan 2018 11:06:42 +0800 Subject: [PATCH 003/111] fix(build): remove unused target. (#1183) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9ba475a4..51a2d191 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ GOFMT ?= gofmt "-s" PACKAGES ?= $(shell go list ./... | grep -v /vendor/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") -all: build +all: install install: deps govendor sync From 783c7ee9c14eac0e65b501664b4f553291556b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 26 Jan 2018 11:46:11 +0800 Subject: [PATCH 004/111] Add some test cases and run test cases on binding/render dir (#1168) * Travis run test cases on binding and render dir and add some test cases for binding and render --- .gitignore | 1 + Makefile | 2 +- binding/binding_test.go | 768 ++++++++++++++++++++++++++++++++++++++++ coverage.sh | 13 + render/render_test.go | 175 ++++++++- 5 files changed, 957 insertions(+), 2 deletions(-) create mode 100644 coverage.sh diff --git a/.gitignore b/.gitignore index f3b636df..14dc8f20 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/* !vendor/vendor.json coverage.out count.out +test diff --git a/Makefile b/Makefile index 51a2d191..5468563a 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ install: deps .PHONY: test test: - go test -v -covermode=count -coverprofile=coverage.out + sh coverage.sh .PHONY: fmt fmt: diff --git a/binding/binding_test.go b/binding/binding_test.go index 5575e166..0c0f9f81 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -6,9 +6,13 @@ package binding import ( "bytes" + "encoding/json" + "errors" + "io/ioutil" "mime/multipart" "net/http" "testing" + "time" "github.com/gin-gonic/gin/binding/example" "github.com/golang/protobuf/proto" @@ -25,6 +29,116 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooStructUseNumber struct { + Foo interface{} `json:"foo" binding:"required"` +} + +type FooBarStructForTimeType struct { + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` + TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` +} + +type FooStructForTimeTypeNotFormat struct { + TimeFoo time.Time `form:"time_foo"` +} + +type FooStructForTimeTypeFailFormat struct { + TimeFoo time.Time `form:"time_foo" time_format:"2017-11-15"` +} + +type FooStructForTimeTypeFailLocation struct { + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_location:"/asia/chongqing"` +} + +type FooStructForMapType struct { + // Unknown type: not support map + MapFoo map[string]interface{} `form:"map_foo"` +} + +type InvalidNameType struct { + TestName string `invalid_name:"test_name"` +} + +type InvalidNameMapType struct { + TestName struct { + MapFoo map[string]interface{} `form:"map_foo"` + } +} + +type FooStructForSliceType struct { + SliceFoo []int `form:"slice_foo"` +} + +type FooStructForSliceMapType struct { + // Unknown type: not support map + SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` +} + +type FooBarStructForIntType struct { + IntFoo int `form:"int_foo"` + IntBar int `form:"int_bar" binding:"required"` +} + +type FooBarStructForInt8Type struct { + Int8Foo int8 `form:"int8_foo"` + Int8Bar int8 `form:"int8_bar" binding:"required"` +} + +type FooBarStructForInt16Type struct { + Int16Foo int16 `form:"int16_foo"` + Int16Bar int16 `form:"int16_bar" binding:"required"` +} + +type FooBarStructForInt32Type struct { + Int32Foo int32 `form:"int32_foo"` + Int32Bar int32 `form:"int32_bar" binding:"required"` +} + +type FooBarStructForInt64Type struct { + Int64Foo int64 `form:"int64_foo"` + Int64Bar int64 `form:"int64_bar" binding:"required"` +} + +type FooBarStructForUintType struct { + UintFoo uint `form:"uint_foo"` + UintBar uint `form:"uint_bar" binding:"required"` +} + +type FooBarStructForUint8Type struct { + Uint8Foo uint8 `form:"uint8_foo"` + Uint8Bar uint8 `form:"uint8_bar" binding:"required"` +} + +type FooBarStructForUint16Type struct { + Uint16Foo uint16 `form:"uint16_foo"` + Uint16Bar uint16 `form:"uint16_bar" binding:"required"` +} + +type FooBarStructForUint32Type struct { + Uint32Foo uint32 `form:"uint32_foo"` + Uint32Bar uint32 `form:"uint32_bar" binding:"required"` +} + +type FooBarStructForUint64Type struct { + Uint64Foo uint64 `form:"uint64_foo"` + Uint64Bar uint64 `form:"uint64_bar" binding:"required"` +} + +type FooBarStructForBoolType struct { + BoolFoo bool `form:"bool_foo"` + BoolBar bool `form:"bool_bar" binding:"required"` +} + +type FooBarStructForFloat32Type struct { + Float32Foo float32 `form:"float32_foo"` + Float32Bar float32 `form:"float32_bar" binding:"required"` +} + +type FooBarStructForFloat64Type struct { + Float64Foo float64 `form:"float64_foo"` + Float64Bar float64 `form:"float64_bar" binding:"required"` +} + func TestBindingDefault(t *testing.T) { assert.Equal(t, Default("GET", ""), Form) assert.Equal(t, Default("GET", MIMEJSON), Form) @@ -55,6 +169,20 @@ func TestBindingJSON(t *testing.T) { `{"foo": "bar"}`, `{"bar": "foo"}`) } +func TestBindingJSONUseNumber(t *testing.T) { + testBodyBindingUseNumber(t, + JSON, "json", + "/", "/", + `{"foo": 123}`, `{"bar": "foo"}`) +} + +func TestBindingJSONUseNumber2(t *testing.T) { + testBodyBindingUseNumber2(t, + JSON, "json", + "/", "/", + `{"foo": 123}`, `{"bar": "foo"}`) +} + func TestBindingForm(t *testing.T) { testFormBinding(t, "POST", "/", "/", @@ -67,6 +195,174 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingFormForTime(t *testing.T) { + testFormBindingForTime(t, "POST", + "/", "/", + "time_foo=2017-11-15&time_bar=", "bar2=foo") + testFormBindingForTimeNotFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15", "bar2=foo") + testFormBindingForTimeFailFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15", "bar2=foo") + testFormBindingForTimeFailLocation(t, "POST", + "/", "/", + "time_foo=2017-11-15", "bar2=foo") +} + +func TestBindingFormForTime2(t *testing.T) { + testFormBindingForTime(t, "GET", + "/?time_foo=2017-11-15&time_bar=", "/?bar2=foo", + "", "") + testFormBindingForTimeNotFormat(t, "GET", + "/?time_foo=2017-11-15", "/?bar2=foo", + "", "") + testFormBindingForTimeFailFormat(t, "GET", + "/?time_foo=2017-11-15", "/?bar2=foo", + "", "") + testFormBindingForTimeFailLocation(t, "GET", + "/?time_foo=2017-11-15", "/?bar2=foo", + "", "") +} + +func TestBindingFormInvalidName(t *testing.T) { + testFormBindingInvalidName(t, "POST", + "/", "/", + "test_name=bar", "bar2=foo") +} + +func TestBindingFormInvalidName2(t *testing.T) { + testFormBindingInvalidName2(t, "POST", + "/", "/", + "map_foo=bar", "bar2=foo") +} + +func TestBindingFormForType(t *testing.T) { + testFormBindingForType(t, "POST", + "/", "/", + "map_foo=", "bar2=1", "Map") + + testFormBindingForType(t, "POST", + "/", "/", + "slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice") + + testFormBindingForType(t, "GET", + "/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2", + "", "", "Slice") + + testFormBindingForType(t, "POST", + "/", "/", + "slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap") + + testFormBindingForType(t, "GET", + "/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2", + "", "", "SliceMap") + + testFormBindingForType(t, "POST", + "/", "/", + "int_foo=&int_bar=-12", "bar2=-123", "Int") + + testFormBindingForType(t, "GET", + "/?int_foo=&int_bar=-12", "/?bar2=-123", + "", "", "Int") + + testFormBindingForType(t, "POST", + "/", "/", + "int8_foo=&int8_bar=-12", "bar2=-123", "Int8") + + testFormBindingForType(t, "GET", + "/?int8_foo=&int8_bar=-12", "/?bar2=-123", + "", "", "Int8") + + testFormBindingForType(t, "POST", + "/", "/", + "int16_foo=&int16_bar=-12", "bar2=-123", "Int16") + + testFormBindingForType(t, "GET", + "/?int16_foo=&int16_bar=-12", "/?bar2=-123", + "", "", "Int16") + + testFormBindingForType(t, "POST", + "/", "/", + "int32_foo=&int32_bar=-12", "bar2=-123", "Int32") + + testFormBindingForType(t, "GET", + "/?int32_foo=&int32_bar=-12", "/?bar2=-123", + "", "", "Int32") + + testFormBindingForType(t, "POST", + "/", "/", + "int64_foo=&int64_bar=-12", "bar2=-123", "Int64") + + testFormBindingForType(t, "GET", + "/?int64_foo=&int64_bar=-12", "/?bar2=-123", + "", "", "Int64") + + testFormBindingForType(t, "POST", + "/", "/", + "uint_foo=&uint_bar=12", "bar2=123", "Uint") + + testFormBindingForType(t, "GET", + "/?uint_foo=&uint_bar=12", "/?bar2=123", + "", "", "Uint") + + testFormBindingForType(t, "POST", + "/", "/", + "uint8_foo=&uint8_bar=12", "bar2=123", "Uint8") + + testFormBindingForType(t, "GET", + "/?uint8_foo=&uint8_bar=12", "/?bar2=123", + "", "", "Uint8") + + testFormBindingForType(t, "POST", + "/", "/", + "uint16_foo=&uint16_bar=12", "bar2=123", "Uint16") + + testFormBindingForType(t, "GET", + "/?uint16_foo=&uint16_bar=12", "/?bar2=123", + "", "", "Uint16") + + testFormBindingForType(t, "POST", + "/", "/", + "uint32_foo=&uint32_bar=12", "bar2=123", "Uint32") + + testFormBindingForType(t, "GET", + "/?uint32_foo=&uint32_bar=12", "/?bar2=123", + "", "", "Uint32") + + testFormBindingForType(t, "POST", + "/", "/", + "uint64_foo=&uint64_bar=12", "bar2=123", "Uint64") + + testFormBindingForType(t, "GET", + "/?uint64_foo=&uint64_bar=12", "/?bar2=123", + "", "", "Uint64") + + testFormBindingForType(t, "POST", + "/", "/", + "bool_foo=&bool_bar=true", "bar2=true", "Bool") + + testFormBindingForType(t, "GET", + "/?bool_foo=&bool_bar=true", "/?bar2=true", + "", "", "Bool") + + testFormBindingForType(t, "POST", + "/", "/", + "float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32") + + testFormBindingForType(t, "GET", + "/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3", + "", "", "Float32") + + testFormBindingForType(t, "POST", + "/", "/", + "float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64") + + testFormBindingForType(t, "GET", + "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", + "", "", "Float64") +} + func TestBindingQuery(t *testing.T) { testQueryBinding(t, "POST", "/?foo=bar&bar=foo", "/", @@ -79,6 +375,18 @@ func TestBindingQuery2(t *testing.T) { "foo=unused", "") } +func TestBindingQueryFail(t *testing.T) { + testQueryBindingFail(t, "POST", + "/?map_foo=", "/", + "map_foo=unused", "bar2=foo") +} + +func TestBindingQueryFail2(t *testing.T) { + testQueryBindingFail(t, "GET", + "/?map_foo=", "/?bar2=foo", + "map_foo=unused", "") +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -86,12 +394,25 @@ func TestBindingXML(t *testing.T) { "bar", "foo") } +func TestBindingXMLFail(t *testing.T) { + testBodyBindingFail(t, + XML, "xml", + "/", "/", + "bar", "foo") +} + func createFormPostRequest() *http.Request { req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) req.Header.Set("Content-Type", MIMEPOSTForm) return req } +func createFormPostRequestFail() *http.Request { + req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) + req.Header.Set("Content-Type", MIMEPOSTForm) + return req +} + func createFormMultipartRequest() *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) @@ -106,24 +427,53 @@ func createFormMultipartRequest() *http.Request { return req } +func createFormMultipartRequestFail() *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + mw.SetBoundary(boundary) + mw.WriteField("map_foo", "bar") + req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + return req +} + func TestBindingFormPost(t *testing.T) { req := createFormPostRequest() var obj FooBarStruct FormPost.Bind(req, &obj) + assert.Equal(t, FormPost.Name(), "form-urlencoded") assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Bar, "foo") } +func TestBindingFormPostFail(t *testing.T) { + req := createFormPostRequestFail() + var obj FooStructForMapType + err := FormPost.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest() var obj FooBarStruct FormMultipart.Bind(req, &obj) + assert.Equal(t, FormMultipart.Name(), "multipart/form-data") assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Bar, "foo") } +func TestBindingFormMultipartFail(t *testing.T) { + req := createFormMultipartRequestFail() + var obj FooStructForMapType + err := FormMultipart.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingProtoBuf(t *testing.T) { test := &example.Test{ Label: proto.String("yes"), @@ -136,6 +486,18 @@ func TestBindingProtoBuf(t *testing.T) { string(data), string(data[1:])) } +func TestBindingProtoBufFail(t *testing.T) { + test := &example.Test{ + Label: proto.String("yes"), + } + data, _ := proto.Marshal(test) + + testProtoBodyBindingFail(t, + ProtoBuf, "protobuf", + "/", "/", + string(data), string(data[1:])) +} + func TestBindingMsgPack(t *testing.T) { test := FooStruct{ Foo: "bar", @@ -216,6 +578,323 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Error(t, err) } +func TestFormBindingFail(t *testing.T) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooBarStruct{} + req, _ := http.NewRequest("POST", "/", nil) + err := b.Bind(req, &obj) + assert.Error(t, err) +} + +func TestFormPostBindingFail(t *testing.T) { + b := FormPost + assert.Equal(t, b.Name(), "form-urlencoded") + + obj := FooBarStruct{} + req, _ := http.NewRequest("POST", "/", nil) + err := b.Bind(req, &obj) + assert.Error(t, err) +} + +func TestFormMultipartBindingFail(t *testing.T) { + b := FormMultipart + assert.Equal(t, b.Name(), "multipart/form-data") + + obj := FooBarStruct{} + req, _ := http.NewRequest("POST", "/", nil) + err := b.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooBarStructForTimeType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + + assert.NoError(t, err) + assert.Equal(t, obj.TimeFoo.Unix(), int64(1510675200)) + assert.Equal(t, obj.TimeFoo.Location().String(), "Asia/Chongqing") + assert.Equal(t, obj.TimeBar.Unix(), int64(-62135596800)) + assert.Equal(t, obj.TimeBar.Location().String(), "UTC") + + obj = FooBarStructForTimeType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooStructForTimeTypeNotFormat{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeNotFormat{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooStructForTimeTypeFailFormat{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeFailFormat{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooStructForTimeTypeFailLocation{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeFailLocation{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := InvalidNameType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.TestName, "") + + obj = InvalidNameType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := InvalidNameMapType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = InvalidNameMapType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { + b := Form + assert.Equal(t, b.Name(), "form") + + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + switch typ { + case "Int": + obj := FooBarStructForIntType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.IntFoo, int(0)) + assert.Equal(t, obj.IntBar, int(-12)) + + obj = FooBarStructForIntType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int8": + obj := FooBarStructForInt8Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int8Foo, int8(0)) + assert.Equal(t, obj.Int8Bar, int8(-12)) + + obj = FooBarStructForInt8Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int16": + obj := FooBarStructForInt16Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int16Foo, int16(0)) + assert.Equal(t, obj.Int16Bar, int16(-12)) + + obj = FooBarStructForInt16Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int32": + obj := FooBarStructForInt32Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int32Foo, int32(0)) + assert.Equal(t, obj.Int32Bar, int32(-12)) + + obj = FooBarStructForInt32Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int64": + obj := FooBarStructForInt64Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int64Foo, int64(0)) + assert.Equal(t, obj.Int64Bar, int64(-12)) + + obj = FooBarStructForInt64Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint": + obj := FooBarStructForUintType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.UintFoo, uint(0x0)) + assert.Equal(t, obj.UintBar, uint(0xc)) + + obj = FooBarStructForUintType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint8": + obj := FooBarStructForUint8Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint8Foo, uint8(0x0)) + assert.Equal(t, obj.Uint8Bar, uint8(0xc)) + + obj = FooBarStructForUint8Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint16": + obj := FooBarStructForUint16Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint16Foo, uint16(0x0)) + assert.Equal(t, obj.Uint16Bar, uint16(0xc)) + + obj = FooBarStructForUint16Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint32": + obj := FooBarStructForUint32Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint32Foo, uint32(0x0)) + assert.Equal(t, obj.Uint32Bar, uint32(0xc)) + + obj = FooBarStructForUint32Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint64": + obj := FooBarStructForUint64Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint64Foo, uint64(0x0)) + assert.Equal(t, obj.Uint64Bar, uint64(0xc)) + + obj = FooBarStructForUint64Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Float32": + obj := FooBarStructForFloat32Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Float32Foo, float32(0.0)) + assert.Equal(t, obj.Float32Bar, float32(-12.34)) + + obj = FooBarStructForFloat32Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Float64": + obj := FooBarStructForFloat64Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Float64Foo, float64(0.0)) + assert.Equal(t, obj.Float64Bar, float64(-12.34)) + + obj = FooBarStructForFloat64Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Bool": + obj := FooBarStructForBoolType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.BoolFoo, false) + assert.Equal(t, obj.BoolBar, true) + + obj = FooBarStructForBoolType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Slice": + obj := FooStructForSliceType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.SliceFoo, []int{1, 2}) + + obj = FooStructForSliceType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Map": + obj := FooStructForMapType{} + err := b.Bind(req, &obj) + assert.Error(t, err) + case "SliceMap": + obj := FooStructForSliceMapType{} + err := b.Bind(req, &obj) + assert.Error(t, err) + } +} + func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Query assert.Equal(t, b.Name(), "query") @@ -231,6 +910,19 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Equal(t, obj.Bar, "foo") } +func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) { + b := Query + assert.Equal(t, b.Name(), "query") + + obj := FooStructForMapType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) +} + func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, b.Name(), name) @@ -246,6 +938,58 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Error(t, err) } +func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := FooStructUseNumber{} + req := requestWithBody("POST", path, body) + EnableDecoderUseNumber = true + err := b.Bind(req, &obj) + assert.NoError(t, err) + // we hope it is int64(123) + v, e := obj.Foo.(json.Number).Int64() + assert.NoError(t, e) + assert.Equal(t, v, int64(123)) + + obj = FooStructUseNumber{} + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := FooStructUseNumber{} + req := requestWithBody("POST", path, body) + EnableDecoderUseNumber = false + err := b.Bind(req, &obj) + assert.NoError(t, err) + // it will return float64(123) if not use EnableDecoderUseNumber + // maybe it is not hoped + assert.Equal(t, obj.Foo, float64(123)) + + obj = FooStructUseNumber{} + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := FooStruct{} + req := requestWithBody("POST", path, body) + err := b.Bind(req, &obj) + assert.Error(t, err) + assert.Equal(t, obj.Foo, "") + + obj = FooStruct{} + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, b.Name(), name) @@ -263,6 +1007,30 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba assert.Error(t, err) } +type hook struct{} + +func (h hook) Read([]byte) (int, error) { + return 0, errors.New("error") +} + +func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := example.Test{} + req := requestWithBody("POST", path, body) + + req.Body = ioutil.NopCloser(&hook{}) + req.Header.Add("Content-Type", MIMEPROTOBUF) + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = example.Test{} + req = requestWithBody("POST", badPath, badBody) + req.Header.Add("Content-Type", MIMEPROTOBUF) + err = ProtoBuf.Bind(req, &obj) + assert.Error(t, err) +} + func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, b.Name(), name) diff --git a/coverage.sh b/coverage.sh new file mode 100644 index 00000000..81437f91 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +echo "mode: count" > coverage.out + +for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do + go test -v -covermode=count -coverprofile=profile.out $d + if [ -f profile.out ]; then + cat profile.out | grep -v "mode:" >> coverage.out + rm profile.out + fi +done diff --git a/render/render_test.go b/render/render_test.go index 35662cf3..530e222a 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -7,7 +7,9 @@ package render import ( "bytes" "encoding/xml" + "errors" "html/template" + "net/http" "net/http/httptest" "testing" @@ -24,6 +26,9 @@ func TestRenderMsgPack(t *testing.T) { "foo": "bar", } + (MsgPack{data}).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + err := (MsgPack{data}).Render(w) assert.NoError(t, err) @@ -45,6 +50,9 @@ func TestRenderJSON(t *testing.T) { "foo": "bar", } + (JSON{data}).WriteContentType(w) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + err := (JSON{data}).Render(w) assert.NoError(t, err) @@ -52,6 +60,14 @@ func TestRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } +func TestRenderJSONPanics(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + assert.Panics(t, func() { (JSON{data}).Render(w) }) +} + func TestRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ @@ -66,12 +82,24 @@ func TestRenderIndentedJSON(t *testing.T) { assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") } +func TestRenderIndentedJSONPanics(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (IndentedJSON{data}).Render(w) + assert.Error(t, err) +} + func TestRenderSecureJSON(t *testing.T) { w1 := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", } + (SecureJSON{"while(1);", data}).WriteContentType(w1) + assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) + err1 := (SecureJSON{"while(1);", data}).Render(w1) assert.NoError(t, err1) @@ -91,6 +119,15 @@ func TestRenderSecureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) } +func TestRenderSecureJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (SecureJSON{"while(1);", data}).Render(w) + assert.Error(t, err) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal @@ -115,12 +152,45 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return e.EncodeToken(xml.EndElement{Name: start.Name}) } +func TestRenderYAML(t *testing.T) { + w := httptest.NewRecorder() + data := ` +a : Easy! +b: + c: 2 + d: [3, 4] + ` + (YAML{data}).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + + err := (YAML{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n") + assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") +} + +type fail struct{} + +// Hook MarshalYAML +func (ft *fail) MarshalYAML() (interface{}, error) { + return nil, errors.New("fail") +} + +func TestRenderYAMLFail(t *testing.T) { + w := httptest.NewRecorder() + err := (YAML{&fail{}}).Render(w) + assert.Error(t, err) +} + func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ "foo": "bar", } + (XML{data}).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + err := (XML{data}).Render(w) assert.NoError(t, err) @@ -129,7 +199,30 @@ func TestRenderXML(t *testing.T) { } func TestRenderRedirect(t *testing.T) { - // TODO + req, err := http.NewRequest("GET", "/test-redirect", nil) + assert.NoError(t, err) + + data1 := Redirect{ + Code: 301, + Request: req, + Location: "/new/location", + } + + w := httptest.NewRecorder() + err = data1.Render(w) + assert.NoError(t, err) + + data2 := Redirect{ + Code: 200, + Request: req, + Location: "/new/location", + } + + w = httptest.NewRecorder() + assert.Panics(t, func() { data2.Render(w) }) + + // only improve coverage + data2.WriteContentType(w) } func TestRenderData(t *testing.T) { @@ -149,6 +242,12 @@ func TestRenderData(t *testing.T) { func TestRenderString(t *testing.T) { w := httptest.NewRecorder() + (String{ + Format: "hello %s %d", + Data: []interface{}{}, + }).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + err := (String{ Format: "hola %s %d", Data: []interface{}{"manu", 2}, @@ -159,6 +258,19 @@ func TestRenderString(t *testing.T) { assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") } +func TestRenderStringLenZero(t *testing.T) { + w := httptest.NewRecorder() + + err := (String{ + Format: "hola %s %d", + Data: []interface{}{}, + }).Render(w) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "hola %s %d") + assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") +} + func TestRenderHTMLTemplate(t *testing.T) { w := httptest.NewRecorder() templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) @@ -174,3 +286,64 @@ func TestRenderHTMLTemplate(t *testing.T) { assert.Equal(t, w.Body.String(), "Hello alexandernyquist") assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") } + +func TestRenderHTMLTemplateEmptyName(t *testing.T) { + w := httptest.NewRecorder() + templ := template.Must(template.New("").Parse(`Hello {{.name}}`)) + + htmlRender := HTMLProduction{Template: templ} + instance := htmlRender.Instance("", map[string]interface{}{ + "name": "alexandernyquist", + }) + + err := instance.Render(w) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "Hello alexandernyquist") + assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") +} + +func TestRenderHTMLDebugFiles(t *testing.T) { + w := httptest.NewRecorder() + htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"}, + Glob: "", + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, + } + instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ + "name": "thinkerou", + }) + + err := instance.Render(w) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "

Hello thinkerou

") + assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") +} + +func TestRenderHTMLDebugGlob(t *testing.T) { + w := httptest.NewRecorder() + htmlRender := HTMLDebug{Files: nil, + Glob: "../fixtures/basic/hello*", + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, + } + instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ + "name": "thinkerou", + }) + + err := instance.Render(w) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "

Hello thinkerou

") + assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") +} + +func TestRenderHTMLDebugPanics(t *testing.T) { + htmlRender := HTMLDebug{Files: nil, + Glob: "", + Delims: Delims{"{{", "}}"}, + FuncMap: nil, + } + assert.Panics(t, func() { htmlRender.Instance("", nil) }) +} From ae22f0c87091eb75e08429082935ec48035320ac Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 22 Feb 2018 21:00:20 +0800 Subject: [PATCH 005/111] chore(travis): add 1.10 version (#1256) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ec12cad6..63b5d113 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ go: - 1.7.x - 1.8.x - 1.9.x + - 1.10.x - master git: From cbb1ee80b1c2a53c46872e43ed15c664917eb905 Mon Sep 17 00:00:00 2001 From: README Bot <35302948+codetriage-readme-bot@users.noreply.github.com> Date: Thu, 22 Feb 2018 07:28:50 -0600 Subject: [PATCH 006/111] Add CodeTriage badge to gin-gonic/gin (#1249) Adds a badge showing the number of people helping this repo on CodeTriage. [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) ## What is CodeTriage? CodeTriage is an Open Source app that is designed to make contributing to Open Source projects easier. It works by sending subscribers a few open issues in their inbox. If subscribers get busy, there is an algorithm that backs off issue load so they do not get overwhelmed [Read more about the CodeTriage project](https://www.codetriage.com/what). ## Why am I getting this PR? Your project was picked by the human, @schneems. They selected it from the projects submitted to https://www.codetriage.com and hand edited the PR. How did your project get added to [CodeTriage](https://www.codetriage.com/what)? Roughly 6 months ago, [dinsaw](https://github.com/dinsaw) added this project to CodeTriage in order to start contributing. Since then, 5 people have subscribed to help this repo. ## What does adding a badge accomplish? Adding a badge invites people to help contribute to your project. It also lets developers know that others are invested in the longterm success and maintainability of the project. You can see an example of a CodeTriage badge on these popular OSS READMEs: - [![](https://www.codetriage.com/rails/rails/badges/users.svg)](https://www.codetriage.com/rails/rails) https://github.com/rails/rails - [![](https://www.codetriage.com/crystal-lang/crystal/badges/users.svg)](https://www.codetriage.com/crystal-lang/crystal) https://github.com/crystal-lang/crystal ## Have a question or comment? While I am a bot, this PR was manually reviewed and monitored by a human - @schneems. My job is writing commit messages and handling PR logistics. If you have any questions, you can reply back to this PR and they will be answered by @schneems. If you do not want a badge right now, no worries, close the PR, you will not hear from me again. Thanks for making your project Open Source! Any feedback is greatly appreciated. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6e421f02..b51aa10c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. From 5d3f30cfc81f9109850fa9043a19783cbbde68a5 Mon Sep 17 00:00:00 2001 From: Mario Kostelac Date: Fri, 23 Feb 2018 01:09:33 +0000 Subject: [PATCH 007/111] Make "" mode being the same as debug mode (#1250) Not setting mode explicitly sets gin into debug mode, but it does not make it possible to retrieve gin mode as Debug since it's set to "". --- mode.go | 3 +++ mode_test.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/mode.go b/mode.go index 9c4f0246..9df4e45f 100644 --- a/mode.go +++ b/mode.go @@ -53,6 +53,9 @@ func SetMode(value string) { default: panic("gin mode unknown: " + value) } + if value == "" { + value = DebugMode + } modeName = value } diff --git a/mode_test.go b/mode_test.go index 7eaca823..cf27acd8 100644 --- a/mode_test.go +++ b/mode_test.go @@ -21,6 +21,10 @@ func TestSetMode(t *testing.T) { assert.Equal(t, TestMode, Mode()) os.Unsetenv(ENV_GIN_MODE) + SetMode("") + assert.Equal(t, debugCode, ginMode) + assert.Equal(t, DebugMode, Mode()) + SetMode(DebugMode) assert.Equal(t, debugCode, ginMode) assert.Equal(t, DebugMode, Mode()) From 3e3f9bca81da5a4d2664d075012ae5aca9e70b9e Mon Sep 17 00:00:00 2001 From: Romain Beuque Date: Tue, 20 Mar 2018 07:05:24 +0100 Subject: [PATCH 008/111] doc(graceful-shutdown): failure to ListenAndServe should be a reason to exit (#1287) Signed-off-by: Romain Beuque --- README.md | 4 ++-- examples/graceful-shutdown/graceful-shutdown/server.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b51aa10c..72ac99bd 100644 --- a/README.md +++ b/README.md @@ -1324,8 +1324,8 @@ func main() { go func() { // service connections - if err := srv.ListenAndServe(); err != nil { - log.Printf("listen: %s\n", err) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) } }() diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index 6debe7f5..af4f2146 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -27,8 +27,8 @@ func main() { go func() { // service connections - if err := srv.ListenAndServe(); err != nil { - log.Printf("listen: %s\n", err) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) } }() From 65a65c2edd4e86f96634a553f07a25c38f2b1c75 Mon Sep 17 00:00:00 2001 From: hellojukay Date: Tue, 20 Mar 2018 14:42:51 +0800 Subject: [PATCH 009/111] add gin panic time log (#1270) * add gin pinic time log * Update recovery.go --- recovery.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 89b39fec..dcbeba74 100644 --- a/recovery.go +++ b/recovery.go @@ -12,6 +12,7 @@ import ( "log" "net/http/httputil" "runtime" + "time" ) var ( @@ -38,7 +39,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { if logger != nil { stack := stack(3) httprequest, _ := httputil.DumpRequest(c.Request, false) - logger.Printf("[Recovery] panic recovered:\n%s\n%s\n%s%s", string(httprequest), err, stack, reset) + logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) } c.AbortWithStatus(500) } @@ -107,3 +108,8 @@ func function(pc uintptr) []byte { name = bytes.Replace(name, centerDot, dot, -1) return name } + +func timeFormat(t time.Time) string { + var timeString = t.Format("2006/01/02 - 15:04:05") + return timeString +} From 6d913fc343cfac80d656db5be67a0ffb1323ba0d Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Thu, 29 Mar 2018 12:03:07 +0530 Subject: [PATCH 010/111] fix(binding): Expose validator engine used by the default Validator (#1277) * fix(binding): Expose validator engine used by the default Validator - Add func ValidatorEngine for returning the underlying validator engine used in the default StructValidator implementation. - Remove the function RegisterValidation from the StructValidator interface which made it immpossible to use a StructValidator implementation without the validator.v8 library. - Update and rename test for registering validation Test{RegisterValidation => ValidatorEngine}. - Update readme and example for registering custom validation. - Add example for registering struct level validation. - Add documentation for the following binding funcs/types: - Binding interface - StructValidator interface - Validator instance - Binding implementations - Default func * fix(binding): Move validator engine getter inside interface * docs: rm date cmd from custom validation demo --- README.md | 13 +++-- binding/binding.go | 23 +++++--- binding/default_validator.go | 8 ++- binding/json.go | 3 ++ binding/validate_test.go | 9 ++-- examples/custom-validation/server.go | 6 ++- examples/struct-lvl-validations/README.md | 50 ++++++++++++++++++ examples/struct-lvl-validations/server.go | 64 +++++++++++++++++++++++ 8 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 examples/struct-lvl-validations/README.md create mode 100644 examples/struct-lvl-validations/server.go diff --git a/README.md b/README.md index 72ac99bd..7dd7f734 100644 --- a/README.md +++ b/README.md @@ -564,7 +564,11 @@ func bookableDate( func main() { route := gin.Default() - binding.Validator.RegisterValidation("bookabledate", bookableDate) + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + route.GET("/bookable", getBookable) route.Run(":8085") } @@ -580,13 +584,16 @@ func getBookable(c *gin.Context) { ``` ```console -$ curl "localhost:8085/bookable?check_in=2017-08-16&check_out=2017-08-17" +$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" {"message":"Booking dates are valid!"} -$ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16" +$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} ``` +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. +See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. + ### Only Bind Query String `ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). diff --git a/binding/binding.go b/binding/binding.go index dc32d538..646eb80a 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -6,8 +6,6 @@ package binding import ( "net/http" - - "gopkg.in/go-playground/validator.v8" ) const ( @@ -23,11 +21,18 @@ const ( MIMEMSGPACK2 = "application/msgpack" ) +// Binding describes the interface which needs to be implemented for binding the +// data present in the request such as JSON request body, query parameters or +// the form POST. type Binding interface { Name() string Bind(*http.Request, interface{}) error } +// StructValidator is the minimal interface which needs to be implemented in +// order for it to be used as the validator engine for ensuring the correctness +// of the reqest. Gin provides a default implementation for this using +// https://github.com/go-playground/validator/tree/v8.18.2. type StructValidator interface { // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // If the received type is not a struct, any validation should be skipped and nil must be returned. @@ -36,14 +41,18 @@ type StructValidator interface { // Otherwise nil must be returned. ValidateStruct(interface{}) error - // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key - // NOTE: if the key already exists, the previous validation function will be replaced. - // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation - RegisterValidation(string, validator.Func) error + // Engine returns the underlying validator engine which powers the + // StructValidator implementation. + Engine() interface{} } +// Validator is the default validator which implements the StructValidator +// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 +// under the hood. var Validator StructValidator = &defaultValidator{} +// These implement the Binding interface and can be used to bind the data +// present in the request to struct instances. var ( JSON = jsonBinding{} XML = xmlBinding{} @@ -55,6 +64,8 @@ var ( MsgPack = msgpackBinding{} ) +// Default returns the appropriate Binding instance based on the HTTP method +// and the content type. func Default(method, contentType string) Binding { if method == "GET" { return Form diff --git a/binding/default_validator.go b/binding/default_validator.go index 6336bb6e..c67aa8a3 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -28,9 +28,13 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return nil } -func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error { +// Engine returns the underlying validator engine which powers the default +// Validator instance. This is useful if you want to register custom validations +// or struct level validations. See validator GoDoc for more info - +// https://godoc.org/gopkg.in/go-playground/validator.v8 +func (v *defaultValidator) Engine() interface{} { v.lazyinit() - return v.validate.RegisterValidation(key, fn) + return v.validate } func (v *defaultValidator) lazyinit() { diff --git a/binding/json.go b/binding/json.go index b7c856af..e928a8c1 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,6 +10,9 @@ import ( "github.com/gin-gonic/gin/json" ) +// EnableDecoderUseNumber is used to call the UseNumber method on the JSON +// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an +// interface{} as a Number instead of as a float64. var EnableDecoderUseNumber = false type jsonBinding struct{} diff --git a/binding/validate_test.go b/binding/validate_test.go index 8ca79989..cb76063c 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -214,11 +214,14 @@ func notOne( return false } -func TestRegisterValidation(t *testing.T) { +func TestValidatorEngine(t *testing.T) { // This validates that the function `notOne` matches // the expected function signature by `defaultValidator` // and by extension the validator library. - err := Validator.RegisterValidation("notone", notOne) + engine, ok := Validator.Engine().(*validator.Validate) + assert.True(t, ok) + + err := engine.RegisterValidation("notone", notOne) // Check that we can register custom validation without error assert.Nil(t, err) @@ -228,6 +231,6 @@ func TestRegisterValidation(t *testing.T) { // Check that we got back non-nil errs assert.NotNil(t, errs) - // Check that the error matches expactation + // Check that the error matches expectation assert.Error(t, errs, "", "", "notone") } diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go index 31d449f0..dea0c302 100644 --- a/examples/custom-validation/server.go +++ b/examples/custom-validation/server.go @@ -30,7 +30,11 @@ func bookableDate( func main() { route := gin.Default() - binding.Validator.RegisterValidation("bookabledate", bookableDate) + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + route.GET("/bookable", getBookable) route.Run(":8085") } diff --git a/examples/struct-lvl-validations/README.md b/examples/struct-lvl-validations/README.md new file mode 100644 index 00000000..1bd57f03 --- /dev/null +++ b/examples/struct-lvl-validations/README.md @@ -0,0 +1,50 @@ +## Struct level validations + +Validations can also be registered at the `struct` level when field level validations +don't make much sense. This can also be used to solve cross-field validation elegantly. +Additionally, it can be combined with tag validations. Struct Level validations run after +the structs tag validations. + +### Example requests + +```shell +# Validation errors are generated for struct tags as well as at the struct level +$ curl -s -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{}' | jq +{ + "error": "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", + "message": "User validation failed!" +} + +# Validation fails at the struct level because neither first name nor last name are present +$ curl -s -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"email": "george@vandaley.com"}' | jq +{ + "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", + "message": "User validation failed!" +} + +# No validation errors when either first name or last name is present +$ curl -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"fname": "George", "email": "george@vandaley.com"}' +{"message":"User validation successful."} + +$ curl -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"lname": "Contanza", "email": "george@vandaley.com"}' +{"message":"User validation successful."} + +$ curl -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"fname": "George", "lname": "Costanza", "email": "george@vandaley.com"}' +{"message":"User validation successful."} +``` + +### Useful links + +- Validator docs - https://godoc.org/gopkg.in/go-playground/validator.v8#Validate.RegisterStructValidation +- Struct level example - https://github.com/go-playground/validator/blob/v8.18.2/examples/struct-level/struct_level.go +- Validator release notes - https://github.com/go-playground/validator/releases/tag/v8.7 diff --git a/examples/struct-lvl-validations/server.go b/examples/struct-lvl-validations/server.go new file mode 100644 index 00000000..be807b78 --- /dev/null +++ b/examples/struct-lvl-validations/server.go @@ -0,0 +1,64 @@ +package main + +import ( + "net/http" + "reflect" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + validator "gopkg.in/go-playground/validator.v8" +) + +// User contains user information. +type User struct { + FirstName string `json:"fname"` + LastName string `json:"lname"` + Email string `binding:"required,email"` +} + +// UserStructLevelValidation contains custom struct level validations that don't always +// make sense at the field validation level. For example, this function validates that either +// FirstName or LastName exist; could have done that with a custom field validation but then +// would have had to add it to both fields duplicating the logic + overhead, this way it's +// only validated once. +// +// NOTE: you may ask why wouldn't not just do this outside of validator. Doing this way +// hooks right into validator and you can combine with validation tags and still have a +// common error output format. +func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) { + user := structLevel.CurrentStruct.Interface().(User) + + if len(user.FirstName) == 0 && len(user.LastName) == 0 { + structLevel.ReportError( + reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname", + ) + structLevel.ReportError( + reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname", + ) + } + + // plus can to more, even with different tag than "fnameorlname" +} + +func main() { + route := gin.Default() + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterStructValidation(UserStructLevelValidation, User{}) + } + + route.POST("/user", validateUser) + route.Run(":8085") +} + +func validateUser(c *gin.Context) { + var u User + if err := c.ShouldBindJSON(&u); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "User validation successful."}) + } else { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "User validation failed!", + "error": err.Error(), + }) + } +} From 6ad7b9c9d382844036334ad061b832e56269e14f Mon Sep 17 00:00:00 2001 From: Yoshiyuki Kinjo Date: Tue, 17 Apr 2018 11:54:40 +0900 Subject: [PATCH 011/111] Fix documentation typo (#1321) --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 99d19af4..278029d7 100644 --- a/utils.go +++ b/utils.go @@ -49,7 +49,7 @@ func WrapH(h http.Handler) HandlerFunc { } } -// H is a shortcup for map[string]interface{} +// H is a shortcut for map[string]interface{} type H map[string]interface{} // MarshalXML allows type H to be used with xml.Marshal. From 3455d7f38877a931d0b97bc7c800e6963351ed2f Mon Sep 17 00:00:00 2001 From: esplo Date: Thu, 19 Apr 2018 13:00:22 +0900 Subject: [PATCH 012/111] change README for app-engine example because of goapp deprecation (#1324) the App Engine SDK is superseded by the Cloud SDK --- examples/app-engine/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/app-engine/README.md b/examples/app-engine/README.md index 48505de8..b3dd7c78 100644 --- a/examples/app-engine/README.md +++ b/examples/app-engine/README.md @@ -1,7 +1,8 @@ # Guide to run Gin under App Engine LOCAL Development Server 1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) -2. Download SDK for your platform from here: `https://developers.google.com/appengine/downloads?hl=es#Google_App_Engine_SDK_for_Go` +2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download` 3. Download Gin source code using: `$ go get github.com/gin-gonic/gin` -4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/` -5. Run it: `$ goapp serve app-engine/` \ No newline at end of file +4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/` +5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2) + From 248c522e4a829f10cc525136d8e8f3d44f093ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 20 Apr 2018 09:54:00 +0800 Subject: [PATCH 013/111] Add Contents for README because it too long (#1325) --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 7dd7f734..38bde8d2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,47 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) +## Contents + +- [Quick start](#quick-start) +- [Benchmarks](#benchmarks) +- [Gin v1.stable](#gin-v1-stable) +- [Start using it](#start-using-it) +- [Build with jsoniter](#build-with-jsoniter) +- [API Examples](#api-examples) + - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) + - [Parameters in path](#parameters-in-path) + - [Querystring parameters](#querystring-parameters) + - [Multipart/Urlencoded Form](#multiparturlencoded-form) + - [Another example: query + post form](#another-example-query--post-form) + - [Upload files](#upload-files) + - [Grouping routes](#grouping-routes) + - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) + - [Using middleware](#using-middleware) + - [How to write log file](#how-to-write-log-file) + - [Model binding and validation](#model-binding-and-validation) + - [Custom Validators](#custom-validators) + - [Only Bind Query String](#only-bind-query-string) + - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind HTML checkboxes](#bind-html-checkboxes) + - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) + - [Serving static files](#serving-static-files) + - [HTML rendering](#html-rendering) + - [Multitemplate](#multitemplate) + - [Redirects](#redirects) + - [Custom Middleware](#custom-middleware) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Custom HTTP configuration](#custom-http-configuration) + - [Support Let's Encrypt](#support-lets-encrypt) + - [Run multiple service using Gin](#run-multiple-service-using-gin) + - [Graceful restart or stop](#graceful-restart-or-stop) +- [Testing](#testing) +- [Users](#users--) + +## Quick start + ```sh # assume the following codes in example.go file $ cat example.go From dfe37ea6f1b9127be4cff4822a1308b4349444e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 20 Apr 2018 10:27:44 +0800 Subject: [PATCH 014/111] unify assert.Equal usage (#1327) * unify assert.Equal usage * fix typo --- binding/binding_test.go | 164 +++++++++++++++++++-------------------- binding/validate_test.go | 6 +- render/render_test.go | 50 ++++++------ 3 files changed, 110 insertions(+), 110 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 0c0f9f81..b239cafb 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -140,26 +140,26 @@ type FooBarStructForFloat64Type struct { } func TestBindingDefault(t *testing.T) { - assert.Equal(t, Default("GET", ""), Form) - assert.Equal(t, Default("GET", MIMEJSON), Form) + assert.Equal(t, Form, Default("GET", "")) + assert.Equal(t, Form, Default("GET", MIMEJSON)) - assert.Equal(t, Default("POST", MIMEJSON), JSON) - assert.Equal(t, Default("PUT", MIMEJSON), JSON) + assert.Equal(t, JSON, Default("POST", MIMEJSON)) + assert.Equal(t, JSON, Default("PUT", MIMEJSON)) - assert.Equal(t, Default("POST", MIMEXML), XML) - assert.Equal(t, Default("PUT", MIMEXML2), XML) + assert.Equal(t, XML, Default("POST", MIMEXML)) + assert.Equal(t, XML, Default("PUT", MIMEXML2)) - assert.Equal(t, Default("POST", MIMEPOSTForm), Form) - assert.Equal(t, Default("PUT", MIMEPOSTForm), Form) + assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) + assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) - assert.Equal(t, Default("POST", MIMEMultipartPOSTForm), Form) - assert.Equal(t, Default("PUT", MIMEMultipartPOSTForm), Form) + assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm)) + assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) - assert.Equal(t, Default("POST", MIMEPROTOBUF), ProtoBuf) - assert.Equal(t, Default("PUT", MIMEPROTOBUF), ProtoBuf) + assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) - assert.Equal(t, Default("POST", MIMEMSGPACK), MsgPack) - assert.Equal(t, Default("PUT", MIMEMSGPACK2), MsgPack) + assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) + assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) } func TestBindingJSON(t *testing.T) { @@ -445,9 +445,9 @@ func TestBindingFormPost(t *testing.T) { var obj FooBarStruct FormPost.Bind(req, &obj) - assert.Equal(t, FormPost.Name(), "form-urlencoded") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "form-urlencoded", FormPost.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func TestBindingFormPostFail(t *testing.T) { @@ -462,9 +462,9 @@ func TestBindingFormMultipart(t *testing.T) { var obj FooBarStruct FormMultipart.Bind(req, &obj) - assert.Equal(t, FormMultipart.Name(), "multipart/form-data") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "multipart/form-data", FormMultipart.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func TestBindingFormMultipartFail(t *testing.T) { @@ -560,7 +560,7 @@ func TestExistsFails(t *testing.T) { func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) @@ -569,8 +569,8 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) obj = FooBarStruct{} req = requestWithBody(method, badPath, badBody) @@ -580,7 +580,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) func TestFormBindingFail(t *testing.T) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -590,7 +590,7 @@ func TestFormBindingFail(t *testing.T) { func TestFormPostBindingFail(t *testing.T) { b := FormPost - assert.Equal(t, b.Name(), "form-urlencoded") + assert.Equal(t, "form-urlencoded", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -600,7 +600,7 @@ func TestFormPostBindingFail(t *testing.T) { func TestFormMultipartBindingFail(t *testing.T) { b := FormMultipart - assert.Equal(t, b.Name(), "multipart/form-data") + assert.Equal(t, "multipart/form-data", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -610,7 +610,7 @@ func TestFormMultipartBindingFail(t *testing.T) { func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStructForTimeType{} req := requestWithBody(method, path, body) @@ -620,10 +620,10 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.TimeFoo.Unix(), int64(1510675200)) - assert.Equal(t, obj.TimeFoo.Location().String(), "Asia/Chongqing") - assert.Equal(t, obj.TimeBar.Unix(), int64(-62135596800)) - assert.Equal(t, obj.TimeBar.Location().String(), "UTC") + assert.Equal(t, int64(1510675200), obj.TimeFoo.Unix()) + assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) + assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) + assert.Equal(t, "UTC", obj.TimeBar.Location().String()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) @@ -633,7 +633,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeNotFormat{} req := requestWithBody(method, path, body) @@ -651,7 +651,7 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailFormat{} req := requestWithBody(method, path, body) @@ -669,7 +669,7 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailLocation{} req := requestWithBody(method, path, body) @@ -687,7 +687,7 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := InvalidNameType{} req := requestWithBody(method, path, body) @@ -696,7 +696,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.TestName, "") + assert.Equal(t, "", obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) @@ -706,7 +706,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := InvalidNameMapType{} req := requestWithBody(method, path, body) @@ -724,7 +724,7 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) req := requestWithBody(method, path, body) if method == "POST" { @@ -735,8 +735,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForIntType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.IntFoo, int(0)) - assert.Equal(t, obj.IntBar, int(-12)) + assert.Equal(t, int(0), obj.IntFoo) + assert.Equal(t, int(-12), obj.IntBar) obj = FooBarStructForIntType{} req = requestWithBody(method, badPath, badBody) @@ -746,8 +746,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int8Foo, int8(0)) - assert.Equal(t, obj.Int8Bar, int8(-12)) + assert.Equal(t, int8(0), obj.Int8Foo) + assert.Equal(t, int8(-12), obj.Int8Bar) obj = FooBarStructForInt8Type{} req = requestWithBody(method, badPath, badBody) @@ -757,8 +757,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int16Foo, int16(0)) - assert.Equal(t, obj.Int16Bar, int16(-12)) + assert.Equal(t, int16(0), obj.Int16Foo) + assert.Equal(t, int16(-12), obj.Int16Bar) obj = FooBarStructForInt16Type{} req = requestWithBody(method, badPath, badBody) @@ -768,8 +768,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int32Foo, int32(0)) - assert.Equal(t, obj.Int32Bar, int32(-12)) + assert.Equal(t, int32(0), obj.Int32Foo) + assert.Equal(t, int32(-12), obj.Int32Bar) obj = FooBarStructForInt32Type{} req = requestWithBody(method, badPath, badBody) @@ -779,8 +779,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int64Foo, int64(0)) - assert.Equal(t, obj.Int64Bar, int64(-12)) + assert.Equal(t, int64(0), obj.Int64Foo) + assert.Equal(t, int64(-12), obj.Int64Bar) obj = FooBarStructForInt64Type{} req = requestWithBody(method, badPath, badBody) @@ -790,8 +790,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUintType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.UintFoo, uint(0x0)) - assert.Equal(t, obj.UintBar, uint(0xc)) + assert.Equal(t, uint(0x0), obj.UintFoo) + assert.Equal(t, uint(0xc), obj.UintBar) obj = FooBarStructForUintType{} req = requestWithBody(method, badPath, badBody) @@ -801,8 +801,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint8Foo, uint8(0x0)) - assert.Equal(t, obj.Uint8Bar, uint8(0xc)) + assert.Equal(t, uint8(0x0), obj.Uint8Foo) + assert.Equal(t, uint8(0xc), obj.Uint8Bar) obj = FooBarStructForUint8Type{} req = requestWithBody(method, badPath, badBody) @@ -812,8 +812,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint16Foo, uint16(0x0)) - assert.Equal(t, obj.Uint16Bar, uint16(0xc)) + assert.Equal(t, uint16(0x0), obj.Uint16Foo) + assert.Equal(t, uint16(0xc), obj.Uint16Bar) obj = FooBarStructForUint16Type{} req = requestWithBody(method, badPath, badBody) @@ -823,8 +823,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint32Foo, uint32(0x0)) - assert.Equal(t, obj.Uint32Bar, uint32(0xc)) + assert.Equal(t, uint32(0x0), obj.Uint32Foo) + assert.Equal(t, uint32(0xc), obj.Uint32Bar) obj = FooBarStructForUint32Type{} req = requestWithBody(method, badPath, badBody) @@ -834,8 +834,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint64Foo, uint64(0x0)) - assert.Equal(t, obj.Uint64Bar, uint64(0xc)) + assert.Equal(t, uint64(0x0), obj.Uint64Foo) + assert.Equal(t, uint64(0xc), obj.Uint64Bar) obj = FooBarStructForUint64Type{} req = requestWithBody(method, badPath, badBody) @@ -845,8 +845,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForFloat32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Float32Foo, float32(0.0)) - assert.Equal(t, obj.Float32Bar, float32(-12.34)) + assert.Equal(t, float32(0.0), obj.Float32Foo) + assert.Equal(t, float32(-12.34), obj.Float32Bar) obj = FooBarStructForFloat32Type{} req = requestWithBody(method, badPath, badBody) @@ -856,8 +856,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForFloat64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Float64Foo, float64(0.0)) - assert.Equal(t, obj.Float64Bar, float64(-12.34)) + assert.Equal(t, float64(0.0), obj.Float64Foo) + assert.Equal(t, float64(-12.34), obj.Float64Bar) obj = FooBarStructForFloat64Type{} req = requestWithBody(method, badPath, badBody) @@ -867,8 +867,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForBoolType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.BoolFoo, false) - assert.Equal(t, obj.BoolBar, true) + assert.False(t, obj.BoolFoo) + assert.True(t, obj.BoolBar) obj = FooBarStructForBoolType{} req = requestWithBody(method, badPath, badBody) @@ -878,7 +878,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooStructForSliceType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.SliceFoo, []int{1, 2}) + assert.Equal(t, []int{1, 2}, obj.SliceFoo) obj = FooStructForSliceType{} req = requestWithBody(method, badPath, badBody) @@ -897,7 +897,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Query - assert.Equal(t, b.Name(), "query") + assert.Equal(t, "query", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) @@ -906,13 +906,13 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) { b := Query - assert.Equal(t, b.Name(), "query") + assert.Equal(t, "query", b.Name()) obj := FooStructForMapType{} req := requestWithBody(method, path, body) @@ -924,13 +924,13 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str } func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) @@ -939,7 +939,7 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody } func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) @@ -949,7 +949,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body // we hope it is int64(123) v, e := obj.Foo.(json.Number).Int64() assert.NoError(t, e) - assert.Equal(t, v, int64(123)) + assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) @@ -958,7 +958,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body } func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) @@ -967,7 +967,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.NoError(t, err) // it will return float64(123) if not use EnableDecoderUseNumber // maybe it is not hoped - assert.Equal(t, obj.Foo, float64(123)) + assert.Equal(t, float64(123), obj.Foo) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) @@ -976,13 +976,13 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod } func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.Error(t, err) - assert.Equal(t, obj.Foo, "") + assert.Equal(t, "", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) @@ -991,14 +991,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad } func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := example.Test{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, *obj.Label, "yes") + assert.Equal(t, "yes", *obj.Label) obj = example.Test{} req = requestWithBody("POST", badPath, badBody) @@ -1014,7 +1014,7 @@ func (h hook) Read([]byte) (int, error) { } func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := example.Test{} req := requestWithBody("POST", path, body) @@ -1032,14 +1032,14 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body } func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) diff --git a/binding/validate_test.go b/binding/validate_test.go index cb76063c..2c76b6d6 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -176,7 +176,7 @@ func TestValidatePrimitives(t *testing.T) { obj := Object{"foo": "bar", "bar": 1} assert.NoError(t, validate(obj)) assert.NoError(t, validate(&obj)) - assert.Equal(t, obj, Object{"foo": "bar", "bar": 1}) + assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} assert.NoError(t, validate(obj2)) @@ -185,12 +185,12 @@ func TestValidatePrimitives(t *testing.T) { nu := 10 assert.NoError(t, validate(nu)) assert.NoError(t, validate(&nu)) - assert.Equal(t, nu, 10) + assert.Equal(t, 10, nu) str := "value" assert.NoError(t, validate(str)) assert.NoError(t, validate(&str)) - assert.Equal(t, str, "value") + assert.Equal(t, "value", str) } // structCustomValidation is a helper struct we use to check that diff --git a/render/render_test.go b/render/render_test.go index 530e222a..d825d041 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -27,7 +27,7 @@ func TestRenderMsgPack(t *testing.T) { } (MsgPack{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) err := (MsgPack{data}).Render(w) @@ -41,7 +41,7 @@ func TestRenderMsgPack(t *testing.T) { assert.NoError(t, err) assert.Equal(t, w.Body.String(), string(buf.Bytes())) - assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderJSON(t *testing.T) { @@ -78,8 +78,8 @@ func TestRenderIndentedJSON(t *testing.T) { err := (IndentedJSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}") - assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderIndentedJSONPanics(t *testing.T) { @@ -161,12 +161,12 @@ b: d: [3, 4] ` (YAML{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n") - assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String()) + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } type fail struct{} @@ -189,13 +189,13 @@ func TestRenderXML(t *testing.T) { } (XML{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) err := (XML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "bar") - assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + assert.Equal(t, "bar", w.Body.String()) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderRedirect(t *testing.T) { @@ -235,8 +235,8 @@ func TestRenderData(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "#!PNG some raw data") - assert.Equal(t, w.Header().Get("Content-Type"), "image/png") + assert.Equal(t, "#!PNG some raw data", w.Body.String()) + assert.Equal(t, "image/png", w.Header().Get("Content-Type")) } func TestRenderString(t *testing.T) { @@ -246,7 +246,7 @@ func TestRenderString(t *testing.T) { Format: "hello %s %d", Data: []interface{}{}, }).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) err := (String{ Format: "hola %s %d", @@ -254,8 +254,8 @@ func TestRenderString(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "hola manu 2") - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "hola manu 2", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderStringLenZero(t *testing.T) { @@ -267,8 +267,8 @@ func TestRenderStringLenZero(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "hola %s %d") - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "hola %s %d", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplate(t *testing.T) { @@ -283,8 +283,8 @@ func TestRenderHTMLTemplate(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "Hello alexandernyquist") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplateEmptyName(t *testing.T) { @@ -299,8 +299,8 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "Hello alexandernyquist") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugFiles(t *testing.T) { @@ -317,8 +317,8 @@ func TestRenderHTMLDebugFiles(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "

Hello thinkerou

") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "

Hello thinkerou

", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugGlob(t *testing.T) { @@ -335,8 +335,8 @@ func TestRenderHTMLDebugGlob(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "

Hello thinkerou

") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "

Hello thinkerou

", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugPanics(t *testing.T) { From 814ac9490a38bb955a742d8ea15fa140489ee658 Mon Sep 17 00:00:00 2001 From: JINNOUCHI Yasushi Date: Sun, 22 Apr 2018 16:04:38 +0900 Subject: [PATCH 015/111] Add example to build single binary with templates (#1328) --- .travis.yml | 2 +- README.md | 45 +++++++++++++++++++++ examples/assets-in-binary/README.md | 33 ++++++++++++++++ examples/assets-in-binary/assets.go | 34 ++++++++++++++++ examples/assets-in-binary/html/bar.tmpl | 4 ++ examples/assets-in-binary/html/index.tmpl | 4 ++ examples/assets-in-binary/main.go | 48 +++++++++++++++++++++++ vendor/vendor.json | 6 +++ 8 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 examples/assets-in-binary/README.md create mode 100644 examples/assets-in-binary/assets.go create mode 100644 examples/assets-in-binary/html/bar.tmpl create mode 100644 examples/assets-in-binary/html/index.tmpl create mode 100644 examples/assets-in-binary/main.go diff --git a/.travis.yml b/.travis.yml index 63b5d113..e9101568 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ go: - master git: - depth: 3 + depth: 10 install: - make install diff --git a/README.md b/README.md index 38bde8d2..6c8988e7 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Support Let's Encrypt](#support-lets-encrypt) - [Run multiple service using Gin](#run-multiple-service-using-gin) - [Graceful restart or stop](#graceful-restart-or-stop) + - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Testing](#testing) - [Users](#users--) @@ -1393,6 +1394,50 @@ func main() { } ``` +### Build a single binary with templates + +You can build a server into a single binary containing templates by using [go-assets][]. + +[go-assets]: https://github.com/jessevdk/go-assets + +```go +func main() { + r := gin.New() + + t, err := loadTemplate() + if err != nil { + panic(err) + } + r.SetHTMLTemplate(t) + + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/index.tmpl",nil) + }) + r.Run(":8080") +} + +// loadTemplate loads templates embedded by go-assets-builder +func loadTemplate() (*template.Template, error) { + t := template.New("") + for name, file := range Assets.Files { + if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { + continue + } + h, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + t, err = t.New(name).Parse(string(h)) + if err != nil { + return nil, err + } + } + return t, nil +} +``` + +See a complete example in the `examples/assets-in-binary` directory. + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/examples/assets-in-binary/README.md b/examples/assets-in-binary/README.md new file mode 100644 index 00000000..0c23bb0d --- /dev/null +++ b/examples/assets-in-binary/README.md @@ -0,0 +1,33 @@ +# Building a single binary containing templates + +This is a complete example to create a single binary with the +[gin-gonic/gin][gin] Web Server with HTML templates. + +[gin]: https://github.com/gin-gonic/gin + +## How to use + +### Prepare Packages + +``` +go get github.com/gin-gonic/gin +go get github.com/jessevdk/go-assets-builder +``` + +### Generate assets.go + +``` +go-assets-builder html -o assets.go +``` + +### Build the server + +``` +go build -o assets-in-binary +``` + +### Run + +``` +./assets-in-binary +``` diff --git a/examples/assets-in-binary/assets.go b/examples/assets-in-binary/assets.go new file mode 100644 index 00000000..dcc5c465 --- /dev/null +++ b/examples/assets-in-binary/assets.go @@ -0,0 +1,34 @@ +package main + +import ( + "time" + + "github.com/jessevdk/go-assets" +) + +var _Assetsbfa8d115ce0617d89507412d5393a462f8e9b003 = "\n\n

Can you see this? → {{.Bar}}

\n\n" +var _Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2 = "\n\n

Hello, {{.Foo}}

\n\n" + +// Assets returns go-assets FileSystem +var Assets = assets.NewFileSystem(map[string][]string{"/": {"html"}, "/html": {"bar.tmpl", "index.tmpl"}}, map[string]*assets.File{ + "/": { + Path: "/", + FileMode: 0x800001ed, + Mtime: time.Unix(1524365738, 1524365738517125470), + Data: nil, + }, "/html": { + Path: "/html", + FileMode: 0x800001ed, + Mtime: time.Unix(1524365491, 1524365491289799093), + Data: nil, + }, "/html/bar.tmpl": { + Path: "/html/bar.tmpl", + FileMode: 0x1a4, + Mtime: time.Unix(1524365491, 1524365491289611557), + Data: []byte(_Assetsbfa8d115ce0617d89507412d5393a462f8e9b003), + }, "/html/index.tmpl": { + Path: "/html/index.tmpl", + FileMode: 0x1a4, + Mtime: time.Unix(1524365491, 1524365491289995821), + Data: []byte(_Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2), + }}, "") diff --git a/examples/assets-in-binary/html/bar.tmpl b/examples/assets-in-binary/html/bar.tmpl new file mode 100644 index 00000000..c8e1c0ff --- /dev/null +++ b/examples/assets-in-binary/html/bar.tmpl @@ -0,0 +1,4 @@ + + +

Can you see this? → {{.Bar}}

+ diff --git a/examples/assets-in-binary/html/index.tmpl b/examples/assets-in-binary/html/index.tmpl new file mode 100644 index 00000000..6904fd58 --- /dev/null +++ b/examples/assets-in-binary/html/index.tmpl @@ -0,0 +1,4 @@ + + +

Hello, {{.Foo}}

+ diff --git a/examples/assets-in-binary/main.go b/examples/assets-in-binary/main.go new file mode 100644 index 00000000..27bc3b17 --- /dev/null +++ b/examples/assets-in-binary/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "html/template" + "io/ioutil" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.New() + t, err := loadTemplate() + if err != nil { + panic(err) + } + r.SetHTMLTemplate(t) + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/index.tmpl", gin.H{ + "Foo": "World", + }) + }) + r.GET("/bar", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/bar.tmpl", gin.H{ + "Bar": "World", + }) + }) + r.Run(":8080") +} + +func loadTemplate() (*template.Template, error) { + t := template.New("") + for name, file := range Assets.Files { + if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { + continue + } + h, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + t, err = t.New(name).Parse(string(h)) + if err != nil { + return nil, err + } + } + return t, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 573abe24..5ace49df 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -33,6 +33,12 @@ "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, + { + "checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=", + "path": "github.com/jessevdk/go-assets", + "revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5", + "revisionTime": "2016-09-21T14:41:39Z" + }, { "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", From 41f951e0cd126aa45a5cae1dc5c0d26f7cf80561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 25 Apr 2018 16:24:03 +0800 Subject: [PATCH 016/111] support default value for form (#1138) * support default value for form * fix bug for nil interface * use SplitN and optimization code * add test case * add test cases for form(own default value) * fix invalid code * fix code indent * assert order --- binding/binding_test.go | 52 +++++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 17 +++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index b239cafb..ac826417 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -29,6 +29,11 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooDefaultBarStruct struct { + FooStruct + Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` +} + type FooStructUseNumber struct { Foo interface{} `json:"foo" binding:"required"` } @@ -195,6 +200,18 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingFormDefaultValue(t *testing.T) { + testFormBindingDefaultValue(t, "POST", + "/", "/", + "foo=bar", "bar2=foo") +} + +func TestBindingFormDefaultValue2(t *testing.T) { + testFormBindingDefaultValue(t, "GET", + "/?foo=bar", "/?bar2=foo", + "", "") +} + func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, "POST", "/", "/", @@ -407,6 +424,12 @@ func createFormPostRequest() *http.Request { return req } +func createDefaultFormPostRequest() *http.Request { + req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) + req.Header.Set("Content-Type", MIMEPOSTForm) + return req +} + func createFormPostRequestFail() *http.Request { req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) req.Header.Set("Content-Type", MIMEPOSTForm) @@ -450,6 +473,15 @@ func TestBindingFormPost(t *testing.T) { assert.Equal(t, "foo", obj.Bar) } +func TestBindingDefaultValueFormPost(t *testing.T) { + req := createDefaultFormPostRequest() + var obj FooDefaultBarStruct + FormPost.Bind(req, &obj) + + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "hello", obj.Bar) +} + func TestBindingFormPostFail(t *testing.T) { req := createFormPostRequestFail() var obj FooStructForMapType @@ -578,6 +610,26 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Error(t, err) } +func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := FooDefaultBarStruct{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "hello", obj.Bar) + + obj = FooDefaultBarStruct{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + func TestFormBindingFail(t *testing.T) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index dd8c6246..7c680d99 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -8,6 +8,7 @@ import ( "errors" "reflect" "strconv" + "strings" "time" ) @@ -23,6 +24,15 @@ func mapForm(ptr interface{}, form map[string][]string) error { structFieldKind := structField.Kind() inputFieldName := typeField.Tag.Get("form") + inputFieldNameList := strings.Split(inputFieldName, ",") + inputFieldName = inputFieldNameList[0] + var defaultValue string + if len(inputFieldNameList) > 1 { + defaultList := strings.SplitN(inputFieldNameList[1], "=", 2) + if defaultList[0] == "default" { + defaultValue = defaultList[1] + } + } if inputFieldName == "" { inputFieldName = typeField.Name @@ -38,8 +48,13 @@ func mapForm(ptr interface{}, form map[string][]string) error { } } inputValue, exists := form[inputFieldName] + if !exists { - continue + if defaultValue == "" { + continue + } + inputValue = make([]string, 1) + inputValue[0] = defaultValue } numElems := len(inputValue) From 8c2401829041c89ddd6d89a0820acdd19a53855e Mon Sep 17 00:00:00 2001 From: senhtry Date: Thu, 26 Apr 2018 11:52:19 +0800 Subject: [PATCH 017/111] Add Jsonp Support to Context (#1333) --- README.md | 23 +++++++++++++++++++++++ context.go | 7 +++++++ context_test.go | 14 ++++++++++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 37 +++++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+) mode change 100644 => 100755 context.go mode change 100644 => 100755 render/json.go mode change 100644 => 100755 render/render.go mode change 100644 => 100755 render/render_test.go diff --git a/README.md b/README.md index 6c8988e7..aba9ef31 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) + - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) - [HTML rendering](#html-rendering) - [Multitemplate](#multitemplate) @@ -861,6 +862,28 @@ func main() { r.Run(":8080") } ``` +#### JSONP + +Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. + +```go +func main() { + r := gin.Default() + + r.GET("/JSONP?callback=x", func(c *gin.Context) { + data := map[string]interface{}{ + "foo": "bar", + } + + //callback is x + // Will output : x({\"foo\":\"bar\"}) + c.JSONP(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` ### Serving static files diff --git a/context.go b/context.go old mode 100644 new mode 100755 index 90d4c6e5..be205f45 --- a/context.go +++ b/context.go @@ -670,6 +670,13 @@ func (c *Context) SecureJSON(code int, obj interface{}) { c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) } +// JSONP serializes the given struct as JSON into the response body. +// It add padding to response body to request data from a server residing in a different domain than the client. +// It also sets the Content-Type as "application/javascript". +func (c *Context) JSONP(code int, obj interface{}) { + c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) +} + // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { diff --git a/context_test.go b/context_test.go index 9024cfc1..841d8af0 100644 --- a/context_test.go +++ b/context_test.go @@ -581,6 +581,20 @@ func TestContextRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that the response is serialized as JSONP +// and Content-Type is set to application/javascript +func TestContextRenderJSONP(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) + + c.JSONP(201, H{"foo": "bar"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() diff --git a/render/json.go b/render/json.go old mode 100644 new mode 100755 index eb2548e2..3a2e8b2f --- a/render/json.go +++ b/render/json.go @@ -6,6 +6,7 @@ package render import ( "bytes" + "html/template" "net/http" "github.com/gin-gonic/gin/json" @@ -24,9 +25,15 @@ type SecureJSON struct { Data interface{} } +type JsonpJSON struct { + Callback string + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} +var jsonpContentType = []string{"application/javascript; charset=utf-8"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -80,3 +87,28 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } + +func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + if r.Callback == "" { + w.Write(ret) + return nil + } + + callback := template.JSEscapeString(r.Callback) + w.Write([]byte(callback)) + w.Write([]byte("(")) + w.Write(ret) + w.Write([]byte(")")) + + return nil +} + +func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonpContentType) +} diff --git a/render/render.go b/render/render.go old mode 100644 new mode 100755 index 71852364..578f2784 --- a/render/render.go +++ b/render/render.go @@ -15,6 +15,7 @@ var ( _ Render = JSON{} _ Render = IndentedJSON{} _ Render = SecureJSON{} + _ Render = JsonpJSON{} _ Render = XML{} _ Render = String{} _ Render = Redirect{} diff --git a/render/render_test.go b/render/render_test.go old mode 100644 new mode 100755 index d825d041..c7b51ef4 --- a/render/render_test.go +++ b/render/render_test.go @@ -128,6 +128,43 @@ func TestRenderSecureJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderJsonpJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + (JsonpJSON{"x", data}).WriteContentType(w1) + assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + + err1 := (JsonpJSON{"x", data}).Render(w1) + + assert.NoError(t, err1) + assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + + w2 := httptest.NewRecorder() + datas := []map[string]interface{}{{ + "foo": "bar", + }, { + "bar": "foo", + }} + + err2 := (JsonpJSON{"x", datas}).Render(w2) + assert.NoError(t, err2) + assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) +} + +func TestRenderJsonpJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (JsonpJSON{"x", data}).Render(w) + assert.Error(t, err) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 2282be059be78f9f7f70a72ca4dd3bcfe5f972ee Mon Sep 17 00:00:00 2001 From: Alexander Lokhman Date: Thu, 26 Apr 2018 15:09:34 +0100 Subject: [PATCH 018/111] Add support of pointers in form binding (#1336) * Add support of pointers in form binding * Add tests for pointer form binding --- binding/binding_test.go | 38 ++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 6 ++++++ 2 files changed, 44 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index ac826417..e29e6dfe 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -144,6 +144,15 @@ type FooBarStructForFloat64Type struct { Float64Bar float64 `form:"float64_bar" binding:"required"` } +type FooStructForStringPtrType struct { + PtrFoo *string `form:"ptr_foo"` + PtrBar *string `form:"ptr_bar" binding:"required"` +} + +type FooStructForMapPtrType struct { + PtrBar *map[string]interface{} `form:"ptr_bar"` +} + func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("GET", "")) assert.Equal(t, Form, Default("GET", MIMEJSON)) @@ -378,6 +387,14 @@ func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "GET", "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", "", "", "Float64") + + testFormBindingForType(t, "POST", + "/", "/", + "ptr_bar=test", "bar2=test", "Ptr") + + testFormBindingForType(t, "GET", + "/?ptr_bar=test", "/?bar2=test", + "", "", "Ptr") } func TestBindingQuery(t *testing.T) { @@ -944,6 +961,27 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooStructForSliceMapType{} err := b.Bind(req, &obj) assert.Error(t, err) + case "Ptr": + obj := FooStructForStringPtrType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Nil(t, obj.PtrFoo) + assert.Equal(t, "test", *obj.PtrBar) + + obj = FooStructForStringPtrType{} + obj.PtrBar = new(string) + err = b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "test", *obj.PtrBar) + + objErr := FooStructForMapPtrType{} + err = b.Bind(req, &objErr) + assert.Error(t, err) + + obj = FooStructForStringPtrType{} + req = requestWithBody(method, badPath, badBody) + err = b.Bind(req, &obj) + assert.Error(t, err) } } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 7c680d99..7d5c1022 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -112,6 +112,12 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V return setFloatField(val, 64, structField) case reflect.String: structField.SetString(val) + case reflect.Ptr: + if !structField.Elem().IsValid() { + structField.Set(reflect.New(structField.Type().Elem())) + } + structFieldElem := structField.Elem() + return setWithProperType(structFieldElem.Kind(), val, structFieldElem) default: return errors.New("Unknown type") } From bd4f73af679e7d645f6d0277258fa360eda96f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 1 May 2018 14:24:18 +0800 Subject: [PATCH 019/111] support struct pointer (#1342) * support struct pointer * add readme --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++ binding/binding_test.go | 50 ++++++++++++++++++++++ binding/form_mapping.go | 9 +++- 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aba9ef31..cff9bc8a 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Run multiple service using Gin](#run-multiple-service-using-gin) - [Graceful restart or stop](#graceful-restart-or-stop) - [Build a single binary with templates](#build-a-single-binary-with-templates) + - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Testing](#testing) - [Users](#users--) @@ -1461,6 +1462,98 @@ func loadTemplate() (*template.Template, error) { See a complete example in the `examples/assets-in-binary` directory. +### Bind form-data request with custom struct + +The follow example using custom struct: + +```go +type StructA struct { + FieldA string `form:"field_a"` +} + +type StructB struct { + NestedStruct StructA + FieldB string `form:"field_b"` +} + +type StructC struct { + NestedStructPointer *StructA + FieldC string `form:"field_c"` +} + +type StructD struct { + NestedAnonyStruct struct { + FieldX string `form:"field_x"` + } + FieldD string `form:"field_d"` +} + +func GetDataB(c *gin.Context) { + var b StructB + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStruct, + "b": b.FieldB, + }) +} + +func GetDataC(c *gin.Context) { + var b StructC + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStructPointer, + "c": b.FieldC, + }) +} + +func GetDataD(c *gin.Context) { + var b StructD + c.Bind(&b) + c.JSON(200, gin.H{ + "x": b.NestedAnonyStruct, + "d": b.FieldD, + }) +} + +func main() { + r := gin.Default() + r.GET("/getb", GetDataB) + r.GET("/getc", GetDataC) + r.GET("/getd", GetDataD) + + r.Run() +} +``` + +Using the command `curl` command result: + +``` +$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" +{"a":{"FieldA":"hello"},"b":"world"} +$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" +{"a":{"FieldA":"hello"},"c":"world"} +$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" +{"d":"world","x":{"FieldX":"hello"}} +``` + +**NOTE**: NOT support the follow style struct: + +```go +type StructX struct { + X struct {} `form:"name_x"` // HERE have form +} + +type StructY struct { + Y StructX `form:"name_y"` // HERE hava form +} + +type StructZ struct { + Z *StructZ `form:"name_z"` // HERE hava form +} +``` + +In a word, only support nested custom struct which have no `form` now. + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/binding/binding_test.go b/binding/binding_test.go index e29e6dfe..d6899dbb 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -74,6 +74,18 @@ type FooStructForSliceType struct { SliceFoo []int `form:"slice_foo"` } +type FooStructForStructType struct { + StructFoo struct { + Idx int `form:"idx"` + } +} + +type FooStructForStructPointerType struct { + StructPointerFoo *struct { + Name string `form:"name"` + } +} + type FooStructForSliceMapType struct { // Unknown type: not support map SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` @@ -395,6 +407,22 @@ func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "GET", "/?ptr_bar=test", "/?bar2=test", "", "", "Ptr") + + testFormBindingForType(t, "POST", + "/", "/", + "idx=123", "id1=1", "Struct") + + testFormBindingForType(t, "GET", + "/?idx=123", "/?id1=1", + "", "", "Struct") + + testFormBindingForType(t, "POST", + "/", "/", + "name=thinkerou", "name1=ou", "StructPointer") + + testFormBindingForType(t, "GET", + "/?name=thinkerou", "/?name1=ou", + "", "", "StructPointer") } func TestBindingQuery(t *testing.T) { @@ -953,6 +981,28 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) + case "Struct": + obj := FooStructForStructType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, + struct { + Idx int "form:\"idx\"" + }(struct { + Idx int "form:\"idx\"" + }{Idx: 123}), + obj.StructFoo) + case "StructPointer": + obj := FooStructForStructPointerType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, + struct { + Name string "form:\"name\"" + }(struct { + Name string "form:\"name\"" + }{Name: "thinkerou"}), + *obj.StructPointerFoo) case "Map": obj := FooStructForMapType{} err := b.Bind(req, &obj) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 7d5c1022..6ff726df 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -36,9 +36,16 @@ func mapForm(ptr interface{}, form map[string][]string) error { if inputFieldName == "" { inputFieldName = typeField.Name - // if "form" tag is nil, we inspect if the field is a struct. + // if "form" tag is nil, we inspect if the field is a struct or struct pointer. // this would not make sense for JSON parsing but it does for a form // since data is flatten + if structFieldKind == reflect.Ptr { + if !structField.Elem().IsValid() { + structField.Set(reflect.New(structField.Type().Elem())) + } + structField = structField.Elem() + structFieldKind = structField.Kind() + } if structFieldKind == reflect.Struct { err := mapForm(structField.Addr().Interface(), form) if err != nil { From 6e09ef03b0d292c0a636edae960694d9ae3ade8d Mon Sep 17 00:00:00 2001 From: Aurelien Regat-Barrel Date: Fri, 11 May 2018 03:57:21 +0200 Subject: [PATCH 020/111] Fix typo in panic() message (extra single quote) (#1352) Also fix the same typo in a comment --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index 20e3704b..b6530665 100644 --- a/tree.go +++ b/tree.go @@ -232,7 +232,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { } else if i == len(path) { // Make node a (in-path) leaf if n.handlers != nil { - panic("handlers are already registered for path ''" + fullPath + "'") + panic("handlers are already registered for path '" + fullPath + "'") } n.handlers = handlers } @@ -247,7 +247,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { var offset int // already handled bytes of the path - // find prefix until first wildcard (beginning with ':'' or '*'') + // find prefix until first wildcard (beginning with ':' or '*') for i, max := 0, len(path); numParams > 0; i++ { c := path[i] if c != ':' && c != '*' { From 995fa8e9ce404a7a8dc293c90ec018367639ffa9 Mon Sep 17 00:00:00 2001 From: JINNOUCHI Yasushi Date: Fri, 11 May 2018 11:33:33 +0900 Subject: [PATCH 021/111] Fix #216: Enable to call binding multiple times in some formats (#1341) * Add interface to read body bytes in binding * Add BindingBody implementation for some binding * Fix to use `BindBodyBytesKey` for key * Revert "Fix to use `BindBodyBytesKey` for key" This reverts commit 2c82901ceab6ae53730a3cfcd9839bee11a08f13. * Use private-like key for body bytes * Add tests for BindingBody & ShouldBindBodyWith * Add note for README * Remove redundant space between sentences --- README.md | 59 ++++++++++++++++++++++++++ binding/binding.go | 7 ++++ binding/binding_body_test.go | 67 ++++++++++++++++++++++++++++++ binding/json.go | 12 +++++- binding/msgpack.go | 13 +++++- binding/protobuf.go | 15 +++---- binding/xml.go | 11 ++++- context.go | 25 +++++++++++ context_test.go | 80 ++++++++++++++++++++++++++++++++++++ 9 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 binding/binding_body_test.go diff --git a/README.md b/README.md index cff9bc8a..70a5a13b 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Graceful restart or stop](#graceful-restart-or-stop) - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) + - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [Testing](#testing) - [Users](#users--) @@ -1554,6 +1555,64 @@ type StructZ struct { In a word, only support nested custom struct which have no `form` now. +### Try to bind body into different structs + +The normal methods for binding request body consumes `c.Request.Body` and they +cannot be called multiple times. + +```go +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This c.ShouldBind consumes c.Request.Body and it cannot be reused. + if errA := c.ShouldBind(&objA); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // Always an error is occurred by this because c.Request.Body is EOF now. + } else if errB := c.ShouldBind(&objB); errB == nil { + c.String(http.StatusOK, `the body should be formB`) + } else { + ... + } +} +``` + +For this, you can use `c.ShouldBindBodyWith`. + +```go +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This reads c.Request.Body and stores the result into the context. + if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // At this time, it reuses body stored in the context. + } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { + c.String(http.StatusOK, `the body should be formB JSON`) + // And it can accepts other formats + } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { + c.String(http.StatusOK, `the body should be formB XML`) + } else { + ... + } +} +``` + +* `c.ShouldBindBodyWith` stores body into the context before binding. This has +a slight impact to performance, so you should not use this method if you are +enough to call binding at once. +* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, +`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, +can be called by `c.ShouldBind()` multiple times without any damage to +performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/binding/binding.go b/binding/binding.go index 646eb80a..1a984777 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -29,6 +29,13 @@ type Binding interface { Bind(*http.Request, interface{}) error } +// BindingBody adds BindBody method to Binding. BindBody is similar with Bind, +// but it reads the body from supplied bytes instead of req.Body. +type BindingBody interface { + Binding + BindBody([]byte, interface{}) error +} + // StructValidator is the minimal interface which needs to be implemented in // order for it to be used as the validator engine for ensuring the correctness // of the reqest. Gin provides a default implementation for this using diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go new file mode 100644 index 00000000..c41d9f86 --- /dev/null +++ b/binding/binding_body_test.go @@ -0,0 +1,67 @@ +package binding + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/gin-gonic/gin/binding/example" + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" +) + +func TestBindingBody(t *testing.T) { + for _, tt := range []struct { + name string + binding BindingBody + body string + want string + }{ + { + name: "JSON bidning", + binding: JSON, + body: `{"foo":"FOO"}`, + }, + { + name: "XML bidning", + binding: XML, + body: ` + + FOO +`, + }, + { + name: "MsgPack binding", + binding: MsgPack, + body: msgPackBody(t), + }, + } { + t.Logf("testing: %s", tt.name) + req := requestWithBody("POST", "/", tt.body) + form := FooStruct{} + body, _ := ioutil.ReadAll(req.Body) + assert.NoError(t, tt.binding.BindBody(body, &form)) + assert.Equal(t, FooStruct{"FOO"}, form) + } +} + +func msgPackBody(t *testing.T) string { + test := FooStruct{"FOO"} + h := new(codec.MsgpackHandle) + buf := bytes.NewBuffer(nil) + assert.NoError(t, codec.NewEncoder(buf, h).Encode(test)) + return buf.String() +} + +func TestBindingBodyProto(t *testing.T) { + test := example.Test{ + Label: proto.String("FOO"), + } + data, _ := proto.Marshal(&test) + req := requestWithBody("POST", "/", string(data)) + form := example.Test{} + body, _ := ioutil.ReadAll(req.Body) + assert.NoError(t, ProtoBuf.BindBody(body, &form)) + assert.Equal(t, test, form) +} diff --git a/binding/json.go b/binding/json.go index e928a8c1..fea17bb2 100644 --- a/binding/json.go +++ b/binding/json.go @@ -5,6 +5,8 @@ package binding import ( + "bytes" + "io" "net/http" "github.com/gin-gonic/gin/json" @@ -22,7 +24,15 @@ func (jsonBinding) Name() string { } func (jsonBinding) Bind(req *http.Request, obj interface{}) error { - decoder := json.NewDecoder(req.Body) + return decodeJSON(req.Body, obj) +} + +func (jsonBinding) BindBody(body []byte, obj interface{}) error { + return decodeJSON(bytes.NewReader(body), obj) +} + +func decodeJSON(r io.Reader, obj interface{}) error { + decoder := json.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() } diff --git a/binding/msgpack.go b/binding/msgpack.go index 7faea4b5..b7f73197 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -5,6 +5,8 @@ package binding import ( + "bytes" + "io" "net/http" "github.com/ugorji/go/codec" @@ -17,7 +19,16 @@ func (msgpackBinding) Name() string { } func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { - if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil { + return decodeMsgPack(req.Body, obj) +} + +func (msgpackBinding) BindBody(body []byte, obj interface{}) error { + return decodeMsgPack(bytes.NewReader(body), obj) +} + +func decodeMsgPack(r io.Reader, obj interface{}) error { + cdc := new(codec.MsgpackHandle) + if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { return err } return validate(obj) diff --git a/binding/protobuf.go b/binding/protobuf.go index c7eb84e9..540e9c1f 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -17,19 +17,20 @@ func (protobufBinding) Name() string { return "protobuf" } -func (protobufBinding) Bind(req *http.Request, obj interface{}) error { - +func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { buf, err := ioutil.ReadAll(req.Body) if err != nil { return err } + return b.BindBody(buf, obj) +} - if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil { +func (protobufBinding) BindBody(body []byte, obj interface{}) error { + if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { return err } - - //Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct - //which automatically generate by gen-proto + // Here it's same to return validate(obj), but util now we cann't add + // `binding:""` to the struct which automatically generate by gen-proto return nil - //return validate(obj) + // return validate(obj) } diff --git a/binding/xml.go b/binding/xml.go index f84a6b7f..4e901149 100644 --- a/binding/xml.go +++ b/binding/xml.go @@ -5,7 +5,9 @@ package binding import ( + "bytes" "encoding/xml" + "io" "net/http" ) @@ -16,7 +18,14 @@ func (xmlBinding) Name() string { } func (xmlBinding) Bind(req *http.Request, obj interface{}) error { - decoder := xml.NewDecoder(req.Body) + return decodeXML(req.Body, obj) +} + +func (xmlBinding) BindBody(body []byte, obj interface{}) error { + return decodeXML(bytes.NewReader(body), obj) +} +func decodeXML(r io.Reader, obj interface{}) error { + decoder := xml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { return err } diff --git a/context.go b/context.go index be205f45..9a51e461 100755 --- a/context.go +++ b/context.go @@ -31,6 +31,7 @@ const ( MIMEPlain = binding.MIMEPlain MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm + BodyBytesKey = "_gin-gonic/gin/bodybyteskey" ) const abortIndex int8 = math.MaxInt8 / 2 @@ -508,6 +509,30 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { return b.Bind(c.Request, obj) } +// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request +// body into the context, and reuse when it is called again. +// +// NOTE: This method reads the body before binding. So you should use +// ShouldBindWith for better performance if you need to call only once. +func (c *Context) ShouldBindBodyWith( + obj interface{}, bb binding.BindingBody, +) (err error) { + var body []byte + if cb, ok := c.Get(BodyBytesKey); ok { + if cbb, ok := cb.([]byte); ok { + body = cbb + } + } + if body == nil { + body, err = ioutil.ReadAll(c.Request.Body) + if err != nil { + return err + } + c.Set(BodyBytesKey, body) + } + return bb.BindBody(body, obj) +} + // ClientIP implements a best effort algorithm to return the real client IP, it parses // X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. // Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. diff --git a/context_test.go b/context_test.go index 841d8af0..e94866b6 100644 --- a/context_test.go +++ b/context_test.go @@ -18,6 +18,7 @@ import ( "time" "github.com/gin-contrib/sse" + "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -1334,6 +1335,85 @@ func TestContextBadAutoShouldBind(t *testing.T) { assert.False(t, c.IsAborted()) } +func TestContextShouldBindBodyWith(t *testing.T) { + type typeA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` + } + type typeB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` + } + for _, tt := range []struct { + name string + bindingA, bindingB binding.BindingBody + bodyA, bodyB string + }{ + { + name: "JSON & JSON", + bindingA: binding.JSON, + bindingB: binding.JSON, + bodyA: `{"foo":"FOO"}`, + bodyB: `{"bar":"BAR"}`, + }, + { + name: "JSON & XML", + bindingA: binding.JSON, + bindingB: binding.XML, + bodyA: `{"foo":"FOO"}`, + bodyB: ` + + BAR +`, + }, + { + name: "XML & XML", + bindingA: binding.XML, + bindingB: binding.XML, + bodyA: ` + + FOO +`, + bodyB: ` + + BAR +`, + }, + } { + t.Logf("testing: %s", tt.name) + // bodyA to typeA and typeB + { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest( + "POST", "http://example.com", bytes.NewBufferString(tt.bodyA), + ) + // When it binds to typeA and typeB, it finds the body is + // not typeB but typeA. + objA := typeA{} + assert.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + assert.Equal(t, typeA{"FOO"}, objA) + objB := typeB{} + assert.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + assert.NotEqual(t, typeB{"BAR"}, objB) + } + // bodyB to typeA and typeB + { + // When it binds to typeA and typeB, it finds the body is + // not typeA but typeB. + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest( + "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), + ) + objA := typeA{} + assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + assert.NotEqual(t, typeA{"FOO"}, objA) + objB := typeB{} + assert.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + assert.Equal(t, typeB{"BAR"}, objB) + } + } +} + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From 5636afe02d00308bc5ade9dd80acfa7646b608df Mon Sep 17 00:00:00 2001 From: chainhelen Date: Fri, 11 May 2018 22:40:33 +0800 Subject: [PATCH 022/111] fix bug, return err when failed binding bool (#1350) * fix bug, return err when failed binding bool * add test, return err when failed binding bool --- binding/binding_test.go | 23 +++++++++++++++++++++++ binding/form_mapping.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index d6899dbb..936deac7 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -91,6 +91,10 @@ type FooStructForSliceMapType struct { SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` } +type FooStructForBoolType struct { + BoolFoo bool `form:"bool_foo"` +} + type FooBarStructForIntType struct { IntFoo int `form:"int_foo"` IntBar int `form:"int_bar" binding:"required"` @@ -449,6 +453,12 @@ func TestBindingQueryFail2(t *testing.T) { "map_foo=unused", "") } +func TestBindingQueryBoolFail(t *testing.T) { + testQueryBindingBoolFail(t, "GET", + "/?bool_foo=fasl", "/?bar2=foo", + "bool_foo=unused", "") +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -1063,6 +1073,19 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str assert.Error(t, err) } +func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) { + b := Query + assert.Equal(t, "query", b.Name()) + + obj := FooStructForBoolType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) +} + func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 6ff726df..3f6b9bfa 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -161,7 +161,7 @@ func setBoolField(val string, field reflect.Value) error { if err == nil { field.SetBool(boolVal) } - return nil + return err } func setFloatField(val string, bitSize int, field reflect.Value) error { From bf7803815b0baa22ff7a10457932882dfbf09925 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Lebreton Date: Sat, 12 May 2018 05:00:42 +0200 Subject: [PATCH 023/111] Serve easily dynamic files with `DataFromReader` context method (#1304) * Add DataFromReader context method * Replace fmt by strconv.FormatInt * Add pull request link to README --- README.md | 27 +++++++++++++++++++++++++++ context.go | 10 ++++++++++ context_test.go | 19 +++++++++++++++++++ render/reader.go | 36 ++++++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 23 +++++++++++++++++++++++ 6 files changed, 116 insertions(+) create mode 100644 render/reader.go diff --git a/README.md b/README.md index 70a5a13b..51a807b5 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) + - [Serving data from reader](#serving-data-from-reader) - [HTML rendering](#html-rendering) - [Multitemplate](#multitemplate) - [Redirects](#redirects) @@ -901,6 +902,32 @@ func main() { } ``` +### Serving data from reader + +```go +func main() { + router := gin.Default() + router.GET("/someDataFromReader", func(c *gin.Context) { + response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") + if err != nil || response.StatusCode != http.StatusOK { + c.Status(http.StatusServiceUnavailable) + return + } + + reader := response.Body + contentLength := response.ContentLength + contentType := response.Header.Get("Content-Type") + + extraHeaders := map[string]string{ + "Content-Disposition": `attachment; filename="gopher.png"`, + } + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + }) + router.Run(":8080") +} +``` + ### HTML rendering Using LoadHTMLGlob() or LoadHTMLFiles() diff --git a/context.go b/context.go index 9a51e461..462357e1 100755 --- a/context.go +++ b/context.go @@ -741,6 +741,16 @@ func (c *Context) Data(code int, contentType string, data []byte) { }) } +// DataFromReader writes the specified reader into the body stream and updates the HTTP code. +func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) { + c.Render(code, render.Reader{ + Headers: extraHeaders, + ContentType: contentType, + ContentLength: contentLength, + Reader: reader, + }) +} + // File writes the specified file into the body stream in a efficient way. func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) diff --git a/context_test.go b/context_test.go index e94866b6..12e02fa0 100644 --- a/context_test.go +++ b/context_test.go @@ -1471,3 +1471,22 @@ func TestContextGetRawData(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "Fetch binary post data", string(data)) } + +func TestContextRenderDataFromReader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + body := "#!PNG some raw data" + reader := strings.NewReader(body) + contentLength := int64(len(body)) + contentType := "image/png" + extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`} + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, contentType, w.HeaderMap.Get("Content-Type")) + assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) + assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) +} diff --git a/render/reader.go b/render/reader.go new file mode 100644 index 00000000..be2132c8 --- /dev/null +++ b/render/reader.go @@ -0,0 +1,36 @@ +package render + +import ( + "io" + "net/http" + "strconv" +) + +type Reader struct { + ContentType string + ContentLength int64 + Reader io.Reader + Headers map[string]string +} + +// Render (Reader) writes data with custom ContentType and headers. +func (r Reader) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) + r.writeHeaders(w, r.Headers) + _, err = io.Copy(w, r.Reader) + return +} + +func (r Reader) WriteContentType(w http.ResponseWriter) { + writeContentType(w, []string{r.ContentType}) +} + +func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { + header := w.Header() + for k, v := range headers { + if val := header[k]; len(val) == 0 { + header[k] = []string{v} + } + } +} diff --git a/render/render.go b/render/render.go index 578f2784..7caf9bba 100755 --- a/render/render.go +++ b/render/render.go @@ -25,6 +25,7 @@ var ( _ HTMLRender = HTMLProduction{} _ Render = YAML{} _ Render = MsgPack{} + _ Render = Reader{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index c7b51ef4..40ec806e 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -11,6 +11,8 @@ import ( "html/template" "net/http" "net/http/httptest" + "strconv" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -384,3 +386,24 @@ func TestRenderHTMLDebugPanics(t *testing.T) { } assert.Panics(t, func() { htmlRender.Instance("", nil) }) } + +func TestRenderReader(t *testing.T) { + w := httptest.NewRecorder() + + body := "#!PNG some raw data" + headers := make(map[string]string) + headers["Content-Disposition"] = `attachment; filename="filename.png"` + + err := (Reader{ + ContentLength: int64(len(body)), + ContentType: "image/png", + Reader: strings.NewReader(body), + Headers: headers, + }).Render(w) + + assert.NoError(t, err) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, "image/png", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length")) + assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) +} From 07cbe116a058f13026fa6b23f675155968d47cc3 Mon Sep 17 00:00:00 2001 From: chainhelen Date: Wed, 30 May 2018 09:19:04 +0800 Subject: [PATCH 024/111] Add and fix the explanation of `HandleContext` (#1371) Reference this issue #1323 1. There isn't any eg about `HandleContext` 2. The `c.Request.Path` of `HandleContext` Comment is not right Based on the above two points, I pull this request. If you think it's unnecessary, I will close this. Thx. --- README.md | 16 ++++++++++++++-- gin.go | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51a807b5..fd5c1755 100644 --- a/README.md +++ b/README.md @@ -1082,14 +1082,26 @@ Gin allow by default use only one html.Template. Check [a multitemplate render]( ### Redirects -Issuing a HTTP redirect is easy: +Issuing a HTTP redirect is easy. Both internal and external locations are supported. ```go r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") }) ``` -Both internal and external locations are supported. + + +Issuing a Router redirect, use `HandleContext` like below. + +``` go +r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) +}) +r.GET("/test2", func(c *gin.Context) { + c.JSON(200, gin.H{"hello": "world"}) +}) +``` ### Custom Middleware diff --git a/gin.go b/gin.go index 4205eff0..b91fc2fa 100644 --- a/gin.go +++ b/gin.go @@ -329,7 +329,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // HandleContext re-enter a context that has been rewritten. -// This can be done by setting c.Request.Path to your new target. +// This can be done by setting c.Request.URL.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { c.reset() From c2f083fc953073a5cfdcdd386fb3c7d1f004c2cc Mon Sep 17 00:00:00 2001 From: MW Lim Date: Thu, 31 May 2018 11:41:45 +0800 Subject: [PATCH 025/111] minor typo in routergroup.go (#1360) --- routergroup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 5e681c1c..53045d10 100644 --- a/routergroup.go +++ b/routergroup.go @@ -139,7 +139,7 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou return group.returnObj() } -// StaticFile registers a single route in order to server a single file of the local filesystem. +// StaticFile registers a single route in order to serve a single file of the local filesystem. // router.StaticFile("favicon.ico", "./resources/favicon.ico") func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { From caf3e350a548af1add9def68087ac53d1d000caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 31 May 2018 14:13:40 +0800 Subject: [PATCH 026/111] doc: update readme for adding binding about skip validate (#1359) * update readme for adding binding about skip validate * update readme for adding binding about skip validate --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fd5c1755..0bac65b2 100644 --- a/README.md +++ b/README.md @@ -572,6 +572,10 @@ $ curl -v -X POST \ {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} ``` +**Skip validate** + +When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. + ### Custom Validators It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). From 87d536c00120e6631cd7e6ff100739858e07e002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Jun 2018 09:31:43 +0800 Subject: [PATCH 027/111] utils: use strings.Split instead of strings.IndexByte (#1400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And I test them benchmark: code: ```go # stringsbench.go package stringsbench import "strings" func index(part string) string { if index := strings.IndexByte(part, ';'); index >= 0 { if part := strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { return part[0:index] } } return "" } func split(part string) string { return strings.Split(part, ";")[0] } ``` ```go # stringsbench_test.go package stringsbench import ( "testing" ) func BenchmarkIndex(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { index("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") } }) } func BenchmarkSplit(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { split("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") } }) } ``` And the result: ```shell ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 46.1 ns/op BenchmarkSplit-8 50000000 35.9 ns/op PASS ok _/Users/tianou/strings 3.271s ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 44.2 ns/op BenchmarkSplit-8 50000000 34.7 ns/op PASS ok _/Users/tianou/strings 3.156s ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 45.6 ns/op BenchmarkSplit-8 50000000 35.3 ns/op PASS ok _/Users/tianou/strings 3.230s ``` --- utils.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/utils.go b/utils.go index 278029d7..bf32c775 100644 --- a/utils.go +++ b/utils.go @@ -103,10 +103,7 @@ func parseAccept(acceptHeader string) []string { parts := strings.Split(acceptHeader, ",") out := make([]string, 0, len(parts)) for _, part := range parts { - if index := strings.IndexByte(part, ';'); index >= 0 { - part = part[0:index] - } - if part = strings.TrimSpace(part); part != "" { + if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { out = append(out, part) } } From bf85b32c1d11dcfa2202daa6d1ad2addae2d1619 Mon Sep 17 00:00:00 2001 From: htobenothing Date: Thu, 21 Jun 2018 09:53:52 +0800 Subject: [PATCH 028/111] Add Pusher() function for support http2 server push (#1273) gin already support http2, while previously not support server push. Add Pusher() function to extend the ResponseWriter interface. ```golang // get http.Pusher if pusher := c.Writer.Pusher(); pusher != nil { // use pusher.Push() to do server push } ``` screen shot 2018-03-07 at 11 20 49 pm --- README.md | 50 ++++++++++++++++++++++++ examples/http-pusher/assets/app.js | 1 + examples/http-pusher/main.go | 41 +++++++++++++++++++ examples/http-pusher/testdata/ca.pem | 15 +++++++ examples/http-pusher/testdata/server.key | 16 ++++++++ examples/http-pusher/testdata/server.pem | 16 ++++++++ response_writer.go | 2 +- response_writer_1.7.go | 12 ++++++ response_writer_1.8.go | 25 ++++++++++++ 9 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 examples/http-pusher/assets/app.js create mode 100644 examples/http-pusher/main.go create mode 100644 examples/http-pusher/testdata/ca.pem create mode 100644 examples/http-pusher/testdata/server.key create mode 100644 examples/http-pusher/testdata/server.pem create mode 100644 response_writer_1.7.go create mode 100644 response_writer_1.8.go diff --git a/README.md b/README.md index 0bac65b2..c2917a5f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [http2 server push](#http2-server-push) - [Testing](#testing) - [Users](#users--) @@ -1656,6 +1657,55 @@ enough to call binding at once. can be called by `c.ShouldBind()` multiple times without any damage to performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). +### http2 server push + +http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. + +[embedmd]:# (examples/http-pusher/main.go go) +```go +package main + +import ( + "html/template" + "log" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} +``` + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/examples/http-pusher/assets/app.js b/examples/http-pusher/assets/app.js new file mode 100644 index 00000000..05271b67 --- /dev/null +++ b/examples/http-pusher/assets/app.js @@ -0,0 +1 @@ +console.log("http2 pusher"); diff --git a/examples/http-pusher/main.go b/examples/http-pusher/main.go new file mode 100644 index 00000000..d4f33aa0 --- /dev/null +++ b/examples/http-pusher/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "html/template" + "log" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} diff --git a/examples/http-pusher/testdata/ca.pem b/examples/http-pusher/testdata/ca.pem new file mode 100644 index 00000000..6c8511a7 --- /dev/null +++ b/examples/http-pusher/testdata/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/examples/http-pusher/testdata/server.key b/examples/http-pusher/testdata/server.key new file mode 100644 index 00000000..143a5b87 --- /dev/null +++ b/examples/http-pusher/testdata/server.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD +M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf +3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY +AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm +V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY +tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p +dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q +K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR +81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff +DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd +aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 +ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 +XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe +F98XJ7tIFfJq +-----END PRIVATE KEY----- diff --git a/examples/http-pusher/testdata/server.pem b/examples/http-pusher/testdata/server.pem new file mode 100644 index 00000000..f3d43fcc --- /dev/null +++ b/examples/http-pusher/testdata/server.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET +MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx +MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV +BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 +ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco +LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg +zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd +9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw +CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy +em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G +CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 +hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh +y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 +-----END CERTIFICATE----- diff --git a/response_writer.go b/response_writer.go index 232f00aa..14b1cfc3 100644 --- a/response_writer.go +++ b/response_writer.go @@ -16,7 +16,7 @@ const ( defaultStatus = 200 ) -type ResponseWriter interface { +type responseWriterBase interface { http.ResponseWriter http.Hijacker http.Flusher diff --git a/response_writer_1.7.go b/response_writer_1.7.go new file mode 100644 index 00000000..801d196b --- /dev/null +++ b/response_writer_1.7.go @@ -0,0 +1,12 @@ +// +build !go1.8 + +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +// ResponseWriter ... +type ResponseWriter interface { + responseWriterBase +} diff --git a/response_writer_1.8.go b/response_writer_1.8.go new file mode 100644 index 00000000..527c0038 --- /dev/null +++ b/response_writer_1.8.go @@ -0,0 +1,25 @@ +// +build go1.8 + +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "net/http" +) + +// ResponseWriter ... +type ResponseWriter interface { + responseWriterBase + // get the http.Pusher for server push + Pusher() http.Pusher +} + +func (w *responseWriter) Pusher() (pusher http.Pusher) { + if pusher, ok := w.ResponseWriter.(http.Pusher); ok { + return pusher + } + return nil +} From 737d2fb7ab4ad358c5fa01b7a08ce346dcaebd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 22 Jun 2018 09:51:06 +0800 Subject: [PATCH 029/111] add grpc example (#1401) use grpc helloworld example. --- Makefile | 3 +- coverage.sh | 2 +- examples/grpc/README.md | 19 ++++ examples/grpc/gin/main.go | 46 +++++++++ examples/grpc/grpc/server.go | 34 +++++++ examples/grpc/pb/helloworld.pb.go | 151 ++++++++++++++++++++++++++++++ examples/grpc/pb/helloworld.proto | 37 ++++++++ 7 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 examples/grpc/README.md create mode 100644 examples/grpc/gin/main.go create mode 100644 examples/grpc/grpc/server.go create mode 100644 examples/grpc/pb/helloworld.pb.go create mode 100644 examples/grpc/pb/helloworld.proto diff --git a/Makefile b/Makefile index 5468563a..51b9969f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ GOFMT ?= gofmt "-s" PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") all: install @@ -26,7 +27,7 @@ fmt-check: fi; vet: - go vet $(PACKAGES) + go vet $(VETPACKAGES) deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ diff --git a/coverage.sh b/coverage.sh index 81437f91..4d1ee036 100644 --- a/coverage.sh +++ b/coverage.sh @@ -4,7 +4,7 @@ set -e echo "mode: count" > coverage.out -for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do +for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do go test -v -covermode=count -coverprofile=profile.out $d if [ -f profile.out ]; then cat profile.out | grep -v "mode:" >> coverage.out diff --git a/examples/grpc/README.md b/examples/grpc/README.md new file mode 100644 index 00000000..a96d3c1c --- /dev/null +++ b/examples/grpc/README.md @@ -0,0 +1,19 @@ +## How to run this example + +1. run grpc server + +```sh +$ go run grpc/server.go +``` + +2. run gin server + +```sh +$ go run gin/main.go +``` + +3. use curl command to test it + +```sh +$ curl -v 'http://localhost:8052/rest/n/thinkerou' +``` diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go new file mode 100644 index 00000000..c21692ae --- /dev/null +++ b/examples/grpc/gin/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/gin-gonic/gin" + pb "github.com/gin-gonic/gin/examples/grpc/pb" + "google.golang.org/grpc" +) + +func main() { + // Set up a connection to the server. + conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + client := pb.NewGreeterClient(conn) + + // Set up a http setver. + r := gin.Default() + r.GET("/rest/n/:name", func(c *gin.Context) { + name := c.Param("name") + + // Contact the server and print out its response. + req := &pb.HelloRequest{Name: name} + res, err := client.SayHello(g, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "result": fmt.Sprint(res.Message), + }) + }) + + // Run http server + if err := r.Run(":8052"); err != nil { + log.Fatalf("could not run server: %v", err) + } +} diff --git a/examples/grpc/grpc/server.go b/examples/grpc/grpc/server.go new file mode 100644 index 00000000..d9bf9fc5 --- /dev/null +++ b/examples/grpc/grpc/server.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + "net" + + pb "github.com/gin-gonic/gin/examples/grpc/pb" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +// server is used to implement helloworld.GreeterServer. +type server struct{} + +// SayHello implements helloworld.GreeterServer +func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + return &pb.HelloReply{Message: "Hello " + in.Name}, nil +} + +func main() { + lis, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + pb.RegisterGreeterServer(s, &server{}) + + // Register reflection service on gRPC server. + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} diff --git a/examples/grpc/pb/helloworld.pb.go b/examples/grpc/pb/helloworld.pb.go new file mode 100644 index 00000000..c8c8942a --- /dev/null +++ b/examples/grpc/pb/helloworld.pb.go @@ -0,0 +1,151 @@ +// Code generated by protoc-gen-go. +// source: helloworld.proto +// DO NOT EDIT! + +/* +Package helloworld is a generated protocol buffer package. + +It is generated from these files: + helloworld.proto + +It has these top-level messages: + HelloRequest + HelloReply +*/ +package helloworld + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// The request message containing the user's name. +type HelloRequest struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +// The response message containing the greetings +type HelloReply struct { + Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` +} + +func (m *HelloReply) Reset() { *m = HelloReply{} } +func (m *HelloReply) String() string { return proto.CompactTextString(m) } +func (*HelloReply) ProtoMessage() {} +func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") + proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Greeter service + +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc *grpc.ClientConn +} + +func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Greeter service + +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) +} + +func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { + s.RegisterService(&_Greeter_serviceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/helloworld.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Greeter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld.proto", +} + +func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 174 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, + 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, + 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, + 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, + 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, + 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, + 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, + 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7, + 0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb, + 0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b, + 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00, +} diff --git a/examples/grpc/pb/helloworld.proto b/examples/grpc/pb/helloworld.proto new file mode 100644 index 00000000..d79a6a0d --- /dev/null +++ b/examples/grpc/pb/helloworld.proto @@ -0,0 +1,37 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} From 605aa1c30f196733a90a4f773b691d2350a30e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 22 Jun 2018 23:44:45 +0800 Subject: [PATCH 030/111] example: fix typo for grpc (#1405) sorry...fixed #1403 --- examples/grpc/gin/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go index c21692ae..edc1ca9b 100644 --- a/examples/grpc/gin/main.go +++ b/examples/grpc/gin/main.go @@ -26,7 +26,7 @@ func main() { // Contact the server and print out its response. req := &pb.HelloRequest{Name: name} - res, err := client.SayHello(g, req) + res, err := client.SayHello(c, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), From 8035359102b5384ad1f860675c85c0238b06b6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 00:08:58 +0800 Subject: [PATCH 031/111] use strings.Split instead of strings.IndexByte (#1406) --- context.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) mode change 100755 => 100644 context.go diff --git a/context.go b/context.go old mode 100755 new mode 100644 index 462357e1..6a84de5e --- a/context.go +++ b/context.go @@ -539,14 +539,10 @@ func (c *Context) ShouldBindBodyWith( func (c *Context) ClientIP() string { if c.engine.ForwardedByClientIP { clientIP := c.requestHeader("X-Forwarded-For") - if index := strings.IndexByte(clientIP, ','); index >= 0 { - clientIP = clientIP[0:index] + clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) + if clientIP == "" { + clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) } - clientIP = strings.TrimSpace(clientIP) - if clientIP != "" { - return clientIP - } - clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) if clientIP != "" { return clientIP } From 760d0574db7432376d91b53dba9146c1f32d28c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 00:45:43 +0800 Subject: [PATCH 032/111] vendor: remove vendor package from example folder (#1402) updated `vendor.json` is ok. but set `ignore test` in `vendor.json`, `x/net/context` package only use in `context_test.go`, I don't know why vendor still need it. please @appleboy review the pull request, thanks a lot. --- vendor/vendor.json | 61 ---------------------------------------------- 1 file changed, 61 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 5ace49df..c34c2de3 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,61 +9,30 @@ "revision": "346938d642f2ec3594ed81d874461961cd0faa76", "revisionTime": "2016-10-29T20:57:26Z" }, - { - "checksumSHA1": "7c3FuEadBInl/4ExSrB7iJMXpe4=", - "path": "github.com/dustin/go-broadcast", - "revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1", - "revisionTime": "2014-06-27T04:00:55Z" - }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "path": "github.com/gin-contrib/sse", "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", "revisionTime": "2017-01-09T09:34:21Z" }, - { - "checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=", - "path": "github.com/gin-gonic/autotls", - "revision": "8ca25fbde72bb72a00466215b94b489c71fcb815", - "revisionTime": "2017-09-16T16:54:15Z" - }, { "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "path": "github.com/golang/protobuf/proto", "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, - { - "checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=", - "path": "github.com/jessevdk/go-assets", - "revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5", - "revisionTime": "2016-09-21T14:41:39Z" - }, { "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", "revision": "36b14963da70d11297d313183d7e6388c8510e1e", "revisionTime": "2017-08-29T15:58:51Z" }, - { - "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", - "path": "github.com/manucorporat/stats", - "revision": "8f2d6ace262eba462e9beb552382c98be51d807b", - "revisionTime": "2015-05-31T20:46:25Z" - }, { "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", "path": "github.com/mattn/go-isatty", "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", "revisionTime": "2017-03-07T16:30:44Z" }, - { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", - "comment": "v1.0.0", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "792786c7400a136282c1664665ae0a8db921c6c2", - "revisionTime": "2016-01-10T10:55:54Z" - }, { "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", "comment": "v1.1.4", @@ -71,30 +40,12 @@ "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, - { - "checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=", - "path": "github.com/thinkerou/favicon", - "revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93", - "revisionTime": "2017-07-10T14:05:20Z" - }, { "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "path": "github.com/ugorji/go/codec", "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revisionTime": "2017-02-15T20:11:44Z" }, - { - "checksumSHA1": "W0j4I7QpxXlChjyhAojZmFjU6Bg=", - "path": "golang.org/x/crypto/acme", - "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", - "revisionTime": "2017-06-19T06:03:41Z" - }, - { - "checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=", - "path": "golang.org/x/crypto/acme/autocert", - "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", - "revisionTime": "2017-06-19T06:03:41Z" - }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7", @@ -102,18 +53,6 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, - { - "checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=", - "path": "golang.org/x/sync/errgroup", - "revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690", - "revisionTime": "2017-07-19T03:38:01Z" - }, - { - "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=", - "path": "golang.org/x/sys/unix", - "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", - "revisionTime": "2017-03-08T15:04:45Z" - }, { "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", "comment": "v8.18.1", From 1f59bad84b577735b27a1ee855f01ed2f50cd88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 11:06:27 +0800 Subject: [PATCH 033/111] add an edge case from httprouter (#1407) --- path.go | 2 +- path_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/path.go b/path.go index ed63ad1a..1c2b8498 100644 --- a/path.go +++ b/path.go @@ -41,7 +41,7 @@ func cleanPath(p string) string { buf[0] = '/' } - trailing := n > 2 && p[n-1] == '/' + trailing := n > 1 && p[n-1] == '/' // A bit more clunky without a 'lazybuf' like the path package, but the loop // gets completely inlined (bufApp). So in contrast to the path package this diff --git a/path_test.go b/path_test.go index 4a6d945b..c1e6ed4f 100644 --- a/path_test.go +++ b/path_test.go @@ -24,6 +24,7 @@ var cleanTests = []struct { // missing root {"", "/"}, + {"a/", "/a/"}, {"abc", "/abc"}, {"abc/def", "/abc/def"}, {"a/b/c", "/a/b/c"}, From 6c6d97ba2e7eeaf78e20fc4344b21c4e057e2a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Jun 2018 17:21:32 +0800 Subject: [PATCH 034/111] remove hardcode instead of http status value (#1411) --- auth.go | 3 ++- context.go | 6 +++--- gin.go | 12 ++++++------ recovery.go | 3 ++- response_writer.go | 2 +- routergroup.go | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/auth.go b/auth.go index c2143091..9ed81b5d 100644 --- a/auth.go +++ b/auth.go @@ -7,6 +7,7 @@ package gin import ( "crypto/subtle" "encoding/base64" + "net/http" "strconv" ) @@ -51,7 +52,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { if !found { // Credentials doesn't match, we return 401 and abort handlers chain. c.Header("WWW-Authenticate", realm) - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) return } diff --git a/context.go b/context.go index 6a84de5e..65fbb629 100644 --- a/context.go +++ b/context.go @@ -474,7 +474,7 @@ func (c *Context) BindQuery(obj interface{}) error { // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { if err = c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(400, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) } return @@ -589,9 +589,9 @@ func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: return false - case status == 204: + case status == http.StatusNoContent: return false - case status == 304: + case status == http.StatusNotModified: return false } return true diff --git a/gin.go b/gin.go index b91fc2fa..65d6d446 100644 --- a/gin.go +++ b/gin.go @@ -378,14 +378,14 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method != httpMethod { if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { c.handlers = engine.allNoMethod - serveError(c, 405, default405Body) + serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } } c.handlers = engine.allNoRoute - serveError(c, 404, default404Body) + serveError(c, http.StatusNotFound, default404Body) } var mimePlain = []string{MIMEPlain} @@ -406,9 +406,9 @@ func serveError(c *Context, code int, defaultMessage []byte) { func redirectTrailingSlash(c *Context) { req := c.Request path := req.URL.Path - code := 301 // Permanent redirect, request with GET method + code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { - code = 307 + code = http.StatusTemporaryRedirect } if length := len(path); length > 1 && path[length-1] == '/' { @@ -430,9 +430,9 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { trailingSlash, ) if found { - code := 301 // Permanent redirect, request with GET method + code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { - code = 307 + code = http.StatusTemporaryRedirect } req.URL.Path = string(fixedPath) debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) diff --git a/recovery.go b/recovery.go index dcbeba74..61c5bd53 100644 --- a/recovery.go +++ b/recovery.go @@ -10,6 +10,7 @@ import ( "io" "io/ioutil" "log" + "net/http" "net/http/httputil" "runtime" "time" @@ -41,7 +42,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { httprequest, _ := httputil.DumpRequest(c.Request, false) logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) } - c.AbortWithStatus(500) + c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() diff --git a/response_writer.go b/response_writer.go index 14b1cfc3..166ae8c3 100644 --- a/response_writer.go +++ b/response_writer.go @@ -13,7 +13,7 @@ import ( const ( noWritten = -1 - defaultStatus = 200 + defaultStatus = http.StatusOK ) type responseWriterBase interface { diff --git a/routergroup.go b/routergroup.go index 53045d10..876a61b8 100644 --- a/routergroup.go +++ b/routergroup.go @@ -184,7 +184,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS _, nolisting := fs.(*onlyfilesFS) return func(c *Context) { if nolisting { - c.Writer.WriteHeader(404) + c.Writer.WriteHeader(http.StatusNotFound) } fileServer.ServeHTTP(c.Writer, c.Request) } From c00f21ff239822d013b80e54bd403fbc08a0b5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Jun 2018 18:56:43 +0800 Subject: [PATCH 035/111] add go version prerequisite and debug warning (#1394) * add go version prerequisite and debug warning * merge duplicate content * remove duplicate content --- README.md | 113 +++++++++++++++++++++++++++----------------------- debug.go | 3 ++ debug_test.go | 2 +- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c2917a5f..e09a51f2 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ## Contents +- [Installation](#installation) +- [Prerequisite](#prerequisite) - [Quick start](#quick-start) - [Benchmarks](#benchmarks) - [Gin v1.stable](#gin-v1-stable) -- [Start using it](#start-using-it) - [Build with jsoniter](#build-with-jsoniter) - [API Examples](#api-examples) - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) @@ -58,6 +59,64 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Testing](#testing) - [Users](#users--) +## Installation + +To install Gin package, you need to install Go and set your Go workspace first. + +1. Download and install it: + +```sh +$ go get -u github.com/gin-gonic/gin +``` + +2. Import it in your code: + +```go +import "github.com/gin-gonic/gin" +``` + +3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. + +```go +import "net/http" +``` + +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor fetch github.com/gin-gonic/gin@v1.2 +``` + +4. Copy a starting template inside your project + +```sh +$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go +``` + +5. Run your project + +```sh +$ go run main.go +``` + +## Prerequisite + +Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + ## Quick start ```sh @@ -135,58 +194,6 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - [x] Battle tested - [x] API frozen, new releases will not break your code. -## Start using it - -1. Download and install it: - -```sh -$ go get github.com/gin-gonic/gin -``` - -2. Import it in your code: - -```go -import "github.com/gin-gonic/gin" -``` - -3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. - -```go -import "net/http" -``` - -### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2. Create your project folder and `cd` inside - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -3. Vendor init your project and add gin - -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.2 -``` - -4. Copy a starting template inside your project - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go -``` - -5. Run your project - -```sh -$ go run main.go -``` - ## Build with [jsoniter](https://github.com/json-iterator/go) Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. diff --git a/debug.go b/debug.go index 897c4943..f11156b3 100644 --- a/debug.go +++ b/debug.go @@ -47,6 +47,9 @@ func debugPrint(format string, values ...interface{}) { } func debugPrintWARNINGDefault() { + debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + +`) debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. `) diff --git a/debug_test.go b/debug_test.go index dfd54c82..440173b7 100644 --- a/debug_test.go +++ b/debug_test.go @@ -92,7 +92,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { defer teardown() debugPrintWARNINGDefault() - assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) } func TestDebugPrintWARNINGNew(t *testing.T) { From eb9f313144e6768d91421855810ed5937ce348c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Jun 2018 17:08:09 +0800 Subject: [PATCH 036/111] add comment for context (#1413) ref #1075 annotation from go context source. --- context.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/context.go b/context.go index 65fbb629..6fc5d25f 100644 --- a/context.go +++ b/context.go @@ -836,18 +836,33 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. func (c *Context) Deadline() (deadline time.Time, ok bool) { return } +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. func (c *Context) Done() <-chan struct{} { return nil } +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. func (c *Context) Err() error { return nil } +// Value returns the value associated with this context for key, or nil +// if no value is associated with key. Successive calls to Value with +// the same key returns the same result. func (c *Context) Value(key interface{}) interface{} { if key == 0 { return c.Request From cdd02fa9d65c15de42f1a4522fcb9af28fe56cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 1 Jul 2018 21:10:48 +0800 Subject: [PATCH 037/111] update error(err) to err (#1416) the pull request update `return error(err)` to `return err`, and remove `kindOfData`. --- binding/binding.go | 5 ++--- binding/default_validator.go | 19 ++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 1a984777..3a2aad9c 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -4,10 +4,9 @@ package binding -import ( - "net/http" -) +import "net/http" +// Content-Type MIME of the most common data formats. const ( MIMEJSON = "application/json" MIMEHTML = "text/html" diff --git a/binding/default_validator.go b/binding/default_validator.go index c67aa8a3..e7a302de 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -18,11 +18,17 @@ type defaultValidator struct { var _ StructValidator = &defaultValidator{} +// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. func (v *defaultValidator) ValidateStruct(obj interface{}) error { - if kindOfData(obj) == reflect.Struct { + value := reflect.ValueOf(obj) + valueType := value.Kind() + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + if valueType == reflect.Struct { v.lazyinit() if err := v.validate.Struct(obj); err != nil { - return error(err) + return err } } return nil @@ -43,12 +49,3 @@ func (v *defaultValidator) lazyinit() { v.validate = validator.New(config) }) } - -func kindOfData(data interface{}) reflect.Kind { - value := reflect.ValueOf(data) - valueType := value.Kind() - if valueType == reflect.Ptr { - valueType = value.Elem().Kind() - } - return valueType -} From 1c4cbfae59e0f62a6812b7808839eed45b66515f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 2 Jul 2018 11:06:56 +0800 Subject: [PATCH 038/111] chore: remove duplicate code (#1418) --- gin.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/gin.go b/gin.go index 65d6d446..3ee8018d 100644 --- a/gin.go +++ b/gin.go @@ -171,14 +171,14 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { func (engine *Engine) LoadHTMLGlob(pattern string) { left := engine.delims.Left right := engine.delims.Right + templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) if IsDebugging() { - debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))) + debugPrintLoadTemplate(templ) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} return } - templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } @@ -425,11 +425,7 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request path := req.URL.Path - fixedPath, found := root.findCaseInsensitivePath( - cleanPath(path), - trailingSlash, - ) - if found { + if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok { code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect From d17a12591fb637da47576b8669fabb8752fe656d Mon Sep 17 00:00:00 2001 From: vz Date: Tue, 3 Jul 2018 15:39:18 +0800 Subject: [PATCH 039/111] update assert param(expect, actual) position (#1421) - update assert param(expect, actual) position --- errors_test.go | 18 +++++++++--------- response_writer_test.go | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/errors_test.go b/errors_test.go index a666d7c1..0626611f 100644 --- a/errors_test.go +++ b/errors_test.go @@ -19,17 +19,17 @@ func TestError(t *testing.T) { Type: ErrorTypePrivate, } assert.Equal(t, err.Error(), baseError.Error()) - assert.Equal(t, err.JSON(), H{"error": baseError.Error()}) + assert.Equal(t, H{"error": baseError.Error()}, err.JSON()) assert.Equal(t, err.SetType(ErrorTypePublic), err) - assert.Equal(t, err.Type, ErrorTypePublic) + assert.Equal(t, ErrorTypePublic, err.Type) assert.Equal(t, err.SetMeta("some data"), err) - assert.Equal(t, err.Meta, "some data") - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, "some data", err.Meta) + assert.Equal(t, H{ "error": baseError.Error(), "meta": "some data", - }) + }, err.JSON()) jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) @@ -38,22 +38,22 @@ func TestError(t *testing.T) { "status": "200", "data": "some data", }) - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, H{ "error": baseError.Error(), "status": "200", "data": "some data", - }) + }, err.JSON()) err.SetMeta(H{ "error": "custom error", "status": "200", "data": "some data", }) - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, H{ "error": "custom error", "status": "200", "data": "some data", - }) + }, err.JSON()) type customError struct { status string diff --git a/response_writer_test.go b/response_writer_test.go index cec27338..4289a7c1 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -51,7 +51,7 @@ func TestResponseWriterWriteHeader(t *testing.T) { w.WriteHeader(300) assert.False(t, w.Written()) assert.Equal(t, 300, w.Status()) - assert.NotEqual(t, testWritter.Code, 300) + assert.NotEqual(t, 300, testWritter.Code) w.WriteHeader(-1) assert.Equal(t, 300, w.Status()) From 85221af84cf6ee99dc900bc41912abc09acfc1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Tue, 3 Jul 2018 17:17:08 +0800 Subject: [PATCH 040/111] add json ASCII string render (#1358) add a json render that rendering json as ASCII string --- README.md | 23 +++++++++++++++++++++++ context.go | 6 ++++++ context_test.go | 11 +++++++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ render/render_test.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+) diff --git a/README.md b/README.md index e09a51f2..93896469 100644 --- a/README.md +++ b/README.md @@ -900,6 +900,29 @@ func main() { } ``` +#### AsciiJSON + +Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. + +```go +func main() { + r := gin.Default() + + r.GET("/someJSON", func(c *gin.Context) { + data := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + ### Serving static files ```go diff --git a/context.go b/context.go index 6fc5d25f..d0b4e87f 100644 --- a/context.go +++ b/context.go @@ -704,6 +704,12 @@ func (c *Context) JSON(code int, obj interface{}) { c.Render(code, render.JSON{Data: obj}) } +// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. +// It also sets the Content-Type as "application/json". +func (c *Context) AsciiJSON(code int, obj interface{}) { + c.Render(code, render.AsciiJSON{Data: obj}) +} + // XML serializes the given struct as XML into the response body. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { diff --git a/context_test.go b/context_test.go index 12e02fa0..6f37682e 100644 --- a/context_test.go +++ b/context_test.go @@ -686,6 +686,17 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +func TestContextRenderNoContentAsciiJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"}) + + assert.Equal(t, http.StatusNoContent, w.Code) + assert.Empty(t, w.Body.String()) + assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type")) +} + // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { diff --git a/render/json.go b/render/json.go index 3a2e8b2f..6e5089a0 100755 --- a/render/json.go +++ b/render/json.go @@ -6,6 +6,7 @@ package render import ( "bytes" + "fmt" "html/template" "net/http" @@ -30,10 +31,15 @@ type JsonpJSON struct { Data interface{} } +type AsciiJSON struct { + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} +var jsonAsciiContentType = []string{"application/json"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -112,3 +118,29 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } + +func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + var buffer bytes.Buffer + for _, r := range string(ret) { + cvt := "" + if r < 128 { + cvt = string(r) + } else { + cvt = fmt.Sprintf("\\u%04x", int64(r)) + } + buffer.WriteString(cvt) + } + + w.Write(buffer.Bytes()) + return nil +} + +func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonAsciiContentType) +} diff --git a/render/render_test.go b/render/render_test.go index 40ec806e..2f728441 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -167,6 +167,35 @@ func TestRenderJsonpJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderAsciiJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data1 := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + err := (AsciiJSON{data1}).Render(w1) + + assert.NoError(t, err) + assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) + assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) + + w2 := httptest.NewRecorder() + data2 := float64(3.1415926) + + err = (AsciiJSON{data2}).Render(w2) + assert.NoError(t, err) + assert.Equal(t, "3.1415926", w2.Body.String()) +} + +func TestRenderAsciiJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + assert.Error(t, (AsciiJSON{data}).Render(w)) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 220e8d34538f5dfdcc299dda79bf83cf52e633f6 Mon Sep 17 00:00:00 2001 From: solos Date: Sat, 21 Jul 2018 00:52:55 +0800 Subject: [PATCH 041/111] return json if jsonp has not callback (#1438) return json if jsonp has not callback --- context.go | 7 ++++++- context_test.go | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index d0b4e87f..720c4251 100644 --- a/context.go +++ b/context.go @@ -695,7 +695,12 @@ func (c *Context) SecureJSON(code int, obj interface{}) { // It add padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". func (c *Context) JSONP(code int, obj interface{}) { - c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) + callback := c.DefaultQuery("callback", "") + if callback == "" { + c.Render(code, render.JSON{Data: obj}) + } else { + c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) + } } // JSON serializes the given struct as JSON into the response body. diff --git a/context_test.go b/context_test.go index 6f37682e..e29569fd 100644 --- a/context_test.go +++ b/context_test.go @@ -596,6 +596,20 @@ func TestContextRenderJSONP(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that the response is serialized as JSONP +// and Content-Type is set to application/json +func TestContextRenderJSONPWithoutCallback(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "http://example.com", nil) + + c.JSONP(201, H{"foo": "bar"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() From 631cfbd1ef2374f5a01f5d17ed8c8f08779ad6fe Mon Sep 17 00:00:00 2001 From: Dmitry Dorogin Date: Sun, 5 Aug 2018 08:29:26 +0300 Subject: [PATCH 042/111] Simplify context error (#1431) Hello! Looking through context package and found a little bit complicated switch block. And tried to make it easier. Thanks! --- context.go | 9 ++++----- ginS/gins.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 720c4251..779484b1 100644 --- a/context.go +++ b/context.go @@ -159,16 +159,15 @@ func (c *Context) Error(err error) *Error { if err == nil { panic("err is nil") } - var parsedError *Error - switch err.(type) { - case *Error: - parsedError = err.(*Error) - default: + + parsedError, ok := err.(*Error) + if !ok { parsedError = &Error{ Err: err, Type: ErrorTypePrivate, } } + c.Errors = append(c.Errors, parsedError) return parsedError } diff --git a/ginS/gins.go b/ginS/gins.go index ee00b381..a7686f23 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -128,7 +128,7 @@ func Run(addr ...string) (err error) { // RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine undefinitelly unless an error happens. -func RunTLS(addr string, certFile string, keyFile string) (err error) { +func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } From 647535cd9b780b6ccbeccfedfba36896e1a1da37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 6 Aug 2018 12:07:11 +0800 Subject: [PATCH 043/111] Support map as query string or post form parameters (#1383) * support query map * add GetQueryMap and unittest * support post-form map * add readme for query map * attempt to fix bug for post-form map when go version is 1.6 * remove duplicate code * remove comment --- README.md | 29 +++++++++++++++++++++++++++++ context.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ context_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 93896469..11c91c9a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Querystring parameters](#querystring-parameters) - [Multipart/Urlencoded Form](#multiparturlencoded-form) - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - [Upload files](#upload-files) - [Grouping routes](#grouping-routes) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) @@ -323,6 +324,34 @@ func main() { id: 1234; page: 1; name: manu; message: this_is_great ``` +### Map as querystring or postform parameters + +``` +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +``` +ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] +``` + ### Upload files #### Single file diff --git a/context.go b/context.go index 779484b1..724ded79 100644 --- a/context.go +++ b/context.go @@ -360,6 +360,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) { return []string{}, false } +// QueryMap returns a map for a given query key. +func (c *Context) QueryMap(key string) map[string]string { + dicts, _ := c.GetQueryMap(key) + return dicts +} + +// GetQueryMap returns a map for a given query key, plus a boolean value +// whether at least one value exists for the given key. +func (c *Context) GetQueryMap(key string) (map[string]string, bool) { + return c.get(c.Request.URL.Query(), key) +} + // PostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns an empty string `("")`. func (c *Context) PostForm(key string) string { @@ -415,6 +427,42 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) { return []string{}, false } +// PostFormMap returns a map for a given form key. +func (c *Context) PostFormMap(key string) map[string]string { + dicts, _ := c.GetPostFormMap(key) + return dicts +} + +// GetPostFormMap returns a map for a given form key, plus a boolean value +// whether at least one value exists for the given key. +func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { + req := c.Request + req.ParseForm() + req.ParseMultipartForm(c.engine.MaxMultipartMemory) + dicts, exist := c.get(req.PostForm, key) + + if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { + dicts, exist = c.get(req.MultipartForm.Value, key) + } + + return dicts, exist +} + +// get is an internal method and returns a map which satisfy conditions. +func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { + dicts := make(map[string]string) + exist := false + for k, v := range m { + if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { + if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { + exist = true + dicts[k[i+1:][:j]] = v[0] + } + } + } + return dicts, exist +} + // FormFile returns the first file for the provided form key. func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { _, fh, err := c.Request.FormFile(name) diff --git a/context_test.go b/context_test.go index e29569fd..589ab336 100644 --- a/context_test.go +++ b/context_test.go @@ -47,6 +47,8 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_utc", "31/12/2016 14:55")) must(mw.WriteField("time_location", "31/12/2016 14:55")) + must(mw.WriteField("names[a]", "thinkerou")) + must(mw.WriteField("names[b]", "tianou")) req, err := http.NewRequest("POST", "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -371,7 +373,8 @@ func TestContextQuery(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") - c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body) + c.Request, _ = http.NewRequest("POST", + "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) assert.Equal(t, "bar", c.DefaultPostForm("foo", "none")) @@ -439,6 +442,30 @@ func TestContextQueryAndPostForm(t *testing.T) { values = c.QueryArray("both") assert.Equal(t, 1, len(values)) assert.Equal(t, "GET", values[0]) + + dicts, ok := c.GetQueryMap("ids") + assert.True(t, ok) + assert.Equal(t, "hi", dicts["a"]) + assert.Equal(t, "3.14", dicts["b"]) + + dicts, ok = c.GetQueryMap("nokey") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts, ok = c.GetQueryMap("both") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts, ok = c.GetQueryMap("array") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts = c.QueryMap("ids") + assert.Equal(t, "hi", dicts["a"]) + assert.Equal(t, "3.14", dicts["b"]) + + dicts = c.QueryMap("nokey") + assert.Equal(t, 0, len(dicts)) } func TestContextPostFormMultipart(t *testing.T) { @@ -515,6 +542,22 @@ func TestContextPostFormMultipart(t *testing.T) { values = c.PostFormArray("foo") assert.Equal(t, 1, len(values)) assert.Equal(t, "bar", values[0]) + + dicts, ok := c.GetPostFormMap("names") + assert.True(t, ok) + assert.Equal(t, "thinkerou", dicts["a"]) + assert.Equal(t, "tianou", dicts["b"]) + + dicts, ok = c.GetPostFormMap("nokey") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts = c.PostFormMap("names") + assert.Equal(t, "thinkerou", dicts["a"]) + assert.Equal(t, "tianou", dicts["b"]) + + dicts = c.PostFormMap("nokey") + assert.Equal(t, 0, len(dicts)) } func TestContextSetCookie(t *testing.T) { From e2b4cf6e2dffb4e59f95f96b78e25e65f9037dec Mon Sep 17 00:00:00 2001 From: grapeVine Date: Mon, 6 Aug 2018 23:08:01 +0800 Subject: [PATCH 044/111] interface implement type check (#1459) interface implement type check --- render/render.go | 1 + 1 file changed, 1 insertion(+) diff --git a/render/render.go b/render/render.go index 7caf9bba..4ff1c7b6 100755 --- a/render/render.go +++ b/render/render.go @@ -26,6 +26,7 @@ var ( _ Render = YAML{} _ Render = MsgPack{} _ Render = Reader{} + _ Render = AsciiJSON{} ) func writeContentType(w http.ResponseWriter, value []string) { From 9b7e7bdce6e073d47635d57fbacfb3205cb3127e Mon Sep 17 00:00:00 2001 From: Dmitry Dorogin Date: Tue, 7 Aug 2018 01:44:32 +0300 Subject: [PATCH 045/111] Add tests for context.Stream (#1433) --- context_test.go | 36 ++++++++++++++++++++++++++++++++++++ test_helpers.go | 21 +++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/context_test.go b/context_test.go index 589ab336..40ae5bf8 100644 --- a/context_test.go +++ b/context_test.go @@ -21,6 +21,7 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" + "io" ) var _ context.Context = &Context{} @@ -1558,3 +1559,38 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } + +func TestContextStream(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + stopStream := true + c.Stream(func(w io.Writer) bool { + defer func() { + stopStream = false + }() + + w.Write([]byte("test")) + + return stopStream + }) + + assert.Equal(t, "testtest", w.Body.String()) +} + +func TestContextStreamWithClientGone(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + c.Stream(func(writer io.Writer) bool { + defer func() { + w.closeClient() + }() + + writer.Write([]byte("test")) + + return true + }) + + assert.Equal(t, "test", w.Body.String()) +} diff --git a/test_helpers.go b/test_helpers.go index 2aed37f2..554568d9 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -6,6 +6,7 @@ package gin import ( "net/http" + "net/http/httptest" ) // CreateTestContext returns a fresh engine and context for testing purposes @@ -16,3 +17,23 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { c.writermem.reset(w) return } + +type TestResponseRecorder struct { + *httptest.ResponseRecorder + closeChannel chan bool +} + +func (r *TestResponseRecorder) CloseNotify() <-chan bool { + return r.closeChannel +} + +func (r *TestResponseRecorder) closeClient() { + r.closeChannel <- true +} + +func CreateTestResponseRecorder() *TestResponseRecorder { + return &TestResponseRecorder{ + httptest.NewRecorder(), + make(chan bool, 1), + } +} From 0552c3bc3a6de6b802684b024ae49c3195cc70dd Mon Sep 17 00:00:00 2001 From: zhanweidu Date: Tue, 7 Aug 2018 12:41:28 +0800 Subject: [PATCH 046/111] flush operation will overwrite the origin status code (#1460) The status of responseWriter will be overwrite if flush was called. This is caused by the Flush of http.response.Flush(). --- response_writer.go | 1 + response_writer_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/response_writer.go b/response_writer.go index 166ae8c3..923b53f8 100644 --- a/response_writer.go +++ b/response_writer.go @@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool { // Flush implements the http.Flush interface. func (w *responseWriter) Flush() { + w.WriteHeaderNow() w.ResponseWriter.(http.Flusher).Flush() } diff --git a/response_writer_test.go b/response_writer_test.go index 4289a7c1..fcc48b3b 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) { w.Flush() } + +func TestResponseWriterFlush(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writer := &responseWriter{} + writer.reset(w) + + writer.WriteHeader(http.StatusInternalServerError) + writer.Flush() + })) + defer testServer.Close() + + // should return 500 + resp, err := http.Get(testServer.URL) + assert.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) +} From 9666ba67383f6cc7bed3bb18691c2382717d51ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 7 Aug 2018 13:49:31 +0800 Subject: [PATCH 047/111] chore: update top bar header (#1461) --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 11c91c9a..c2cfe2d0 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) - [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) - [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) - [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) - [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) +[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) +[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) +[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. @@ -1811,7 +1812,7 @@ func TestPingRoute(t *testing.T) { } ``` -## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) +## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. From 1f1bc429ed7a4d591a7374f0e267b59747b177bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 9 Aug 2018 17:20:06 +0800 Subject: [PATCH 048/111] chore: add test case for source/function of recovery.go (#1467) --- recovery_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/recovery_test.go b/recovery_test.go index de3b62a5..fa065b13 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -41,3 +41,23 @@ func TestPanicWithAbort(t *testing.T) { // TEST assert.Equal(t, 400, w.Code) } + +func TestSource(t *testing.T) { + bs := source(nil, 0) + assert.Equal(t, []byte("???"), bs) + + in := [][]byte{ + []byte("Hello world."), + []byte("Hi, gin.."), + } + bs = source(in, 10) + assert.Equal(t, []byte("???"), bs) + + bs = source(in, 1) + assert.Equal(t, []byte("Hello world."), bs) +} + +func TestFunction(t *testing.T) { + bs := function(1) + assert.Equal(t, []byte("???"), bs) +} From 8fc8ce047243dbbbc5eb3d01b9fd026fc3b08c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 10 Aug 2018 20:50:23 +0800 Subject: [PATCH 049/111] small enhance for cleanPath (#1469) from httprouter patch: https://github.com/julienschmidt/httprouter/pull/243 --- path.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/path.go b/path.go index 1c2b8498..d1f59622 100644 --- a/path.go +++ b/path.go @@ -59,11 +59,11 @@ func cleanPath(p string) string { case p[r] == '.' && p[r+1] == '/': // . element - r++ + r += 2 case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): // .. element: remove to last / - r += 2 + r += 3 if w > 1 { // can backtrack From 7e64d32269d817b645d5d4694100a8a8ad6960cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 10:12:33 +0800 Subject: [PATCH 050/111] Attempt to fix #1462 (#1463) #1462 --- context_test.go | 20 ++++++++++++++++++++ test_helpers.go | 25 +------------------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/context_test.go b/context_test.go index 40ae5bf8..0185507f 100644 --- a/context_test.go +++ b/context_test.go @@ -1560,6 +1560,26 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } +type TestResponseRecorder struct { + *httptest.ResponseRecorder + closeChannel chan bool +} + +func (r *TestResponseRecorder) CloseNotify() <-chan bool { + return r.closeChannel +} + +func (r *TestResponseRecorder) closeClient() { + r.closeChannel <- true +} + +func CreateTestResponseRecorder() *TestResponseRecorder { + return &TestResponseRecorder{ + httptest.NewRecorder(), + make(chan bool, 1), + } +} + func TestContextStream(t *testing.T) { w := CreateTestResponseRecorder() c, _ := CreateTestContext(w) diff --git a/test_helpers.go b/test_helpers.go index 554568d9..3a7a5ddf 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -4,10 +4,7 @@ package gin -import ( - "net/http" - "net/http/httptest" -) +import "net/http" // CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { @@ -17,23 +14,3 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { c.writermem.reset(w) return } - -type TestResponseRecorder struct { - *httptest.ResponseRecorder - closeChannel chan bool -} - -func (r *TestResponseRecorder) CloseNotify() <-chan bool { - return r.closeChannel -} - -func (r *TestResponseRecorder) closeClient() { - r.closeChannel <- true -} - -func CreateTestResponseRecorder() *TestResponseRecorder { - return &TestResponseRecorder{ - httptest.NewRecorder(), - make(chan bool, 1), - } -} From e5bb4f62a284426de199d68d71900fcbd1285691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 21:17:57 +0800 Subject: [PATCH 051/111] chore: add return or remove else for reduce indent (#1470) --- gin.go | 69 ++++++++++++++++++++++++++------------------------ routes_test.go | 10 ++++++++ 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/gin.go b/gin.go index 3ee8018d..fc4d6564 100644 --- a/gin.go +++ b/gin.go @@ -349,38 +349,40 @@ func (engine *Engine) handleHTTPRequest(c *Context) { // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { - if t[i].method == httpMethod { - root := t[i].root - // Find route in tree - handlers, params, tsr := root.getValue(path, c.Params, unescape) - if handlers != nil { - c.handlers = handlers - c.Params = params - c.Next() - c.writermem.WriteHeaderNow() + if t[i].method != httpMethod { + continue + } + root := t[i].root + // Find route in tree + handlers, params, tsr := root.getValue(path, c.Params, unescape) + if handlers != nil { + c.handlers = handlers + c.Params = params + c.Next() + c.writermem.WriteHeaderNow() + return + } + if httpMethod != "CONNECT" && path != "/" { + if tsr && engine.RedirectTrailingSlash { + redirectTrailingSlash(c) return } - if httpMethod != "CONNECT" && path != "/" { - if tsr && engine.RedirectTrailingSlash { - redirectTrailingSlash(c) - return - } - if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { - return - } + if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { + return } - break } + break } if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { - if tree.method != httpMethod { - if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { - c.handlers = engine.allNoMethod - serveError(c, http.StatusMethodNotAllowed, default405Body) - return - } + if tree.method == httpMethod { + continue + } + if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { + c.handlers = engine.allNoMethod + serveError(c, http.StatusMethodNotAllowed, default405Body) + return } } } @@ -393,14 +395,16 @@ var mimePlain = []string{MIMEPlain} func serveError(c *Context, code int, defaultMessage []byte) { c.writermem.status = code c.Next() - if !c.writermem.Written() { - if c.writermem.Status() == code { - c.writermem.Header()["Content-Type"] = mimePlain - c.Writer.Write(defaultMessage) - } else { - c.writermem.WriteHeaderNow() - } + if c.writermem.Written() { + return } + if c.writermem.Status() == code { + c.writermem.Header()["Content-Type"] = mimePlain + c.Writer.Write(defaultMessage) + return + } + c.writermem.WriteHeaderNow() + return } func redirectTrailingSlash(c *Context) { @@ -411,10 +415,9 @@ func redirectTrailingSlash(c *Context) { code = http.StatusTemporaryRedirect } + req.URL.Path = path + "/" if length := len(path); length > 1 && path[length-1] == '/' { req.URL.Path = path[:length-1] - } else { - req.URL.Path = path + "/" } debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) diff --git a/routes_test.go b/routes_test.go index 81293907..a2389f9b 100644 --- a/routes_test.go +++ b/routes_test.go @@ -333,6 +333,16 @@ func TestRouteNotAllowedEnabled(t *testing.T) { assert.Equal(t, http.StatusTeapot, w.Code) } +func TestRouteNotAllowedEnabled2(t *testing.T) { + router := New() + router.HandleMethodNotAllowed = true + // add one methodTree to trees + router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}}) + router.GET("/path2", func(c *Context) {}) + w := performRequest(router, "POST", "/path2") + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) +} + func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false From 202db4fb117768795c0fcc909cc9c3d75b9172b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 21:38:07 +0800 Subject: [PATCH 052/111] improve utils code coverage (#1473) --- utils_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utils_test.go b/utils_test.go index 3d019e7e..95b5de5e 100644 --- a/utils_test.go +++ b/utils_test.go @@ -5,6 +5,8 @@ package gin import ( + "bytes" + "encoding/xml" "fmt" "net/http" "testing" @@ -124,3 +126,14 @@ func TestBindMiddleware(t *testing.T) { Bind(&bindTestStruct{}) }) } + +func TestMarshalXMLforH(t *testing.T) { + h := H{ + "": "test", + } + var b bytes.Buffer + enc := xml.NewEncoder(&b) + var x xml.StartElement + e := h.MarshalXML(enc, x) + assert.Error(t, e) +} From 1ae32f3a2cfa53d1ae3e5c10a72841673bedc449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 22:02:37 +0800 Subject: [PATCH 053/111] improve render code coverage (#1474) all code coverage > 99% --- render/render_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 2f728441..9e768850 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -158,6 +158,21 @@ func TestRenderJsonpJSON(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } +func TestRenderJsonpJSONError2(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + (JsonpJSON{"", data}).WriteContentType(w) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) + + e := (JsonpJSON{"", data}).Render(w) + assert.NoError(t, e) + + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) +} + func TestRenderJsonpJSONFail(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) From 61592134625e0fca1b2de7c22107a01684e8b130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 23:38:31 +0800 Subject: [PATCH 054/111] unify test data (#1417) mkdir a test data dir. --- README.md | 2 +- binding/binding_body_test.go | 6 +++--- binding/binding_test.go | 14 +++++++------- debug_test.go | 2 +- examples/template/main.go | 2 +- gin_test.go | 8 ++++---- render/render_test.go | 4 ++-- .../testdata => testdata/certificate}/cert.pem | 0 .../testdata => testdata/certificate}/key.pem | 0 .../example => testdata/protoexample}/test.pb.go | 6 +++--- .../example => testdata/protoexample}/test.proto | 2 +- {fixtures/basic => testdata/template}/hello.tmpl | 0 {fixtures/basic => testdata/template}/raw.tmpl | 0 13 files changed, 23 insertions(+), 23 deletions(-) rename {fixtures/testdata => testdata/certificate}/cert.pem (100%) rename {fixtures/testdata => testdata/certificate}/key.pem (100%) rename {binding/example => testdata/protoexample}/test.pb.go (94%) rename {binding/example => testdata/protoexample}/test.proto (90%) rename {fixtures/basic => testdata/template}/hello.tmpl (100%) rename {fixtures/basic => testdata/template}/raw.tmpl (100%) diff --git a/README.md b/README.md index c2cfe2d0..bf1bbb49 100644 --- a/README.md +++ b/README.md @@ -1117,7 +1117,7 @@ func main() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("./fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("./testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go index c41d9f86..dfd761e1 100644 --- a/binding/binding_body_test.go +++ b/binding/binding_body_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "testing" - "github.com/gin-gonic/gin/binding/example" + "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" @@ -55,12 +55,12 @@ func msgPackBody(t *testing.T) string { } func TestBindingBodyProto(t *testing.T) { - test := example.Test{ + test := protoexample.Test{ Label: proto.String("FOO"), } data, _ := proto.Marshal(&test) req := requestWithBody("POST", "/", string(data)) - form := example.Test{} + form := protoexample.Test{} body, _ := ioutil.ReadAll(req.Body) assert.NoError(t, ProtoBuf.BindBody(body, &form)) assert.Equal(t, test, form) diff --git a/binding/binding_test.go b/binding/binding_test.go index 936deac7..efe87669 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - "github.com/gin-gonic/gin/binding/example" + "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" @@ -562,7 +562,7 @@ func TestBindingFormMultipartFail(t *testing.T) { } func TestBindingProtoBuf(t *testing.T) { - test := &example.Test{ + test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) @@ -574,7 +574,7 @@ func TestBindingProtoBuf(t *testing.T) { } func TestBindingProtoBufFail(t *testing.T) { - test := &example.Test{ + test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) @@ -1156,14 +1156,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) - obj := example.Test{} + obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "yes", *obj.Label) - obj = example.Test{} + obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) @@ -1179,7 +1179,7 @@ func (h hook) Read([]byte) (int, error) { func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) - obj := example.Test{} + obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Body = ioutil.NopCloser(&hook{}) @@ -1187,7 +1187,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body err := b.Bind(req, &obj) assert.Error(t, err) - obj = example.Test{} + obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) diff --git a/debug_test.go b/debug_test.go index 440173b7..ed5a6a54 100644 --- a/debug_test.go +++ b/debug_test.go @@ -72,7 +72,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { setup(&w) defer teardown() - templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl")) + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) } diff --git a/examples/template/main.go b/examples/template/main.go index f9e611df..e20a3b98 100644 --- a/examples/template/main.go +++ b/examples/template/main.go @@ -20,7 +20,7 @@ func main() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("../../fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("../../testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ diff --git a/gin_test.go b/gin_test.go index 3ac60577..b3cab715 100644 --- a/gin_test.go +++ b/gin_test.go @@ -30,7 +30,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) @@ -41,7 +41,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { }) if tls { // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") + router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } @@ -59,7 +59,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLGlob("./fixtures/basic/*") + router.LoadHTMLGlob("./testdata/template/*") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) @@ -70,7 +70,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { }) if tls { // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") + router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } diff --git a/render/render_test.go b/render/render_test.go index 9e768850..8fad12b3 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -388,7 +388,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) { w := httptest.NewRecorder() - htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"}, + htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"}, Glob: "", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, @@ -407,7 +407,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{Files: nil, - Glob: "../fixtures/basic/hello*", + Glob: "../testdata/template/hello*", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, } diff --git a/fixtures/testdata/cert.pem b/testdata/certificate/cert.pem similarity index 100% rename from fixtures/testdata/cert.pem rename to testdata/certificate/cert.pem diff --git a/fixtures/testdata/key.pem b/testdata/certificate/key.pem similarity index 100% rename from fixtures/testdata/key.pem rename to testdata/certificate/key.pem diff --git a/binding/example/test.pb.go b/testdata/protoexample/test.pb.go similarity index 94% rename from binding/example/test.pb.go rename to testdata/protoexample/test.pb.go index 3de8444f..21997ca1 100644 --- a/binding/example/test.pb.go +++ b/testdata/protoexample/test.pb.go @@ -3,7 +3,7 @@ // DO NOT EDIT! /* -Package example is a generated protocol buffer package. +Package protoexample is a generated protocol buffer package. It is generated from these files: test.proto @@ -11,7 +11,7 @@ It is generated from these files: It has these top-level messages: Test */ -package example +package protoexample import proto "github.com/golang/protobuf/proto" import math "math" @@ -109,5 +109,5 @@ func (m *Test_OptionalGroup) GetRequiredField() string { } func init() { - proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value) } diff --git a/binding/example/test.proto b/testdata/protoexample/test.proto similarity index 90% rename from binding/example/test.proto rename to testdata/protoexample/test.proto index 8ee9800a..3e734287 100644 --- a/binding/example/test.proto +++ b/testdata/protoexample/test.proto @@ -1,4 +1,4 @@ -package example; +package protoexample; enum FOO {X=17;}; diff --git a/fixtures/basic/hello.tmpl b/testdata/template/hello.tmpl similarity index 100% rename from fixtures/basic/hello.tmpl rename to testdata/template/hello.tmpl diff --git a/fixtures/basic/raw.tmpl b/testdata/template/raw.tmpl similarity index 100% rename from fixtures/basic/raw.tmpl rename to testdata/template/raw.tmpl From 8aef947f6eeeb47872c2e5910d20258cc6ccf375 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 12 Aug 2018 22:54:22 +0200 Subject: [PATCH 055/111] docs: remove double negative in README.md (#1480) "not match neither" means that it will match. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf1bbb49..28598baf 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ func main() { func main() { router := gin.Default() - // This handler will match /user/john but will not match neither /user/ or /user + // This handler will match /user/john but will not match /user/ or /user router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) From f45c928a156e60e521dceea99a183f9c9cf5b2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 14 Aug 2018 09:51:56 +0800 Subject: [PATCH 056/111] chore: use http.Status* instead of hard code (#1482) --- auth_test.go | 12 +-- benchmarks_test.go | 6 +- context_test.go | 153 ++++++++++++++------------- examples/basic/main.go | 10 +- examples/basic/main_test.go | 2 +- examples/favicon/main.go | 4 +- examples/http2/main.go | 3 +- examples/realtime-advanced/routes.go | 11 +- examples/realtime-chat/main.go | 5 +- githubapi_test.go | 2 +- logger.go | 7 +- logger_test.go | 17 +-- middleware_test.go | 33 +++--- recovery_test.go | 7 +- render/redirect.go | 2 + render/render_test.go | 4 +- response_writer_test.go | 20 ++-- routergroup_test.go | 7 +- routes_test.go | 88 +++++++-------- utils_test.go | 8 +- 20 files changed, 209 insertions(+), 192 deletions(-) diff --git a/auth_test.go b/auth_test.go index dc8523b0..ab7e94be 100644 --- a/auth_test.go +++ b/auth_test.go @@ -93,7 +93,7 @@ func TestBasicAuthSucceed(t *testing.T) { router := New() router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -101,7 +101,7 @@ func TestBasicAuthSucceed(t *testing.T) { req.Header.Set("Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "admin", w.Body.String()) } @@ -112,7 +112,7 @@ func TestBasicAuth401(t *testing.T) { router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { called = true - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -121,7 +121,7 @@ func TestBasicAuth401(t *testing.T) { router.ServeHTTP(w, req) assert.False(t, called) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate")) } @@ -132,7 +132,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { router.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\"")) router.GET("/login", func(c *Context) { called = true - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { router.ServeHTTP(w, req) assert.False(t, called) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate")) } diff --git a/benchmarks_test.go b/benchmarks_test.go index e7970034..0b3f82df 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -54,7 +54,7 @@ func BenchmarkOneRouteJSON(B *testing.B) { Status string `json:"status"` }{"ok"} router.GET("/json", func(c *Context) { - c.JSON(200, data) + c.JSON(http.StatusOK, data) }) runRequest(B, router, "GET", "/json") } @@ -66,7 +66,7 @@ func BenchmarkOneRouteHTML(B *testing.B) { router.SetHTMLTemplate(t) router.GET("/html", func(c *Context) { - c.HTML(200, "index", "hola") + c.HTML(http.StatusOK, "index", "hola") }) runRequest(B, router, "GET", "/html") } @@ -82,7 +82,7 @@ func BenchmarkOneRouteSet(B *testing.B) { func BenchmarkOneRouteString(B *testing.B) { router := New() router.GET("/text", func(c *Context) { - c.String(200, "this is a plain text") + c.String(http.StatusOK, "this is a plain text") }) runRequest(B, router, "GET", "/text") } diff --git a/context_test.go b/context_test.go index 0185507f..99d5267d 100644 --- a/context_test.go +++ b/context_test.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "html/template" + "io" "mime/multipart" "net/http" "net/http/httptest" @@ -21,7 +22,6 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" - "io" ) var _ context.Context = &Context{} @@ -585,10 +585,11 @@ func TestContextGetCookie(t *testing.T) { } func TestContextBodyAllowedForStatus(t *testing.T) { + // todo(thinkerou): go1.6 not support StatusProcessing assert.False(t, false, bodyAllowedForStatus(102)) - assert.False(t, false, bodyAllowedForStatus(204)) - assert.False(t, false, bodyAllowedForStatus(304)) - assert.True(t, true, bodyAllowedForStatus(500)) + assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent)) + assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified)) + assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) } type TestPanicRender struct { @@ -619,9 +620,9 @@ func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(201, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -633,9 +634,9 @@ func TestContextRenderJSONP(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) - c.JSONP(201, H{"foo": "bar"}) + c.JSONP(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -647,9 +648,9 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com", nil) - c.JSONP(201, H{"foo": "bar"}) + c.JSONP(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -659,9 +660,9 @@ func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(204, H{"foo": "bar"}) + c.JSON(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -673,9 +674,9 @@ func TestContextRenderAPIJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") - c.JSON(201, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) } @@ -686,9 +687,9 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") - c.JSON(204, H{"foo": "bar"}) + c.JSON(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") } @@ -699,9 +700,9 @@ func TestContextRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) + c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -711,9 +712,9 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) + c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -725,9 +726,9 @@ func TestContextRenderSecureJSON(t *testing.T) { c, router := CreateTestContext(w) router.SecureJsonPrefix("&&&START&&&") - c.SecureJSON(201, []string{"foo", "bar"}) + c.SecureJSON(http.StatusCreated, []string{"foo", "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -737,9 +738,9 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.SecureJSON(204, []string{"foo", "bar"}) + c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -764,9 +765,9 @@ func TestContextRenderHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.HTML(201, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -788,9 +789,9 @@ func TestContextRenderHTML2(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String()) - c.HTML(201, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -802,9 +803,9 @@ func TestContextRenderNoContentHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.HTML(204, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusNoContent, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -815,9 +816,9 @@ func TestContextRenderXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.XML(201, H{"foo": "bar"}) + c.XML(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -827,9 +828,9 @@ func TestContextRenderNoContentXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.XML(204, H{"foo": "bar"}) + c.XML(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -840,9 +841,9 @@ func TestContextRenderString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.String(201, "test %s %d", "string", 2) + c.String(http.StatusCreated, "test %s %d", "string", 2) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "test string 2", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -852,9 +853,9 @@ func TestContextRenderNoContentString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.String(204, "test %s %d", "string", 2) + c.String(http.StatusNoContent, "test %s %d", "string", 2) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -866,9 +867,9 @@ func TestContextRenderHTMLString(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") - c.String(201, "%s %d", "string", 3) + c.String(http.StatusCreated, "%s %d", "string", 3) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "string 3", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -879,9 +880,9 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") - c.String(204, "%s %d", "string", 3) + c.String(http.StatusNoContent, "%s %d", "string", 3) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -892,9 +893,9 @@ func TestContextRenderData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Data(201, "text/csv", []byte(`foo,bar`)) + c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`)) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo,bar", w.Body.String()) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } @@ -904,9 +905,9 @@ func TestContextRenderNoContentData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Data(204, "text/csv", []byte(`foo,bar`)) + c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`)) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } @@ -935,7 +936,7 @@ func TestContextRenderFile(t *testing.T) { c.Request, _ = http.NewRequest("GET", "/", nil) c.File("./gin.go") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -946,9 +947,9 @@ func TestContextRenderYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.YAML(201, H{"foo": "bar"}) + c.YAML(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -978,9 +979,9 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) { assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") }) - c.Redirect(301, "/path") + c.Redirect(http.StatusMovedPermanently, "/path") c.Writer.WriteHeaderNow() - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, "/path", w.Header().Get("Location")) } @@ -989,10 +990,10 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - c.Redirect(302, "http://google.com") + c.Redirect(http.StatusFound, "http://google.com") c.Writer.WriteHeaderNow() - assert.Equal(t, 302, w.Code) + assert.Equal(t, http.StatusFound, w.Code) assert.Equal(t, "http://google.com", w.Header().Get("Location")) } @@ -1001,21 +1002,23 @@ func TestContextRenderRedirectWith201(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - c.Redirect(201, "/resource") + c.Redirect(http.StatusCreated, "/resource") c.Writer.WriteHeaderNow() - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "/resource", w.Header().Get("Location")) } func TestContextRenderRedirectAll(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - assert.Panics(t, func() { c.Redirect(200, "/resource") }) - assert.Panics(t, func() { c.Redirect(202, "/resource") }) + assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") }) + assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") }) assert.Panics(t, func() { c.Redirect(309, "/resource") }) - assert.NotPanics(t, func() { c.Redirect(300, "/resource") }) + assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") }) + // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) + // when we upgrade go version we can use http.StatusPermanentRedirect assert.NotPanics(t, func() { c.Redirect(308, "/resource") }) } @@ -1024,12 +1027,12 @@ func TestContextNegotiationWithJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEJSON, MIMEXML}, Data: H{"foo": "bar"}, }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1039,12 +1042,12 @@ func TestContextNegotiationWithXML(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEXML, MIMEJSON}, Data: H{"foo": "bar"}, }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1056,13 +1059,13 @@ func TestContextNegotiationWithHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEHTML}, Data: H{"name": "gin"}, HTMLName: "t", }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Hello gin", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1072,11 +1075,11 @@ func TestContextNegotiationNotSupport(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEPOSTForm}, }) - assert.Equal(t, 406, w.Code) + assert.Equal(t, http.StatusNotAcceptable, w.Code) assert.Equal(t, c.index, abortIndex) assert.True(t, c.IsAborted()) } @@ -1134,11 +1137,11 @@ func TestContextAbortWithStatus(t *testing.T) { c, _ := CreateTestContext(w) c.index = 4 - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) assert.Equal(t, abortIndex, c.index) - assert.Equal(t, 401, c.Writer.Status()) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, c.Writer.Status()) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.True(t, c.IsAborted()) } @@ -1156,11 +1159,11 @@ func TestContextAbortWithStatusJSON(t *testing.T) { in.Bar = "barValue" in.Foo = "fooValue" - c.AbortWithStatusJSON(415, in) + c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in) assert.Equal(t, abortIndex, c.index) - assert.Equal(t, 415, c.Writer.Status()) - assert.Equal(t, 415, w.Code) + assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status()) + assert.Equal(t, http.StatusUnsupportedMediaType, w.Code) assert.True(t, c.IsAborted()) contentType := w.Header().Get("Content-Type") @@ -1223,9 +1226,9 @@ func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") + c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) assert.True(t, c.IsAborted()) } @@ -1333,7 +1336,7 @@ func TestContextBadAutoBind(t *testing.T) { assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.True(t, c.IsAborted()) } diff --git a/examples/basic/main.go b/examples/basic/main.go index 473c6a09..48fa7bba 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -1,6 +1,8 @@ package main import ( + "net/http" + "github.com/gin-gonic/gin" ) @@ -13,7 +15,7 @@ func setupRouter() *gin.Engine { // Ping test r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) // Get user value @@ -21,9 +23,9 @@ func setupRouter() *gin.Engine { user := c.Params.ByName("name") value, ok := DB[user] if ok { - c.JSON(200, gin.H{"user": user, "value": value}) + c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) } else { - c.JSON(200, gin.H{"user": user, "status": "no value"}) + c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"}) } }) @@ -49,7 +51,7 @@ func setupRouter() *gin.Engine { if c.Bind(&json) == nil { DB[user] = json.Value - c.JSON(200, gin.H{"status": "ok"}) + c.JSON(http.StatusOK, gin.H{"status": "ok"}) } }) diff --git a/examples/basic/main_test.go b/examples/basic/main_test.go index 61203d66..5eb85240 100644 --- a/examples/basic/main_test.go +++ b/examples/basic/main_test.go @@ -15,6 +15,6 @@ func TestPingRoute(t *testing.T) { req, _ := http.NewRequest("GET", "/ping", nil) router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "pong", w.Body.String()) } diff --git a/examples/favicon/main.go b/examples/favicon/main.go index 5ad39331..d32ca098 100644 --- a/examples/favicon/main.go +++ b/examples/favicon/main.go @@ -1,6 +1,8 @@ package main import ( + "net/http" + "github.com/gin-gonic/gin" "github.com/thinkerou/favicon" ) @@ -9,7 +11,7 @@ func main() { app := gin.Default() app.Use(favicon.New("./favicon.ico")) app.GET("/ping", func(c *gin.Context) { - c.String(200, "Hello favicon.") + c.String(http.StatusOK, "Hello favicon.") }) app.Run(":8080") } diff --git a/examples/http2/main.go b/examples/http2/main.go index 07df01e2..6598a4c9 100644 --- a/examples/http2/main.go +++ b/examples/http2/main.go @@ -3,6 +3,7 @@ package main import ( "html/template" "log" + "net/http" "os" "github.com/gin-gonic/gin" @@ -27,7 +28,7 @@ func main() { r.SetHTMLTemplate(html) r.GET("/welcome", func(c *gin.Context) { - c.HTML(200, "https", gin.H{ + c.HTML(http.StatusOK, "https", gin.H{ "status": "success", }) }) diff --git a/examples/realtime-advanced/routes.go b/examples/realtime-advanced/routes.go index 86da9bea..03c69910 100644 --- a/examples/realtime-advanced/routes.go +++ b/examples/realtime-advanced/routes.go @@ -4,6 +4,7 @@ import ( "fmt" "html" "io" + "net/http" "strings" "time" @@ -21,12 +22,12 @@ func rateLimit(c *gin.Context) { fmt.Println("ip blocked") } c.Abort() - c.String(503, "you were automatically banned :)") + c.String(http.StatusServiceUnavailable, "you were automatically banned :)") } } func index(c *gin.Context) { - c.Redirect(301, "/room/hn") + c.Redirect(http.StatusMovedPermanently, "/room/hn") } func roomGET(c *gin.Context) { @@ -38,7 +39,7 @@ func roomGET(c *gin.Context) { if len(nick) > 13 { nick = nick[0:12] + "..." } - c.HTML(200, "room_login.templ.html", gin.H{ + c.HTML(http.StatusOK, "room_login.templ.html", gin.H{ "roomid": roomid, "nick": nick, "timestamp": time.Now().Unix(), @@ -55,7 +56,7 @@ func roomPOST(c *gin.Context) { validMessage := len(message) > 1 && len(message) < 200 validNick := len(nick) > 1 && len(nick) < 14 if !validMessage || !validNick { - c.JSON(400, gin.H{ + c.JSON(http.StatusBadRequest, gin.H{ "status": "failed", "error": "the message or nickname is too long", }) @@ -68,7 +69,7 @@ func roomPOST(c *gin.Context) { } messages.Add("inbound", 1) room(roomid).Submit(post) - c.JSON(200, post) + c.JSON(http.StatusOK, post) } func streamRoom(c *gin.Context) { diff --git a/examples/realtime-chat/main.go b/examples/realtime-chat/main.go index e4b55a0f..5741fcba 100644 --- a/examples/realtime-chat/main.go +++ b/examples/realtime-chat/main.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "math/rand" + "net/http" "github.com/gin-gonic/gin" ) @@ -34,7 +35,7 @@ func stream(c *gin.Context) { func roomGET(c *gin.Context) { roomid := c.Param("roomid") userid := fmt.Sprint(rand.Int31()) - c.HTML(200, "chat_room", gin.H{ + c.HTML(http.StatusOK, "chat_room", gin.H{ "roomid": roomid, "userid": userid, }) @@ -46,7 +47,7 @@ func roomPOST(c *gin.Context) { message := c.PostForm("message") room(roomid).Submit(userid + ": " + message) - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "status": "success", "message": message, }) diff --git a/githubapi_test.go b/githubapi_test.go index a08c264d..f631035d 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -293,7 +293,7 @@ func githubConfigRouter(router *Engine) { for _, param := range c.Params { output[param.Key] = param.Value } - c.JSON(200, output) + c.JSON(http.StatusOK, output) }) } } diff --git a/logger.go b/logger.go index c679c787..1a8df601 100644 --- a/logger.go +++ b/logger.go @@ -7,6 +7,7 @@ package gin import ( "fmt" "io" + "net/http" "os" "time" @@ -118,11 +119,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { func colorForStatus(code int) string { switch { - case code >= 200 && code < 300: + case code >= http.StatusOK && code < http.StatusMultipleChoices: return green - case code >= 300 && code < 400: + case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: return white - case code >= 400 && code < 500: + case code >= http.StatusBadRequest && code < http.StatusInternalServerError: return yellow default: return red diff --git a/logger_test.go b/logger_test.go index 74a9659c..7dbbf7b1 100644 --- a/logger_test.go +++ b/logger_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "errors" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -93,9 +94,9 @@ func TestColorForMethod(t *testing.T) { } func TestColorForStatus(t *testing.T) { - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } @@ -106,23 +107,23 @@ func TestErrorLogger(t *testing.T) { c.Error(errors.New("this is an error")) }) router.GET("/abort", func(c *Context) { - c.AbortWithError(401, errors.New("no authorized")) + c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) }) router.GET("/print", func(c *Context) { c.Error(errors.New("this is an error")) - c.String(500, "hola!") + c.String(http.StatusInternalServerError, "hola!") }) w := performRequest(router, "GET", "/error") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = performRequest(router, "GET", "/abort") - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = performRequest(router, "GET", "/print") - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } diff --git a/middleware_test.go b/middleware_test.go index aa6a37a8..983ad933 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "net/http" "strings" "testing" @@ -37,7 +38,7 @@ func TestMiddlewareGeneralCase(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "ACDB", signature) } @@ -73,7 +74,7 @@ func TestMiddlewareNoRoute(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "ACEGHFDB", signature) } @@ -110,7 +111,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 405, w.Code) + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, "ACEGHFDB", signature) } @@ -147,7 +148,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "AC X DB", signature) } @@ -159,7 +160,7 @@ func TestMiddlewareAbort(t *testing.T) { }) router.Use(func(c *Context) { signature += "C" - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) c.Next() signature += "D" }) @@ -173,7 +174,7 @@ func TestMiddlewareAbort(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "ACD", signature) } @@ -183,7 +184,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { router.Use(func(c *Context) { signature += "A" c.Next() - c.AbortWithStatus(410) + c.AbortWithStatus(http.StatusGone) signature += "B" }) @@ -195,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 410, w.Code) + assert.Equal(t, http.StatusGone, w.Code) assert.Equal(t, "ACB", signature) } @@ -207,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { router := New() router.Use(func(context *Context) { signature += "A" - context.AbortWithError(500, errors.New("foo")) + context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) }) router.Use(func(context *Context) { signature += "B" @@ -218,25 +219,25 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "A", signature) } func TestMiddlewareWrite(t *testing.T) { router := New() router.Use(func(c *Context) { - c.String(400, "hola\n") + c.String(http.StatusBadRequest, "hola\n") }) router.Use(func(c *Context) { - c.XML(400, H{"foo": "bar"}) + c.XML(http.StatusBadRequest, H{"foo": "bar"}) }) router.Use(func(c *Context) { - c.JSON(400, H{"foo": "bar"}) + c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }) router.GET("/", func(c *Context) { - c.JSON(400, H{"foo": "bar"}) + c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }, func(c *Context) { - c.Render(400, sse.Event{ + c.Render(http.StatusBadRequest, sse.Event{ Event: "test", Data: "message", }) @@ -244,6 +245,6 @@ func TestMiddlewareWrite(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } diff --git a/recovery_test.go b/recovery_test.go index fa065b13..53f4a071 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -6,6 +6,7 @@ package gin import ( "bytes" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -22,7 +23,7 @@ func TestPanicInHandler(t *testing.T) { // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") @@ -33,13 +34,13 @@ func TestPanicWithAbort(t *testing.T) { router := New() router.Use(RecoveryWithWriter(nil)) router.GET("/recovery", func(c *Context) { - c.AbortWithStatus(400) + c.AbortWithStatus(http.StatusBadRequest) panic("Oupps, Houston, we have a problem") }) // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) } func TestSource(t *testing.T) { diff --git a/render/redirect.go b/render/redirect.go index f874a351..a0634f5a 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -16,6 +16,8 @@ type Redirect struct { } func (r Redirect) Render(w http.ResponseWriter) error { + // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) + // when we upgrade go version we can use http.StatusPermanentRedirect if (r.Code < 300 || r.Code > 308) && r.Code != 201 { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } diff --git a/render/render_test.go b/render/render_test.go index 8fad12b3..47abf262 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -286,7 +286,7 @@ func TestRenderRedirect(t *testing.T) { assert.NoError(t, err) data1 := Redirect{ - Code: 301, + Code: http.StatusMovedPermanently, Request: req, Location: "/new/location", } @@ -296,7 +296,7 @@ func TestRenderRedirect(t *testing.T) { assert.NoError(t, err) data2 := Redirect{ - Code: 200, + Code: http.StatusOK, Request: req, Location: "/new/location", } diff --git a/response_writer_test.go b/response_writer_test.go index fcc48b3b..cc5a89dc 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -35,10 +35,10 @@ func TestResponseWriterReset(t *testing.T) { writer.reset(testWritter) assert.Equal(t, -1, writer.size) - assert.Equal(t, 200, writer.status) + assert.Equal(t, http.StatusOK, writer.status) assert.Equal(t, testWritter, writer.ResponseWriter) assert.Equal(t, -1, w.Size()) - assert.Equal(t, 200, w.Status()) + assert.Equal(t, http.StatusOK, w.Status()) assert.False(t, w.Written()) } @@ -48,13 +48,13 @@ func TestResponseWriterWriteHeader(t *testing.T) { writer.reset(testWritter) w := ResponseWriter(writer) - w.WriteHeader(300) + w.WriteHeader(http.StatusMultipleChoices) assert.False(t, w.Written()) - assert.Equal(t, 300, w.Status()) - assert.NotEqual(t, 300, testWritter.Code) + assert.Equal(t, http.StatusMultipleChoices, w.Status()) + assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code) w.WriteHeader(-1) - assert.Equal(t, 300, w.Status()) + assert.Equal(t, http.StatusMultipleChoices, w.Status()) } func TestResponseWriterWriteHeadersNow(t *testing.T) { @@ -63,12 +63,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) { writer.reset(testWritter) w := ResponseWriter(writer) - w.WriteHeader(300) + w.WriteHeader(http.StatusMultipleChoices) w.WriteHeaderNow() assert.True(t, w.Written()) assert.Equal(t, 0, w.Size()) - assert.Equal(t, 300, testWritter.Code) + assert.Equal(t, http.StatusMultipleChoices, testWritter.Code) writer.size = 10 w.WriteHeaderNow() @@ -84,8 +84,8 @@ func TestResponseWriterWrite(t *testing.T) { n, err := w.Write([]byte("hola")) assert.Equal(t, 4, n) assert.Equal(t, 4, w.Size()) - assert.Equal(t, 200, w.Status()) - assert.Equal(t, 200, testWritter.Code) + assert.Equal(t, http.StatusOK, w.Status()) + assert.Equal(t, http.StatusOK, testWritter.Code) assert.Equal(t, "hola", testWritter.Body.String()) assert.NoError(t, err) diff --git a/routergroup_test.go b/routergroup_test.go index a362e23d..ce3d54a2 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -5,6 +5,7 @@ package gin import ( + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -50,7 +51,7 @@ func performRequestInGroup(t *testing.T, method string) { assert.Equal(t, "/v1/login/", login.BasePath()) handler := func(c *Context) { - c.String(400, "the method was %s and index %d", c.Request.Method, c.index) + c.String(http.StatusBadRequest, "the method was %s and index %d", c.Request.Method, c.index) } switch method { @@ -80,11 +81,11 @@ func performRequestInGroup(t *testing.T, method string) { } w := performRequest(router, method, "/v1/login/test") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) w = performRequest(router, method, "/v1/test") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) } diff --git a/routes_test.go b/routes_test.go index a2389f9b..23e749e2 100644 --- a/routes_test.go +++ b/routes_test.go @@ -80,20 +80,20 @@ func testRouteNotOK2(method string, t *testing.T) { func TestRouterMethod(t *testing.T) { router := New() router.PUT("/hey2", func(c *Context) { - c.String(200, "sup2") + c.String(http.StatusOK, "sup2") }) router.PUT("/hey", func(c *Context) { - c.String(200, "called") + c.String(http.StatusOK, "called") }) router.PUT("/hey3", func(c *Context) { - c.String(200, "sup3") + c.String(http.StatusOK, "sup3") }) w := performRequest(router, "PUT", "/hey") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "called", w.Body.String()) } @@ -144,42 +144,42 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w := performRequest(router, "GET", "/path/") assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3/") assert.Equal(t, "/path3", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "PUT", "/path4") assert.Equal(t, "/path4/", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "GET", "/path") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "GET", "/path2/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "POST", "/path3") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "PUT", "/path4/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) router.RedirectTrailingSlash = false w = performRequest(router, "GET", "/path/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "GET", "/path2") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "POST", "/path3/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "PUT", "/path4") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouteRedirectFixedPath(t *testing.T) { @@ -194,19 +194,19 @@ func TestRouteRedirectFixedPath(t *testing.T) { w := performRequest(router, "GET", "/PATH") assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/Path2", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3") assert.Equal(t, "/PATH3", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "POST", "/path4") assert.Equal(t, "/Path4/", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) } // TestContextParamsGet tests that a parameter can be parsed from the URL. @@ -236,7 +236,7 @@ func TestRouteParamsByName(t *testing.T) { w := performRequest(router, "GET", "/test/john/smith/is/super/great") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) assert.Equal(t, "smith", lastName) assert.Equal(t, "/is/super/great", wild) @@ -265,7 +265,7 @@ func TestRouteStaticFile(t *testing.T) { w2 := performRequest(router, "GET", "/result") assert.Equal(t, w, w2) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) @@ -273,7 +273,7 @@ func TestRouteStaticFile(t *testing.T) { w4 := performRequest(router, "HEAD", "/result") assert.Equal(t, w3, w4) - assert.Equal(t, 200, w3.Code) + assert.Equal(t, http.StatusOK, w3.Code) } // TestHandleStaticDir - ensure the root/sub dir handles properly @@ -283,7 +283,7 @@ func TestRouteStaticListingDir(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.NotContains(t, w.Body.String(), "gin.go") } @@ -310,7 +310,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { w := performRequest(router, "GET", "/gin.go") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") @@ -348,14 +348,14 @@ func TestRouteNotAllowedDisabled(t *testing.T) { router.HandleMethodNotAllowed = false router.POST("/path", func(c *Context) {}) w := performRequest(router, "GET", "/path") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, "GET", "/path") assert.Equal(t, "404 page not found", w.Body.String()) - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouterNotFound(t *testing.T) { @@ -370,20 +370,20 @@ func TestRouterNotFound(t *testing.T) { code int location string }{ - {"/path/", 301, "/path"}, // TSR -/ - {"/dir", 301, "/dir/"}, // TSR +/ - {"", 301, "/"}, // TSR +/ - {"/PATH", 301, "/path"}, // Fixed Case - {"/DIR/", 301, "/dir/"}, // Fixed Case - {"/PATH/", 301, "/path"}, // Fixed Case -/ - {"/DIR", 301, "/dir/"}, // Fixed Case +/ - {"/../path", 301, "/path"}, // CleanPath - {"/nope", 404, ""}, // NotFound + {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ + {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ + {"", http.StatusMovedPermanently, "/"}, // TSR +/ + {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case + {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case + {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ + {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ + {"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath + {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) assert.Equal(t, tr.code, w.Code) - if w.Code != 404 { + if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) } } @@ -391,24 +391,24 @@ func TestRouterNotFound(t *testing.T) { // Test custom not found handler var notFound bool router.NoRoute(func(c *Context) { - c.AbortWithStatus(404) + c.AbortWithStatus(http.StatusNotFound) notFound = true }) w := performRequest(router, "GET", "/nope") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.True(t, notFound) // Test other method than GET (want 307 instead of 301) router.PATCH("/path", func(c *Context) {}) w = performRequest(router, "PATCH", "/path/") - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) // Test special case where no node for the prefix "/" exists router = New() router.GET("/a", func(c *Context) {}) w = performRequest(router, "GET", "/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouteRawPath(t *testing.T) { @@ -427,7 +427,7 @@ func TestRouteRawPath(t *testing.T) { }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) } func TestRouteRawPathNoUnescape(t *testing.T) { @@ -447,7 +447,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) { }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) } func TestRouteServeErrorWithWriteHeader(t *testing.T) { diff --git a/utils_test.go b/utils_test.go index 95b5de5e..9b57c57b 100644 --- a/utils_test.go +++ b/utils_test.go @@ -25,7 +25,7 @@ type testStruct struct { func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { assert.Equal(t.T, "POST", req.Method) assert.Equal(t.T, "/path", req.URL.Path) - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "hello") } @@ -35,16 +35,16 @@ func TestWrap(t *testing.T) { router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { assert.Equal(t, "GET", req.Method) assert.Equal(t, "/path2", req.URL.Path) - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "hola!") })) w := performRequest(router, "POST", "/path") - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hello", w.Body.String()) w = performRequest(router, "GET", "/path2") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "hola!", w.Body.String()) } From 6c8a9731345b6782c923054b0c973c2b4b9394a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 14 Aug 2018 11:35:13 +0800 Subject: [PATCH 057/111] add issue and pull request template explain (#1483) * add issue/pr template explain * add issue/pr template explain --- .github/ISSUE_TEMPLATE.md | 13 +++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..de067ce0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +- With issues: + - Use the search tool before opening a new issue. + - Please provide source code and commit sha if you found a bug. + - Review existing issues and provide feedback or react to them. + +- gin version (or commit ref): +- git version: +- operating system: + +## Description + +## Screenshots + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..8630bc35 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +- With pull requests: + - Open your pull request against `master` + - Your pull request should have no more than two commits, if not you should squash them. + - It should pass all tests in the available continuous integrations systems such as TravisCI. + - You should add/modify tests to cover your proposed code changes. + - If your pull request contains a new feature, please document it on the README. + From b869fe1415e4b9eb52f247441830d502aece2d4d Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 14 Aug 2018 10:58:52 +0200 Subject: [PATCH 058/111] docs: add changelog for v1.3.0, update authors and version const (#1478) * docs: add changelog for v1.3.0, update authors and version const * add link for every referenced pull request (#1481) * docs: add changelog for v1.3.0, update authors and version const * add link for pr --- AUTHORS.md | 6 +++++- CHANGELOG.md | 24 +++++++++++++++++++++++- gin.go | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 7ab7213d..dda19bcf 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,8 +1,12 @@ List of all the awesome people working to make Gin the best Web Framework in Go. +## gin 1.x series authors + +**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho) + ## gin 0.x series authors -**Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) +**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) People and companies, who have contributed, in alphabetical order. diff --git a/CHANGELOG.md b/CHANGELOG.md index ee485ec3..e6a108ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # CHANGELOG -### Gin 1.2 +### Gin 1.3.0 + +- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383) +- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358) +- [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273) +- [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304) +- [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341) +- [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336) +- [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333) +- [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138) +- [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277) +- [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047) +- [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117) +- [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029) +- [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026) +- [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999) +- [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993) +- [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie) +- [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072) +- [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250) +- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460) + +### Gin 1.2.0 - [NEW] Switch from godeps to govendor - [NEW] Add support for Let's Encrypt via gin-gonic/autotls diff --git a/gin.go b/gin.go index fc4d6564..aa62e014 100644 --- a/gin.go +++ b/gin.go @@ -16,7 +16,7 @@ import ( const ( // Version is Framework's version. - Version = "v1.2" + Version = "v1.3.0" defaultMultipartMemory = 32 << 20 // 32 MB ) From 64a45486421c37bd60af66ce96a3ef7b62a191aa Mon Sep 17 00:00:00 2001 From: Abner Chen Date: Wed, 15 Aug 2018 13:42:12 +0800 Subject: [PATCH 059/111] Fix typo in readme (#1490) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28598baf..d8e3ffaa 100644 --- a/README.md +++ b/README.md @@ -679,7 +679,7 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} ``` -[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. ### Only Bind Query String From bef6c56c8928cfe1d96a9fdefe29fcfdeb2b2f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 16 Aug 2018 17:38:17 +0800 Subject: [PATCH 060/111] chore: upgrade dependency library version (#1491) upgrade lib version, and upgrade `github.com/json-iterator/go` to add two libs. --- vendor/vendor.json | 94 +++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index c34c2de3..e6d038a4 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,13 +1,12 @@ { - "comment": "v1.2", + "comment": "v1.3.0", "ignore": "test", "package": [ { - "checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", - "comment": "v1.1.0", + "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=", "path": "github.com/davecgh/go-spew/spew", - "revision": "346938d642f2ec3594ed81d874461961cd0faa76", - "revisionTime": "2016-10-29T20:57:26Z" + "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73", + "revisionTime": "2018-02-21T22:46:20Z" }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", @@ -16,35 +15,62 @@ "revisionTime": "2017-01-09T09:34:21Z" }, { - "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", + "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=", "path": "github.com/golang/protobuf/proto", - "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", - "revisionTime": "2017-06-01T23:02:30Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", + "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", "path": "github.com/json-iterator/go", - "revision": "36b14963da70d11297d313183d7e6388c8510e1e", - "revisionTime": "2017-08-29T15:58:51Z" + "revision": "1624edc4454b8682399def8740d46db5e4362ba4", + "revisionTime": "2018-08-06T06:07:27Z", + "version": "1.1.5", + "versionExact": "1.1.5" }, { - "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", + "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", "path": "github.com/mattn/go-isatty", - "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", - "revisionTime": "2017-03-07T16:30:44Z" + "revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39", + "revisionTime": "2017-09-25T05:34:41Z", + "version": "v0.0.3", + "versionExact": "v0.0.3" }, { - "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", - "comment": "v1.1.4", + "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", + "path": "github.com/modern-go/concurrent", + "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", + "revisionTime": "2018-03-06T01:26:44Z" + }, + { + "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", + "path": "github.com/modern-go/reflect2", + "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", + "revisionTime": "2018-07-18T01:23:57Z" + }, + { + "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", + "path": "github.com/pmezard/go-difflib/difflib", + "revision": "792786c7400a136282c1664665ae0a8db921c6c2", + "revisionTime": "2016-01-10T10:55:54Z" + }, + { + "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", - "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", - "revisionTime": "2016-09-25T22:06:09Z" + "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", + "revisionTime": "2018-05-06T18:05:49Z", + "version": "v1.2.2", + "versionExact": "v1.2.2" }, { - "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", + "checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=", "path": "github.com/ugorji/go/codec", - "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", - "revisionTime": "2017-02-15T20:11:44Z" + "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab", + "revisionTime": "2018-04-07T10:07:33Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", @@ -54,18 +80,26 @@ "revisionTime": "2016-10-18T08:54:36Z" }, { - "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", - "comment": "v8.18.1", - "path": "gopkg.in/go-playground/validator.v8", - "revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c", - "revisionTime": "2016-07-18T13:41:25Z" + "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=", + "path": "golang.org/x/sys/unix", + "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded", + "revisionTime": "2018-08-15T07:37:39Z" }, { - "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", - "comment": "v2", + "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", + "path": "gopkg.in/go-playground/validator.v8", + "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf", + "revisionTime": "2017-07-30T05:02:35Z", + "version": "v8.18.2", + "versionExact": "v8.18.2" + }, + { + "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", "path": "gopkg.in/yaml.v2", - "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", - "revisionTime": "2016-09-28T15:37:09Z" + "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", + "revisionTime": "2018-03-28T19:50:20Z", + "version": "v2.2.1", + "versionExact": "v2.2.1" } ], "rootPath": "github.com/gin-gonic/gin" From 40ab9de4b57ac6ce467a96b1fb4c3379658b29c2 Mon Sep 17 00:00:00 2001 From: syssam Date: Fri, 17 Aug 2018 09:12:15 +0800 Subject: [PATCH 061/111] Add BindXML AND ShouldBindXML #1484 (#1485) Add BindXML AND ShouldBindXML #1484 --- README.md | 61 ++++++++++++++++++++++++++++++++++--------------- context.go | 10 ++++++++ context_test.go | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d8e3ffaa..5de22dc3 100644 --- a/README.md +++ b/README.md @@ -534,10 +534,10 @@ Note that you need to set the corresponding binding tag on all fields you want t Also, Gin provides two sets of methods for binding: - **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindQuery` + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery` - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery` + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery` - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. @@ -547,8 +547,8 @@ You can also specify that specific fields are required. If a field is decorated ```go // Binding from JSON type Login struct { - User string `form:"user" json:"user" binding:"required"` - Password string `form:"password" json:"password" binding:"required"` + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` } func main() { @@ -557,30 +557,55 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err := c.ShouldBindJSON(&json); err == nil { - if json.User == "manu" && json.Password == "123" { - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - } else { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - } - } else { + if err := c.ShouldBindXML(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // user + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err == nil { - if form.User == "manu" && form.Password == "123" { - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - } else { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - } - } else { + if err := c.ShouldBind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Listen and serve on 0.0.0.0:8080 diff --git a/context.go b/context.go index 724ded79..bbdb7e4f 100644 --- a/context.go +++ b/context.go @@ -511,6 +511,11 @@ func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } +// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). +func (c *Context) BindXML(obj interface{}) error { + return c.MustBindWith(obj, binding.XML) +} + // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) @@ -545,6 +550,11 @@ func (c *Context) ShouldBindJSON(obj interface{}) error { return c.ShouldBindWith(obj, binding.JSON) } +// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). +func (c *Context) ShouldBindXML(obj interface{}) error { + return c.ShouldBindWith(obj, binding.XML) +} + // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). func (c *Context) ShouldBindQuery(obj interface{}) error { return c.ShouldBindWith(obj, binding.Query) diff --git a/context_test.go b/context_test.go index 99d5267d..13fb9099 100644 --- a/context_test.go +++ b/context_test.go @@ -1302,6 +1302,26 @@ func TestContextBindWithJSON(t *testing.T) { assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + + FOO + BAR + `)) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `xml:"foo"` + Bar string `xml:"bar"` + } + assert.NoError(t, c.BindXML(&obj)) + assert.Equal(t, "FOO", obj.Foo) + assert.Equal(t, "BAR", obj.Bar) + assert.Equal(t, 0, w.Body.Len()) +} func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() @@ -1372,6 +1392,27 @@ func TestContextShouldBindWithJSON(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + + FOO + BAR + `)) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `xml:"foo"` + Bar string `xml:"bar"` + } + assert.NoError(t, c.ShouldBindXML(&obj)) + assert.Equal(t, "FOO", obj.Foo) + assert.Equal(t, "BAR", obj.Bar) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From 7eb0f74b89af91b609f910f549e312a5c5db2ada Mon Sep 17 00:00:00 2001 From: Alexander Lokhman Date: Fri, 17 Aug 2018 02:41:56 +0100 Subject: [PATCH 062/111] Set default time format in form binding (#1487) --- binding/form_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 3f6b9bfa..a714291f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -178,7 +178,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error { func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { timeFormat := structField.Tag.Get("time_format") if timeFormat == "" { - return errors.New("Blank time format") + timeFormat = time.RFC3339 } if val == "" { From a643d206054d630f66fce6513db80d864159030a Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 17 Aug 2018 11:21:14 +0800 Subject: [PATCH 063/111] readme: fix users link (#1493) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5de22dc3..37eec614 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [http2 server push](#http2-server-push) - [Testing](#testing) -- [Users](#users--) +- [Users](#users) ## Installation From f5451bd645be353ba40cd5d308a545f201e68283 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 17 Aug 2018 11:33:23 +0800 Subject: [PATCH 064/111] Fix typo in README [ci skip] (#1492) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37eec614..920b634b 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 ## Build with [jsoniter](https://github.com/json-iterator/go) -Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. +Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. ```sh $ go build -tags=jsoniter . From f856aa85cd175ad10d2f55b0a41a51a08f4bdb02 Mon Sep 17 00:00:00 2001 From: chainhelen Date: Fri, 17 Aug 2018 14:59:55 +0800 Subject: [PATCH 065/111] Update readme about the version of gin (#1494) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 920b634b..161ea28b 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" ```sh $ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.2 +$ govendor fetch github.com/gin-gonic/gin@v1.3 ``` 4. Copy a starting template inside your project From efdd3c8b81a42e1769fe70483d4828d5bba7e87e Mon Sep 17 00:00:00 2001 From: aljun Date: Sun, 19 Aug 2018 10:45:56 +0800 Subject: [PATCH 066/111] Add support for Protobuf format response and unit test (#1479) `Gin` now have the `protobufBinding` function to check the request format, but didn't have a protobuf response function like `c.YAML()`. In our company [ByteDance](http://bytedance.com/), the largest internet company using golang in China, we use `gin` to transfer __Protobuf__ instead of __Json__, we have to write some internal library to make some wrappers to achieve that, and the code is not elegant. So we really want such a feature. --- README.md | 17 +++++++++++++++-- context.go | 6 ++++++ context_test.go | 27 +++++++++++++++++++++++++++ render/protobuf.go | 33 +++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 31 +++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 render/protobuf.go diff --git a/README.md b/README.md index 161ea28b..ae183f92 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) + - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) - [Serving data from reader](#serving-data-from-reader) @@ -871,7 +871,7 @@ Test it with: $ curl -v --form user=user --form password=password http://localhost:8080/login ``` -### XML, JSON and YAML rendering +### XML, JSON, YAML and ProtoBuf rendering ```go func main() { @@ -905,6 +905,19 @@ func main() { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) + r.GET("/someProtoBuf", func(c *gin.Context) { + reps := []int64{int64(1), int64(2)} + label := "test" + // The specific definition of protobuf is written in the testdata/protoexample file. + data := &protoexample.Test{ + Label: &label, + Reps: reps, + } + // Note that data becomes binary data in the response + // Will output protoexample.Test protobuf serialized data + c.ProtoBuf(http.StatusOK, data) + }) + // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } diff --git a/context.go b/context.go index bbdb7e4f..5cd52837 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,7 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" + "github.com/golang/protobuf/proto" ) // Content-Type MIME of the most common data formats. @@ -845,6 +846,11 @@ func (c *Context) Stream(step func(w io.Writer) bool) { } } +// ProtoBuf serializes the given struct as ProtoBuf into the response body. +func (c *Context) ProtoBuf(code int, obj proto.Message) { + c.Render(code, render.ProtoBuf{Data: obj}) +} + /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ diff --git a/context_test.go b/context_test.go index 13fb9099..675c2a49 100644 --- a/context_test.go +++ b/context_test.go @@ -20,8 +20,11 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "golang.org/x/net/context" + + testdata "github.com/gin-gonic/gin/testdata/protoexample" ) var _ context.Context = &Context{} @@ -954,6 +957,30 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf +// and Content-Type is set to application/x-protobuf +// and we just use the example protobuf to check if the response is correct +func TestContextRenderProtoBuf(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + c.ProtoBuf(http.StatusCreated, data) + + protoData, err := proto.Marshal(data) + assert.NoError(t, err) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) +} + func TestContextHeaders(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Header("Content-Type", "text/plain") diff --git a/render/protobuf.go b/render/protobuf.go new file mode 100644 index 00000000..fe826884 --- /dev/null +++ b/render/protobuf.go @@ -0,0 +1,33 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "net/http" + + "github.com/golang/protobuf/proto" +) + +type ProtoBuf struct { + Data proto.Message +} + +var protobufContentType = []string{"application/x-protobuf"} + +func (r ProtoBuf) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + + bytes, err := proto.Marshal(r.Data) + if err != nil { + return err + } + + w.Write(bytes) + return nil +} + +func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { + writeContentType(w, protobufContentType) +} diff --git a/render/render.go b/render/render.go index 4ff1c7b6..df0d1d7c 100755 --- a/render/render.go +++ b/render/render.go @@ -27,6 +27,7 @@ var ( _ Render = MsgPack{} _ Render = Reader{} _ Render = AsciiJSON{} + _ Render = ProtoBuf{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index 47abf262..905b76a1 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,6 +15,8 @@ import ( "strings" "testing" + testdata "github.com/gin-gonic/gin/testdata/protoexample" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" ) @@ -265,6 +267,35 @@ func TestRenderYAMLFail(t *testing.T) { assert.Error(t, err) } +// test Protobuf rendering +func TestRenderProtoBuf(t *testing.T) { + w := httptest.NewRecorder() + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + (ProtoBuf{data}).WriteContentType(w) + protoData, err := proto.Marshal(data) + assert.NoError(t, err) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) + + err = (ProtoBuf{data}).Render(w) + + assert.NoError(t, err) + assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) +} + +func TestRenderProtoBufFail(t *testing.T) { + w := httptest.NewRecorder() + data := &testdata.Test{} + err := (ProtoBuf{data}).Render(w) + assert.Error(t, err) +} + func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ From 6073a79ee08657ea9784c26b8521a8f8ef9e1644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 17:39:58 +0800 Subject: [PATCH 067/111] not use protobuf on context but use it on render (#1496) --- context.go | 11 +++++------ render/json.go | 0 render/protobuf.go | 4 ++-- render/render.go | 0 render/render_test.go | 3 ++- 5 files changed, 9 insertions(+), 9 deletions(-) mode change 100755 => 100644 render/json.go mode change 100755 => 100644 render/render.go mode change 100755 => 100644 render/render_test.go diff --git a/context.go b/context.go index 5cd52837..6d80284e 100644 --- a/context.go +++ b/context.go @@ -20,7 +20,6 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" - "github.com/golang/protobuf/proto" ) // Content-Type MIME of the most common data formats. @@ -784,6 +783,11 @@ func (c *Context) YAML(code int, obj interface{}) { c.Render(code, render.YAML{Data: obj}) } +// ProtoBuf serializes the given struct as ProtoBuf into the response body. +func (c *Context) ProtoBuf(code int, obj interface{}) { + c.Render(code, render.ProtoBuf{Data: obj}) +} + // String writes the given string into the response body. func (c *Context) String(code int, format string, values ...interface{}) { c.Render(code, render.String{Format: format, Data: values}) @@ -846,11 +850,6 @@ func (c *Context) Stream(step func(w io.Writer) bool) { } } -// ProtoBuf serializes the given struct as ProtoBuf into the response body. -func (c *Context) ProtoBuf(code int, obj proto.Message) { - c.Render(code, render.ProtoBuf{Data: obj}) -} - /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ diff --git a/render/json.go b/render/json.go old mode 100755 new mode 100644 diff --git a/render/protobuf.go b/render/protobuf.go index fe826884..34f1e9b5 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -11,7 +11,7 @@ import ( ) type ProtoBuf struct { - Data proto.Message + Data interface{} } var protobufContentType = []string{"application/x-protobuf"} @@ -19,7 +19,7 @@ var protobufContentType = []string{"application/x-protobuf"} func (r ProtoBuf) Render(w http.ResponseWriter) error { r.WriteContentType(w) - bytes, err := proto.Marshal(r.Data) + bytes, err := proto.Marshal(r.Data.(proto.Message)) if err != nil { return err } diff --git a/render/render.go b/render/render.go old mode 100755 new mode 100644 diff --git a/render/render_test.go b/render/render_test.go old mode 100755 new mode 100644 index 905b76a1..cd20d01d --- a/render/render_test.go +++ b/render/render_test.go @@ -15,10 +15,11 @@ import ( "strings" "testing" - testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" + + testdata "github.com/gin-gonic/gin/testdata/protoexample" ) // TODO unit tests From 32b58e0fd2b101cc0bedc925ac71f1bbca620869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 22:14:02 +0800 Subject: [PATCH 068/111] render: update msgpack usage (#1498) please see msgpack usage: https://github.com/ugorji/go/tree/master/codec#usage --- render/msgpack.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/msgpack.go b/render/msgpack.go index e6c13e58..b6b8aa0f 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -26,6 +26,6 @@ func (r MsgPack) Render(w http.ResponseWriter) error { func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) - var h codec.Handle = new(codec.MsgpackHandle) - return codec.NewEncoder(w, h).Encode(obj) + var mh codec.MsgpackHandle + return codec.NewEncoder(w, &mh).Encode(obj) } From b7bb9baa642d2b9d5918a5bcd3829dae64c65257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 22:52:43 +0800 Subject: [PATCH 069/111] chore: add missing copyright and update if/else (#1497) --- render/reader.go | 4 ++++ render/text.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/render/reader.go b/render/reader.go index be2132c8..7a06cce4 100644 --- a/render/reader.go +++ b/render/reader.go @@ -1,3 +1,7 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package render import ( diff --git a/render/text.go b/render/text.go index 74cd26be..7a6acc44 100644 --- a/render/text.go +++ b/render/text.go @@ -30,7 +30,7 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) if len(data) > 0 { fmt.Fprintf(w, format, data...) - } else { - io.WriteString(w, format) + return } + io.WriteString(w, format) } From c6110f970ca5af365283bd4083b8a105e974e92c Mon Sep 17 00:00:00 2001 From: Filip Figiel Date: Mon, 20 Aug 2018 09:15:31 +0200 Subject: [PATCH 070/111] Add PureJSON renderer (#694) Closes #693 --- README.md | 28 ++++++++++++++++++++++++++++ context_17.go | 17 +++++++++++++++++ context_17_test.go | 27 +++++++++++++++++++++++++++ context_test.go | 5 +++-- json/json.go | 1 + render/json_17.go | 28 ++++++++++++++++++++++++++++ render/render_17_test.go | 26 ++++++++++++++++++++++++++ render/render_test.go | 5 +++-- 8 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 context_17.go create mode 100644 context_17_test.go create mode 100644 render/json_17.go create mode 100644 render/render_17_test.go diff --git a/README.md b/README.md index ae183f92..053750bb 100644 --- a/README.md +++ b/README.md @@ -991,6 +991,34 @@ func main() { } ``` +#### PureJSON + +Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. +This feature is unavailable in Go 1.6 and lower. + +```go +func main() { + r := gin.Default() + + // Serves unicode entities + r.GET("/json", func(c *gin.Context) { + c.JSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // Serves literal characters + r.GET("/purejson", func(c *gin.Context) { + c.PureJSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // listen and serve on 0.0.0.0:8080 + r.Run(":8080) +} +``` + ### Serving static files ```go diff --git a/context_17.go b/context_17.go new file mode 100644 index 00000000..8e9f75ad --- /dev/null +++ b/context_17.go @@ -0,0 +1,17 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package gin + +import ( + "github.com/gin-gonic/gin/render" +) + +// PureJSON serializes the given struct as JSON into the response body. +// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. +func (c *Context) PureJSON(code int, obj interface{}) { + c.Render(code, render.PureJSON{Data: obj}) +} diff --git a/context_17_test.go b/context_17_test.go new file mode 100644 index 00000000..d2251904 --- /dev/null +++ b/context_17_test.go @@ -0,0 +1,27 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package gin + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// Tests that the response is serialized as JSON +// and Content-Type is set to application/json +// and special HTML characters are preserved +func TestContextRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} diff --git a/context_test.go b/context_test.go index 675c2a49..782f7bed 100644 --- a/context_test.go +++ b/context_test.go @@ -619,14 +619,15 @@ func TestContextRenderPanicIfErr(t *testing.T) { // Tests that the response is serialized as JSON // and Content-Type is set to application/json +// and special HTML characters are escaped func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(http.StatusCreated, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } diff --git a/json/json.go b/json/json.go index aa76aa30..4f643c56 100644 --- a/json/json.go +++ b/json/json.go @@ -12,4 +12,5 @@ var ( Marshal = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder ) diff --git a/render/json_17.go b/render/json_17.go new file mode 100644 index 00000000..df0dc145 --- /dev/null +++ b/render/json_17.go @@ -0,0 +1,28 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package render + +import ( + "net/http" + + "github.com/gin-gonic/gin/json" +) + +type PureJSON struct { + Data interface{} +} + +func (r PureJSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + encoder := json.NewEncoder(w) + encoder.SetEscapeHTML(false) + return encoder.Encode(r.Data) +} + +func (r PureJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/render_17_test.go b/render/render_17_test.go new file mode 100644 index 00000000..68330090 --- /dev/null +++ b/render/render_17_test.go @@ -0,0 +1,26 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package render + +import ( + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + "html": "", + } + err := (PureJSON{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} diff --git a/render/render_test.go b/render/render_test.go index cd20d01d..fe9228e9 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -52,7 +52,8 @@ func TestRenderMsgPack(t *testing.T) { func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ - "foo": "bar", + "foo": "bar", + "html": "", } (JSON{data}).WriteContentType(w) @@ -61,7 +62,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } From 0ebd42d0a921e49ed6a36fae33f2a2dcae800d6f Mon Sep 17 00:00:00 2001 From: junfengye Date: Mon, 20 Aug 2018 18:25:45 +0800 Subject: [PATCH 071/111] Update jsoniter.go (#1500) add newencoder to fix compile error for -tags=jsoniter --- json/jsoniter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/json/jsoniter.go b/json/jsoniter.go index ffe1424a..f1ed5bea 100644 --- a/json/jsoniter.go +++ b/json/jsoniter.go @@ -13,4 +13,5 @@ var ( Marshal = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder ) From 85f3e78abcd408e7e155f58f72094446c4411e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 20 Aug 2018 21:49:24 +0800 Subject: [PATCH 072/111] chore: remove else instead of return/continue (#1502) As[ Effective Go](https://golang.org/doc/effective_go.html?#if) about `if` said, remove else statement instead of return/continue statement. --- binding/form_mapping.go | 16 ++++++++-------- context.go | 8 ++++---- render/json.go | 6 ++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index a714291f..f46a0dc1 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -74,16 +74,16 @@ func mapForm(ptr interface{}, form map[string][]string) error { } } val.Field(i).Set(slice) - } else { - if _, isTime := structField.Interface().(time.Time); isTime { - if err := setTimeField(inputValue[0], typeField, structField); err != nil { - return err - } - continue - } - if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + continue + } + if _, isTime := structField.Interface().(time.Time); isTime { + if err := setTimeField(inputValue[0], typeField, structField); err != nil { return err } + continue + } + if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + return err } } return nil diff --git a/context.go b/context.go index 6d80284e..063c72f0 100644 --- a/context.go +++ b/context.go @@ -665,9 +665,9 @@ func (c *Context) Status(code int) { func (c *Context) Header(key, value string) { if value == "" { c.Writer.Header().Del(key) - } else { - c.Writer.Header().Set(key, value) + return } + c.Writer.Header().Set(key, value) } // GetHeader returns value from request headers. @@ -755,9 +755,9 @@ func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") if callback == "" { c.Render(code, render.JSON{Data: obj}) - } else { - c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) + return } + c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) } // JSON serializes the given struct as JSON into the response body. diff --git a/render/json.go b/render/json.go index 6e5089a0..4d1857f5 100644 --- a/render/json.go +++ b/render/json.go @@ -128,10 +128,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { var buffer bytes.Buffer for _, r := range string(ret) { - cvt := "" - if r < 128 { - cvt = string(r) - } else { + cvt := string(r) + if r >= 128 { cvt = fmt.Sprintf("\\u%04x", int64(r)) } buffer.WriteString(cvt) From 0da5b0c85ac2539e775a7e63aebf3e0b5808dab4 Mon Sep 17 00:00:00 2001 From: anoty Date: Tue, 21 Aug 2018 13:29:25 +0800 Subject: [PATCH 073/111] format readme code import (#1503) --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 053750bb..9f66dd76 100644 --- a/README.md +++ b/README.md @@ -750,9 +750,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco ```go package main -import "log" -import "github.com/gin-gonic/gin" -import "time" +import ( + "log" + "time" + + "github.com/gin-gonic/gin" +) type Person struct { Name string `form:"name"` From 09d342abbc35e473bfbe11abaecdfe237df0630d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 30 Aug 2018 14:22:51 +0800 Subject: [PATCH 074/111] Add golang 1.11.x testing (#1514) * Add golang 1.11.x testing * remove the latest golang testing See the issue: https://github.com/gin-gonic/gin/pull/1510 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9101568..a93458f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.8.x - 1.9.x - 1.10.x - - master + - 1.11.x git: depth: 10 From 708b76adf011840eb587d57804edf93b2ef4ba75 Mon Sep 17 00:00:00 2001 From: llgoer Date: Thu, 30 Aug 2018 14:29:26 +0800 Subject: [PATCH 075/111] Update README.md (#1509) change `ShouldBindXML` to `ShouldBindJSON` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f66dd76..7d13cf20 100644 --- a/README.md +++ b/README.md @@ -557,7 +557,7 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err := c.ShouldBindXML(&json); err != nil { + if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } From 72db8acd99feab85e81ac6e2ebd16c76e0f986be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 30 Aug 2018 19:04:03 +0800 Subject: [PATCH 076/111] add internal package which includes json package (#1504) --- binding/json.go | 2 +- errors.go | 2 +- errors_test.go | 2 +- {json => internal/json}/json.go | 0 {json => internal/json}/jsoniter.go | 0 render/json.go | 2 +- render/json_17.go | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename {json => internal/json}/json.go (100%) rename {json => internal/json}/jsoniter.go (100%) diff --git a/binding/json.go b/binding/json.go index fea17bb2..310922c1 100644 --- a/binding/json.go +++ b/binding/json.go @@ -9,7 +9,7 @@ import ( "io" "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) // EnableDecoderUseNumber is used to call the UseNumber method on the JSON diff --git a/errors.go b/errors.go index dbfccd85..6f90377e 100644 --- a/errors.go +++ b/errors.go @@ -9,7 +9,7 @@ import ( "fmt" "reflect" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type ErrorType uint64 diff --git a/errors_test.go b/errors_test.go index 0626611f..9351b578 100644 --- a/errors_test.go +++ b/errors_test.go @@ -8,7 +8,7 @@ import ( "errors" "testing" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" "github.com/stretchr/testify/assert" ) diff --git a/json/json.go b/internal/json/json.go similarity index 100% rename from json/json.go rename to internal/json/json.go diff --git a/json/jsoniter.go b/internal/json/jsoniter.go similarity index 100% rename from json/jsoniter.go rename to internal/json/jsoniter.go diff --git a/render/json.go b/render/json.go index 4d1857f5..f6b7878e 100644 --- a/render/json.go +++ b/render/json.go @@ -10,7 +10,7 @@ import ( "html/template" "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type JSON struct { diff --git a/render/json_17.go b/render/json_17.go index df0dc145..75f7f374 100644 --- a/render/json_17.go +++ b/render/json_17.go @@ -9,7 +9,7 @@ package render import ( "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type PureJSON struct { From 7451a402bbab9dd439b09aa377f18638c7d8fd55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 30 Aug 2018 23:36:53 +0800 Subject: [PATCH 077/111] chore: update vendor version (#1520) #1491 adds some lib when upgrade json-iterator but it is not needed, and use `v1.1.5` not `1.1.5` version for json-iterator. --- vendor/vendor.json | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index e6d038a4..86df11be 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -27,8 +27,8 @@ "path": "github.com/json-iterator/go", "revision": "1624edc4454b8682399def8740d46db5e4362ba4", "revisionTime": "2018-08-06T06:07:27Z", - "version": "1.1.5", - "versionExact": "1.1.5" + "version": "v1.1", + "versionExact": "v1.1.5" }, { "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", @@ -38,24 +38,6 @@ "version": "v0.0.3", "versionExact": "v0.0.3" }, - { - "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", - "path": "github.com/modern-go/concurrent", - "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", - "revisionTime": "2018-03-06T01:26:44Z" - }, - { - "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", - "path": "github.com/modern-go/reflect2", - "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", - "revisionTime": "2018-07-18T01:23:57Z" - }, - { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "792786c7400a136282c1664665ae0a8db921c6c2", - "revisionTime": "2016-01-10T10:55:54Z" - }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", @@ -79,12 +61,6 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, - { - "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=", - "path": "golang.org/x/sys/unix", - "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded", - "revisionTime": "2018-08-15T07:37:39Z" - }, { "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", "path": "gopkg.in/go-playground/validator.v8", From 705e1992981b974ce131303cdf415ff78114f728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 1 Sep 2018 00:40:33 +0800 Subject: [PATCH 078/111] chore: update issue_implate (#1524) --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index de067ce0..9d49aa41 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,8 +3,8 @@ - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. +- go version: - gin version (or commit ref): -- git version: - operating system: ## Description From 500ebd9ea866c57427aeaddc31f374adedd4084b Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 31 Aug 2018 22:38:16 +0200 Subject: [PATCH 079/111] docs: add fnproject to gin's user list (#1505) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d13cf20..a4bc4e76 100644 --- a/README.md +++ b/README.md @@ -1885,5 +1885,6 @@ func TestPingRoute(t *testing.T) { Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. -* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go +* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. From df1e17c2f0ca7179b5acf03ce1da5c4f07c4dd7d Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Wed, 12 Sep 2018 02:13:16 +0100 Subject: [PATCH 080/111] remove debug print statements from test code (#1540) Found using https://go-critic.github.io/overview#commentedOutCode-ref --- tree_test.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tree_test.go b/tree_test.go index 5bc27171..152f6331 100644 --- a/tree_test.go +++ b/tree_test.go @@ -125,8 +125,6 @@ func TestTreeAddAndGet(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/a", false, "/a", nil}, {"/", true, "", nil}, @@ -168,8 +166,6 @@ func TestTreeWildcard(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, @@ -208,7 +204,6 @@ func TestUnescapeParameters(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") unescape := true checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, @@ -260,8 +255,6 @@ func testRoutes(t *testing.T, routes []testRoute) { t.Errorf("unexpected panic for route '%s': %v", route.path, recv) } } - - //printChildren(tree, "") } func TestTreeWildcardConflict(t *testing.T) { @@ -328,8 +321,6 @@ func TestTreeDupliatePath(t *testing.T) { } } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/doc/", false, "/doc/", nil}, @@ -444,8 +435,6 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { } } - //printChildren(tree, "") - tsrRoutes := [...]string{ "/hi/", "/b", From 3f27866f806536be319b3e342001dd4e0fb81691 Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Wed, 12 Sep 2018 14:21:26 +0100 Subject: [PATCH 081/111] simplify slice expressions: s[:] => s (#1541) Found using https://go-critic.github.io/overview#unslice-ref --- context_test.go | 2 +- gin_test.go | 20 ++++++++++---------- render/render_test.go | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/context_test.go b/context_test.go index 782f7bed..4c307080 100644 --- a/context_test.go +++ b/context_test.go @@ -978,7 +978,7 @@ func TestContextRenderProtoBuf(t *testing.T) { assert.NoError(t, err) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) } diff --git a/gin_test.go b/gin_test.go index b3cab715..95b9cc16 100644 --- a/gin_test.go +++ b/gin_test.go @@ -88,7 +88,7 @@ func TestLoadHTMLGlob(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -101,7 +101,7 @@ func TestLoadHTMLGlob2(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -114,7 +114,7 @@ func TestLoadHTMLGlob3(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -134,7 +134,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -148,7 +148,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + assert.Equal(t, "Date: 2017/07/01\n", string(resp)) td() } @@ -187,7 +187,7 @@ func TestLoadHTMLFiles(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -199,7 +199,7 @@ func TestLoadHTMLFiles2(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -211,7 +211,7 @@ func TestLoadHTMLFiles3(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -230,7 +230,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -243,7 +243,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + assert.Equal(t, "Date: 2017/07/01\n", string(resp)) td() } diff --git a/render/render_test.go b/render/render_test.go index fe9228e9..09ccc658 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -287,7 +287,7 @@ func TestRenderProtoBuf(t *testing.T) { err = (ProtoBuf{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } From d510595aa58c2417373d89a8d8ffa21cf58673cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 15 Sep 2018 10:23:32 +0800 Subject: [PATCH 082/111] chore: add some annotations (#1544) ref: #1075 because I am not a native English, maybe have a bit problem. --- README.md | 1 + examples/basic/main.go | 6 +++--- examples/custom-validation/server.go | 1 + examples/realtime-advanced/main.go | 3 +++ examples/realtime-advanced/stats.go | 1 + ginS/gins.go | 9 ++++++++- internal/json/json.go | 10 +++++++--- internal/json/jsoniter.go | 12 ++++++++---- render/data.go | 2 ++ render/html.go | 14 +++++++++++++- render/json.go | 17 +++++++++++++++++ render/json_17.go | 3 +++ render/msgpack.go | 4 ++++ render/protobuf.go | 3 +++ render/reader.go | 3 +++ render/redirect.go | 3 +++ render/render.go | 3 +++ render/text.go | 4 ++++ render/xml.go | 3 +++ render/yaml.go | 3 +++ utils.go | 8 ++++---- 21 files changed, 97 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a4bc4e76..9a97ec54 100644 --- a/README.md +++ b/README.md @@ -657,6 +657,7 @@ import ( "gopkg.in/go-playground/validator.v8" ) +// Booking contains binded and validated data. type Booking struct { CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` diff --git a/examples/basic/main.go b/examples/basic/main.go index 48fa7bba..1c9e0ac4 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" ) -var DB = make(map[string]string) +var db = make(map[string]string) func setupRouter() *gin.Engine { // Disable Console Color @@ -21,7 +21,7 @@ func setupRouter() *gin.Engine { // Get user value r.GET("/user/:name", func(c *gin.Context) { user := c.Params.ByName("name") - value, ok := DB[user] + value, ok := db[user] if ok { c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) } else { @@ -50,7 +50,7 @@ func setupRouter() *gin.Engine { } if c.Bind(&json) == nil { - DB[user] = json.Value + db[user] = json.Value c.JSON(http.StatusOK, gin.H{"status": "ok"}) } }) diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go index dea0c302..9238200b 100644 --- a/examples/custom-validation/server.go +++ b/examples/custom-validation/server.go @@ -10,6 +10,7 @@ import ( "gopkg.in/go-playground/validator.v8" ) +// Booking contains binded and validated data. type Booking struct { CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go index 1f3c8585..a16c58b0 100644 --- a/examples/realtime-advanced/main.go +++ b/examples/realtime-advanced/main.go @@ -13,16 +13,19 @@ func main() { StartGin() } +// ConfigRuntime sets the number of operating system threads. func ConfigRuntime() { nuCPU := runtime.NumCPU() runtime.GOMAXPROCS(nuCPU) fmt.Printf("Running with %d CPUs\n", nuCPU) } +// StartWrokers start starsWorker by goroutine. func StartWorkers() { go statsWorker() } +// StartGin starts gin web server with setting router. func StartGin() { gin.SetMode(gin.ReleaseMode) diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 4afedcb5..a6488035 100644 --- a/examples/realtime-advanced/stats.go +++ b/examples/realtime-advanced/stats.go @@ -50,6 +50,7 @@ func connectedUsers() uint64 { return uint64(connected) } +// Stats returns savedStats data. func Stats() map[string]uint64 { mutexStats.RLock() defer mutexStats.RUnlock() diff --git a/ginS/gins.go b/ginS/gins.go index a7686f23..04cf131c 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -22,14 +22,17 @@ func engine() *gin.Engine { return internalEngine } +// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob. func LoadHTMLGlob(pattern string) { engine().LoadHTMLGlob(pattern) } +// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles. func LoadHTMLFiles(files ...string) { engine().LoadHTMLFiles(files...) } +// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate. func SetHTMLTemplate(templ *template.Template) { engine().SetHTMLTemplate(templ) } @@ -39,7 +42,7 @@ func NoRoute(handlers ...gin.HandlerFunc) { engine().NoRoute(handlers...) } -// NoMethod sets the handlers called when... TODO +// NoMethod is a wrapper for Engine.NoMethod. func NoMethod(handlers ...gin.HandlerFunc) { engine().NoMethod(handlers...) } @@ -50,6 +53,7 @@ func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup { return engine().Group(relativePath, handlers...) } +// Handle is a wrapper for Engine.Handle. func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Handle(httpMethod, relativePath, handlers...) } @@ -89,10 +93,12 @@ func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().HEAD(relativePath, handlers...) } +// Any is a wrapper for Engine.Any. func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Any(relativePath, handlers...) } +// StaticFile is a wrapper for Engine.StaticFile. func StaticFile(relativePath, filepath string) gin.IRoutes { return engine().StaticFile(relativePath, filepath) } @@ -107,6 +113,7 @@ func Static(relativePath, root string) gin.IRoutes { return engine().Static(relativePath, root) } +// StaticFS is a wrapper for Engine.StaticFS. func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } diff --git a/internal/json/json.go b/internal/json/json.go index 4f643c56..419d35f2 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -9,8 +9,12 @@ package json import "encoding/json" var ( - Marshal = json.Marshal + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent - NewDecoder = json.NewDecoder - NewEncoder = json.NewEncoder + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder ) diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index f1ed5bea..2021c53c 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -9,9 +9,13 @@ package json import "github.com/json-iterator/go" var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary - Marshal = json.Marshal + json = jsoniter.ConfigCompatibleWithStandardLibrary + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent - NewDecoder = json.NewDecoder - NewEncoder = json.NewEncoder + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder ) diff --git a/render/data.go b/render/data.go index 33194913..6ba657ba 100644 --- a/render/data.go +++ b/render/data.go @@ -6,6 +6,7 @@ package render import "net/http" +// Data contains ContentType and bytes data. type Data struct { ContentType string Data []byte @@ -18,6 +19,7 @@ func (r Data) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (Data) writes custom ContentType. func (r Data) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } diff --git a/render/html.go b/render/html.go index 1e3be65b..4d3d5812 100644 --- a/render/html.go +++ b/render/html.go @@ -9,20 +9,27 @@ import ( "net/http" ) +// Delims represents a set of Left and Right delimiters for HTML template rendering. type Delims struct { - Left string + // Left delimiter, defaults to {{. + Left string + // Right delimiter, defaults to }}. Right string } +// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. type HTMLRender interface { + // Instance returns an HTML instance. Instance(string, interface{}) Render } +// HTMLProduction contains template reference and its delims. type HTMLProduction struct { Template *template.Template Delims Delims } +// HTMLDebug contains template delims and pattern and function with file list. type HTMLDebug struct { Files []string Glob string @@ -30,6 +37,7 @@ type HTMLDebug struct { FuncMap template.FuncMap } +// HTML contains template reference and its name with given interface object. type HTML struct { Template *template.Template Name string @@ -38,6 +46,7 @@ type HTML struct { var htmlContentType = []string{"text/html; charset=utf-8"} +// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.. func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, @@ -46,6 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render { } } +// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.. func (r HTMLDebug) Instance(name string, data interface{}) Render { return HTML{ Template: r.loadTemplate(), @@ -66,6 +76,7 @@ func (r HTMLDebug) loadTemplate() *template.Template { panic("the HTML debug render was created without files or glob pattern") } +// Render (HTML) executes template and writes its result with custom ContentType for response. func (r HTML) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -75,6 +86,7 @@ func (r HTML) Render(w http.ResponseWriter) error { return r.Template.ExecuteTemplate(w, r.Name, r.Data) } +// WriteContentType (HTML) writes HTML ContentType. func (r HTML) WriteContentType(w http.ResponseWriter) { writeContentType(w, htmlContentType) } diff --git a/render/json.go b/render/json.go index f6b7878e..32d0fc42 100644 --- a/render/json.go +++ b/render/json.go @@ -13,34 +13,41 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// JSON contains the given interface object. type JSON struct { Data interface{} } +// IndentedJSON contains the given interface object. type IndentedJSON struct { Data interface{} } +// SecureJSON contains the given interface object and its prefix. type SecureJSON struct { Prefix string Data interface{} } +// JsonpJSON contains the given interface object its callback. type JsonpJSON struct { Callback string Data interface{} } +// AsciiJSON contains the given interface object. type AsciiJSON struct { Data interface{} } +// SecureJSONPrefix is a string which represents SecureJSON prefix. type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} +// Render (JSON) writes data with custom ContentType. func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { panic(err) @@ -48,10 +55,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (JSON) writes JSON ContentType. func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) @@ -62,6 +71,7 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { return nil } +// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.MarshalIndent(r.Data, "", " ") @@ -72,10 +82,12 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (IndentedJSON) writes JSON ContentType. func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType. func (r SecureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.Marshal(r.Data) @@ -90,10 +102,12 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (SecureJSON) writes JSON ContentType. func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) @@ -115,10 +129,12 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { return nil } +// WriteContentType (JsonpJSON) writes Javascript ContentType. func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } +// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) @@ -139,6 +155,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { return nil } +// WriteContentType (AsciiJSON) writes JSON ContentType. func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonAsciiContentType) } diff --git a/render/json_17.go b/render/json_17.go index 75f7f374..208193c7 100644 --- a/render/json_17.go +++ b/render/json_17.go @@ -12,10 +12,12 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// PureJSON contains the given interface object. type PureJSON struct { Data interface{} } +// Render (PureJSON) writes custom ContentType and encodes the given interface object. func (r PureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) encoder := json.NewEncoder(w) @@ -23,6 +25,7 @@ func (r PureJSON) Render(w http.ResponseWriter) error { return encoder.Encode(r.Data) } +// WriteContentType (PureJSON) writes custom ContentType. func (r PureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } diff --git a/render/msgpack.go b/render/msgpack.go index b6b8aa0f..dc681fcf 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -10,20 +10,24 @@ import ( "github.com/ugorji/go/codec" ) +// MsgPack contains the given interface object. type MsgPack struct { Data interface{} } var msgpackContentType = []string{"application/msgpack; charset=utf-8"} +// WriteContentType (MsgPack) writes MsgPack ContentType. func (r MsgPack) WriteContentType(w http.ResponseWriter) { writeContentType(w, msgpackContentType) } +// Render (MsgPack) encodes the given interface object and writes data with custom ContentType. func (r MsgPack) Render(w http.ResponseWriter) error { return WriteMsgPack(w, r.Data) } +// WriteMsgPack writes MsgPack ContentType and encodes the given interface object. func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) var mh codec.MsgpackHandle diff --git a/render/protobuf.go b/render/protobuf.go index 34f1e9b5..47895072 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -10,12 +10,14 @@ import ( "github.com/golang/protobuf/proto" ) +// ProtoBuf contains the given interface object. type ProtoBuf struct { Data interface{} } var protobufContentType = []string{"application/x-protobuf"} +// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType. func (r ProtoBuf) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -28,6 +30,7 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (ProtoBuf) writes ProtoBuf ContentType. func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { writeContentType(w, protobufContentType) } diff --git a/render/reader.go b/render/reader.go index 7a06cce4..ab60e53a 100644 --- a/render/reader.go +++ b/render/reader.go @@ -10,6 +10,7 @@ import ( "strconv" ) +// Reader contains the IO reader and its length, and custom ContentType and other headers. type Reader struct { ContentType string ContentLength int64 @@ -26,10 +27,12 @@ func (r Reader) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (Reader) writes custom ContentType. func (r Reader) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } +// writeHeaders writes custom Header. func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { header := w.Header() for k, v := range headers { diff --git a/render/redirect.go b/render/redirect.go index a0634f5a..9c145fe2 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -9,12 +9,14 @@ import ( "net/http" ) +// Redirect contains the http request reference and redirects status code and location. type Redirect struct { Code int Request *http.Request Location string } +// Render (Redirect) redirects the http request to new location and writes redirect response. func (r Redirect) Render(w http.ResponseWriter) error { // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) // when we upgrade go version we can use http.StatusPermanentRedirect @@ -25,4 +27,5 @@ func (r Redirect) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (Redirect) don't write any ContentType. func (r Redirect) WriteContentType(http.ResponseWriter) {} diff --git a/render/render.go b/render/render.go index df0d1d7c..abfc79fc 100644 --- a/render/render.go +++ b/render/render.go @@ -6,8 +6,11 @@ package render import "net/http" +// Render interface is to be implemented by JSON, XML, HTML, YAML and so on. type Render interface { + // Render writes data with custom ContentType. Render(http.ResponseWriter) error + // WriteContentType writes custom ContentType. WriteContentType(w http.ResponseWriter) } diff --git a/render/text.go b/render/text.go index 7a6acc44..2ea7343c 100644 --- a/render/text.go +++ b/render/text.go @@ -10,6 +10,7 @@ import ( "net/http" ) +// String contains the given interface object slice and its format. type String struct { Format string Data []interface{} @@ -17,15 +18,18 @@ type String struct { var plainContentType = []string{"text/plain; charset=utf-8"} +// Render (String) writes data with custom ContentType. func (r String) Render(w http.ResponseWriter) error { WriteString(w, r.Format, r.Data) return nil } +// WriteContentType (String) writes Plain ContentType. func (r String) WriteContentType(w http.ResponseWriter) { writeContentType(w, plainContentType) } +// WriteString writes data according to its format and write custom ContentType. func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) if len(data) > 0 { diff --git a/render/xml.go b/render/xml.go index cff1ac3e..cc5390a2 100644 --- a/render/xml.go +++ b/render/xml.go @@ -9,17 +9,20 @@ import ( "net/http" ) +// XML contains the given interface object. type XML struct { Data interface{} } var xmlContentType = []string{"application/xml; charset=utf-8"} +// Render (XML) encodes the given interface object and writes data with custom ContentType. func (r XML) Render(w http.ResponseWriter) error { r.WriteContentType(w) return xml.NewEncoder(w).Encode(r.Data) } +// WriteContentType (XML) writes XML ContentType for response. func (r XML) WriteContentType(w http.ResponseWriter) { writeContentType(w, xmlContentType) } diff --git a/render/yaml.go b/render/yaml.go index 25d0ebd4..33bc3254 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -10,12 +10,14 @@ import ( "gopkg.in/yaml.v2" ) +// YAML contains the given interface object. type YAML struct { Data interface{} } var yamlContentType = []string{"application/x-yaml; charset=utf-8"} +// Render (YAML) marshals the given interface object and writes data with custom ContentType. func (r YAML) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -28,6 +30,7 @@ func (r YAML) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (YAML) writes YAML ContentType for response. func (r YAML) WriteContentType(w http.ResponseWriter) { writeContentType(w, yamlContentType) } diff --git a/utils.go b/utils.go index bf32c775..f4532d56 100644 --- a/utils.go +++ b/utils.go @@ -14,8 +14,10 @@ import ( "strings" ) +// BindKey indicates a default bind key. const BindKey = "_gin-gonic/gin/bindkey" +// Bind is a helper function for given interface object and returns a Gin middleware. func Bind(val interface{}) HandlerFunc { value := reflect.ValueOf(val) if value.Kind() == reflect.Ptr { @@ -33,16 +35,14 @@ func Bind(val interface{}) HandlerFunc { } } -// WrapF is a helper function for wrapping http.HandlerFunc -// Returns a Gin middleware +// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware. func WrapF(f http.HandlerFunc) HandlerFunc { return func(c *Context) { f(c.Writer, c.Request) } } -// WrapH is a helper function for wrapping http.Handler -// Returns a Gin middleware +// WrapH is a helper function for wrapping http.Handler and returns a Gin middleware. func WrapH(h http.Handler) HandlerFunc { return func(c *Context) { h.ServeHTTP(c.Writer, c.Request) From 6db092f778277ef13da427acfa3501890e952436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 15 Sep 2018 15:21:54 +0800 Subject: [PATCH 083/111] chore: add some annotations (#1550) ref #1075 should all annotations and can close #1075 . --- context.go | 6 ++++++ errors.go | 20 +++++++++++++++----- examples/realtime-advanced/main.go | 2 +- gin.go | 6 ++++++ mode.go | 13 +++++++++++-- render/html.go | 4 ++-- routergroup.go | 8 ++++++-- 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/context.go b/context.go index 063c72f0..809902a3 100644 --- a/context.go +++ b/context.go @@ -711,6 +711,7 @@ func (c *Context) Cookie(name string) (string, error) { return val, nil } +// Render writes the response headers and calls render.Render to render data. func (c *Context) Render(code int, r render.Render) { c.Status(code) @@ -833,6 +834,7 @@ func (c *Context) SSEvent(name string, message interface{}) { }) } +// Stream sends a streaming response. func (c *Context) Stream(step func(w io.Writer) bool) { w := c.Writer clientGone := w.CloseNotify() @@ -854,6 +856,7 @@ func (c *Context) Stream(step func(w io.Writer) bool) { /******** CONTENT NEGOTIATION *******/ /************************************/ +// Negotiate contains all negotiations data. type Negotiate struct { Offered []string HTMLName string @@ -863,6 +866,7 @@ type Negotiate struct { Data interface{} } +// Negotiate calls different Render according acceptable Accept format. func (c *Context) Negotiate(code int, config Negotiate) { switch c.NegotiateFormat(config.Offered...) { case binding.MIMEJSON: @@ -882,6 +886,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { } } +// NegotiateFormat returns an acceptable Accept format. func (c *Context) NegotiateFormat(offered ...string) string { assert1(len(offered) > 0, "you must provide at least one offer") @@ -901,6 +906,7 @@ func (c *Context) NegotiateFormat(offered ...string) string { return "" } +// SetAccepted sets Accept header data. func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } diff --git a/errors.go b/errors.go index 6f90377e..477b9d58 100644 --- a/errors.go +++ b/errors.go @@ -12,18 +12,24 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// ErrorType is an unsigned 64-bit error code as defined in the gin spec. type ErrorType uint64 const ( - ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails - ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() fails + // ErrorTypeBind is used when Context.Bind() fails. + ErrorTypeBind ErrorType = 1 << 63 + // ErrorTypeRender is used when Context.Render() fails. + ErrorTypeRender ErrorType = 1 << 62 + // ErrorTypePrivate indicates a private error. ErrorTypePrivate ErrorType = 1 << 0 - ErrorTypePublic ErrorType = 1 << 1 - + // ErrorTypePublic indicates a public error. + ErrorTypePublic ErrorType = 1 << 1 + // ErrorTypeAny indicates other any error. ErrorTypeAny ErrorType = 1<<64 - 1 ErrorTypeNu = 2 ) +// Error represents a error's specification. type Error struct { Err error Type ErrorType @@ -34,11 +40,13 @@ type errorMsgs []*Error var _ error = &Error{} +// SetType sets the error's type. func (msg *Error) SetType(flags ErrorType) *Error { msg.Type = flags return msg } +// SetMeta sets the error's meta data. func (msg *Error) SetMeta(data interface{}) *Error { msg.Meta = data return msg @@ -70,11 +78,12 @@ func (msg *Error) MarshalJSON() ([]byte, error) { return json.Marshal(msg.JSON()) } -// Error implements the error interface +// Error implements the error interface. func (msg Error) Error() string { return msg.Err.Error() } +// IsType judges one error. func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } @@ -138,6 +147,7 @@ func (a errorMsgs) JSON() interface{} { } } +// MarshalJSON implements the json.Marshaller interface. func (a errorMsgs) MarshalJSON() ([]byte, error) { return json.Marshal(a.JSON()) } diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go index a16c58b0..f3ead476 100644 --- a/examples/realtime-advanced/main.go +++ b/examples/realtime-advanced/main.go @@ -20,7 +20,7 @@ func ConfigRuntime() { fmt.Printf("Running with %d CPUs\n", nuCPU) } -// StartWrokers start starsWorker by goroutine. +// StartWorkers start starsWorker by goroutine. func StartWorkers() { go statsWorker() } diff --git a/gin.go b/gin.go index aa62e014..6bff3bd5 100644 --- a/gin.go +++ b/gin.go @@ -26,7 +26,10 @@ var ( defaultAppEngine bool ) +// HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) + +// HandlersChain defines a HandlerFunc array. type HandlersChain []HandlerFunc // Last returns the last handler in the chain. ie. the last handler is the main own. @@ -37,12 +40,14 @@ func (c HandlersChain) Last() HandlerFunc { return nil } +// RouteInfo represents a request route's specification which contains method and path and its handler. type RouteInfo struct { Method string Path string Handler string } +// RoutesInfo defines a RouteInfo array. type RoutesInfo []RouteInfo // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. @@ -155,6 +160,7 @@ func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} } +// Delims sets template left and right delims and returns a Engine instance. func (engine *Engine) Delims(left, right string) *Engine { engine.delims = render.Delims{Left: left, Right: right} return engine diff --git a/mode.go b/mode.go index 9df4e45f..7cb0143a 100644 --- a/mode.go +++ b/mode.go @@ -11,12 +11,16 @@ import ( "github.com/gin-gonic/gin/binding" ) +// ENV_GIN_MODE indicates environment name for gin mode. const ENV_GIN_MODE = "GIN_MODE" const ( - DebugMode = "debug" + // DebugMode indicates gin mode is debug. + DebugMode = "debug" + // ReleaseMode indicates gin mode is relase. ReleaseMode = "release" - TestMode = "test" + // TestMode indicates gin mode is test. + TestMode = "test" ) const ( debugCode = iota @@ -42,6 +46,7 @@ func init() { SetMode(mode) } +// SetMode sets gin mode according to input string. func SetMode(value string) { switch value { case DebugMode, "": @@ -59,14 +64,18 @@ func SetMode(value string) { modeName = value } +// DisableBindValidation closes the default validator. func DisableBindValidation() { binding.Validator = nil } +// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to +// call the UseNumber method on the JSON Decoder instance. func EnableJsonDecoderUseNumber() { binding.EnableDecoderUseNumber = true } +// Mode returns currently gin mode. func Mode() string { return modeName } diff --git a/render/html.go b/render/html.go index 4d3d5812..6696ece9 100644 --- a/render/html.go +++ b/render/html.go @@ -46,7 +46,7 @@ type HTML struct { var htmlContentType = []string{"text/html; charset=utf-8"} -// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.. +// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, @@ -55,7 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render { } } -// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.. +// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface. func (r HTMLDebug) Instance(name string, data interface{}) Render { return HTML{ Template: r.loadTemplate(), diff --git a/routergroup.go b/routergroup.go index 876a61b8..be561dee 100644 --- a/routergroup.go +++ b/routergroup.go @@ -11,11 +11,13 @@ import ( "strings" ) +// IRouter defines all router handle interface includes single and group router. type IRouter interface { IRoutes Group(string, ...HandlerFunc) *RouterGroup } +// Iroutes defins all router handle interface. type IRoutes interface { Use(...HandlerFunc) IRoutes @@ -34,8 +36,8 @@ type IRoutes interface { StaticFS(string, http.FileSystem) IRoutes } -// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix -// and an array of handlers (middleware). +// RouterGroup is used internally to configure router, a RouterGroup is associated with +// a prefix and an array of handlers (middleware). type RouterGroup struct { Handlers HandlersChain basePath string @@ -61,6 +63,8 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R } } +// BasePath returns the base path of router group. +// For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api". func (group *RouterGroup) BasePath() string { return group.basePath } From 7c7f703cc559af6c67fc82c3c39959a0c7d42b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 16 Sep 2018 23:22:54 +0800 Subject: [PATCH 084/111] initial go.mod module definition (#1554) --- go.mod | 27 ++++++++++++++++++++++++ go.sum | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..bd4ad975 --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module github.com/gin-gonic/gin + +require ( + github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 // indirect + github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 + github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 + github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b + github.com/golang/protobuf v1.2.0 + github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 + github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b + github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 + github.com/mattn/go-isatty v0.0.3 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 + github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 + github.com/ugorji/go v1.1.1 + golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f + golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect + google.golang.org/grpc v1.15.0 + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/go-playground/validator.v8 v8.18.2 + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..3382beed --- /dev/null +++ b/go.sum @@ -0,0 +1,65 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 h1:HqkufMBR7waVfFFRABWqHa1WgTvjtVDJTLJe3CR576A= +github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY= +github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8Jc6zMvyRz3PCQrTTCXnVRvEzyBcM890= +github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc= +github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k= +github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU= +github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= +github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 h1:d/LEgOfWe+AlOCz/kzmkvlO+gq9LRGhjSHqt2nx8Muc= +github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= +github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= +github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw= +google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 90c680ef5c360ccd2f0f5f9f0ab1f44f3ce9eef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laforge?= Date: Mon, 17 Sep 2018 06:09:34 +0200 Subject: [PATCH 085/111] Let's user define how he wants to log his routes (eg. JSON, key value, or something else) (#1553) (#1555) --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ debug.go | 8 +++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a97ec54..45bfce5a 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [http2 server push](#http2-server-push) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Testing](#testing) - [Users](#users) @@ -1836,6 +1837,49 @@ func main() { } ``` +### Define format for the log of routes + +The default log of routes is: +``` +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. +In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/debug.go b/debug.go index f11156b3..693486a9 100644 --- a/debug.go +++ b/debug.go @@ -20,11 +20,17 @@ func IsDebugging() bool { return ginMode == debugCode } +var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) + func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { nuHandlers := len(handlers) handlerName := nameOfFunction(handlers.Last()) - debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) + if DebugPrintRouteFunc == nil { + debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) + } else { + DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers) + } } } From b27b7026c70697d84226f15d6d433bdf07023c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 17 Sep 2018 15:08:11 +0800 Subject: [PATCH 086/111] chore: add a version file includes gin version (#1549) * chore: add a version file includes gin version * update version for dev version --- gin.go | 6 +----- version.go | 8 ++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 version.go diff --git a/gin.go b/gin.go index 6bff3bd5..11f6d94a 100644 --- a/gin.go +++ b/gin.go @@ -14,11 +14,7 @@ import ( "github.com/gin-gonic/gin/render" ) -const ( - // Version is Framework's version. - Version = "v1.3.0" - defaultMultipartMemory = 32 << 20 // 32 MB -) +const defaultMultipartMemory = 32 << 20 // 32 MB var ( default404Body = []byte("404 page not found") diff --git a/version.go b/version.go new file mode 100644 index 00000000..028caebe --- /dev/null +++ b/version.go @@ -0,0 +1,8 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +// Version is the current gin framework's version. +const Version = "v1.4.0-dev" From 07f1bf0e63512fbf9c7d7984d235a399eb916244 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 19 Sep 2018 13:57:00 +0800 Subject: [PATCH 087/111] feat: replace debug log with fmt package. (#1560) --- context_test.go | 12 ++--- debug.go | 8 +-- debug_test.go | 140 +++++++++++++++++++++++++++--------------------- 3 files changed, 86 insertions(+), 74 deletions(-) diff --git a/context_test.go b/context_test.go index 4c307080..5a5bb6e1 100644 --- a/context_test.go +++ b/context_test.go @@ -784,14 +784,14 @@ func TestContextRenderHTML2(t *testing.T) { router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) - var b bytes.Buffer - setup(&b) - defer teardown() - templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) - router.SetHTMLTemplate(templ) + re := captureOutput(func() { + SetMode(DebugMode) + router.SetHTMLTemplate(templ) + SetMode(TestMode) + }) - assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String()) + assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", re) c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) diff --git a/debug.go b/debug.go index 693486a9..d6d8bce6 100644 --- a/debug.go +++ b/debug.go @@ -6,14 +6,10 @@ package gin import ( "bytes" + "fmt" "html/template" - "log" ) -func init() { - log.SetFlags(0) -} - // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { @@ -48,7 +44,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) { func debugPrint(format string, values ...interface{}) { if IsDebugging() { - log.Printf("[GIN-debug] "+format, values...) + fmt.Printf("[GIN-debug] "+format, values...) } } diff --git a/debug_test.go b/debug_test.go index ed5a6a54..0b9d7910 100644 --- a/debug_test.go +++ b/debug_test.go @@ -11,6 +11,7 @@ import ( "io" "log" "os" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -30,86 +31,101 @@ func TestIsDebugging(t *testing.T) { } func TestDebugPrint(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - SetMode(ReleaseMode) - debugPrint("DEBUG this!") - SetMode(TestMode) - debugPrint("DEBUG this!") - assert.Empty(t, w.String()) - - SetMode(DebugMode) - debugPrint("these are %d %s\n", 2, "error messages") - assert.Equal(t, "[GIN-debug] these are 2 error messages\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + SetMode(ReleaseMode) + debugPrint("DEBUG this!") + SetMode(TestMode) + debugPrint("DEBUG this!") + SetMode(DebugMode) + debugPrint("these are %d %s\n", 2, "error messages") + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re) } func TestDebugPrintError(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - SetMode(DebugMode) - debugPrintError(nil) - assert.Empty(t, w.String()) - - debugPrintError(errors.New("this is an error")) - assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintError(nil) + debugPrintError(errors.New("this is an error")) + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", re) } func TestDebugPrintRoutes(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) - assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) + SetMode(TestMode) + }) + assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) } func TestDebugPrintLoadTemplate(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) - debugPrintLoadTemplate(templ) - assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) + debugPrintLoadTemplate(templ) + SetMode(TestMode) + }) + assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, re) } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintWARNINGSetHTMLTemplate() - assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintWARNINGSetHTMLTemplate() + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", re) } func TestDebugPrintWARNINGDefault(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintWARNINGDefault() - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintWARNINGDefault() + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } func TestDebugPrintWARNINGNew(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintWARNINGNew() - assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintWARNINGNew() + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re) } -func setup(w io.Writer) { - SetMode(DebugMode) - log.SetOutput(w) -} - -func teardown() { - SetMode(TestMode) - log.SetOutput(os.Stdout) +func captureOutput(f func()) string { + reader, writer, err := os.Pipe() + if err != nil { + panic(err) + } + stdout := os.Stdout + stderr := os.Stderr + defer func() { + os.Stdout = stdout + os.Stderr = stderr + log.SetOutput(os.Stderr) + }() + os.Stdout = writer + os.Stderr = writer + log.SetOutput(writer) + out := make(chan string) + wg := new(sync.WaitGroup) + wg.Add(1) + go func() { + var buf bytes.Buffer + wg.Done() + io.Copy(&buf, reader) + out <- buf.String() + }() + wg.Wait() + f() + writer.Close() + return <-out } From c617b6241a1891081b3632ed89c1887dc4c353af Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Thu, 20 Sep 2018 03:13:04 +0200 Subject: [PATCH 088/111] chore: recover go master build, partial revert #1514 (#1561) * chore: recover go master build, partial revert #1514 * chore: add master to go branch build targets --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index a93458f9..398c1409 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,11 @@ go: - 1.9.x - 1.10.x - 1.11.x + - master + +matrix: + allow_failures: + - go: master git: depth: 10 From f2cd3fcb2a9b4b108f5ce34a9f121ec13ac1cc16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 20 Sep 2018 11:53:58 +0800 Subject: [PATCH 089/111] chore: fix typo and add a little anotation (#1562) --- debug.go | 1 + routergroup.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/debug.go b/debug.go index d6d8bce6..ce908dc7 100644 --- a/debug.go +++ b/debug.go @@ -16,6 +16,7 @@ func IsDebugging() bool { return ginMode == debugCode } +// DebugPrintRouteFunc indicates debug log output format. var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { diff --git a/routergroup.go b/routergroup.go index be561dee..579aa7dc 100644 --- a/routergroup.go +++ b/routergroup.go @@ -17,7 +17,7 @@ type IRouter interface { Group(string, ...HandlerFunc) *RouterGroup } -// Iroutes defins all router handle interface. +// IRoutes defines all router handle interface. type IRoutes interface { Use(...HandlerFunc) IRoutes From a210eea3bd1c3766d76968108dfcd83c331f549c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 21 Sep 2018 10:21:59 +0800 Subject: [PATCH 090/111] improve panic information when a catch-all wildcard conflict occurs (#1529) --- tree.go | 11 +++++++++-- tree_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index b6530665..ada62ceb 100644 --- a/tree.go +++ b/tree.go @@ -193,9 +193,16 @@ func (n *node) addRoute(path string, handlers HandlersChain) { } } - panic("path segment '" + path + + pathSeg := path + if n.nType != catchAll { + pathSeg = strings.SplitN(path, "/", 2)[0] + } + prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path + panic("'" + pathSeg + + "' in new path '" + fullPath + "' conflicts with existing wildcard '" + n.path + - "' in path '" + fullPath + "'") + "' in existing prefix '" + prefix + + "'") } c := path[0] diff --git a/tree_test.go b/tree_test.go index 152f6331..a1b3bbe7 100644 --- a/tree_test.go +++ b/tree_test.go @@ -5,7 +5,9 @@ package gin import ( + "fmt" "reflect" + "regexp" "strings" "testing" ) @@ -653,3 +655,43 @@ func TestTreeInvalidNodeType(t *testing.T) { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) } } + +func TestTreeWildcardConflictEx(t *testing.T) { + conflicts := [...]struct { + route string + segPath string + existPath string + existSegPath string + }{ + {"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`}, + {"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`}, + {"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`}, + {"/conxxx", "xxx", `/con:tact`, `:tact`}, + {"/conooo/xxx", "ooo", `/con:tact`, `:tact`}, + } + + for _, conflict := range conflicts { + // I have to re-create a 'tree', because the 'tree' will be + // in an inconsistent state when the loop recovers from the + // panic which threw by 'addRoute' function. + tree := &node{} + routes := [...]string{ + "/con:tact", + "/who/are/*you", + "/who/foo/hello", + } + + for _, route := range routes { + tree.addRoute(route, fakeHandler(route)) + } + + recv := catchPanic(func() { + tree.addRoute(conflict.route, fakeHandler(conflict.route)) + }) + + if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'", + conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) { + t.Fatalf("invalid wildcard conflict error (%v)", recv) + } + } +} From 5a75dc712705510ab39fe7c448e7ab11459ce5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 22 Sep 2018 11:37:28 +0800 Subject: [PATCH 091/111] add release badge for readme (#1533) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 45bfce5a..6fd2dd34 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) +[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. From ad53619b15fda20df57c16cf19fe83f4a428cefa Mon Sep 17 00:00:00 2001 From: Dustin Decker Date: Sun, 23 Sep 2018 00:15:23 -0700 Subject: [PATCH 092/111] Don't log requests (#1370) Fixes #1331 HTTP logging leaks sensitive request information. This PR removes HTTP request logging during panics. --- recovery.go | 8 ++++++-- recovery_test.go | 12 +++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/recovery.go b/recovery.go index 61c5bd53..6c28b4fa 100644 --- a/recovery.go +++ b/recovery.go @@ -39,8 +39,12 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { if err := recover(); err != nil { if logger != nil { stack := stack(3) - httprequest, _ := httputil.DumpRequest(c.Request, false) - logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) + if IsDebugging() { + httprequest, _ := httputil.DumpRequest(c.Request, false) + logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) + } else { + logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset) + } } c.AbortWithStatus(http.StatusInternalServerError) } diff --git a/recovery_test.go b/recovery_test.go index 53f4a071..7d422b74 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -24,9 +24,19 @@ func TestPanicInHandler(t *testing.T) { w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.Contains(t, buffer.String(), "GET /recovery") + assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") + assert.NotContains(t, buffer.String(), "GET /recovery") + + // Debug mode prints the request + SetMode(DebugMode) + // RUN + w = performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.Contains(t, buffer.String(), "GET /recovery") + } // TestPanicWithAbort assert that panic has been recovered even if context.Abort was used. From b02e4f2ed68e1bd9598b8eced0b21c9aacfed5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 25 Sep 2018 21:52:21 +0800 Subject: [PATCH 093/111] ci: fast finish when build failed (#1568) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 398c1409..f9e241d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ go: matrix: allow_failures: - go: master + fast_finish: true git: depth: 10 From fd599fcceadba48be39ded04d1e2fb3181b43e8c Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 25 Sep 2018 21:28:25 -0500 Subject: [PATCH 094/111] Make logger use a yellow background and a darkgray text for legibility (#1570) 1. Why is this change neccesary? White text on a yellow background was illegible with most terminal color schemes 2. How does it address the issue? The white text was replaced with a bash compatible dark gray while keeping the yellow background colour 3. What side effects does this change have? Resolves #1552 --- logger.go | 2 +- logger_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/logger.go b/logger.go index 1a8df601..5f986853 100644 --- a/logger.go +++ b/logger.go @@ -17,7 +17,7 @@ import ( 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, 55, 59, 52, 51, 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}) diff --git a/logger_test.go b/logger_test.go index 7dbbf7b1..6118cb04 100644 --- a/logger_test.go +++ b/logger_test.go @@ -85,7 +85,7 @@ func TestLogger(t *testing.T) { func TestColorForMethod(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta") @@ -96,7 +96,7 @@ func TestColorForMethod(t *testing.T) { func TestColorForStatus(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } From 834a2ec64ca1ce077e4bdeb5edec019df1c01e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 26 Sep 2018 13:49:11 +0800 Subject: [PATCH 095/111] feat: add go version judge when print debug warning log (#1572) * feat: add go version judge when print debug warning log * remove invalid statement * use one const --- debug.go | 18 +++++++++++++++++- debug_test.go | 24 +++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/debug.go b/debug.go index ce908dc7..4fd23fa1 100644 --- a/debug.go +++ b/debug.go @@ -8,8 +8,13 @@ import ( "bytes" "fmt" "html/template" + "runtime" + "strconv" + "strings" ) +const ginSupportMinGoVer = 6 + // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { @@ -49,10 +54,21 @@ func debugPrint(format string, values ...interface{}) { } } +func getMinVer(v string) (uint64, error) { + first := strings.IndexByte(v, '.') + last := strings.LastIndexByte(v, '.') + if first == last { + return strconv.ParseUint(v[first+1:], 10, 64) + } + return strconv.ParseUint(v[first+1:last], 10, 64) +} + func debugPrintWARNINGDefault() { - debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { + debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. `) + } debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. `) diff --git a/debug_test.go b/debug_test.go index 0b9d7910..c8a010ff 100644 --- a/debug_test.go +++ b/debug_test.go @@ -11,6 +11,7 @@ import ( "io" "log" "os" + "runtime" "sync" "testing" @@ -88,7 +89,13 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { debugPrintWARNINGDefault() SetMode(TestMode) }) - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + m, e := getMinVer(runtime.Version()) + assert.Nil(t, e) + if m <= ginSupportMinGoVer { + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + } else { + assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + } } func TestDebugPrintWARNINGNew(t *testing.T) { @@ -129,3 +136,18 @@ func captureOutput(f func()) string { writer.Close() return <-out } + +func TestGetMinVer(t *testing.T) { + var m uint64 + var e error + _, e = getMinVer("go1") + assert.NotNil(t, e) + m, e = getMinVer("go1.1") + assert.Equal(t, uint64(1), m) + assert.Nil(t, e) + m, e = getMinVer("go1.1.1") + assert.Nil(t, e) + assert.Equal(t, uint64(1), m) + _, e = getMinVer("go1.1.1.1") + assert.NotNil(t, e) +} From 402ef120e19218544152502d1ea20709ff5adbc8 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 27 Sep 2018 08:59:44 +0800 Subject: [PATCH 096/111] fix: fmt output log to os.Stderr (#1571) fix #1560 changes are breaking in App Engine. cc @giulianobr @philippgille --- debug.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debug.go b/debug.go index 4fd23fa1..c5e65b22 100644 --- a/debug.go +++ b/debug.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "html/template" + "os" "runtime" "strconv" "strings" @@ -50,7 +51,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) { func debugPrint(format string, values ...interface{}) { if IsDebugging() { - fmt.Printf("[GIN-debug] "+format, values...) + fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...) } } From b7314d943c81052dd7155499c7313d7524cb024c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 27 Sep 2018 09:42:41 +0800 Subject: [PATCH 097/111] chore: fix debug test error (#1574) --- debug_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/debug_test.go b/debug_test.go index c8a010ff..97ff166b 100644 --- a/debug_test.go +++ b/debug_test.go @@ -90,8 +90,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { SetMode(TestMode) }) m, e := getMinVer(runtime.Version()) - assert.Nil(t, e) - if m <= ginSupportMinGoVer { + if e == nil && m <= ginSupportMinGoVer { assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) From 91a4459dd27a311c2b959708f328d60177fa4046 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 27 Sep 2018 09:58:47 +0800 Subject: [PATCH 098/111] remove allow fail flag (#1573) golang team revert the net/url issue: https://github.com/golang/go/issues/27302 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f9e241d7..a765f37f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,6 @@ go: - master matrix: - allow_failures: - - go: master fast_finish: true git: From e9f187f60a27477e54c177f314db00d6a1abf062 Mon Sep 17 00:00:00 2001 From: James Pettyjohn Date: Sun, 30 Sep 2018 19:49:39 -0700 Subject: [PATCH 099/111] removed use of sync.pool from HandleContext and added test coverage (#1565) As per #1230 there is an issue when using HandleContext where the context of the request is returned to the context sync.Pool before the parent request has finished, causing context to be used in a non-thread safe manner. I've removed the bug by not entering the context back in the pool and leaving that to ServeHTTP. There was no test coverage for this function so I've also added the test to cover it. As the bug only happens when there are concurrent requests, the tests issues hundreds of concurrent requests. Without the bug fixed the tests do consistently recreate the error. --- gin.go | 1 - gin_integration_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 11f6d94a..92c24ba2 100644 --- a/gin.go +++ b/gin.go @@ -336,7 +336,6 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (engine *Engine) HandleContext(c *Context) { c.reset() engine.handleHTTPRequest(c) - engine.pool.Put(c) } func (engine *Engine) handleHTTPRequest(c *Context) { diff --git a/gin_integration_test.go b/gin_integration_test.go index 52f78842..12a943b0 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -12,6 +12,7 @@ import ( "net/http" "net/http/httptest" "os" + "sync" "testing" "time" @@ -119,6 +120,29 @@ func TestWithHttptestWithAutoSelectedPort(t *testing.T) { testRequest(t, ts.URL+"/example") } +func TestConcurrentHandleContext(t *testing.T) { + router := New() + router.GET("/", func(c *Context) { + c.Request.URL.Path = "/example" + router.HandleContext(c) + }) + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + + ts := httptest.NewServer(router) + defer ts.Close() + + var wg sync.WaitGroup + iterations := 200 + wg.Add(iterations) + for i := 0; i < iterations; i++ { + go func() { + testRequest(t, ts.URL+"/") + wg.Done() + }() + } + wg.Wait() +} + // func TestWithHttptestWithSpecifiedPort(t *testing.T) { // router := New() // router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) From fbdcbd22751c7174d4f363995fca296e4670726b Mon Sep 17 00:00:00 2001 From: zesani <7sin@outlook.co.th> Date: Tue, 9 Oct 2018 06:14:21 +0700 Subject: [PATCH 100/111] Update README.md (#1583) change "hava" to "have" --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fd2dd34..95bd3204 100644 --- a/README.md +++ b/README.md @@ -1721,11 +1721,11 @@ type StructX struct { } type StructY struct { - Y StructX `form:"name_y"` // HERE hava form + Y StructX `form:"name_y"` // HERE have form } type StructZ struct { - Z *StructZ `form:"name_z"` // HERE hava form + Z *StructZ `form:"name_z"` // HERE have form } ``` From 6ab50f944ca52bdd4d982d0bec454d94bf7b802a Mon Sep 17 00:00:00 2001 From: andriikushch Date: Fri, 12 Oct 2018 01:31:31 +0200 Subject: [PATCH 101/111] replace deprecated HeaderMap with Header() (#1585) --- auth_test.go | 4 +-- context_17_test.go | 2 +- context_test.go | 64 +++++++++++++++++++++---------------------- render/render_test.go | 6 ++-- routes_test.go | 12 ++++---- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/auth_test.go b/auth_test.go index ab7e94be..197e9208 100644 --- a/auth_test.go +++ b/auth_test.go @@ -122,7 +122,7 @@ func TestBasicAuth401(t *testing.T) { assert.False(t, called) assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate")) + assert.Equal(t, "Basic realm=\"Authorization Required\"", w.Header().Get("WWW-Authenticate")) } func TestBasicAuth401WithCustomRealm(t *testing.T) { @@ -142,5 +142,5 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { assert.False(t, called) assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate")) + assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate")) } diff --git a/context_17_test.go b/context_17_test.go index d2251904..5b9ebcdc 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -23,5 +23,5 @@ func TestContextRenderPureJSON(t *testing.T) { c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/context_test.go b/context_test.go index 5a5bb6e1..2e972f18 100644 --- a/context_test.go +++ b/context_test.go @@ -628,7 +628,7 @@ func TestContextRenderJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSONP @@ -642,7 +642,7 @@ func TestContextRenderJSONP(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) - assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSONP @@ -656,7 +656,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no JSON is rendered if code is 204 @@ -668,7 +668,7 @@ func TestContextRenderNoContentJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSON @@ -682,7 +682,7 @@ func TestContextRenderAPIJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) - assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -695,7 +695,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") + assert.Equal(t, w.Header().Get("Content-Type"), "application/vnd.api+json") } // Tests that the response is serialized as JSON @@ -708,7 +708,7 @@ func TestContextRenderIndentedJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -720,7 +720,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as Secure JSON @@ -734,7 +734,7 @@ func TestContextRenderSecureJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -746,7 +746,7 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextRenderNoContentAsciiJSON(t *testing.T) { @@ -757,7 +757,7 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) } // Tests that the response executes the templates @@ -773,7 +773,7 @@ func TestContextRenderHTML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextRenderHTML2(t *testing.T) { @@ -797,7 +797,7 @@ func TestContextRenderHTML2(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no HTML is rendered if code is 204 @@ -811,7 +811,7 @@ func TestContextRenderNoContentHTML(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextXML tests that the response is serialized as XML @@ -824,7 +824,7 @@ func TestContextRenderXML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "bar", w.Body.String()) - assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no XML is rendered if code is 204 @@ -836,7 +836,7 @@ func TestContextRenderNoContentXML(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextString tests that the response is returned @@ -849,7 +849,7 @@ func TestContextRenderString(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "test string 2", w.Body.String()) - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no String is rendered if code is 204 @@ -861,7 +861,7 @@ func TestContextRenderNoContentString(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextString tests that the response is returned @@ -875,7 +875,7 @@ func TestContextRenderHTMLString(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "string 3", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no HTML String is rendered if code is 204 @@ -888,7 +888,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextData tests that the response can be written from `bytesting` @@ -901,7 +901,7 @@ func TestContextRenderData(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo,bar", w.Body.String()) - assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/csv", w.Header().Get("Content-Type")) } // Tests that no Custom Data is rendered if code is 204 @@ -913,7 +913,7 @@ func TestContextRenderNoContentData(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/csv", w.Header().Get("Content-Type")) } func TestContextRenderSSE(t *testing.T) { @@ -942,7 +942,7 @@ func TestContextRenderFile(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextRenderYAML tests that the response is serialized as YAML @@ -955,7 +955,7 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf @@ -979,7 +979,7 @@ func TestContextRenderProtoBuf(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, string(protoData), w.Body.String()) - assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } func TestContextHeaders(t *testing.T) { @@ -1062,7 +1062,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithXML(t *testing.T) { @@ -1077,7 +1077,7 @@ func TestContextNegotiationWithXML(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "bar", w.Body.String()) - assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithHTML(t *testing.T) { @@ -1095,7 +1095,7 @@ func TestContextNegotiationWithHTML(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Hello gin", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationNotSupport(t *testing.T) { @@ -1131,7 +1131,7 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) { assert.Empty(t, c.NegotiateFormat(MIMEJSON)) } -func TestContextNegotiationFormatCustum(t *testing.T) { +func TestContextNegotiationFormatCustom(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") @@ -1627,9 +1627,9 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) - assert.Equal(t, contentType, w.HeaderMap.Get("Content-Type")) - assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) - assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) + assert.Equal(t, contentType, w.Header().Get("Content-Type")) + assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) + assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition")) } type TestResponseRecorder struct { diff --git a/render/render_test.go b/render/render_test.go index 09ccc658..4c9b180d 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -480,7 +480,7 @@ func TestRenderReader(t *testing.T) { assert.NoError(t, err) assert.Equal(t, body, w.Body.String()) - assert.Equal(t, "image/png", w.HeaderMap.Get("Content-Type")) - assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length")) - assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) + assert.Equal(t, "image/png", w.Header().Get("Content-Type")) + assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) + assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) } diff --git a/routes_test.go b/routes_test.go index 23e749e2..60f1c81b 100644 --- a/routes_test.go +++ b/routes_test.go @@ -267,7 +267,7 @@ func TestRouteStaticFile(t *testing.T) { assert.Equal(t, w, w2) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) w3 := performRequest(router, "HEAD", "/using_static/"+filename) w4 := performRequest(router, "HEAD", "/result") @@ -285,7 +285,7 @@ func TestRouteStaticListingDir(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestHandleHeadToDir - ensure the root/sub dir handles properly @@ -312,10 +312,10 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) - assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") - assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.HeaderMap.Get("Expires")) - assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") + assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) + assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) } func TestRouteNotAllowedEnabled(t *testing.T) { From 268e30710b77e1cd48b25df235cf702dc8e942a2 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 14 Oct 2018 11:05:24 +0800 Subject: [PATCH 102/111] fix(Makefile): golint to new URL (#1592) as title. Just update the golint to new URL. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 51b9969f..ea7b4f7c 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ embedmd: .PHONY: lint lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/golang/lint/golint; \ + go get -u golang.org/x/lint/golint; \ fi for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; From 01ca2530d4b6c44c718b00c06f0e1d092572d49e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 14 Oct 2018 12:39:16 +0800 Subject: [PATCH 103/111] refactor(Makefile): allow overriding default go program (#1593) --- Makefile | 28 ++++++++++++++++++---------- coverage.sh | 13 ------------- 2 files changed, 18 insertions(+), 23 deletions(-) delete mode 100644 coverage.sh diff --git a/Makefile b/Makefile index ea7b4f7c..c20429a1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ +GO ?= go GOFMT ?= gofmt "-s" -PACKAGES ?= $(shell go list ./... | grep -v /vendor/) -VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/) +PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/) +VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/ | grep -v /examples/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") +TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples) all: install @@ -10,7 +12,14 @@ install: deps .PHONY: test test: - sh coverage.sh + echo "mode: count" > coverage.out + for d in $(TESTFOLDER); do \ + $(GO) test -v -covermode=count -coverprofile=profile.out $$d; \ + if [ -f profile.out ]; then \ + cat profile.out | grep -v "mode:" >> coverage.out; \ + rm profile.out; \ + fi; \ + done .PHONY: fmt fmt: @@ -18,7 +27,6 @@ fmt: .PHONY: fmt-check fmt-check: - # get all go files and run go fmt on them @diff=$$($(GOFMT) -d $(GOFILES)); \ if [ -n "$$diff" ]; then \ echo "Please run 'make fmt' and commit the result:"; \ @@ -27,14 +35,14 @@ fmt-check: fi; vet: - go vet $(VETPACKAGES) + $(GO) vet $(VETPACKAGES) deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/kardianos/govendor; \ + $(GO) get -u github.com/kardianos/govendor; \ fi @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/campoy/embedmd; \ + $(GO) get -u github.com/campoy/embedmd; \ fi embedmd: @@ -43,20 +51,20 @@ embedmd: .PHONY: lint lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u golang.org/x/lint/golint; \ + $(GO) get -u golang.org/x/lint/golint; \ fi for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; .PHONY: misspell-check misspell-check: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/client9/misspell/cmd/misspell; \ + $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -error $(GOFILES) .PHONY: misspell misspell: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/client9/misspell/cmd/misspell; \ + $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -w $(GOFILES) diff --git a/coverage.sh b/coverage.sh deleted file mode 100644 index 4d1ee036..00000000 --- a/coverage.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e - -echo "mode: count" > coverage.out - -for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do - go test -v -covermode=count -coverprofile=profile.out $d - if [ -f profile.out ]; then - cat profile.out | grep -v "mode:" >> coverage.out - rm profile.out - fi -done From 523435e5245faade0a43a33bd3faab32456bab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 15 Oct 2018 12:52:51 +0800 Subject: [PATCH 104/111] attempt to support go module (#1569) * support go module * update golint package url * update golint --- .travis.yml | 10 +++++++++- Makefile | 6 ++++++ go.mod | 13 ++++++++----- go.sum | 26 ++++++++++++++++---------- tools.go | 25 +++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 tools.go diff --git a/.travis.yml b/.travis.yml index a765f37f..2eeb0b3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,12 +11,20 @@ go: matrix: fast_finish: true + include: + - go: 1.11.x + env: GO111MODULE=on git: depth: 10 +before_install: + - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi + install: - - make install + - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; else make install; fi + - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi + - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi go_import_path: github.com/gin-gonic/gin diff --git a/Makefile b/Makefile index c20429a1..b698ac09 100644 --- a/Makefile +++ b/Makefile @@ -68,3 +68,9 @@ misspell: $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -w $(GOFILES) + +.PHONY: tools +tools: + go install golang.org/x/lint/golint; \ + go install github.com/client9/misspell/cmd/misspell; \ + go install github.com/campoy/embedmd; diff --git a/go.mod b/go.mod index bd4ad975..0797b934 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,28 @@ module github.com/gin-gonic/gin require ( - github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 // indirect + github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 + github.com/client9/misspell v0.3.4 + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b github.com/golang/protobuf v1.2.0 github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 - github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b + github.com/json-iterator/go v1.1.5 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 github.com/mattn/go-isatty v0.0.3 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 - github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 + github.com/thinkerou/favicon v0.1.0 github.com/ugorji/go v1.1.1 - golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b + golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 + golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f - golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect + golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect google.golang.org/grpc v1.15.0 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 diff --git a/go.sum b/go.sum index 3382beed..2d307e7e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 h1:tRsilif6pbtt+PX6uRoyGd+qR+4ZPucFZLHlc3Ak6z8= +github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296/go.mod h1:/dBk8ICkslPCmyRdn4azP+QvBxL6Eg3EYxUGI9xMMFw= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 h1:HqkufMBR7waVfFFRABWqHa1WgTvjtVDJTLJe3CR576A= -github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY= github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= @@ -10,14 +13,15 @@ github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8J github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc= github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k= -github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU= -github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= @@ -31,12 +35,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 h1:d/LEgOfWe+AlOCz/kzmkvlO+gq9LRGhjSHqt2nx8Muc= -github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= +github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8ABMs= +github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= -golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 h1:00BeQWmeaGazuOrq8Q5K5d3/cHaGuFrZzpaHBXfrsUA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -44,10 +49,11 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= diff --git a/tools.go b/tools.go new file mode 100644 index 00000000..9f96406a --- /dev/null +++ b/tools.go @@ -0,0 +1,25 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build tools + +// This file exists to cause `go mod` and `go get` to believe these tools +// are dependencies, even though they are not runtime dependencies of any +// gin package. This means they will appear in `go.mod` file, but will not +// be a part of the build. + +package gin + +import ( + _ "github.com/campoy/embedmd" + _ "github.com/client9/misspell/cmd/misspell" + _ "github.com/dustin/go-broadcast" + _ "github.com/gin-gonic/autotls" + _ "github.com/jessevdk/go-assets" + _ "github.com/manucorporat/stats" + _ "github.com/thinkerou/favicon" + _ "golang.org/x/crypto/acme/autocert" + _ "golang.org/x/lint/golint" + _ "google.golang.org/grpc" +) From 98082fd590798eda8bdada82fd1550dfe6941964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 15 Oct 2018 13:01:44 +0800 Subject: [PATCH 105/111] document: add docs dir and middleware document (#1521) * init docs dir * add middleware document * fix indent * update docs --- docs/how-to-build-an-effective-middleware.md | 137 +++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/how-to-build-an-effective-middleware.md diff --git a/docs/how-to-build-an-effective-middleware.md b/docs/how-to-build-an-effective-middleware.md new file mode 100644 index 00000000..db04428c --- /dev/null +++ b/docs/how-to-build-an-effective-middleware.md @@ -0,0 +1,137 @@ +# How to build one effective middleware? + +## Consitituent part + +The middleware has two parts: + + - part one is what is executed once, when you initalize your middleware. That's where you set up all the global objects, logicals etc. Everything that happens one per application lifetime. + + - part two is what executes on every request. For example, a database middleware you simply inject your "global" database object into the context. Once it's inside the context, you can retrieve it from within other middlewares and your handler furnction. + +```go +func funcName(params string) gin.HandlerFunc { + // <--- + // This is part one + // ---> + // The follow code is an example + if err := check(params); err != nil { + panic(err) + } + + return func(c *gin.Context) { + // <--- + // This is part two + // ---> + // The follow code is an example + c.Set("TestVar", params) + c.Next() + } +} +``` + +## Execution process + +Firstly, we have the follow example code: + +```go +func main() { + router := gin.Default() + + router.Use(globalMiddleware()) + + router.GET("/rest/n/api/*some", mid1(), mid2(), handler) + + router.Run() +} + +func globalMiddleware() gin.HandlerFunc { + fmt.Println("globalMiddleware...1") + + return func(c *gin.Context) { + fmt.Println("globalMiddleware...2") + c.Next() + fmt.Println("globalMiddleware...3") + } +} + +func handler(c *gin.Context) { + fmt.Println("exec handler.") +} + +func mid1() gin.HandlerFunc { + fmt.Println("mid1...1") + + return func(c *gin.Context) { + + fmt.Println("mid1...2") + c.Next() + fmt.Println("mid1...3") + } +} + +func mid2() gin.HandlerFunc { + fmt.Println("mid2...1") + + return func(c *gin.Context) { + fmt.Println("mid2...2") + c.Next() + fmt.Println("mid2...3") + } +} +``` + +According to [Consitituent part](#consitituent-part) said, when we run the gin process, **part one** will execute firstly and will print the follow information: + +```go +globalMiddleware...1 +mid1...1 +mid2...1 +``` + +And init order are: + +```go +globalMiddleware...1 + | + v +mid1...1 + | + v +mid2...1 +``` + +When we curl one request `curl -v localhost:8080/rest/n/api/some`, **part two** will execute their middleware and output the following information: + +```go +globalMiddleware...2 +mid1...2 +mid2...2 +exec handler. +mid2...3 +mid1...3 +globalMiddleware...3 +``` + +In other words, run order are: + +```go +globalMiddleware...2 + | + v +mid1...2 + | + v +mid2...2 + | + v +exec handler. + | + v +mid2...3 + | + v +mid1...3 + | + v +globalMiddleware...3 +``` From 524757b81c99e51a64e6ecc7ee0c181abc39bd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 15 Oct 2018 20:24:32 +0800 Subject: [PATCH 106/111] vendor: upgrade some dependency package version (#1596) ref https://github.com/gin-gonic/gin/pull/1569#issuecomment-429731722 --- go.mod | 10 +++++----- go.sum | 19 ++++++++++--------- vendor/vendor.json | 43 +++++++++++++++++++++++++------------------ 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index 0797b934..ef4103fd 100644 --- a/go.mod +++ b/go.mod @@ -11,18 +11,18 @@ require ( github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 github.com/json-iterator/go v1.1.5 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 - github.com/mattn/go-isatty v0.0.3 + github.com/mattn/go-isatty v0.0.4 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 github.com/thinkerou/favicon v0.1.0 github.com/ugorji/go v1.1.1 - golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 - golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 - golang.org/x/net v0.0.0-20180826012351-8a410e7b638d + golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e + golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd + golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f - golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect + golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba // indirect google.golang.org/grpc v1.15.0 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 diff --git a/go.sum b/go.sum index 2d307e7e..2ef7f13b 100644 --- a/go.sum +++ b/go.sum @@ -13,7 +13,6 @@ github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8J github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -25,8 +24,8 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -39,18 +38,20 @@ github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8AB github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= -golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 h1:00BeQWmeaGazuOrq8Q5K5d3/cHaGuFrZzpaHBXfrsUA= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= +golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd h1:cgsAvzdqkDKdI02tIvDjO225vDPHMDCgfKqx5KEVI7U= +golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= -golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= diff --git a/vendor/vendor.json b/vendor/vendor.json index 86df11be..af1a0148 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -6,7 +6,9 @@ "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=", "path": "github.com/davecgh/go-spew/spew", "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73", - "revisionTime": "2018-02-21T22:46:20Z" + "revisionTime": "2018-02-21T22:46:20Z", + "version": "v1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", @@ -15,12 +17,12 @@ "revisionTime": "2017-01-09T09:34:21Z" }, { - "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=", + "checksumSHA1": "mE9XW26JSpe4meBObM6J/Oeq0eg=", "path": "github.com/golang/protobuf/proto", - "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", - "revisionTime": "2018-04-30T18:52:41Z", - "version": "v1.1.0", - "versionExact": "v1.1.0" + "revision": "aa810b61a9c79d51363740d207bb46cf8e620ed5", + "revisionTime": "2018-08-14T21:14:27Z", + "version": "v1.2", + "versionExact": "v1.2.0" }, { "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", @@ -31,19 +33,19 @@ "versionExact": "v1.1.5" }, { - "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", + "checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=", "path": "github.com/mattn/go-isatty", - "revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39", - "revisionTime": "2017-09-25T05:34:41Z", - "version": "v0.0.3", - "versionExact": "v0.0.3" + "revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c", + "revisionTime": "2017-11-07T05:05:31Z", + "version": "v0.0", + "versionExact": "v0.0.4" }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", "revisionTime": "2018-05-06T18:05:49Z", - "version": "v1.2.2", + "version": "v1.2", "versionExact": "v1.2.2" }, { @@ -51,15 +53,20 @@ "path": "github.com/ugorji/go/codec", "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab", "revisionTime": "2018-04-07T10:07:33Z", - "version": "v1.1.1", + "version": "v1.1", "versionExact": "v1.1.1" }, { - "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", - "comment": "release-branch.go1.7", + "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", "path": "golang.org/x/net/context", - "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", - "revisionTime": "2016-10-18T08:54:36Z" + "revision": "49bb7cea24b1df9410e1712aa6433dae904ff66a", + "revisionTime": "2018-10-11T05:27:23Z" + }, + { + "checksumSHA1": "SiJNkx+YGtq3Gtr6Ldu6OW83O+U=", + "path": "golang.org/x/sys/unix", + "revision": "fa43e7bc11baaae89f3f902b2b4d832b68234844", + "revisionTime": "2018-10-11T14:35:51Z" }, { "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", @@ -74,7 +81,7 @@ "path": "gopkg.in/yaml.v2", "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", "revisionTime": "2018-03-28T19:50:20Z", - "version": "v2.2.1", + "version": "v2.2", "versionExact": "v2.2.1" } ], From cfa092f4f045d004b4c03d5b69af8bab7ed5fc1b Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Tue, 16 Oct 2018 03:48:41 +0300 Subject: [PATCH 107/111] Fix LoadHTML* tests (#1559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Digging into the test code base I've found out that some of the tests for `LoadHTML*` methods are not reliable and efficient. They use timeouts to be sure that goroutine with the server has started. And even more, in old implementation, the server started only once – all the new instances silently failed due to the occupied network port. Here is a short overview of the proposed changes: - it's not necessary to rely on timeouts, the server starts listening synchronously and returns control when it is ready - once the server is run, it's stopped after a test passes - dry out http server setup - magic with empty closure return is eliminated - preserve router.RunTLS coverage with integration tests --- gin_integration_test.go | 26 ++++- gin_test.go | 252 ++++++++++++++++++++++------------------ 2 files changed, 165 insertions(+), 113 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index 12a943b0..038c8b7c 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -6,6 +6,7 @@ package gin import ( "bufio" + "crypto/tls" "fmt" "io/ioutil" "net" @@ -20,7 +21,14 @@ import ( ) func testRequest(t *testing.T, url string) { - resp, err := http.Get(url) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + + resp, err := client.Get(url) assert.NoError(t, err) defer resp.Body.Close() @@ -45,6 +53,22 @@ func TestRunEmpty(t *testing.T) { testRequest(t, "http://localhost:8080/example") } +func TestRunTLS(t *testing.T) { + router := New() + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + + assert.NoError(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + }() + + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + assert.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + testRequest(t, "https://localhost:8443/example") +} + func TestRunEmptyWithEnv(t *testing.T) { os.Setenv("PORT", "3123") router := New() diff --git a/gin_test.go b/gin_test.go index 95b9cc16..353c9be1 100644 --- a/gin_test.go +++ b/gin_test.go @@ -10,6 +10,7 @@ import ( "html/template" "io/ioutil" "net/http" + "net/http/httptest" "reflect" "testing" "time" @@ -22,105 +23,105 @@ func formatAsDate(t time.Time) string { return fmt.Sprintf("%d/%02d/%02d", year, month, day) } -func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { - go func() { - SetMode(mode) - router := New() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, +func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server { + SetMode(mode) + router := New() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + loadMethod(router) + router.GET("/test", func(c *Context) { + c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) + }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) - router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") - router.GET("/test", func(c *Context) { - c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) - }) - router.GET("/raw", func(c *Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - if tls { - // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") - } else { - router.Run(":8888") - } - }() - t.Log("waiting 1 second for server startup") - time.Sleep(1 * time.Second) - return func() {} + }) + + var ts *httptest.Server + + if tls { + ts = httptest.NewTLSServer(router) + } else { + ts = httptest.NewServer(router) + } + + return ts } -func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { - go func() { - SetMode(mode) - router := New() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLGlob("./testdata/template/*") - router.GET("/test", func(c *Context) { - c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) - }) - router.GET("/raw", func(c *Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - if tls { - // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") - } else { - router.Run(":8888") - } - }() - t.Log("waiting 1 second for server startup") - time.Sleep(1 * time.Second) - return func() {} -} +func TestLoadHTMLGlobDebugMode(t *testing.T) { + ts := setupHTMLFiles( + t, + DebugMode, + false, + func(router *Engine) { + router.LoadHTMLGlob("./testdata/template/*") + }, + ) + defer ts.Close() -func TestLoadHTMLGlob(t *testing.T) { - td := setupHTMLGlob(t, DebugMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - - td() } -func TestLoadHTMLGlob2(t *testing.T) { - td := setupHTMLGlob(t, TestMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") +func TestLoadHTMLGlobTestMode(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLGlob("./testdata/template/*") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - - td() } -func TestLoadHTMLGlob3(t *testing.T) { - td := setupHTMLGlob(t, ReleaseMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") +func TestLoadHTMLGlobReleaseMode(t *testing.T) { + ts := setupHTMLFiles( + t, + ReleaseMode, + false, + func(router *Engine) { + router.LoadHTMLGlob("./testdata/template/*") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - - td() } func TestLoadHTMLGlobUsingTLS(t *testing.T) { - td := setupHTMLGlob(t, DebugMode, true) + ts := setupHTMLFiles( + t, + DebugMode, + true, + func(router *Engine) { + router.LoadHTMLGlob("./testdata/template/*") + }, + ) + defer ts.Close() + // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error tr := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -128,29 +129,33 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get("https://127.0.0.1:9999/test") + res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - - td() } func TestLoadHTMLGlobFromFuncMap(t *testing.T) { - time.Now() - td := setupHTMLGlob(t, DebugMode, false) - res, err := http.Get("http://127.0.0.1:8888/raw") + ts := setupHTMLFiles( + t, + DebugMode, + false, + func(router *Engine) { + router.LoadHTMLGlob("./testdata/template/*") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp)) - - td() } func init() { @@ -164,59 +169,77 @@ func TestCreateEngine(t *testing.T) { assert.Empty(t, router.Handlers) } -// func TestLoadHTMLDebugMode(t *testing.T) { -// router := New() -// SetMode(DebugMode) -// router.LoadHTMLGlob("*.testtmpl") -// r := router.HTMLRender.(render.HTMLDebug) -// assert.Empty(t, r.Files) -// assert.Equal(t, "*.testtmpl", r.Glob) -// -// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl") -// r = router.HTMLRender.(render.HTMLDebug) -// assert.Empty(t, r.Glob) -// assert.Equal(t, []string{"index.html", "login.html"}, r.Files) -// SetMode(TestMode) -// } +func TestLoadHTMLFilesTestMode(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") + }, + ) + defer ts.Close() -func TestLoadHTMLFiles(t *testing.T) { - td := setupHTMLFiles(t, TestMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - td() } -func TestLoadHTMLFiles2(t *testing.T) { - td := setupHTMLFiles(t, DebugMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") +func TestLoadHTMLFilesDebugMode(t *testing.T) { + ts := setupHTMLFiles( + t, + DebugMode, + false, + func(router *Engine) { + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - td() } -func TestLoadHTMLFiles3(t *testing.T) { - td := setupHTMLFiles(t, ReleaseMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") +func TestLoadHTMLFilesReleaseMode(t *testing.T) { + ts := setupHTMLFiles( + t, + ReleaseMode, + false, + func(router *Engine) { + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - td() } func TestLoadHTMLFilesUsingTLS(t *testing.T) { - td := setupHTMLFiles(t, TestMode, true) + ts := setupHTMLFiles( + t, + TestMode, + true, + func(router *Engine) { + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") + }, + ) + defer ts.Close() + // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error tr := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -224,28 +247,33 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get("https://127.0.0.1:9999/test") + res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) - td() } func TestLoadHTMLFilesFuncMap(t *testing.T) { - time.Now() - td := setupHTMLFiles(t, TestMode, false) - res, err := http.Get("http://127.0.0.1:8888/raw") + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp)) - - td() } func TestAddRoute(t *testing.T) { From 333bac5f94a1bd3237996fbcae6e78fee8dbc5c6 Mon Sep 17 00:00:00 2001 From: "A. F" Date: Wed, 17 Oct 2018 09:40:57 +0200 Subject: [PATCH 108/111] add example to set and get cookies (#1599) --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 95bd3204..5c14c440 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) + - [Set and get a cookie](#set-and-get-a-cookie) - [Testing](#testing) - [Users](#users) @@ -1880,6 +1881,35 @@ func main() { } ``` +### Set and get a cookie + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + cookie, err := c.Cookie("gin_cookie") + + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + ## Testing From a1a32562de0fe61b17aab42cd4fa7847ae814cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 19 Oct 2018 11:06:23 +0800 Subject: [PATCH 109/111] add gin user - photoprism (#1601) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5c14c440..1b5fb493 100644 --- a/README.md +++ b/README.md @@ -1964,3 +1964,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. +* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. From dbc330b804052d7db230729c15d041cbb775e7b6 Mon Sep 17 00:00:00 2001 From: Ismail Gjevori Date: Mon, 22 Oct 2018 17:01:14 +0200 Subject: [PATCH 110/111] Pass MaxMultipartMemory when FormFile is called (#1600) When `gin.Context.FormFile("...")` is called the `engine.MaxMultipartMemory` is never used. This PR makes sure that the `MaxMultipartMemory` is passed and removes 2 calls to `http.Request.ParseForm` since they are called from `http.Request.ParseMultipartForm` --- context.go | 7 +++++-- context_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 809902a3..c8a59bb0 100644 --- a/context.go +++ b/context.go @@ -414,7 +414,6 @@ func (c *Context) PostFormArray(key string) []string { // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request - req.ParseForm() req.ParseMultipartForm(c.engine.MaxMultipartMemory) if values := req.PostForm[key]; len(values) > 0 { return values, true @@ -437,7 +436,6 @@ func (c *Context) PostFormMap(key string) map[string]string { // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { req := c.Request - req.ParseForm() req.ParseMultipartForm(c.engine.MaxMultipartMemory) dicts, exist := c.get(req.PostForm, key) @@ -465,6 +463,11 @@ func (c *Context) get(m map[string][]string, key string) (map[string]string, boo // FormFile returns the first file for the provided form key. func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { + if c.Request.MultipartForm == nil { + if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + return nil, err + } + } _, fh, err := c.Request.FormFile(name) return fh, err } diff --git a/context_test.go b/context_test.go index 2e972f18..fb492e02 100644 --- a/context_test.go +++ b/context_test.go @@ -84,6 +84,19 @@ func TestContextFormFile(t *testing.T) { assert.NoError(t, c.SaveUploadedFile(f, "test")) } +func TestContextFormFileFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) +} + func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) From c65e5efc9a0ae6666819cda8e9a86db4d3d19833 Mon Sep 17 00:00:00 2001 From: Thomas Schaffer Date: Tue, 23 Oct 2018 04:56:33 +0200 Subject: [PATCH 111/111] Expose HandlerFunc in RouteInfos (#1272) --- gin.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/gin.go b/gin.go index 92c24ba2..440519f5 100644 --- a/gin.go +++ b/gin.go @@ -38,9 +38,10 @@ func (c HandlersChain) Last() HandlerFunc { // RouteInfo represents a request route's specification which contains method and path and its handler. type RouteInfo struct { - Method string - Path string - Handler string + Method string + Path string + Handler string + HandlerFunc HandlerFunc } // RoutesInfo defines a RouteInfo array. @@ -266,10 +267,12 @@ func (engine *Engine) Routes() (routes RoutesInfo) { func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { path += root.path if len(root.handlers) > 0 { + handlerFunc := root.handlers.Last() routes = append(routes, RouteInfo{ - Method: method, - Path: path, - Handler: nameOfFunction(root.handlers.Last()), + Method: method, + Path: path, + Handler: nameOfFunction(handlerFunc), + HandlerFunc: handlerFunc, }) } for _, child := range root.children {