diff --git a/.gitignore b/.gitignore
index 14dc8f20..bdd50c95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@ vendor/*
coverage.out
count.out
test
+profile.out
+tmp.out
diff --git a/.travis.yml b/.travis.yml
index a765f37f..be196feb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,16 +7,27 @@ go:
- 1.9.x
- 1.10.x
- 1.11.x
+ - 1.12.x
- master
matrix:
fast_finish: true
+ include:
+ - go: 1.11.x
+ env: GO111MODULE=on
+ - go: 1.12.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 51b9969f..7ab57e27 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,25 @@ 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 > tmp.out; \
+ cat tmp.out; \
+ if grep -q "^--- FAIL" tmp.out; then \
+ rm tmp.out; \
+ exit 1; \
+ elif grep -q "build failed" tmp.out; then \
+ rm tmp.out; \
+ exit 1; \
+ elif grep -q "setup failed" tmp.out; then \
+ rm tmp.out; \
+ exit 1; \
+ fi; \
+ if [ -f profile.out ]; then \
+ cat profile.out | grep -v "mode:" >> coverage.out; \
+ rm profile.out; \
+ fi; \
+ done
.PHONY: fmt
fmt:
@@ -18,7 +38,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 +46,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 +62,26 @@ 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;
.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)
+
+.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/README.md b/README.md
index 95bd3204..058eee2a 100644
--- a/README.md
+++ b/README.md
@@ -35,10 +35,12 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [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)
+ - [Custom Log Format](#custom-log-format)
- [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 Uri](#bind-uri)
- [Bind HTML checkboxes](#bind-html-checkboxes)
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
- [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
@@ -60,6 +62,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)
@@ -212,9 +215,6 @@ $ go build -tags=jsoniter .
```go
func main() {
- // Disable Console Color
- // gin.DisableConsoleColor()
-
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
@@ -361,6 +361,10 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single).
+`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693)
+
+> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.
+
```go
func main() {
router := gin.Default()
@@ -526,9 +530,88 @@ func main() {
}
```
+### Custom Log Format
+```go
+func main() {
+ router := gin.New()
+
+ // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
+ // By default gin.DefaultWriter = os.Stdout
+ router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
+
+ // your custom format
+ return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
+ param.ClientIP,
+ param.TimeStamp.Format(time.RFC1123),
+ param.Method,
+ param.Path,
+ param.Request.Proto,
+ param.StatusCode,
+ param.Latency,
+ param.Request.UserAgent(),
+ param.ErrorMessage,
+ )
+ }))
+ router.Use(gin.Recovery())
+
+ router.GET("/ping", func(c *gin.Context) {
+ c.String(200, "pong")
+ })
+
+ router.Run(":8080")
+}
+```
+
+**Sample Output**
+```
+::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
+```
+
+### Controlling Log output coloring
+
+By default, logs output on console should be colorized depending on the detected TTY.
+
+Never colorize logs:
+
+```go
+func main() {
+ // Disable log's color
+ gin.DisableConsoleColor()
+
+ // Creates a gin router with default middleware:
+ // logger and recovery (crash-free) middleware
+ router := gin.Default()
+
+ router.GET("/ping", func(c *gin.Context) {
+ c.String(200, "pong")
+ })
+
+ router.Run(":8080")
+}
+```
+
+Always colorize logs:
+
+```go
+func main() {
+ // Force log's color
+ gin.ForceConsoleColor()
+
+ // Creates a gin router with default middleware:
+ // logger and recovery (crash-free) middleware
+ router := gin.Default()
+
+ router.GET("/ping", func(c *gin.Context) {
+ c.String(200, "pong")
+ })
+
+ router.Run(":8080")
+}
+```
+
### Model binding and validation
-To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).
+To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags).
@@ -536,10 +619,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`, `BindXML`, `BindQuery`
+ - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`
- **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`, `ShouldBindXML`, `ShouldBindQuery`
+ - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`
- **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`.
@@ -576,7 +659,7 @@ func main() {
//
//
// user
- // 123
+ // 123
// )
router.POST("/loginXML", func(c *gin.Context) {
var xml Login
@@ -792,6 +875,40 @@ Test it with:
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
```
+### Bind Uri
+
+See the [detail information](https://github.com/gin-gonic/gin/issues/846).
+
+```go
+package main
+
+import "github.com/gin-gonic/gin"
+
+type Person struct {
+ ID string `uri:"id" binding:"required,uuid"`
+ Name string `uri:"name" binding:"required"`
+}
+
+func main() {
+ route := gin.Default()
+ route.GET("/:name/:id", func(c *gin.Context) {
+ var person Person
+ if err := c.ShouldBindUri(&person); err != nil {
+ c.JSON(400, gin.H{"msg": err})
+ return
+ }
+ c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
+ })
+ route.Run(":8088")
+}
+```
+
+Test it with:
+```sh
+$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
+$ curl -v localhost:8088/thinkerou/not-uuid
+```
+
### Bind HTML checkboxes
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
@@ -823,12 +940,12 @@ form.html
```
@@ -1021,7 +1138,7 @@ func main() {
})
// listen and serve on 0.0.0.0:8080
- r.Run(":8080)
+ r.Run(":8080")
}
```
@@ -1159,7 +1276,7 @@ You may use custom delims
```go
r := gin.Default()
r.Delims("{[{", "}]}")
- r.LoadHTMLGlob("/path/to/templates"))
+ r.LoadHTMLGlob("/path/to/templates")
```
#### Custom Template Funcs
@@ -1555,6 +1672,7 @@ import (
"net/http"
"os"
"os/signal"
+ "syscall"
"time"
"github.com/gin-gonic/gin"
@@ -1582,7 +1700,10 @@ func main() {
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal)
- signal.Notify(quit, os.Interrupt)
+ // kill (no param) default send syscanll.SIGTERM
+ // kill -2 is syscall.SIGINT
+ // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown Server ...")
@@ -1591,6 +1712,11 @@ func main() {
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
+ // catching ctx.Done(). timeout of 5 seconds.
+ select {
+ case <-ctx.Done():
+ log.Println("timeout of 5 seconds.")
+ }
log.Println("Server exiting")
}
```
@@ -1880,6 +2006,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
@@ -1934,3 +2089,6 @@ 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.
+* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
+* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
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/binding/binding.go b/binding/binding.go
index 3a2aad9c..26d71c9f 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -18,6 +18,7 @@ const (
MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
+ MIMEYAML = "application/x-yaml"
)
// Binding describes the interface which needs to be implemented for binding the
@@ -35,9 +36,16 @@ type BindingBody interface {
BindBody([]byte, interface{}) error
}
+// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
+// but it read the Params.
+type BindingUri interface {
+ Name() string
+ BindUri(map[string][]string, 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
+// of the request. 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.
@@ -68,6 +76,8 @@ var (
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
+ YAML = yamlBinding{}
+ Uri = uriBinding{}
)
// Default returns the appropriate Binding instance based on the HTTP method
@@ -86,6 +96,8 @@ func Default(method, contentType string) Binding {
return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack
+ case MIMEYAML:
+ return YAML
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
return Form
}
diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go
index dfd761e1..901d429c 100644
--- a/binding/binding_body_test.go
+++ b/binding/binding_body_test.go
@@ -19,12 +19,12 @@ func TestBindingBody(t *testing.T) {
want string
}{
{
- name: "JSON bidning",
+ name: "JSON binding",
binding: JSON,
body: `{"foo":"FOO"}`,
},
{
- name: "XML bidning",
+ name: "XML binding",
binding: XML,
body: `
@@ -36,6 +36,11 @@ func TestBindingBody(t *testing.T) {
binding: MsgPack,
body: msgPackBody(t),
},
+ {
+ name: "YAML binding",
+ binding: YAML,
+ body: `foo: FOO`,
+ },
} {
t.Logf("testing: %s", tt.name)
req := requestWithBody("POST", "/", tt.body)
diff --git a/binding/binding_test.go b/binding/binding_test.go
index efe87669..c9dea347 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -11,6 +11,7 @@ import (
"io/ioutil"
"mime/multipart"
"net/http"
+ "strconv"
"testing"
"time"
@@ -60,6 +61,10 @@ type FooStructForMapType struct {
MapFoo map[string]interface{} `form:"map_foo"`
}
+type FooStructForIgnoreFormTag struct {
+ Foo *string `form:"-"`
+}
+
type InvalidNameType struct {
TestName string `invalid_name:"test_name"`
}
@@ -190,6 +195,16 @@ func TestBindingDefault(t *testing.T) {
assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK))
assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2))
+
+ assert.Equal(t, YAML, Default("POST", MIMEYAML))
+ assert.Equal(t, YAML, Default("PUT", MIMEYAML))
+}
+
+func TestBindingJSONNilBody(t *testing.T) {
+ var obj FooStruct
+ req, _ := http.NewRequest(http.MethodPost, "/", nil)
+ err := JSON.Bind(req, &obj)
+ assert.Error(t, err)
}
func TestBindingJSON(t *testing.T) {
@@ -267,6 +282,12 @@ func TestBindingFormForTime2(t *testing.T) {
"", "")
}
+func TestFormBindingIgnoreField(t *testing.T) {
+ testFormBindingIgnoreField(t, "POST",
+ "/", "/",
+ "-=bar", "")
+}
+
func TestBindingFormInvalidName(t *testing.T) {
testFormBindingInvalidName(t, "POST",
"/", "/",
@@ -473,6 +494,20 @@ func TestBindingXMLFail(t *testing.T) {
"", "")
}
+func TestBindingYAML(t *testing.T) {
+ testBodyBinding(t,
+ YAML, "yaml",
+ "/", "/",
+ `foo: bar`, `bar: foo`)
+}
+
+func TestBindingYAMLFail(t *testing.T) {
+ testBodyBindingFail(t,
+ YAML, "yaml",
+ "/", "/",
+ `foo:\nbar`, `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)
@@ -491,28 +526,28 @@ func createFormPostRequestFail() *http.Request {
return req
}
-func createFormMultipartRequest() *http.Request {
+func createFormMultipartRequest(t *testing.T) *http.Request {
boundary := "--testboundary"
body := new(bytes.Buffer)
mw := multipart.NewWriter(body)
defer mw.Close()
- mw.SetBoundary(boundary)
- mw.WriteField("foo", "bar")
- mw.WriteField("bar", "foo")
+ assert.NoError(t, mw.SetBoundary(boundary))
+ assert.NoError(t, mw.WriteField("foo", "bar"))
+ assert.NoError(t, mw.WriteField("bar", "foo"))
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req
}
-func createFormMultipartRequestFail() *http.Request {
+func createFormMultipartRequestFail(t *testing.T) *http.Request {
boundary := "--testboundary"
body := new(bytes.Buffer)
mw := multipart.NewWriter(body)
defer mw.Close()
- mw.SetBoundary(boundary)
- mw.WriteField("map_foo", "bar")
+ assert.NoError(t, mw.SetBoundary(boundary))
+ assert.NoError(t, mw.WriteField("map_foo", "bar"))
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req
@@ -521,7 +556,7 @@ func createFormMultipartRequestFail() *http.Request {
func TestBindingFormPost(t *testing.T) {
req := createFormPostRequest()
var obj FooBarStruct
- FormPost.Bind(req, &obj)
+ assert.NoError(t, FormPost.Bind(req, &obj))
assert.Equal(t, "form-urlencoded", FormPost.Name())
assert.Equal(t, "bar", obj.Foo)
@@ -531,7 +566,7 @@ func TestBindingFormPost(t *testing.T) {
func TestBindingDefaultValueFormPost(t *testing.T) {
req := createDefaultFormPostRequest()
var obj FooDefaultBarStruct
- FormPost.Bind(req, &obj)
+ assert.NoError(t, FormPost.Bind(req, &obj))
assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, "hello", obj.Bar)
@@ -545,9 +580,9 @@ func TestBindingFormPostFail(t *testing.T) {
}
func TestBindingFormMultipart(t *testing.T) {
- req := createFormMultipartRequest()
+ req := createFormMultipartRequest(t)
var obj FooBarStruct
- FormMultipart.Bind(req, &obj)
+ assert.NoError(t, FormMultipart.Bind(req, &obj))
assert.Equal(t, "multipart/form-data", FormMultipart.Name())
assert.Equal(t, "bar", obj.Foo)
@@ -555,7 +590,7 @@ func TestBindingFormMultipart(t *testing.T) {
}
func TestBindingFormMultipartFail(t *testing.T) {
- req := createFormMultipartRequestFail()
+ req := createFormMultipartRequestFail(t)
var obj FooStructForMapType
err := FormMultipart.Bind(req, &obj)
assert.Error(t, err)
@@ -645,6 +680,49 @@ func TestExistsFails(t *testing.T) {
assert.Error(t, err)
}
+func TestUriBinding(t *testing.T) {
+ b := Uri
+ assert.Equal(t, "uri", b.Name())
+
+ type Tag struct {
+ Name string `uri:"name"`
+ }
+ var tag Tag
+ m := make(map[string][]string)
+ m["name"] = []string{"thinkerou"}
+ assert.NoError(t, b.BindUri(m, &tag))
+ assert.Equal(t, "thinkerou", tag.Name)
+
+ type NotSupportStruct struct {
+ Name map[string]interface{} `uri:"name"`
+ }
+ var not NotSupportStruct
+ assert.Error(t, b.BindUri(m, ¬))
+ assert.Equal(t, map[string]interface{}(nil), not.Name)
+}
+
+func TestUriInnerBinding(t *testing.T) {
+ type Tag struct {
+ Name string `uri:"name"`
+ S struct {
+ Age int `uri:"age"`
+ }
+ }
+
+ expectedName := "mike"
+ expectedAge := 25
+
+ m := map[string][]string{
+ "name": {expectedName},
+ "age": {strconv.Itoa(expectedAge)},
+ }
+
+ var tag Tag
+ assert.NoError(t, Uri.BindUri(m, &tag))
+ assert.Equal(t, tag.Name, expectedName)
+ assert.Equal(t, tag.S.Age, expectedAge)
+}
+
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
b := Form
assert.Equal(t, "form", b.Name())
@@ -792,6 +870,21 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod
assert.Error(t, err)
}
+func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) {
+ b := Form
+ assert.Equal(t, "form", b.Name())
+
+ obj := FooStructForIgnoreFormTag{}
+ req := requestWithBody(method, path, body)
+ if method == "POST" {
+ req.Header.Add("Content-Type", MIMEPOSTForm)
+ }
+ err := b.Bind(req, &obj)
+ assert.NoError(t, err)
+
+ assert.Nil(t, obj.Foo)
+}
+
func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) {
b := Form
assert.Equal(t, "form", b.Name())
@@ -1215,3 +1308,12 @@ func requestWithBody(method, path, body string) (req *http.Request) {
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
return
}
+
+func TestCanSet(t *testing.T) {
+ type CanSetStruct struct {
+ lowerStart string `form:"lower"`
+ }
+
+ var c CanSetStruct
+ assert.Nil(t, mapForm(&c, nil))
+}
diff --git a/binding/form.go b/binding/form.go
index 0be59660..8955c95b 100644
--- a/binding/form.go
+++ b/binding/form.go
@@ -20,7 +20,11 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
- req.ParseMultipartForm(defaultMemory)
+ if err := req.ParseMultipartForm(defaultMemory); err != nil {
+ if err != http.ErrNotMultipart {
+ return err
+ }
+ }
if err := mapForm(obj, req.Form); err != nil {
return err
}
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index f46a0dc1..8eb5c0d1 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -12,7 +12,15 @@ import (
"time"
)
+func mapUri(ptr interface{}, m map[string][]string) error {
+ return mapFormByTag(ptr, m, "uri")
+}
+
func mapForm(ptr interface{}, form map[string][]string) error {
+ return mapFormByTag(ptr, form, "form")
+}
+
+func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
typ := reflect.TypeOf(ptr).Elem()
val := reflect.ValueOf(ptr).Elem()
for i := 0; i < typ.NumField(); i++ {
@@ -23,7 +31,7 @@ func mapForm(ptr interface{}, form map[string][]string) error {
}
structFieldKind := structField.Kind()
- inputFieldName := typeField.Tag.Get("form")
+ inputFieldName := typeField.Tag.Get(tag)
inputFieldNameList := strings.Split(inputFieldName, ",")
inputFieldName = inputFieldNameList[0]
var defaultValue string
@@ -33,6 +41,9 @@ func mapForm(ptr interface{}, form map[string][]string) error {
defaultValue = defaultList[1]
}
}
+ if inputFieldName == "-" {
+ continue
+ }
if inputFieldName == "" {
inputFieldName = typeField.Name
@@ -47,7 +58,7 @@ func mapForm(ptr interface{}, form map[string][]string) error {
structFieldKind = structField.Kind()
}
if structFieldKind == reflect.Struct {
- err := mapForm(structField.Addr().Interface(), form)
+ err := mapFormByTag(structField.Addr().Interface(), form, tag)
if err != nil {
return err
}
diff --git a/binding/json.go b/binding/json.go
index 310922c1..f968161b 100644
--- a/binding/json.go
+++ b/binding/json.go
@@ -6,6 +6,7 @@ package binding
import (
"bytes"
+ "fmt"
"io"
"net/http"
@@ -24,6 +25,9 @@ func (jsonBinding) Name() string {
}
func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
+ if req == nil || req.Body == nil {
+ return fmt.Errorf("invalid request")
+ }
return decodeJSON(req.Body, obj)
}
diff --git a/binding/protobuf.go b/binding/protobuf.go
index 540e9c1f..f9ece928 100644
--- a/binding/protobuf.go
+++ b/binding/protobuf.go
@@ -29,7 +29,7 @@ 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
+ // Here it's same to return validate(obj), but util now we can't add
// `binding:""` to the struct which automatically generate by gen-proto
return nil
// return validate(obj)
diff --git a/binding/uri.go b/binding/uri.go
new file mode 100644
index 00000000..f91ec381
--- /dev/null
+++ b/binding/uri.go
@@ -0,0 +1,18 @@
+// 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 binding
+
+type uriBinding struct{}
+
+func (uriBinding) Name() string {
+ return "uri"
+}
+
+func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
+ if err := mapUri(obj, m); err != nil {
+ return err
+ }
+ return validate(obj)
+}
diff --git a/binding/yaml.go b/binding/yaml.go
new file mode 100644
index 00000000..a2d36d6a
--- /dev/null
+++ b/binding/yaml.go
@@ -0,0 +1,35 @@
+// 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 binding
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+
+ "gopkg.in/yaml.v2"
+)
+
+type yamlBinding struct{}
+
+func (yamlBinding) Name() string {
+ return "yaml"
+}
+
+func (yamlBinding) Bind(req *http.Request, obj interface{}) error {
+ return decodeYAML(req.Body, obj)
+}
+
+func (yamlBinding) BindBody(body []byte, obj interface{}) error {
+ return decodeYAML(bytes.NewReader(body), obj)
+}
+
+func decodeYAML(r io.Reader, obj interface{}) error {
+ decoder := yaml.NewDecoder(r)
+ if err := decoder.Decode(obj); err != nil {
+ return err
+ }
+ return validate(obj)
+}
diff --git a/context.go b/context.go
index 9ee5d084..5063b498 100644
--- a/context.go
+++ b/context.go
@@ -32,6 +32,7 @@ const (
MIMEPlain = binding.MIMEPlain
MIMEPOSTForm = binding.MIMEPOSTForm
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
+ MIMEYAML = binding.MIMEYAML
BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
)
@@ -82,6 +83,10 @@ func (c *Context) Copy() *Context {
cp.Writer = &cp.writermem
cp.index = abortIndex
cp.handlers = nil
+ cp.Keys = map[string]interface{}{}
+ for k, v := range c.Keys {
+ cp.Keys[k] = v
+ }
return &cp
}
@@ -91,6 +96,16 @@ func (c *Context) HandlerName() string {
return nameOfFunction(c.handlers.Last())
}
+// HandlerNames returns a list of all registered handlers for this context in descending order,
+// following the semantics of HandlerName()
+func (c *Context) HandlerNames() []string {
+ hn := make([]string, 0, len(c.handlers))
+ for _, val := range c.handlers {
+ hn = append(hn, nameOfFunction(val))
+ }
+ return hn
+}
+
// Handler returns the main handler.
func (c *Context) Handler() HandlerFunc {
return c.handlers.Last()
@@ -105,8 +120,9 @@ func (c *Context) Handler() HandlerFunc {
// See example in GitHub.
func (c *Context) Next() {
c.index++
- for s := int8(len(c.handlers)); c.index < s; c.index++ {
+ for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
+ c.index++
}
}
@@ -415,8 +431,11 @@ 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 err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
+ if err != http.ErrNotMultipart {
+ debugPrint("error on parse multipart form array: %v", err)
+ }
+ }
if values := req.PostForm[key]; len(values) > 0 {
return values, true
}
@@ -438,8 +457,11 @@ 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)
+ if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
+ if err != http.ErrNotMultipart {
+ debugPrint("error on parse multipart form map: %v", err)
+ }
+ }
dicts, exist := c.get(req.PostForm, key)
if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil {
@@ -466,6 +488,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
}
@@ -490,8 +517,8 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
}
defer out.Close()
- io.Copy(out, src)
- return nil
+ _, err = io.Copy(out, src)
+ return err
}
// Bind checks the Content-Type to select a binding engine automatically,
@@ -522,15 +549,30 @@ func (c *Context) BindQuery(obj interface{}) error {
return c.MustBindWith(obj, binding.Query)
}
-// MustBindWith binds the passed struct pointer using the specified binding engine.
-// It will abort the request with HTTP 400 if any error ocurrs.
-// 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(http.StatusBadRequest, err).SetType(ErrorTypeBind)
- }
+// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
+func (c *Context) BindYAML(obj interface{}) error {
+ return c.MustBindWith(obj, binding.YAML)
+}
- return
+// BindUri binds the passed struct pointer using binding.Uri.
+// It will abort the request with HTTP 400 if any error occurs.
+func (c *Context) BindUri(obj interface{}) error {
+ if err := c.ShouldBindUri(obj); err != nil {
+ c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
+ return err
+ }
+ return nil
+}
+
+// MustBindWith binds the passed struct pointer using the specified binding engine.
+// It will abort the request with HTTP 400 if any error occurs.
+// See the binding package.
+func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
+ if err := c.ShouldBindWith(obj, b); err != nil {
+ c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
+ return err
+ }
+ return nil
}
// ShouldBind checks the Content-Type to select a binding engine automatically,
@@ -561,6 +603,20 @@ func (c *Context) ShouldBindQuery(obj interface{}) error {
return c.ShouldBindWith(obj, binding.Query)
}
+// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
+func (c *Context) ShouldBindYAML(obj interface{}) error {
+ return c.ShouldBindWith(obj, binding.YAML)
+}
+
+// ShouldBindUri binds the passed struct pointer using the specified binding engine.
+func (c *Context) ShouldBindUri(obj interface{}) error {
+ m := make(map[string][]string)
+ for _, v := range c.Params {
+ m[v.Key] = []string{v.Value}
+ }
+ return binding.Uri.BindUri(m, obj)
+}
+
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package.
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
@@ -572,9 +628,7 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
//
// 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) {
+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 {
@@ -890,7 +944,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
c.XML(code, data)
default:
- c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
+ c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
}
}
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 351cff3b..230589da 100644
--- a/context_test.go
+++ b/context_test.go
@@ -70,7 +70,8 @@ func TestContextFormFile(t *testing.T) {
mw := multipart.NewWriter(buf)
w, err := mw.CreateFormFile("file", "test")
if assert.NoError(t, err) {
- w.Write([]byte("test"))
+ _, err = w.Write([]byte("test"))
+ assert.NoError(t, err)
}
mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder())
@@ -84,13 +85,27 @@ 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)
- mw.WriteField("foo", "bar")
+ assert.NoError(t, mw.WriteField("foo", "bar"))
w, err := mw.CreateFormFile("file", "test")
if assert.NoError(t, err) {
- w.Write([]byte("test"))
+ _, err = w.Write([]byte("test"))
+ assert.NoError(t, err)
}
mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder())
@@ -124,7 +139,8 @@ func TestSaveUploadedCreateFailed(t *testing.T) {
mw := multipart.NewWriter(buf)
w, err := mw.CreateFormFile("file", "test")
if assert.NoError(t, err) {
- w.Write([]byte("test"))
+ _, err = w.Write([]byte("test"))
+ assert.NoError(t, err)
}
mw.Close()
c, _ := CreateTestContext(httptest.NewRecorder())
@@ -146,7 +162,7 @@ func TestContextReset(t *testing.T) {
c.index = 2
c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()}
c.Params = Params{Param{}}
- c.Error(errors.New("test"))
+ c.Error(errors.New("test")) // nolint: errcheck
c.Set("foo", "bar")
c.reset()
@@ -315,6 +331,8 @@ func TestContextCopy(t *testing.T) {
assert.Equal(t, cp.Keys, c.Keys)
assert.Equal(t, cp.engine, c.engine)
assert.Equal(t, cp.Params, c.Params)
+ cp.Set("foo", "notBar")
+ assert.False(t, cp.Keys["foo"] == c.Keys["foo"])
}
func TestContextHandlerName(t *testing.T) {
@@ -324,10 +342,26 @@ func TestContextHandlerName(t *testing.T) {
assert.Regexp(t, "^(.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest$", c.HandlerName())
}
+func TestContextHandlerNames(t *testing.T) {
+ c, _ := CreateTestContext(httptest.NewRecorder())
+ c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest, func(c *Context) {}, handlerNameTest2}
+
+ names := c.HandlerNames()
+
+ assert.True(t, len(names) == 4)
+ for _, name := range names {
+ assert.Regexp(t, `^(.*/vendor/)?(github\.com/gin-gonic/gin\.){1}(TestContextHandlerNames\.func.*){0,1}(handlerNameTest.*){0,1}`, name)
+ }
+}
+
func handlerNameTest(c *Context) {
}
+func handlerNameTest2(c *Context) {
+
+}
+
var handlerTest HandlerFunc = func(c *Context) {
}
@@ -628,7 +662,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 +676,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 +690,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 +702,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 +716,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 +729,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 +742,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 +754,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 +768,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 +780,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 +791,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 +807,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) {
@@ -785,7 +819,7 @@ func TestContextRenderHTML2(t *testing.T) {
assert.Len(t, router.trees, 1)
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
router.SetHTMLTemplate(templ)
SetMode(TestMode)
@@ -797,7 +831,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 +845,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 +858,7 @@ func TestContextRenderXML(t *testing.T) {
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(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"))
}
// Tests that no XML is rendered if code is 204
@@ -836,7 +870,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 +883,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 +895,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 +909,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 +922,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 +935,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 +947,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 +976,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"))
}
func TestContextRenderAttachment(t *testing.T) {
@@ -968,7 +1002,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
@@ -992,7 +1026,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) {
@@ -1075,7 +1109,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) {
@@ -1090,7 +1124,7 @@ func TestContextNegotiationWithXML(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(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"))
}
func TestContextNegotiationWithHTML(t *testing.T) {
@@ -1108,7 +1142,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) {
@@ -1144,7 +1178,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")
@@ -1211,7 +1245,8 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
assert.Equal(t, "application/json; charset=utf-8", contentType)
buf := new(bytes.Buffer)
- buf.ReadFrom(w.Body)
+ _, err := buf.ReadFrom(w.Body)
+ assert.NoError(t, err)
jsonStringBody := buf.String()
assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody)
}
@@ -1220,11 +1255,11 @@ func TestContextError(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
assert.Empty(t, c.Errors)
- c.Error(errors.New("first error"))
+ c.Error(errors.New("first error")) // nolint: errcheck
assert.Len(t, c.Errors, 1)
assert.Equal(t, "Error #01: first error\n", c.Errors.String())
- c.Error(&Error{
+ c.Error(&Error{ // nolint: errcheck
Err: errors.New("second error"),
Meta: "some data 2",
Type: ErrorTypePublic,
@@ -1246,13 +1281,13 @@ func TestContextError(t *testing.T) {
t.Error("didn't panic")
}
}()
- c.Error(nil)
+ c.Error(nil) // nolint: errcheck
}
func TestContextTypedError(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
- c.Error(errors.New("externo 0")).SetType(ErrorTypePublic)
- c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
+ c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck
+ c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck
for _, err := range c.Errors.ByType(ErrorTypePublic) {
assert.Equal(t, ErrorTypePublic, err.Type)
@@ -1267,7 +1302,7 @@ func TestContextAbortWithError(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
- c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input")
+ c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, abortIndex, c.index)
@@ -1380,6 +1415,23 @@ func TestContextBindWithQuery(t *testing.T) {
assert.Equal(t, 0, w.Body.Len())
}
+func TestContextBindWithYAML(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+
+ c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo"))
+ c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
+
+ var obj struct {
+ Foo string `yaml:"foo"`
+ Bar string `yaml:"bar"`
+ }
+ assert.NoError(t, c.BindYAML(&obj))
+ assert.Equal(t, "foo", obj.Bar)
+ assert.Equal(t, "bar", obj.Foo)
+ assert.Equal(t, 0, w.Body.Len())
+}
+
func TestContextBadAutoBind(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
@@ -1440,7 +1492,7 @@ func TestContextShouldBindWithXML(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`
FOO
- BAR
+ BAR`))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
@@ -1458,15 +1510,36 @@ func TestContextShouldBindWithQuery(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
- c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
+ c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused"))
var obj struct {
- Foo string `form:"foo"`
- Bar string `form:"bar"`
+ Foo string `form:"foo"`
+ Bar string `form:"bar"`
+ Foo1 string `form:"Foo"`
+ Bar1 string `form:"Bar"`
}
assert.NoError(t, c.ShouldBindQuery(&obj))
assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, "bar", obj.Foo)
+ assert.Equal(t, "foo1", obj.Bar1)
+ assert.Equal(t, "bar1", obj.Foo1)
+ assert.Equal(t, 0, w.Body.Len())
+}
+
+func TestContextShouldBindWithYAML(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+
+ c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo"))
+ c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
+
+ var obj struct {
+ Foo string `yaml:"foo"`
+ Bar string `yaml:"bar"`
+ }
+ assert.NoError(t, c.ShouldBindYAML(&obj))
+ assert.Equal(t, "foo", obj.Bar)
+ assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len())
}
@@ -1640,9 +1713,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 {
@@ -1675,7 +1748,8 @@ func TestContextStream(t *testing.T) {
stopStream = false
}()
- w.Write([]byte("test"))
+ _, err := w.Write([]byte("test"))
+ assert.NoError(t, err)
return stopStream
})
@@ -1692,10 +1766,23 @@ func TestContextStreamWithClientGone(t *testing.T) {
w.closeClient()
}()
- writer.Write([]byte("test"))
+ _, err := writer.Write([]byte("test"))
+ assert.NoError(t, err)
return true
})
assert.Equal(t, "test", w.Body.String())
}
+
+func TestContextResetInHandler(t *testing.T) {
+ w := CreateTestResponseRecorder()
+ c, _ := CreateTestContext(w)
+
+ c.handlers = []HandlerFunc{
+ func(c *Context) { c.reset() },
+ }
+ assert.NotPanics(t, func() {
+ c.Next()
+ })
+}
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
diff --git a/debug.go b/debug.go
index c5e65b22..98c67cf7 100644
--- a/debug.go
+++ b/debug.go
@@ -51,6 +51,9 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
func debugPrint(format string, values ...interface{}) {
if IsDebugging() {
+ if !strings.HasSuffix(format, "\n") {
+ format += "\n"
+ }
fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...)
}
}
diff --git a/debug_test.go b/debug_test.go
index 97ff166b..d338f0a0 100644
--- a/debug_test.go
+++ b/debug_test.go
@@ -32,21 +32,21 @@ func TestIsDebugging(t *testing.T) {
}
func TestDebugPrint(t *testing.T) {
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
SetMode(ReleaseMode)
debugPrint("DEBUG this!")
SetMode(TestMode)
debugPrint("DEBUG this!")
SetMode(DebugMode)
- debugPrint("these are %d %s\n", 2, "error messages")
+ debugPrint("these are %d %s", 2, "error messages")
SetMode(TestMode)
})
assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re)
}
func TestDebugPrintError(t *testing.T) {
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
debugPrintError(nil)
debugPrintError(errors.New("this is an error"))
@@ -56,7 +56,7 @@ func TestDebugPrintError(t *testing.T) {
}
func TestDebugPrintRoutes(t *testing.T) {
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
SetMode(TestMode)
@@ -65,7 +65,7 @@ func TestDebugPrintRoutes(t *testing.T) {
}
func TestDebugPrintLoadTemplate(t *testing.T) {
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl"))
debugPrintLoadTemplate(templ)
@@ -75,7 +75,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) {
}
func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
debugPrintWARNINGSetHTMLTemplate()
SetMode(TestMode)
@@ -84,7 +84,7 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
}
func TestDebugPrintWARNINGDefault(t *testing.T) {
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
debugPrintWARNINGDefault()
SetMode(TestMode)
@@ -98,7 +98,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
}
func TestDebugPrintWARNINGNew(t *testing.T) {
- re := captureOutput(func() {
+ re := captureOutput(t, func() {
SetMode(DebugMode)
debugPrintWARNINGNew()
SetMode(TestMode)
@@ -106,7 +106,7 @@ func TestDebugPrintWARNINGNew(t *testing.T) {
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 captureOutput(f func()) string {
+func captureOutput(t *testing.T, f func()) string {
reader, writer, err := os.Pipe()
if err != nil {
panic(err)
@@ -127,7 +127,8 @@ func captureOutput(f func()) string {
go func() {
var buf bytes.Buffer
wg.Done()
- io.Copy(&buf, reader)
+ _, err := io.Copy(&buf, reader)
+ assert.NoError(t, err)
out <- buf.String()
}()
wg.Wait()
diff --git a/deprecated_test.go b/deprecated_test.go
index 7a875fe4..f8df651c 100644
--- a/deprecated_test.go
+++ b/deprecated_test.go
@@ -24,7 +24,9 @@ func TestBindWith(t *testing.T) {
Foo string `form:"foo"`
Bar string `form:"bar"`
}
- assert.NoError(t, c.BindWith(&obj, binding.Form))
+ captureOutput(t, func() {
+ assert.NoError(t, c.BindWith(&obj, binding.Form))
+ })
assert.Equal(t, "foo", obj.Bar)
assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len())
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..568d5720
--- /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 initialize 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 function.
+
+```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
+```
diff --git a/errors.go b/errors.go
index 477b9d58..ab13ca61 100644
--- a/errors.go
+++ b/errors.go
@@ -24,9 +24,10 @@ const (
ErrorTypePrivate ErrorType = 1 << 0
// ErrorTypePublic indicates a public error.
ErrorTypePublic ErrorType = 1 << 1
- // ErrorTypeAny indicates other any error.
+ // ErrorTypeAny indicates any other error.
ErrorTypeAny ErrorType = 1<<64 - 1
- ErrorTypeNu = 2
+ // ErrorTypeNu indicates any other error.
+ ErrorTypeNu = 2
)
// Error represents a error's specification.
@@ -52,6 +53,7 @@ func (msg *Error) SetMeta(data interface{}) *Error {
return msg
}
+// JSON creates a properly formated JSON
func (msg *Error) JSON() interface{} {
json := H{}
if msg.Meta != nil {
diff --git a/errors_test.go b/errors_test.go
index 9351b578..6aae1c10 100644
--- a/errors_test.go
+++ b/errors_test.go
@@ -34,7 +34,7 @@ func TestError(t *testing.T) {
jsonBytes, _ := json.Marshal(err)
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
- err.SetMeta(H{
+ err.SetMeta(H{ // nolint: errcheck
"status": "200",
"data": "some data",
})
@@ -44,7 +44,7 @@ func TestError(t *testing.T) {
"data": "some data",
}, err.JSON())
- err.SetMeta(H{
+ err.SetMeta(H{ // nolint: errcheck
"error": "custom error",
"status": "200",
"data": "some data",
@@ -59,7 +59,7 @@ func TestError(t *testing.T) {
status string
data string
}
- err.SetMeta(customError{status: "200", data: "other data"})
+ err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck
assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON())
}
diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go
index af4f2146..999a209e 100644
--- a/examples/graceful-shutdown/graceful-shutdown/server.go
+++ b/examples/graceful-shutdown/graceful-shutdown/server.go
@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"os/signal"
+ "syscall"
"time"
"github.com/gin-gonic/gin"
@@ -35,7 +36,10 @@ func main() {
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal)
- signal.Notify(quit, os.Interrupt)
+ // kill (no param) default send syscanll.SIGTERM
+ // kill -2 is syscall.SIGINT
+ // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutdown Server ...")
@@ -44,5 +48,10 @@ func main() {
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
+ // catching ctx.Done(). timeout of 5 seconds.
+ select {
+ case <-ctx.Done():
+ log.Println("timeout of 5 seconds.")
+ }
log.Println("Server exiting")
}
diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go
index edc1ca9b..820e65a3 100644
--- a/examples/grpc/gin/main.go
+++ b/examples/grpc/gin/main.go
@@ -19,7 +19,7 @@ func main() {
defer conn.Close()
client := pb.NewGreeterClient(conn)
- // Set up a http setver.
+ // Set up a http server.
r := gin.Default()
r.GET("/rest/n/:name", func(c *gin.Context) {
name := c.Param("name")
diff --git a/examples/new_relic/README.md b/examples/new_relic/README.md
new file mode 100644
index 00000000..70f14942
--- /dev/null
+++ b/examples/new_relic/README.md
@@ -0,0 +1,30 @@
+The [New Relic Go Agent](https://github.com/newrelic/go-agent) provides a nice middleware for the stdlib handler signature.
+The following is an adaptation of that middleware for Gin.
+
+```golang
+const (
+ // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context
+ NewRelicTxnKey = "NewRelicTxnKey"
+)
+
+// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler
+func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request)
+ defer txn.End()
+ ctx.Set(NewRelicTxnKey, txn)
+ ctx.Next()
+ }
+}
+```
+and in `main.go` or equivalent...
+```golang
+router := gin.Default()
+cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY"))
+app, err := newrelic.NewApplication(cfg)
+if err != nil {
+ log.Printf("failed to make new_relic app: %v", err)
+} else {
+ router.Use(adapters.NewRelicMonitoring(app))
+}
+ ```
diff --git a/examples/new_relic/main.go b/examples/new_relic/main.go
new file mode 100644
index 00000000..f85f7831
--- /dev/null
+++ b/examples/new_relic/main.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "log"
+ "net/http"
+ "os"
+
+ "github.com/gin-gonic/gin"
+ "github.com/newrelic/go-agent"
+)
+
+const (
+ // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context
+ NewRelicTxnKey = "NewRelicTxnKey"
+)
+
+// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler
+func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc {
+ return func(ctx *gin.Context) {
+ txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request)
+ defer txn.End()
+ ctx.Set(NewRelicTxnKey, txn)
+ ctx.Next()
+ }
+}
+
+func main() {
+ router := gin.Default()
+
+ cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY"))
+ app, err := newrelic.NewApplication(cfg)
+ if err != nil {
+ log.Printf("failed to make new_relic app: %v", err)
+ } else {
+ router.Use(NewRelicMonitoring(app))
+ }
+
+ router.GET("/", func(c *gin.Context) {
+ c.String(http.StatusOK, "Hello World!\n")
+ })
+ router.Run()
+}
diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html
index 27dac387..905c012f 100644
--- a/examples/realtime-advanced/resources/room_login.templ.html
+++ b/examples/realtime-advanced/resources/room_login.templ.html
@@ -1,4 +1,4 @@
-
+
@@ -20,9 +20,9 @@
-
+
-