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.
+
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/.travis.yml b/.travis.yml
index ec12cad6..e9101568 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,10 +5,11 @@ go:
- 1.7.x
- 1.8.x
- 1.9.x
+ - 1.10.x
- master
git:
- depth: 3
+ depth: 10
install:
- make install
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/Makefile b/Makefile
index 9ba475a4..51b9969f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,16 @@
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: build
+all: install
install: deps
govendor sync
.PHONY: test
test:
- go test -v -covermode=count -coverprofile=coverage.out
+ sh coverage.sh
.PHONY: fmt
fmt:
@@ -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/README.md b/README.md
index 580feb4b..053750bb 100644
--- a/README.md
+++ b/README.md
@@ -3,15 +3,124 @@
[](https://travis-ci.org/gin-gonic/gin)
- [](https://codecov.io/gh/gin-gonic/gin)
- [](https://goreportcard.com/report/github.com/gin-gonic/gin)
- [](https://godoc.org/github.com/gin-gonic/gin)
- [](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[](https://codecov.io/gh/gin-gonic/gin)
+[](https://goreportcard.com/report/github.com/gin-gonic/gin)
+[](https://godoc.org/github.com/gin-gonic/gin)
+[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
+[](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.

+## Contents
+
+- [Installation](#installation)
+- [Prerequisite](#prerequisite)
+- [Quick start](#quick-start)
+- [Benchmarks](#benchmarks)
+- [Gin v1.stable](#gin-v1-stable)
+- [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)
+ - [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)
+ - [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, 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)
+ - [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)
+ - [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)
+
+## 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.3
+```
+
+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
# assume the following codes in example.go file
$ cat example.go
@@ -87,61 +196,9 @@ 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 ~/go/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.
+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 .
@@ -181,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)
@@ -268,6 +325,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
@@ -385,7 +470,7 @@ func main() {
r := gin.New()
// Global middleware
- // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release.
+ // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
@@ -435,7 +520,7 @@ func main() {
c.String(200, "pong")
})
- r.Run(":8080")
+ router.Run(":8080")
}
```
@@ -449,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`.
@@ -462,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() {
@@ -472,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
@@ -525,6 +635,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).
@@ -540,7 +654,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
- validator "gopkg.in/go-playground/validator.v8"
+ "gopkg.in/go-playground/validator.v8"
)
type Booking struct {
@@ -563,7 +677,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")
}
@@ -579,13 +697,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 registered 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).
@@ -750,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() {
@@ -784,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")
}
@@ -811,6 +945,51 @@ 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")
+}
+```
+
+#### 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")
+}
+```
#### PureJSON
@@ -854,6 +1033,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()
@@ -978,7 +1183,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{}{
@@ -1008,14 +1213,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
@@ -1099,7 +1316,7 @@ func main() {
### Goroutines inside a middleware
-When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
+When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
```go
func main() {
@@ -1161,7 +1378,7 @@ func main() {
example for 1-line LetsEncrypt HTTPS servers.
-[embedmd]:# (examples/auto-tls/example1.go go)
+[embedmd]:# (examples/auto-tls/example1/main.go go)
```go
package main
@@ -1186,7 +1403,7 @@ func main() {
example for custom autocert manager.
-[embedmd]:# (examples/auto-tls/example2.go go)
+[embedmd]:# (examples/auto-tls/example2/main.go go)
```go
package main
@@ -1218,7 +1435,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
@@ -1351,8 +1568,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)
}
}()
@@ -1372,7 +1589,296 @@ func main() {
}
```
-## Users [](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
+### 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.
+
+### 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.
+
+### 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)).
+
+### 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.
+
+```go
+package main
+
+func setupRouter() *gin.Engine {
+ r := gin.Default()
+ r.GET("/ping", func(c *gin.Context) {
+ c.String(200, "pong")
+ })
+ return r
+}
+
+func main() {
+ r := setupRouter()
+ r.Run(":8080")
+}
+```
+
+Test for code example above:
+
+```go
+package main
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPingRoute(t *testing.T) {
+ router := setupRouter()
+
+ w := httptest.NewRecorder()
+ req, _ := http.NewRequest("GET", "/ping", nil)
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, 200, w.Code)
+ assert.Equal(t, "pong", w.Body.String())
+}
+```
+
+## Users
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
diff --git a/auth.go b/auth.go
index 302a4fad..9ed81b5d 100644
--- a/auth.go
+++ b/auth.go
@@ -7,6 +7,7 @@ package gin
import (
"crypto/subtle"
"encoding/base64"
+ "net/http"
"strconv"
)
@@ -17,8 +18,8 @@ const AuthUserKey = "user"
type Accounts map[string]string
type authPair struct {
- Value string
- User string
+ value string
+ user string
}
type authPairs []authPair
@@ -28,8 +29,8 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false
}
for _, pair := range a {
- if pair.Value == authValue {
- return pair.User, true
+ if pair.value == authValue {
+ return pair.user, true
}
}
return "", false
@@ -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
}
@@ -74,8 +75,8 @@ func processAccounts(accounts Accounts) authPairs {
assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password)
pairs = append(pairs, authPair{
- Value: value,
- User: user,
+ value: value,
+ user: user,
})
}
return pairs
diff --git a/auth_test.go b/auth_test.go
index 2f1ae70e..ab7e94be 100644
--- a/auth_test.go
+++ b/auth_test.go
@@ -22,16 +22,16 @@ func TestBasicAuth(t *testing.T) {
assert.Len(t, pairs, 3)
assert.Contains(t, pairs, authPair{
- User: "bar",
- Value: "Basic YmFyOmZvbw==",
+ user: "bar",
+ value: "Basic YmFyOmZvbw==",
})
assert.Contains(t, pairs, authPair{
- User: "foo",
- Value: "Basic Zm9vOmJhcg==",
+ user: "foo",
+ value: "Basic Zm9vOmJhcg==",
})
assert.Contains(t, pairs, authPair{
- User: "admin",
- Value: "Basic YWRtaW46cGFzc3dvcmQ=",
+ user: "admin",
+ value: "Basic YWRtaW46cGFzc3dvcmQ=",
})
}
@@ -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 a2c62ba3..0b3f82df 100644
--- a/benchmarks_test.go
+++ b/benchmarks_test.go
@@ -54,13 +54,11 @@ 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")
}
-var htmlContentType = []string{"text/html; charset=utf-8"}
-
func BenchmarkOneRouteHTML(B *testing.B) {
router := New()
t := template.Must(template.New("index").Parse(`
@@ -68,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")
}
@@ -84,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/binding/binding.go b/binding/binding.go
index dc32d538..3a2aad9c 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -4,12 +4,9 @@
package binding
-import (
- "net/http"
-
- "gopkg.in/go-playground/validator.v8"
-)
+import "net/http"
+// Content-Type MIME of the most common data formats.
const (
MIMEJSON = "application/json"
MIMEHTML = "text/html"
@@ -23,11 +20,25 @@ 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
}
+// 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
+// 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 +47,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 +70,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/binding_body_test.go b/binding/binding_body_test.go
new file mode 100644
index 00000000..dfd761e1
--- /dev/null
+++ b/binding/binding_body_test.go
@@ -0,0 +1,67 @@
+package binding
+
+import (
+ "bytes"
+ "io/ioutil"
+ "testing"
+
+ "github.com/gin-gonic/gin/testdata/protoexample"
+ "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 := protoexample.Test{
+ Label: proto.String("FOO"),
+ }
+ data, _ := proto.Marshal(&test)
+ req := requestWithBody("POST", "/", string(data))
+ 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 5575e166..efe87669 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -6,11 +6,15 @@ package binding
import (
"bytes"
+ "encoding/json"
+ "errors"
+ "io/ioutil"
"mime/multipart"
"net/http"
"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"
@@ -25,27 +29,167 @@ 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"`
+}
+
+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 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"`
+}
+
+type FooStructForBoolType struct {
+ BoolFoo bool `form:"bool_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"`
+}
+
+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, 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) {
@@ -55,6 +199,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 +225,210 @@ 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",
+ "/", "/",
+ "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")
+
+ testFormBindingForType(t, "POST",
+ "/", "/",
+ "ptr_bar=test", "bar2=test", "Ptr")
+
+ 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) {
testQueryBinding(t, "POST",
"/?foo=bar&bar=foo", "/",
@@ -79,6 +441,24 @@ 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 TestBindingQueryBoolFail(t *testing.T) {
+ testQueryBindingBoolFail(t, "GET",
+ "/?bool_foo=fasl", "/?bar2=foo",
+ "bool_foo=unused", "")
+}
+
func TestBindingXML(t *testing.T) {
testBodyBinding(t,
XML, "xml",
@@ -86,12 +466,31 @@ func TestBindingXML(t *testing.T) {
"", "")
}
+func TestBindingXMLFail(t *testing.T) {
+ testBodyBindingFail(t,
+ XML, "xml",
+ "/", "/",
+ "", "")
+}
+
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 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)
+ return req
+}
+
func createFormMultipartRequest() *http.Request {
boundary := "--testboundary"
body := new(bytes.Buffer)
@@ -106,13 +505,43 @@ 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, 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 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
+ err := FormPost.Bind(req, &obj)
+ assert.Error(t, err)
}
func TestBindingFormMultipart(t *testing.T) {
@@ -120,12 +549,20 @@ func TestBindingFormMultipart(t *testing.T) {
var obj FooBarStruct
FormMultipart.Bind(req, &obj)
- 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) {
+ req := createFormMultipartRequestFail()
+ var obj FooStructForMapType
+ err := FormMultipart.Bind(req, &obj)
+ assert.Error(t, err)
}
func TestBindingProtoBuf(t *testing.T) {
- test := &example.Test{
+ test := &protoexample.Test{
Label: proto.String("yes"),
}
data, _ := proto.Marshal(test)
@@ -136,6 +573,18 @@ func TestBindingProtoBuf(t *testing.T) {
string(data), string(data[1:]))
}
+func TestBindingProtoBufFail(t *testing.T) {
+ test := &protoexample.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",
@@ -198,7 +647,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)
@@ -207,8 +656,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)
@@ -216,9 +665,389 @@ 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())
+
+ 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, "form-urlencoded", b.Name())
+
+ 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, "multipart/form-data", b.Name())
+
+ 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, "form", b.Name())
+
+ 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, 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)
+ 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, "form", b.Name())
+
+ 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, "form", b.Name())
+
+ 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, "form", b.Name())
+
+ 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, "form", b.Name())
+
+ 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, "form", b.Name())
+
+ 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, "form", b.Name())
+
+ 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, int(0), obj.IntFoo)
+ assert.Equal(t, int(-12), obj.IntBar)
+
+ 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, int8(0), obj.Int8Foo)
+ assert.Equal(t, int8(-12), obj.Int8Bar)
+
+ 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, int16(0), obj.Int16Foo)
+ assert.Equal(t, int16(-12), obj.Int16Bar)
+
+ 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, int32(0), obj.Int32Foo)
+ assert.Equal(t, int32(-12), obj.Int32Bar)
+
+ 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, int64(0), obj.Int64Foo)
+ assert.Equal(t, int64(-12), obj.Int64Bar)
+
+ 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, uint(0x0), obj.UintFoo)
+ assert.Equal(t, uint(0xc), obj.UintBar)
+
+ 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, uint8(0x0), obj.Uint8Foo)
+ assert.Equal(t, uint8(0xc), obj.Uint8Bar)
+
+ 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, uint16(0x0), obj.Uint16Foo)
+ assert.Equal(t, uint16(0xc), obj.Uint16Bar)
+
+ 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, uint32(0x0), obj.Uint32Foo)
+ assert.Equal(t, uint32(0xc), obj.Uint32Bar)
+
+ 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, uint64(0x0), obj.Uint64Foo)
+ assert.Equal(t, uint64(0xc), obj.Uint64Bar)
+
+ 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, float32(0.0), obj.Float32Foo)
+ assert.Equal(t, float32(-12.34), obj.Float32Bar)
+
+ 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, float64(0.0), obj.Float64Foo)
+ assert.Equal(t, float64(-12.34), obj.Float64Bar)
+
+ 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.False(t, obj.BoolFoo)
+ assert.True(t, obj.BoolBar)
+
+ 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, []int{1, 2}, obj.SliceFoo)
+
+ obj = FooStructForSliceType{}
+ 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)
+ assert.Error(t, err)
+ case "SliceMap":
+ 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)
+ }
+}
+
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)
@@ -227,18 +1056,96 @@ 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, "query", b.Name())
+
+ 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 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, 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)
+ err = JSON.Bind(req, &obj)
+ assert.Error(t, err)
+}
+
+func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+ assert.Equal(t, name, b.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, int64(123), v)
+
+ 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, name, b.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, float64(123), obj.Foo)
+
+ 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, name, b.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)
@@ -247,16 +1154,40 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
}
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{}
+ 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, *obj.Label, "yes")
+ 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)
+ 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, name, b.Name())
+
+ obj := protoexample.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 = protoexample.Test{}
req = requestWithBody("POST", badPath, badBody)
req.Header.Add("Content-Type", MIMEPROTOBUF)
err = ProtoBuf.Bind(req, &obj)
@@ -264,14 +1195,14 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba
}
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/default_validator.go b/binding/default_validator.go
index 6336bb6e..e7a302de 100644
--- a/binding/default_validator.go
+++ b/binding/default_validator.go
@@ -18,19 +18,29 @@ 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
}
-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() {
@@ -39,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
-}
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index c968dc08..a714291f 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -8,6 +8,7 @@ import (
"errors"
"reflect"
"strconv"
+ "strings"
"time"
)
@@ -23,12 +24,28 @@ 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
- // 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 {
@@ -38,8 +55,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)
@@ -97,6 +119,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")
}
@@ -133,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 {
@@ -150,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 == "" {
@@ -179,12 +207,3 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
value.Set(reflect.ValueOf(t))
return nil
}
-
-// Don't pass in pointers to bind to. Can lead to bugs. See:
-// https://github.com/codegangsta/martini-contrib/issues/40
-// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
-func ensureNotPointer(obj interface{}) {
- if reflect.TypeOf(obj).Kind() == reflect.Ptr {
- panic("Pointers are not accepted as binding models")
- }
-}
diff --git a/binding/json.go b/binding/json.go
index b7c856af..fea17bb2 100644
--- a/binding/json.go
+++ b/binding/json.go
@@ -5,11 +5,16 @@
package binding
import (
+ "bytes"
+ "io"
"net/http"
"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{}
@@ -19,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/validate_test.go b/binding/validate_test.go
index 8ca79989..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
@@ -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/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 5b67dcc0..6d80284e 100644
--- a/context.go
+++ b/context.go
@@ -31,11 +31,10 @@ const (
MIMEPlain = binding.MIMEPlain
MIMEPOSTForm = binding.MIMEPOSTForm
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
+ BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
)
-const (
- abortIndex int8 = math.MaxInt8 / 2
-)
+const abortIndex int8 = math.MaxInt8 / 2
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
@@ -105,8 +104,7 @@ func (c *Context) Handler() HandlerFunc {
// See example in GitHub.
func (c *Context) Next() {
c.index++
- s := int8(len(c.handlers))
- for ; c.index < s; c.index++ {
+ for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
@@ -161,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
}
@@ -363,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 {
@@ -418,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)
@@ -452,10 +497,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
// Depending the "Content-Type" header different bindings are used:
// "application/json" --> JSON binding
// "application/xml" --> XML binding
-// otherwise --> returns an error
+// otherwise --> returns an error.
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
-// It will writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
+// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
@@ -466,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)
@@ -476,7 +526,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
@@ -500,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)
@@ -511,21 +566,41 @@ 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.
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 len(clientIP) > 0 {
- return clientIP
- }
- clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
- if len(clientIP) > 0 {
+ if clientIP != "" {
return clientIP
}
}
@@ -571,9 +646,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
@@ -588,7 +663,7 @@ func (c *Context) Status(code int) {
// It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) Header(key, value string) {
- if len(value) == 0 {
+ if value == "" {
c.Writer.Header().Del(key)
} else {
c.Writer.Header().Set(key, value)
@@ -673,12 +748,30 @@ 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{}) {
+ 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.
// It also sets the Content-Type as "application/json".
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{}) {
@@ -690,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})
@@ -712,6 +810,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)
@@ -801,18 +909,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
diff --git a/context_test.go b/context_test.go
index 100e6b85..782f7bed 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"
@@ -18,8 +19,12 @@ import (
"time"
"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{}
@@ -46,6 +51,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)
@@ -180,14 +187,14 @@ func TestContextSetGet(t *testing.T) {
c.Set("foo", "bar")
value, err := c.Get("foo")
- assert.Equal(t, value, "bar")
+ assert.Equal(t, "bar", value)
assert.True(t, err)
value, err = c.Get("foo2")
assert.Nil(t, value)
assert.False(t, err)
- assert.Equal(t, c.MustGet("foo"), "bar")
+ assert.Equal(t, "bar", c.MustGet("foo"))
assert.Panics(t, func() { c.MustGet("no_exist") })
}
@@ -221,7 +228,7 @@ func TestContextGetString(t *testing.T) {
func TestContextSetGetBool(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.Set("bool", true)
- assert.Equal(t, true, c.GetBool("bool"))
+ assert.True(t, c.GetBool("bool"))
}
func TestContextGetInt(t *testing.T) {
@@ -338,26 +345,26 @@ func TestContextQuery(t *testing.T) {
value, ok := c.GetQuery("foo")
assert.True(t, ok)
- assert.Equal(t, value, "bar")
- assert.Equal(t, c.DefaultQuery("foo", "none"), "bar")
- assert.Equal(t, c.Query("foo"), "bar")
+ assert.Equal(t, "bar", value)
+ assert.Equal(t, "bar", c.DefaultQuery("foo", "none"))
+ assert.Equal(t, "bar", c.Query("foo"))
value, ok = c.GetQuery("page")
assert.True(t, ok)
- assert.Equal(t, value, "10")
- assert.Equal(t, c.DefaultQuery("page", "0"), "10")
- assert.Equal(t, c.Query("page"), "10")
+ assert.Equal(t, "10", value)
+ assert.Equal(t, "10", c.DefaultQuery("page", "0"))
+ assert.Equal(t, "10", c.Query("page"))
value, ok = c.GetQuery("id")
assert.True(t, ok)
assert.Empty(t, value)
- assert.Equal(t, c.DefaultQuery("id", "nada"), "")
+ assert.Empty(t, c.DefaultQuery("id", "nada"))
assert.Empty(t, c.Query("id"))
value, ok = c.GetQuery("NoKey")
assert.False(t, ok)
assert.Empty(t, value)
- assert.Equal(t, c.DefaultQuery("NoKey", "nada"), "nada")
+ assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada"))
assert.Empty(t, c.Query("NoKey"))
// postform should not mess
@@ -370,32 +377,33 @@ 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, c.DefaultPostForm("foo", "none"), "bar")
- assert.Equal(t, c.PostForm("foo"), "bar")
+ assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
+ assert.Equal(t, "bar", c.PostForm("foo"))
assert.Empty(t, c.Query("foo"))
value, ok := c.GetPostForm("page")
assert.True(t, ok)
- assert.Equal(t, value, "11")
- assert.Equal(t, c.DefaultPostForm("page", "0"), "11")
- assert.Equal(t, c.PostForm("page"), "11")
- assert.Equal(t, c.Query("page"), "")
+ assert.Equal(t, "11", value)
+ assert.Equal(t, "11", c.DefaultPostForm("page", "0"))
+ assert.Equal(t, "11", c.PostForm("page"))
+ assert.Empty(t, c.Query("page"))
value, ok = c.GetPostForm("both")
assert.True(t, ok)
assert.Empty(t, value)
assert.Empty(t, c.PostForm("both"))
- assert.Equal(t, c.DefaultPostForm("both", "nothing"), "")
- assert.Equal(t, c.Query("both"), "GET")
+ assert.Empty(t, c.DefaultPostForm("both", "nothing"))
+ assert.Equal(t, "GET", c.Query("both"), "GET")
value, ok = c.GetQuery("id")
assert.True(t, ok)
- assert.Equal(t, value, "main")
- assert.Equal(t, c.DefaultPostForm("id", "000"), "000")
- assert.Equal(t, c.Query("id"), "main")
+ assert.Equal(t, "main", value)
+ assert.Equal(t, "000", c.DefaultPostForm("id", "000"))
+ assert.Equal(t, "main", c.Query("id"))
assert.Empty(t, c.PostForm("id"))
value, ok = c.GetQuery("NoKey")
@@ -404,8 +412,8 @@ func TestContextQueryAndPostForm(t *testing.T) {
value, ok = c.GetPostForm("NoKey")
assert.False(t, ok)
assert.Empty(t, value)
- assert.Equal(t, c.DefaultPostForm("NoKey", "nada"), "nada")
- assert.Equal(t, c.DefaultQuery("NoKey", "nothing"), "nothing")
+ assert.Equal(t, "nada", c.DefaultPostForm("NoKey", "nada"))
+ assert.Equal(t, "nothing", c.DefaultQuery("NoKey", "nothing"))
assert.Empty(t, c.PostForm("NoKey"))
assert.Empty(t, c.Query("NoKey"))
@@ -417,11 +425,11 @@ func TestContextQueryAndPostForm(t *testing.T) {
Array []string `form:"array[]"`
}
assert.NoError(t, c.Bind(&obj))
- assert.Equal(t, obj.Foo, "bar")
- assert.Equal(t, obj.ID, "main")
- assert.Equal(t, obj.Page, 11)
- assert.Equal(t, obj.Both, "")
- assert.Equal(t, obj.Array, []string{"first", "second"})
+ assert.Equal(t, "bar", obj.Foo, "bar")
+ assert.Equal(t, "main", obj.ID, "main")
+ assert.Equal(t, 11, obj.Page, 11)
+ assert.Empty(t, obj.Both)
+ assert.Equal(t, []string{"first", "second"}, obj.Array)
values, ok := c.GetQueryArray("array[]")
assert.True(t, ok)
@@ -438,6 +446,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) {
@@ -456,37 +488,37 @@ func TestContextPostFormMultipart(t *testing.T) {
BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"`
}
assert.NoError(t, c.Bind(&obj))
- assert.Equal(t, obj.Foo, "bar")
- assert.Equal(t, obj.Bar, "10")
- assert.Equal(t, obj.BarAsInt, 10)
- assert.Equal(t, obj.Array, []string{"first", "second"})
- assert.Equal(t, obj.ID, "")
- assert.Equal(t, obj.TimeLocal.Format("02/01/2006 15:04"), "31/12/2016 14:55")
- assert.Equal(t, obj.TimeLocal.Location(), time.Local)
- assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55")
- assert.Equal(t, obj.TimeUTC.Location(), time.UTC)
+ assert.Equal(t, "bar", obj.Foo)
+ assert.Equal(t, "10", obj.Bar)
+ assert.Equal(t, 10, obj.BarAsInt)
+ assert.Equal(t, []string{"first", "second"}, obj.Array)
+ assert.Empty(t, obj.ID)
+ assert.Equal(t, "31/12/2016 14:55", obj.TimeLocal.Format("02/01/2006 15:04"))
+ assert.Equal(t, time.Local, obj.TimeLocal.Location())
+ assert.Equal(t, "31/12/2016 14:55", obj.TimeUTC.Format("02/01/2006 15:04"))
+ assert.Equal(t, time.UTC, obj.TimeUTC.Location())
loc, _ := time.LoadLocation("Asia/Tokyo")
- assert.Equal(t, obj.TimeLocation.Format("02/01/2006 15:04"), "31/12/2016 14:55")
- assert.Equal(t, obj.TimeLocation.Location(), loc)
+ assert.Equal(t, "31/12/2016 14:55", obj.TimeLocation.Format("02/01/2006 15:04"))
+ assert.Equal(t, loc, obj.TimeLocation.Location())
assert.True(t, obj.BlankTime.IsZero())
value, ok := c.GetQuery("foo")
assert.False(t, ok)
assert.Empty(t, value)
assert.Empty(t, c.Query("bar"))
- assert.Equal(t, c.DefaultQuery("id", "nothing"), "nothing")
+ assert.Equal(t, "nothing", c.DefaultQuery("id", "nothing"))
value, ok = c.GetPostForm("foo")
assert.True(t, ok)
- assert.Equal(t, value, "bar")
- assert.Equal(t, c.PostForm("foo"), "bar")
+ assert.Equal(t, "bar", value)
+ assert.Equal(t, "bar", c.PostForm("foo"))
value, ok = c.GetPostForm("array")
assert.True(t, ok)
- assert.Equal(t, value, "first")
- assert.Equal(t, c.PostForm("array"), "first")
+ assert.Equal(t, "first", value)
+ assert.Equal(t, "first", c.PostForm("array"))
- assert.Equal(t, c.DefaultPostForm("bar", "nothing"), "10")
+ assert.Equal(t, "10", c.DefaultPostForm("bar", "nothing"))
value, ok = c.GetPostForm("id")
assert.True(t, ok)
@@ -497,7 +529,7 @@ func TestContextPostFormMultipart(t *testing.T) {
value, ok = c.GetPostForm("nokey")
assert.False(t, ok)
assert.Empty(t, value)
- assert.Equal(t, c.DefaultPostForm("nokey", "nothing"), "nothing")
+ assert.Equal(t, "nothing", c.DefaultPostForm("nokey", "nothing"))
values, ok := c.GetPostFormArray("array")
assert.True(t, ok)
@@ -514,18 +546,34 @@ 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) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.SetCookie("user", "gin", 1, "/", "localhost", true, true)
- assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
+ assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
}
func TestContextSetCookiePathEmpty(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.SetCookie("user", "gin", 1, "", "localhost", true, true)
- assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
+ assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
}
func TestContextGetCookie(t *testing.T) {
@@ -533,17 +581,18 @@ func TestContextGetCookie(t *testing.T) {
c.Request, _ = http.NewRequest("GET", "/get", nil)
c.Request.Header.Set("Cookie", "user=gin")
cookie, _ := c.Cookie("user")
- assert.Equal(t, cookie, "gin")
+ assert.Equal(t, "gin", cookie)
_, err := c.Cookie("nokey")
assert.Error(t, err)
}
func TestContextBodyAllowedForStatus(t *testing.T) {
- assert.Equal(t, false, bodyAllowedForStatus(102))
- assert.Equal(t, false, bodyAllowedForStatus(204))
- assert.Equal(t, false, bodyAllowedForStatus(304))
- assert.Equal(t, true, bodyAllowedForStatus(500))
+ // todo(thinkerou): go1.6 not support StatusProcessing
+ assert.False(t, false, bodyAllowedForStatus(102))
+ assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
+ assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
+ assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
}
type TestPanicRender struct {
@@ -582,15 +631,43 @@ 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(http.StatusCreated, H{"foo": "bar"})
+
+ 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"))
+}
+
+// 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(http.StatusCreated, H{"foo": "bar"})
+
+ 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"))
+}
+
// Tests that no JSON is rendered if code is 204
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, "", w.Body.String())
+ 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"))
}
@@ -601,9 +678,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"))
}
@@ -614,10 +691,10 @@ 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, "", w.Body.String())
+ 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")
}
@@ -627,9 +704,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, w.Code, 201)
+ 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"))
}
@@ -639,10 +716,10 @@ 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, "", w.Body.String())
+ 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"))
}
@@ -653,11 +730,11 @@ 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, w.Code, 201)
- assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
+ 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"))
}
// Tests that no Custom JSON is rendered if code is 204
@@ -665,11 +742,22 @@ 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, "", w.Body.String())
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
+ 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"))
+}
+
+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
@@ -677,14 +765,39 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
func TestContextRenderHTML(t *testing.T) {
w := httptest.NewRecorder()
c, router := CreateTestContext(w)
+
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, w.Code, 201)
- assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
+ 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"))
+}
+
+func TestContextRenderHTML2(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, router := CreateTestContext(w)
+
+ // print debug warning log when Engine.trees > 0
+ 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)
+
+ 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(http.StatusCreated, "t", H{"name": "alexandernyquist"})
+
+ 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"))
}
// Tests that no HTML is rendered if code is 204
@@ -694,11 +807,11 @@ 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, "", w.Body.String())
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
+ 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"))
}
// TestContextXML tests that the response is serialized as XML
@@ -707,11 +820,11 @@ 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, w.Code, 201)
- assert.Equal(t, w.Body.String(), "")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8")
+ 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"))
}
// Tests that no XML is rendered if code is 204
@@ -719,11 +832,11 @@ 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, "", w.Body.String())
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8")
+ 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"))
}
// TestContextString tests that the response is returned
@@ -732,11 +845,11 @@ 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, w.Code, 201)
- assert.Equal(t, w.Body.String(), "test string 2")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ 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"))
}
// Tests that no String is rendered if code is 204
@@ -744,11 +857,11 @@ 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, "", w.Body.String())
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ 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"))
}
// TestContextString tests that the response is returned
@@ -758,11 +871,11 @@ 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, w.Code, 201)
- assert.Equal(t, w.Body.String(), "string 3")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
+ 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"))
}
// Tests that no HTML String is rendered if code is 204
@@ -771,11 +884,11 @@ 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, "", w.Body.String())
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
+ 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"))
}
// TestContextData tests that the response can be written from `bytesting`
@@ -784,11 +897,11 @@ 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, w.Code, 201)
- assert.Equal(t, w.Body.String(), "foo,bar")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv")
+ 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"))
}
// Tests that no Custom Data is rendered if code is 204
@@ -796,11 +909,11 @@ 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, "", w.Body.String())
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv")
+ assert.Equal(t, http.StatusNoContent, w.Code)
+ assert.Empty(t, w.Body.String())
+ assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
}
func TestContextRenderSSE(t *testing.T) {
@@ -827,9 +940,9 @@ func TestContextRenderFile(t *testing.T) {
c.Request, _ = http.NewRequest("GET", "/", nil)
c.File("./gin.go")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "func New() *Engine {")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
}
// TestContextRenderYAML tests that the response is serialized as YAML
@@ -838,11 +951,35 @@ 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, w.Code, 201)
- assert.Equal(t, w.Body.String(), "foo: bar\n")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/x-yaml; charset=utf-8")
+ 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"))
+}
+
+// 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) {
@@ -850,13 +987,13 @@ func TestContextHeaders(t *testing.T) {
c.Header("Content-Type", "text/plain")
c.Header("X-Custom", "value")
- assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/plain")
- assert.Equal(t, c.Writer.Header().Get("X-Custom"), "value")
+ assert.Equal(t, "text/plain", c.Writer.Header().Get("Content-Type"))
+ assert.Equal(t, "value", c.Writer.Header().Get("X-Custom"))
c.Header("Content-Type", "text/html")
c.Header("X-Custom", "")
- assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/html")
+ assert.Equal(t, "text/html", c.Writer.Header().Get("Content-Type"))
_, exist := c.Writer.Header()["X-Custom"]
assert.False(t, exist)
}
@@ -870,10 +1007,10 @@ 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, w.Code, 301)
- assert.Equal(t, w.Header().Get("Location"), "/path")
+ assert.Equal(t, http.StatusMovedPermanently, w.Code)
+ assert.Equal(t, "/path", w.Header().Get("Location"))
}
func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
@@ -881,11 +1018,11 @@ 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, w.Code, 302)
- assert.Equal(t, w.Header().Get("Location"), "http://google.com")
+ assert.Equal(t, http.StatusFound, w.Code)
+ assert.Equal(t, "http://google.com", w.Header().Get("Location"))
}
func TestContextRenderRedirectWith201(t *testing.T) {
@@ -893,21 +1030,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, w.Code, 201)
- assert.Equal(t, w.Header().Get("Location"), "/resource")
+ 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") })
}
@@ -916,12 +1055,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"))
}
@@ -931,12 +1070,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, "", w.Body.String())
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
}
@@ -948,13 +1087,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"))
}
@@ -964,11 +1103,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())
}
@@ -978,8 +1117,8 @@ func TestContextNegotiationFormat(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "", nil)
assert.Panics(t, func() { c.NegotiateFormat() })
- assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON)
- assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML)
+ assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
+ assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON))
}
func TestContextNegotiationFormatWithAccept(t *testing.T) {
@@ -987,9 +1126,9 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
- assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEXML)
- assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEHTML)
- assert.Equal(t, c.NegotiateFormat(MIMEJSON), "")
+ assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML))
+ assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML))
+ assert.Empty(t, c.NegotiateFormat(MIMEJSON))
}
func TestContextNegotiationFormatCustum(t *testing.T) {
@@ -1000,9 +1139,9 @@ func TestContextNegotiationFormatCustum(t *testing.T) {
c.Accepted = nil
c.SetAccepted(MIMEJSON, MIMEXML)
- assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON)
- assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEXML)
- assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON)
+ assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
+ assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML))
+ assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON))
}
func TestContextIsAborted(t *testing.T) {
@@ -1026,11 +1165,11 @@ func TestContextAbortWithStatus(t *testing.T) {
c, _ := CreateTestContext(w)
c.index = 4
- c.AbortWithStatus(401)
+ c.AbortWithStatus(http.StatusUnauthorized)
- assert.Equal(t, c.index, abortIndex)
- assert.Equal(t, c.Writer.Status(), 401)
- assert.Equal(t, w.Code, 401)
+ assert.Equal(t, abortIndex, c.index)
+ assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
+ assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.True(t, c.IsAborted())
}
@@ -1048,15 +1187,15 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
in.Bar = "barValue"
in.Foo = "fooValue"
- c.AbortWithStatusJSON(415, in)
+ c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in)
- assert.Equal(t, c.index, abortIndex)
- assert.Equal(t, c.Writer.Status(), 415)
- assert.Equal(t, w.Code, 415)
+ assert.Equal(t, abortIndex, c.index)
+ 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")
- assert.Equal(t, contentType, "application/json; charset=utf-8")
+ assert.Equal(t, "application/json; charset=utf-8", contentType)
buf := new(bytes.Buffer)
buf.ReadFrom(w.Body)
@@ -1070,7 +1209,7 @@ func TestContextError(t *testing.T) {
c.Error(errors.New("first error"))
assert.Len(t, c.Errors, 1)
- assert.Equal(t, c.Errors.String(), "Error #01: first error\n")
+ assert.Equal(t, "Error #01: first error\n", c.Errors.String())
c.Error(&Error{
Err: errors.New("second error"),
@@ -1079,13 +1218,13 @@ func TestContextError(t *testing.T) {
})
assert.Len(t, c.Errors, 2)
- assert.Equal(t, c.Errors[0].Err, errors.New("first error"))
+ assert.Equal(t, errors.New("first error"), c.Errors[0].Err)
assert.Nil(t, c.Errors[0].Meta)
- assert.Equal(t, c.Errors[0].Type, ErrorTypePrivate)
+ assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type)
- assert.Equal(t, c.Errors[1].Err, errors.New("second error"))
- assert.Equal(t, c.Errors[1].Meta, "some data 2")
- assert.Equal(t, c.Errors[1].Type, ErrorTypePublic)
+ assert.Equal(t, errors.New("second error"), c.Errors[1].Err)
+ assert.Equal(t, "some data 2", c.Errors[1].Meta)
+ assert.Equal(t, ErrorTypePublic, c.Errors[1].Type)
assert.Equal(t, c.Errors.Last(), c.Errors[1])
@@ -1103,22 +1242,22 @@ func TestContextTypedError(t *testing.T) {
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
for _, err := range c.Errors.ByType(ErrorTypePublic) {
- assert.Equal(t, err.Type, ErrorTypePublic)
+ assert.Equal(t, ErrorTypePublic, err.Type)
}
for _, err := range c.Errors.ByType(ErrorTypePrivate) {
- assert.Equal(t, err.Type, ErrorTypePrivate)
+ assert.Equal(t, ErrorTypePrivate, err.Type)
}
- assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "interno 0"})
+ assert.Equal(t, []string{"externo 0", "interno 0"}, c.Errors.Errors())
}
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, w.Code, 401)
- assert.Equal(t, c.index, abortIndex)
+ assert.Equal(t, http.StatusUnauthorized, w.Code)
+ assert.Equal(t, abortIndex, c.index)
assert.True(t, c.IsAborted())
}
@@ -1149,7 +1288,7 @@ func TestContextClientIP(t *testing.T) {
// no port
c.Request.RemoteAddr = "50.50.50.50"
- assert.Equal(t, "", c.ClientIP())
+ assert.Empty(t, c.ClientIP())
}
func TestContextContentType(t *testing.T) {
@@ -1157,7 +1296,7 @@ func TestContextContentType(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Set("Content-Type", "application/json; charset=utf-8")
- assert.Equal(t, c.ContentType(), "application/json")
+ assert.Equal(t, "application/json", c.ContentType())
}
func TestContextAutoBindJSON(t *testing.T) {
@@ -1170,8 +1309,8 @@ func TestContextAutoBindJSON(t *testing.T) {
Bar string `json:"bar"`
}
assert.NoError(t, c.Bind(&obj))
- assert.Equal(t, obj.Bar, "foo")
- assert.Equal(t, obj.Foo, "bar")
+ assert.Equal(t, "foo", obj.Bar)
+ assert.Equal(t, "bar", obj.Foo)
assert.Empty(t, c.Errors)
}
@@ -1187,9 +1326,29 @@ func TestContextBindWithJSON(t *testing.T) {
Bar string `json:"bar"`
}
assert.NoError(t, c.BindJSON(&obj))
- assert.Equal(t, obj.Bar, "foo")
- assert.Equal(t, obj.Foo, "bar")
- assert.Equal(t, w.Body.Len(), 0)
+ assert.Equal(t, "foo", obj.Bar)
+ 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) {
@@ -1225,7 +1384,7 @@ func TestContextBadAutoBind(t *testing.T) {
assert.Empty(t, obj.Bar)
assert.Empty(t, obj.Foo)
- assert.Equal(t, w.Code, 400)
+ assert.Equal(t, http.StatusBadRequest, w.Code)
assert.True(t, c.IsAborted())
}
@@ -1239,8 +1398,8 @@ func TestContextAutoShouldBindJSON(t *testing.T) {
Bar string `json:"bar"`
}
assert.NoError(t, c.ShouldBind(&obj))
- assert.Equal(t, obj.Bar, "foo")
- assert.Equal(t, obj.Foo, "bar")
+ assert.Equal(t, "foo", obj.Bar)
+ assert.Equal(t, "bar", obj.Foo)
assert.Empty(t, c.Errors)
}
@@ -1256,9 +1415,30 @@ func TestContextShouldBindWithJSON(t *testing.T) {
Bar string `json:"bar"`
}
assert.NoError(t, c.ShouldBindJSON(&obj))
- assert.Equal(t, obj.Bar, "foo")
- assert.Equal(t, obj.Foo, "bar")
- assert.Equal(t, w.Body.Len(), 0)
+ assert.Equal(t, "foo", obj.Bar)
+ assert.Equal(t, "bar", obj.Foo)
+ 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) {
@@ -1296,6 +1476,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\"}"))
@@ -1308,7 +1567,7 @@ func TestContextGolangContext(t *testing.T) {
assert.Nil(t, c.Value("foo"))
c.Set("foo", "bar")
- assert.Equal(t, c.Value("foo"), "bar")
+ assert.Equal(t, "bar", c.Value("foo"))
assert.Nil(t, c.Value(1))
}
@@ -1340,7 +1599,7 @@ func TestGetRequestHeaderValue(t *testing.T) {
c.Request.Header.Set("Gin-Version", "1.0.0")
assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version"))
- assert.Equal(t, "", c.GetHeader("Connection"))
+ assert.Empty(t, c.GetHeader("Connection"))
}
func TestContextGetRawData(t *testing.T) {
@@ -1353,3 +1612,77 @@ 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"))
+}
+
+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)
+
+ 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/coverage.sh b/coverage.sh
new file mode 100644
index 00000000..4d1ee036
--- /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$' | 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 449291e6..f11156b3 100644
--- a/debug.go
+++ b/debug.go
@@ -15,7 +15,7 @@ func init() {
}
// IsDebugging returns true if the framework is running in debug mode.
-// Use SetMode(gin.Release) to disable debug mode.
+// Use SetMode(gin.ReleaseMode) to disable debug mode.
func IsDebugging() bool {
return ginMode == debugCode
}
@@ -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..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())
}
@@ -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) {
diff --git a/deprecated_test.go b/deprecated_test.go
new file mode 100644
index 00000000..7a875fe4
--- /dev/null
+++ b/deprecated_test.go
@@ -0,0 +1,31 @@
+// Copyright 2014 Manu Martinez-Almeida. 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 (
+ "bytes"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gin-gonic/gin/binding"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBindWith(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+
+ c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
+
+ var obj struct {
+ Foo string `form:"foo"`
+ Bar string `form:"bar"`
+ }
+ 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/errors.go b/errors.go
index 6f3c9868..dbfccd85 100644
--- a/errors.go
+++ b/errors.go
@@ -148,7 +148,7 @@ func (a errorMsgs) String() string {
}
var buffer bytes.Buffer
for i, msg := range a {
- fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err)
+ fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
if msg.Meta != nil {
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
}
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/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)
+
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/examples/auto-tls/example1.go b/examples/auto-tls/example1/main.go
similarity index 100%
rename from examples/auto-tls/example1.go
rename to examples/auto-tls/example1/main.go
diff --git a/examples/auto-tls/example2.go b/examples/auto-tls/example2/main.go
similarity index 100%
rename from examples/auto-tls/example2.go
rename to examples/auto-tls/example2/main.go
diff --git a/examples/basic/main.go b/examples/basic/main.go
index 984c06ab..48fa7bba 100644
--- a/examples/basic/main.go
+++ b/examples/basic/main.go
@@ -1,19 +1,21 @@
package main
import (
+ "net/http"
+
"github.com/gin-gonic/gin"
)
var DB = make(map[string]string)
-func main() {
+func setupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
r := gin.Default()
// 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 main() {
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,10 +51,15 @@ func main() {
if c.Bind(&json) == nil {
DB[user] = json.Value
- c.JSON(200, gin.H{"status": "ok"})
+ c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
})
+ return r
+}
+
+func main() {
+ r := setupRouter()
// Listen and Server in 0.0.0.0:8080
r.Run(":8080")
}
diff --git a/examples/basic/main_test.go b/examples/basic/main_test.go
new file mode 100644
index 00000000..5eb85240
--- /dev/null
+++ b/examples/basic/main_test.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPingRoute(t *testing.T) {
+ router := setupRouter()
+
+ w := httptest.NewRecorder()
+ req, _ := http.NewRequest("GET", "/ping", nil)
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Equal(t, "pong", w.Body.String())
+}
diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go
index 0b67ce10..dea0c302 100644
--- a/examples/custom-validation/server.go
+++ b/examples/custom-validation/server.go
@@ -7,7 +7,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
- validator "gopkg.in/go-playground/validator.v8"
+ "gopkg.in/go-playground/validator.v8"
)
type Booking struct {
@@ -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/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/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)
}
}()
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..edc1ca9b
--- /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(c, 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;
+}
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/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-advanced/stats.go b/examples/realtime-advanced/stats.go
index 4bca3ae4..4afedcb5 100644
--- a/examples/realtime-advanced/stats.go
+++ b/examples/realtime-advanced/stats.go
@@ -29,8 +29,8 @@ func statsWorker() {
"timestamp": uint64(time.Now().Unix()),
"HeapInuse": stats.HeapInuse,
"StackInuse": stats.StackInuse,
- "Mallocs": (stats.Mallocs - lastMallocs),
- "Frees": (stats.Frees - lastFrees),
+ "Mallocs": stats.Mallocs - lastMallocs,
+ "Frees": stats.Frees - lastFrees,
"Inbound": uint64(messages.Get("inbound")),
"Outbound": uint64(messages.Get("outbound")),
"Connected": connectedUsers(),
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/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(),
+ })
+ }
+}
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.go b/gin.go
index 4857f3ae..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
)
@@ -49,16 +49,6 @@ type RoutesInfo []RouteInfo
// Create an instance of Engine, by using New() or Default()
type Engine struct {
RouterGroup
- delims render.Delims
- secureJsonPrefix string
- HTMLRender render.HTMLRender
- FuncMap template.FuncMap
- allNoRoute HandlersChain
- allNoMethod HandlersChain
- noRoute HandlersChain
- noMethod HandlersChain
- pool sync.Pool
- trees methodTrees
// Enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists.
@@ -102,6 +92,17 @@ type Engine struct {
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
// method call.
MaxMultipartMemory int64
+
+ delims render.Delims
+ secureJsonPrefix string
+ HTMLRender render.HTMLRender
+ FuncMap template.FuncMap
+ allNoRoute HandlersChain
+ allNoMethod HandlersChain
+ noRoute HandlersChain
+ noMethod HandlersChain
+ pool sync.Pool
+ trees methodTrees
}
var _ IRouter = &Engine{}
@@ -159,25 +160,30 @@ func (engine *Engine) Delims(left, right string) *Engine {
return engine
}
+// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
engine.secureJsonPrefix = prefix
return engine
}
+// LoadHTMLGlob loads HTML files identified by glob pattern
+// and associates the result with HTML renderer.
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)
}
+// LoadHTMLFiles loads a slice of HTML files
+// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLFiles(files ...string) {
if IsDebugging() {
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
@@ -188,6 +194,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
engine.SetHTMLTemplate(templ)
}
+// SetHTMLTemplate associate a template with HTML renderer.
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
if len(engine.trees) > 0 {
debugPrintWARNINGSetHTMLTemplate()
@@ -196,6 +203,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
}
+// SetFuncMap sets the FuncMap used for template.FuncMap.
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
engine.FuncMap = funcMap
}
@@ -321,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()
@@ -341,43 +349,45 @@ 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, 405, 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
}
}
}
c.handlers = engine.allNoRoute
- serveError(c, 404, default404Body)
+ serveError(c, http.StatusNotFound, default404Body)
}
var mimePlain = []string{MIMEPlain}
@@ -385,28 +395,29 @@ 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) {
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 len(path) > 1 && path[len(path)-1] == '/' {
- req.URL.Path = path[:len(path)-1]
- } else {
- req.URL.Path = path + "/"
+ req.URL.Path = path + "/"
+ if length := len(path); length > 1 && path[length-1] == '/' {
+ req.URL.Path = path[:length-1]
}
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
http.Redirect(c.Writer, req, req.URL.String(), code)
@@ -417,14 +428,10 @@ 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 {
- code := 301 // Permanent redirect, request with GET method
+ if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok {
+ 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/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)
}
diff --git a/gin_integration_test.go b/gin_integration_test.go
index f45dd6c1..52f78842 100644
--- a/gin_integration_test.go
+++ b/gin_integration_test.go
@@ -94,7 +94,7 @@ func TestUnixSocket(t *testing.T) {
c, err := net.Dial("unix", "/tmp/unix_unit_test")
assert.NoError(t, err)
- fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
+ fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
scanner := bufio.NewScanner(c)
var response string
for scanner.Scan() {
diff --git a/gin_test.go b/gin_test.go
index bdf5a9a9..b3cab715 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -5,6 +5,7 @@
package gin
import (
+ "crypto/tls"
"fmt"
"html/template"
"io/ioutil"
@@ -21,15 +22,15 @@ func formatAsDate(t time.Time) string {
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}
-func setupHTMLFiles(t *testing.T) func() {
+func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
go func() {
- SetMode(TestMode)
+ SetMode(mode)
router := New()
router.Delims("{[{", "}]}")
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"})
})
@@ -38,22 +39,27 @@ func setupHTMLFiles(t *testing.T) func() {
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
})
})
- router.Run(":8888")
+ 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 setupHTMLGlob(t *testing.T) func() {
+func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
go func() {
- SetMode(DebugMode)
+ SetMode(mode)
router := New()
router.Delims("{[{", "}]}")
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"})
})
@@ -62,16 +68,20 @@ func setupHTMLGlob(t *testing.T) func() {
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
})
})
- router.Run(":8888")
+ 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() {}
}
-//TODO
func TestLoadHTMLGlob(t *testing.T) {
- td := setupHTMLGlob(t)
+ td := setupHTMLGlob(t, DebugMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
if err != nil {
fmt.Println(err)
@@ -83,9 +93,55 @@ func TestLoadHTMLGlob(t *testing.T) {
td()
}
+func TestLoadHTMLGlob2(t *testing.T) {
+ td := setupHTMLGlob(t, TestMode, false)
+ res, err := http.Get("http://127.0.0.1:8888/test")
+ 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")
+ 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)
+ // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ },
+ }
+ client := &http.Client{Transport: tr}
+ res, err := client.Get("https://127.0.0.1:9999/test")
+ 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)
+ td := setupHTMLGlob(t, DebugMode, false)
res, err := http.Get("http://127.0.0.1:8888/raw")
if err != nil {
fmt.Println(err)
@@ -97,9 +153,6 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
td()
}
-// func (engine *Engine) LoadHTMLFiles(files ...string) {
-// func (engine *Engine) RunTLS(addr string, cert string, key string) error {
-
func init() {
SetMode(TestMode)
}
@@ -117,17 +170,17 @@ func TestCreateEngine(t *testing.T) {
// router.LoadHTMLGlob("*.testtmpl")
// r := router.HTMLRender.(render.HTMLDebug)
// assert.Empty(t, r.Files)
-// assert.Equal(t, r.Glob, "*.testtmpl")
+// 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, r.Files, []string{"index.html", "login.html"})
+// assert.Equal(t, []string{"index.html", "login.html"}, r.Files)
// SetMode(TestMode)
// }
func TestLoadHTMLFiles(t *testing.T) {
- td := setupHTMLFiles(t)
+ td := setupHTMLFiles(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/test")
if err != nil {
fmt.Println(err)
@@ -138,9 +191,52 @@ func TestLoadHTMLFiles(t *testing.T) {
td()
}
+func TestLoadHTMLFiles2(t *testing.T) {
+ td := setupHTMLFiles(t, DebugMode, false)
+ res, err := http.Get("http://127.0.0.1:8888/test")
+ 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")
+ 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)
+ // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: true,
+ },
+ }
+ client := &http.Client{Transport: tr}
+ res, err := client.Get("https://127.0.0.1:9999/test")
+ 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)
+ td := setupHTMLFiles(t, TestMode, false)
res, err := http.Get("http://127.0.0.1:8888/raw")
if err != nil {
fmt.Println(err)
@@ -152,10 +248,6 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
td()
}
-func TestLoadHTMLReleaseMode(t *testing.T) {
-
-}
-
func TestAddRoute(t *testing.T) {
router := New()
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
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/json/json.go b/json/json.go
index 7e1e63ff..4f643c56 100644
--- a/json/json.go
+++ b/json/json.go
@@ -6,9 +6,7 @@
package json
-import (
- "encoding/json"
-)
+import "encoding/json"
var (
Marshal = json.Marshal
diff --git a/json/jsoniter.go b/json/jsoniter.go
index 65deee59..ffe1424a 100644
--- a/json/jsoniter.go
+++ b/json/jsoniter.go
@@ -6,9 +6,7 @@
package json
-import (
- "github.com/json-iterator/go"
-)
+import "github.com/json-iterator/go"
var (
json = jsoniter.ConfigCompatibleWithStandardLibrary
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 62c1366f..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"
@@ -82,21 +83,21 @@ func TestLogger(t *testing.T) {
}
func TestColorForMethod(t *testing.T) {
- assert.Equal(t, colorForMethod("GET"), string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), "get should be blue")
- assert.Equal(t, colorForMethod("POST"), string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), "post should be cyan")
- assert.Equal(t, colorForMethod("PUT"), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "put should be yellow")
- assert.Equal(t, colorForMethod("DELETE"), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "delete should be red")
- assert.Equal(t, colorForMethod("PATCH"), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "patch should be green")
- assert.Equal(t, colorForMethod("HEAD"), string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), "head should be magenta")
- assert.Equal(t, colorForMethod("OPTIONS"), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "options should be white")
- assert.Equal(t, colorForMethod("TRACE"), string([]byte{27, 91, 48, 109}), "trace is not defined and should be the reset color")
+ 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, 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")
+ assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white")
+ assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color")
}
func TestColorForStatus(t *testing.T) {
- assert.Equal(t, colorForStatus(200), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "2xx should be green")
- assert.Equal(t, colorForStatus(301), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "3xx should be white")
- assert.Equal(t, colorForStatus(404), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "4xx should be yellow")
- assert.Equal(t, colorForStatus(2), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "other things should be red")
+ 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")
}
func TestErrorLogger(t *testing.T) {
@@ -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\n{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
}
diff --git a/mode.go b/mode.go
index 393e13ec..9df4e45f 100644
--- a/mode.go
+++ b/mode.go
@@ -14,9 +14,9 @@ import (
const ENV_GIN_MODE = "GIN_MODE"
const (
- DebugMode string = "debug"
- ReleaseMode string = "release"
- TestMode string = "test"
+ DebugMode = "debug"
+ ReleaseMode = "release"
+ TestMode = "test"
)
const (
debugCode = iota
@@ -39,16 +39,12 @@ var modeName = DebugMode
func init() {
mode := os.Getenv(ENV_GIN_MODE)
- if mode == "" {
- SetMode(DebugMode)
- } else {
- SetMode(mode)
- }
+ SetMode(mode)
}
func SetMode(value string) {
switch value {
- case DebugMode:
+ case DebugMode, "":
ginMode = debugCode
case ReleaseMode:
ginMode = releaseCode
@@ -57,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 f3b88a12..cf27acd8 100644
--- a/mode_test.go
+++ b/mode_test.go
@@ -17,21 +17,25 @@ func init() {
}
func TestSetMode(t *testing.T) {
- assert.Equal(t, ginMode, testCode)
- assert.Equal(t, Mode(), TestMode)
+ assert.Equal(t, testCode, ginMode)
+ 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, ginMode, debugCode)
- assert.Equal(t, Mode(), DebugMode)
+ assert.Equal(t, debugCode, ginMode)
+ assert.Equal(t, DebugMode, Mode())
SetMode(ReleaseMode)
- assert.Equal(t, ginMode, releaseCode)
- assert.Equal(t, Mode(), ReleaseMode)
+ assert.Equal(t, releaseCode, ginMode)
+ assert.Equal(t, ReleaseMode, Mode())
SetMode(TestMode)
- assert.Equal(t, ginMode, testCode)
- assert.Equal(t, Mode(), TestMode)
+ assert.Equal(t, testCode, ginMode)
+ assert.Equal(t, TestMode, Mode())
assert.Panics(t, func() { SetMode("unknown") })
}
diff --git a/path.go b/path.go
index ed63ad1a..d1f59622 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
@@ -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
diff --git a/path_test.go b/path_test.go
index bf2e5f62..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"},
@@ -67,8 +68,8 @@ var cleanTests = []struct {
func TestPathClean(t *testing.T) {
for _, test := range cleanTests {
- assert.Equal(t, cleanPath(test.path), test.result)
- assert.Equal(t, cleanPath(test.result), test.result)
+ assert.Equal(t, test.result, cleanPath(test.path))
+ assert.Equal(t, test.result, cleanPath(test.result))
}
}
diff --git a/recovery.go b/recovery.go
index 89b39fec..61c5bd53 100644
--- a/recovery.go
+++ b/recovery.go
@@ -10,8 +10,10 @@ import (
"io"
"io/ioutil"
"log"
+ "net/http"
"net/http/httputil"
"runtime"
+ "time"
)
var (
@@ -38,9 +40,9 @@ 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)
+ c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
@@ -107,3 +109,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
+}
diff --git a/recovery_test.go b/recovery_test.go
index 4545ba3c..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, w.Code, 500)
+ 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,11 +34,31 @@ 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, w.Code, 400)
+ assert.Equal(t, http.StatusBadRequest, 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)
}
diff --git a/render/json.go b/render/json.go
index eb2548e2..6e5089a0 100644
--- a/render/json.go
+++ b/render/json.go
@@ -6,6 +6,8 @@ package render
import (
"bytes"
+ "fmt"
+ "html/template"
"net/http"
"github.com/gin-gonic/gin/json"
@@ -24,9 +26,20 @@ type SecureJSON struct {
Data interface{}
}
+type JsonpJSON struct {
+ Callback string
+ 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 {
@@ -80,3 +93,54 @@ 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)
+}
+
+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/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)
}
diff --git a/render/protobuf.go b/render/protobuf.go
new file mode 100644
index 00000000..34f1e9b5
--- /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 interface{}
+}
+
+var protobufContentType = []string{"application/x-protobuf"}
+
+func (r ProtoBuf) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+
+ bytes, err := proto.Marshal(r.Data.(proto.Message))
+ if err != nil {
+ return err
+ }
+
+ w.Write(bytes)
+ return nil
+}
+
+func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, protobufContentType)
+}
diff --git a/render/reader.go b/render/reader.go
new file mode 100644
index 00000000..7a06cce4
--- /dev/null
+++ b/render/reader.go
@@ -0,0 +1,40 @@
+// 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 (
+ "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/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.go b/render/render.go
index 71852364..df0d1d7c 100644
--- 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{}
@@ -24,6 +25,9 @@ var (
_ HTMLRender = HTMLProduction{}
_ Render = YAML{}
_ 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 5189403d..3014c86f 100644
--- a/render/render_test.go
+++ b/render/render_test.go
@@ -7,12 +7,19 @@ package render
import (
"bytes"
"encoding/xml"
+ "errors"
"html/template"
+ "net/http"
"net/http/httptest"
+ "strconv"
+ "strings"
"testing"
+ "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
@@ -24,6 +31,9 @@ func TestRenderMsgPack(t *testing.T) {
"foo": "bar",
}
+ (MsgPack{data}).WriteContentType(w)
+ assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
+
err := (MsgPack{data}).Render(w)
assert.NoError(t, err)
@@ -36,7 +46,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) {
@@ -46,6 +56,9 @@ func TestRenderJSON(t *testing.T) {
"html": "",
}
+ (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)
@@ -54,6 +67,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{}{
@@ -64,8 +85,17 @@ 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) {
+ 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) {
@@ -74,6 +104,9 @@ func TestRenderSecureJSON(t *testing.T) {
"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)
@@ -93,6 +126,96 @@ 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)
+}
+
+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 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)
+
+ // json: unsupported type: chan int
+ err := (JsonpJSON{"x", data}).Render(w)
+ 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
@@ -113,10 +236,67 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return err
}
}
- if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
- return err
+
+ 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, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
+
+ err := (YAML{data}).Render(w)
+ assert.NoError(t, err)
+ 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{}
+
+// 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)
+}
+
+// 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,
}
- return nil
+
+ (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) {
@@ -125,15 +305,41 @@ func TestRenderXML(t *testing.T) {
"foo": "bar",
}
+ (XML{data}).WriteContentType(w)
+ 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(), "")
- assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8")
+ assert.Equal(t, "", w.Body.String())
+ assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestRenderRedirect(t *testing.T) {
- // TODO
+ req, err := http.NewRequest("GET", "/test-redirect", nil)
+ assert.NoError(t, err)
+
+ data1 := Redirect{
+ Code: http.StatusMovedPermanently,
+ Request: req,
+ Location: "/new/location",
+ }
+
+ w := httptest.NewRecorder()
+ err = data1.Render(w)
+ assert.NoError(t, err)
+
+ data2 := Redirect{
+ Code: http.StatusOK,
+ 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) {
@@ -146,21 +352,40 @@ 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) {
w := httptest.NewRecorder()
+ (String{
+ Format: "hello %s %d",
+ Data: []interface{}{},
+ }).WriteContentType(w)
+ assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
+
err := (String{
Format: "hola %s %d",
Data: []interface{}{"manu", 2},
}).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) {
+ w := httptest.NewRecorder()
+
+ err := (String{
+ Format: "hola %s %d",
+ Data: []interface{}{},
+ }).Render(w)
+
+ assert.NoError(t, err)
+ 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) {
@@ -175,6 +400,88 @@ 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) {
+ 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, "Hello alexandernyquist", w.Body.String())
+ assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
+}
+
+func TestRenderHTMLDebugFiles(t *testing.T) {
+ w := httptest.NewRecorder()
+ htmlRender := HTMLDebug{Files: []string{"../testdata/template/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, "Hello thinkerou
", w.Body.String())
+ assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
+}
+
+func TestRenderHTMLDebugGlob(t *testing.T) {
+ w := httptest.NewRecorder()
+ htmlRender := HTMLDebug{Files: nil,
+ Glob: "../testdata/template/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, "Hello thinkerou
", w.Body.String())
+ assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
+}
+
+func TestRenderHTMLDebugPanics(t *testing.T) {
+ htmlRender := HTMLDebug{Files: nil,
+ Glob: "",
+ Delims: Delims{"{{", "}}"},
+ FuncMap: nil,
+ }
+ 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"))
}
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)
}
diff --git a/response_writer.go b/response_writer.go
index 232f00aa..923b53f8 100644
--- a/response_writer.go
+++ b/response_writer.go
@@ -13,10 +13,10 @@ import (
const (
noWritten = -1
- defaultStatus = 200
+ defaultStatus = http.StatusOK
)
-type ResponseWriter interface {
+type responseWriterBase interface {
http.ResponseWriter
http.Hijacker
http.Flusher
@@ -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_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
+}
diff --git a/response_writer_test.go b/response_writer_test.go
index cec27338..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, testWritter.Code, 300)
+ 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)
@@ -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)
+}
diff --git a/routergroup.go b/routergroup.go
index 5e681c1c..876a61b8 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, "*") {
@@ -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)
}
diff --git a/routergroup_test.go b/routergroup_test.go
index b0589b52..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"
@@ -20,15 +21,15 @@ func TestRouterGroupBasic(t *testing.T) {
group.Use(func(c *Context) {})
assert.Len(t, group.Handlers, 2)
- assert.Equal(t, group.BasePath(), "/hola")
- assert.Equal(t, group.engine, router)
+ assert.Equal(t, "/hola", group.BasePath())
+ assert.Equal(t, router, group.engine)
group2 := group.Group("manu")
group2.Use(func(c *Context) {}, func(c *Context) {})
assert.Len(t, group2.Handlers, 4)
- assert.Equal(t, group2.BasePath(), "/hola/manu")
- assert.Equal(t, group2.engine, router)
+ assert.Equal(t, "/hola/manu", group2.BasePath())
+ assert.Equal(t, router, group2.engine)
}
func TestRouterGroupBasicHandle(t *testing.T) {
@@ -44,13 +45,13 @@ func TestRouterGroupBasicHandle(t *testing.T) {
func performRequestInGroup(t *testing.T, method string) {
router := New()
v1 := router.Group("v1", func(c *Context) {})
- assert.Equal(t, v1.BasePath(), "/v1")
+ assert.Equal(t, "/v1", v1.BasePath())
login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {})
- assert.Equal(t, login.BasePath(), "/v1/login/")
+ 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,12 +81,12 @@ func performRequestInGroup(t *testing.T, method string) {
}
w := performRequest(router, method, "/v1/login/test")
- assert.Equal(t, w.Code, 400)
- assert.Equal(t, w.Body.String(), "the method was "+method+" and index 3")
+ 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, w.Code, 400)
- assert.Equal(t, w.Body.String(), "the method was "+method+" and index 1")
+ assert.Equal(t, http.StatusBadRequest, w.Code)
+ assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
}
func TestRouterGroupInvalidStatic(t *testing.T) {
diff --git a/routes_test.go b/routes_test.go
index b44b6431..23e749e2 100644
--- a/routes_test.go
+++ b/routes_test.go
@@ -36,7 +36,7 @@ func testRouteOK(method string, t *testing.T) {
w := performRequest(r, method, "/test")
assert.True(t, passed)
- assert.Equal(t, w.Code, http.StatusOK)
+ assert.Equal(t, http.StatusOK, w.Code)
performRequest(r, method, "/test2")
assert.True(t, passedAny)
@@ -53,7 +53,7 @@ func testRouteNotOK(method string, t *testing.T) {
w := performRequest(router, method, "/test")
assert.False(t, passed)
- assert.Equal(t, w.Code, http.StatusNotFound)
+ assert.Equal(t, http.StatusNotFound, w.Code)
}
// TestSingleRouteOK tests that POST route is correctly invoked.
@@ -74,27 +74,27 @@ func testRouteNotOK2(method string, t *testing.T) {
w := performRequest(router, method, "/test")
assert.False(t, passed)
- assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
+ assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
}
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, w.Code, 200)
- assert.Equal(t, w.Body.String(), "called")
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Equal(t, "called", w.Body.String())
}
func TestRouterGroupRouteOK(t *testing.T) {
@@ -143,43 +143,43 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
router.PUT("/path4/", func(c *Context) {})
w := performRequest(router, "GET", "/path/")
- assert.Equal(t, w.Header().Get("Location"), "/path")
- assert.Equal(t, w.Code, 301)
+ assert.Equal(t, "/path", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "GET", "/path2")
- assert.Equal(t, w.Header().Get("Location"), "/path2/")
- assert.Equal(t, w.Code, 301)
+ assert.Equal(t, "/path2/", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "POST", "/path3/")
- assert.Equal(t, w.Header().Get("Location"), "/path3")
- assert.Equal(t, w.Code, 307)
+ assert.Equal(t, "/path3", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, "PUT", "/path4")
- assert.Equal(t, w.Header().Get("Location"), "/path4/")
- assert.Equal(t, w.Code, 307)
+ assert.Equal(t, "/path4/", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, "GET", "/path")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, "GET", "/path2/")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, "POST", "/path3")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, "PUT", "/path4/")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
router.RedirectTrailingSlash = false
w = performRequest(router, "GET", "/path/")
- assert.Equal(t, w.Code, 404)
+ assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, "GET", "/path2")
- assert.Equal(t, w.Code, 404)
+ assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, "POST", "/path3/")
- assert.Equal(t, w.Code, 404)
+ assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, "PUT", "/path4")
- assert.Equal(t, w.Code, 404)
+ assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouteRedirectFixedPath(t *testing.T) {
@@ -193,20 +193,20 @@ func TestRouteRedirectFixedPath(t *testing.T) {
router.POST("/Path4/", func(c *Context) {})
w := performRequest(router, "GET", "/PATH")
- assert.Equal(t, w.Header().Get("Location"), "/path")
- assert.Equal(t, w.Code, 301)
+ assert.Equal(t, "/path", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "GET", "/path2")
- assert.Equal(t, w.Header().Get("Location"), "/Path2")
- assert.Equal(t, w.Code, 301)
+ assert.Equal(t, "/Path2", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "POST", "/path3")
- assert.Equal(t, w.Header().Get("Location"), "/PATH3")
- assert.Equal(t, w.Code, 307)
+ assert.Equal(t, "/PATH3", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, "POST", "/path4")
- assert.Equal(t, w.Header().Get("Location"), "/Path4/")
- assert.Equal(t, w.Code, 307)
+ assert.Equal(t, "/Path4/", w.Header().Get("Location"))
+ assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
}
// TestContextParamsGet tests that a parameter can be parsed from the URL.
@@ -236,10 +236,10 @@ func TestRouteParamsByName(t *testing.T) {
w := performRequest(router, "GET", "/test/john/smith/is/super/great")
- assert.Equal(t, w.Code, 200)
- assert.Equal(t, name, "john")
- assert.Equal(t, lastName, "smith")
- assert.Equal(t, wild, "/is/super/great")
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Equal(t, "john", name)
+ assert.Equal(t, "smith", lastName)
+ assert.Equal(t, "/is/super/great", wild)
}
// TestHandleStaticFile - ensure the static file handles properly
@@ -265,15 +265,15 @@ func TestRouteStaticFile(t *testing.T) {
w2 := performRequest(router, "GET", "/result")
assert.Equal(t, w, w2)
- assert.Equal(t, w.Code, 200)
- assert.Equal(t, w.Body.String(), "Gin Web Framework")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ 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"))
w3 := performRequest(router, "HEAD", "/using_static/"+filename)
w4 := performRequest(router, "HEAD", "/result")
assert.Equal(t, w3, w4)
- assert.Equal(t, w3.Code, 200)
+ assert.Equal(t, http.StatusOK, w3.Code)
}
// TestHandleStaticDir - ensure the root/sub dir handles properly
@@ -283,9 +283,9 @@ func TestRouteStaticListingDir(t *testing.T) {
w := performRequest(router, "GET", "/")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "gin.go")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
+ assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
}
// TestHandleHeadToDir - ensure the root/sub dir handles properly
@@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) {
w := performRequest(router, "GET", "/")
- assert.Equal(t, w.Code, 404)
+ assert.Equal(t, http.StatusNotFound, w.Code)
assert.NotContains(t, w.Body.String(), "gin.go")
}
@@ -310,12 +310,12 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
w := performRequest(router, "GET", "/gin.go")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "package gin")
- assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ 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, w.HeaderMap.Get("Expires"), "Mon, 02 Jan 2006 15:04:05 MST")
- assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework")
+ 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"))
}
func TestRouteNotAllowedEnabled(t *testing.T) {
@@ -323,14 +323,24 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
router.HandleMethodNotAllowed = true
router.POST("/path", func(c *Context) {})
w := performRequest(router, "GET", "/path")
- assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
+ assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText")
})
w = performRequest(router, "GET", "/path")
- assert.Equal(t, w.Body.String(), "responseText")
- assert.Equal(t, w.Code, http.StatusTeapot)
+ assert.Equal(t, "responseText", w.Body.String())
+ 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) {
@@ -338,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, w.Code, 404)
+ 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, w.Body.String(), "404 page not found")
- assert.Equal(t, w.Code, 404)
+ assert.Equal(t, "404 page not found", w.Body.String())
+ assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouterNotFound(t *testing.T) {
@@ -360,45 +370,45 @@ 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, w.Code, tr.code)
- if w.Code != 404 {
- assert.Equal(t, fmt.Sprint(w.Header().Get("Location")), tr.location)
+ assert.Equal(t, tr.code, w.Code)
+ if w.Code != http.StatusNotFound {
+ assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
}
}
// 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, w.Code, 404)
+ 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, w.Code, 307)
- assert.Equal(t, fmt.Sprint(w.Header()), "map[Location:[/path]]")
+ 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, w.Code, 404)
+ assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestRouteRawPath(t *testing.T) {
@@ -409,15 +419,15 @@ func TestRouteRawPath(t *testing.T) {
name := c.Params.ByName("name")
num := c.Params.ByName("num")
- assert.Equal(t, c.Param("name"), name)
- assert.Equal(t, c.Param("num"), num)
+ assert.Equal(t, name, c.Param("name"))
+ assert.Equal(t, num, c.Param("num"))
assert.Equal(t, "Some/Other/Project", name)
assert.Equal(t, "222", num)
})
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
}
func TestRouteRawPathNoUnescape(t *testing.T) {
@@ -429,15 +439,15 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
name := c.Params.ByName("name")
num := c.Params.ByName("num")
- assert.Equal(t, c.Param("name"), name)
- assert.Equal(t, c.Param("num"), num)
+ assert.Equal(t, name, c.Param("name"))
+ assert.Equal(t, num, c.Param("num"))
assert.Equal(t, "Some%2FOther%2FProject", name)
assert.Equal(t, "333", num)
})
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
- assert.Equal(t, w.Code, 200)
+ assert.Equal(t, http.StatusOK, w.Code)
}
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
diff --git a/test_helpers.go b/test_helpers.go
index 2aed37f2..3a7a5ddf 100644
--- a/test_helpers.go
+++ b/test_helpers.go
@@ -4,9 +4,7 @@
package gin
-import (
- "net/http"
-)
+import "net/http"
// CreateTestContext returns a fresh engine and context for testing purposes
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
diff --git a/testdata/certificate/cert.pem b/testdata/certificate/cert.pem
new file mode 100644
index 00000000..c1d3d632
--- /dev/null
+++ b/testdata/certificate/cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC9DCCAdygAwIBAgIQUNSK+OxWHYYFxHVJV0IlpDANBgkqhkiG9w0BAQsFADAS
+MRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MTExNjEyMDA0N1oXDTE4MTExNjEyMDA0
+N1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAKmyj/YZpD59Bpy4w3qf6VzMw9uUBsWp+IP4kl7z5cmGHYUHn/YopTLH
+vR23GAB12p6Km5QWzCBuJF4j61PJXHfg3/rjShZ77JcQ3kzxuy1iKDI+DNKN7Klz
+rdjJ49QD0lczZHeBvvCL7JsJFKFjGy62rnStuW8LaIEdtjXT+GUZTxJh6G7yPYfD
+MS1IsdMQGOdbGwNa+qogMuQPh0TzHw+C73myKrjY6pREijknMC/rnIMz9dLPt6Kl
+xXy4br443dpY6dYGIhDuKhROT+vZ05HKasuuQUFhY7v/KoUpEZMB9rfUSzjQ5fak
+eDUAMniXRcd+DmwvboG2TI6ixmuPK+ECAwEAAaNGMEQwDgYDVR0PAQH/BAQDAgWg
+MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocE
+fwAAATANBgkqhkiG9w0BAQsFAAOCAQEAMXOLvj7BFsxdbcfRPBd0OFrH/8lI7vPV
+LRcJ6r5iv0cnNvZXXbIOQLbg4clJAWjoE08nRm1KvNXhKdns0ELEV86YN2S6jThq
+rIGrBqKtaJLB3M9BtDSiQ6SGPLYrWvmhj3Avi8PbSGy51bpGbqopd16j6LYU7Cp2
+TefMRlOAFtHojpCVon1CMpqcNxS0WNlQ3lUBSrw3HB0o12x++roja2ibF54tSHXB
+KUuadoEzN+mMBwenEBychmAGzdiG4GQHRmhigh85+mtW6UMGiqyCZHs0EgE9FCLL
+sRrsTI/VOzLz6lluygXkOsXrP+PP0SvmE3eylWjj9e2nj/u/Cy2YKg==
+-----END CERTIFICATE-----
diff --git a/testdata/certificate/key.pem b/testdata/certificate/key.pem
new file mode 100644
index 00000000..c2a0181f
--- /dev/null
+++ b/testdata/certificate/key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAqbKP9hmkPn0GnLjDep/pXMzD25QGxan4g/iSXvPlyYYdhQef
+9iilMse9HbcYAHXanoqblBbMIG4kXiPrU8lcd+Df+uNKFnvslxDeTPG7LWIoMj4M
+0o3sqXOt2Mnj1APSVzNkd4G+8IvsmwkUoWMbLraudK25bwtogR22NdP4ZRlPEmHo
+bvI9h8MxLUix0xAY51sbA1r6qiAy5A+HRPMfD4LvebIquNjqlESKOScwL+ucgzP1
+0s+3oqXFfLhuvjjd2ljp1gYiEO4qFE5P69nTkcpqy65BQWFju/8qhSkRkwH2t9RL
+ONDl9qR4NQAyeJdFx34ObC9ugbZMjqLGa48r4QIDAQABAoIBAD5mhd+GMEo2KU9J
+9b/Ku8I/HapJtW/L/7Fvn0tBPncrVQGM+zpGWfDhV95sbGwG6lwwNeNvuqIWPlNL
+vAY0XkdKrrIQEDdSXH50WnpKzXxzwrou7QIj5Cmvevbjzl4xBZDBOilj0XWczmV4
+IljyG5XC4UXQeAaoWEZaSZ1jk8yAt2Zq1Hgg7HqhHsK/arWXBgax+4K5nV/s9gZx
+yjKU9mXTIs7k/aNnZqwQKqcZF+l3mvbZttOaFwsP14H0I8OFWhnM9hie54Dejqxi
+f4/llNxDqUs6lqJfP3qNxtORLcFe75M+Yl8v7g2hkjtLdZBakPzSTEx3TAK/UHgi
+aM8DdxECgYEA3fmg/PI4EgUEj0C3SCmQXR/CnQLMUQgb54s0asp4akvp+M7YCcr1
+pQd3HFUpBwhBcJg5LeSe87vLupY7pHCKk56cl9WY6hse0b9sP/7DWJuGiO62m0E0
+vNjQ2jpG99oR2ROIHHeWsGCpGLmrRT/kY+vR3M+AOLZniXlOCw8k0aUCgYEAw7WL
+XFWLxgZYQYilywqrQmfv1MBfaUCvykO6oWB+f6mmnihSFjecI+nDw/b3yXVYGEgy
+0ebkuw0jP8suC8wBqX9WuXj+9nZNomJRssJyOMiEhDEqUiTztFPSp9pdruoakLTh
+Wk1p9NralOqGPUmxpXlFKVmYRTUbluikVxDypI0CgYBn6sqEQH0hann0+o4TWWn9
+PrYkPUAbm1k8771tVTZERR/W3Dbldr/DL5iCihe39BR2urziEEqdvkglJNntJMar
+TzDuIBADYQjvltb9qq4XGFBGYMLaMg+XbUVxNKEuvUdnwa4R7aZ9EfN34MwekkfA
+w5Cu9/GGG1ajVEfGA6PwBQKBgA3o71jGs8KFXOx7e90sivOTU5Z5fc6LTHNB0Rf7
+NcJ5GmCPWRY/KZfb25AoE4B8GKDRMNt+X69zxZeZJ1KrU0rqxA02rlhyHB54gnoE
+G/4xMkn6/JkOC0w70PMhMBtohC7YzFOQwQEoNPT0nkno3Pl33xSLS6lPlwBo1JVj
+nPtZAoGACXNLXYkR5vexE+w6FGl59r4RQhu1XU8Mr5DIHeB7kXPN3RKbS201M+Tb
+SB5jbu0iDV477XkzSNmhaksFf2wM9MT6CaE+8n3UU5tMa+MmBGgwYTp/i9HkqVh5
+jjpJifn1VWBINd4cpNzwCg9LXoo0tbtUPWwGzqVeyo/YE5GIHGo=
+-----END RSA PRIVATE KEY-----
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
diff --git a/tree.go b/tree.go
index f67edd5d..b6530665 100644
--- a/tree.go
+++ b/tree.go
@@ -87,13 +87,13 @@ const (
type node struct {
path string
- wildChild bool
- nType nodeType
- maxParams uint8
indices string
children []*node
handlers HandlersChain
priority uint32
+ nType nodeType
+ maxParams uint8
+ wildChild bool
}
// increments priority of the given child and reorders if necessary.
@@ -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 != '*' {
@@ -384,7 +384,7 @@ walk: // Outer loop for walking the tree
// Nothing found.
// We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path.
- tsr = (path == "/" && n.handlers != nil)
+ tsr = path == "/" && n.handlers != nil
return
}
@@ -424,7 +424,7 @@ walk: // Outer loop for walking the tree
}
// ... but we can't
- tsr = (len(path) == end+1)
+ tsr = len(path) == end+1
return
}
@@ -435,7 +435,7 @@ walk: // Outer loop for walking the tree
// No handle found. Check if a handle for this path + a
// trailing slash exists for TSR recommendation
n = n.children[0]
- tsr = (n.path == "/" && n.handlers != nil)
+ tsr = n.path == "/" && n.handlers != nil
}
return
@@ -530,7 +530,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
// Nothing found. We can recommend to redirect to the same URL
// without a trailing slash if a leaf exists for that path
- found = (fixTrailingSlash && path == "/" && n.handlers != nil)
+ found = fixTrailingSlash && path == "/" && n.handlers != nil
return
}
diff --git a/tree_test.go b/tree_test.go
index c0edd42b..5bc27171 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -5,22 +5,11 @@
package gin
import (
- "fmt"
"reflect"
"strings"
"testing"
)
-func printChildren(n *node, prefix string) {
- fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handlers, n.wildChild, n.nType)
- for l := len(n.path); l > 0; l-- {
- prefix += " "
- }
- for _, child := range n.children {
- printChildren(child, prefix)
- }
-}
-
// Used as a workaround since we can't compare functions or their addressses
var fakeHandlerValue string
diff --git a/utils.go b/utils.go
index e909bb70..bf32c775 100644
--- a/utils.go
+++ b/utils.go
@@ -33,18 +33,23 @@ func Bind(val interface{}) HandlerFunc {
}
}
+// WrapF is a helper function for wrapping http.HandlerFunc
+// 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
func WrapH(h http.Handler) HandlerFunc {
return func(c *Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}
+// H is a shortcut for map[string]interface{}
type H map[string]interface{}
// MarshalXML allows type H to be used with xml.Marshal.
@@ -65,10 +70,8 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return err
}
}
- if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
- return err
- }
- return nil
+
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
}
func assert1(guard bool, text string) {
@@ -100,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)
}
}
diff --git a/utils_test.go b/utils_test.go
index 599172fe..9b57c57b 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -5,6 +5,8 @@
package gin
import (
+ "bytes"
+ "encoding/xml"
"fmt"
"net/http"
"testing"
@@ -21,9 +23,9 @@ type testStruct struct {
}
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- assert.Equal(t.T, req.Method, "POST")
- assert.Equal(t.T, req.URL.Path, "/path")
- w.WriteHeader(500)
+ assert.Equal(t.T, "POST", req.Method)
+ assert.Equal(t.T, "/path", req.URL.Path)
+ w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "hello")
}
@@ -31,50 +33,50 @@ func TestWrap(t *testing.T) {
router := New()
router.POST("/path", WrapH(&testStruct{t}))
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
- assert.Equal(t, req.Method, "GET")
- assert.Equal(t, req.URL.Path, "/path2")
- w.WriteHeader(400)
+ assert.Equal(t, "GET", req.Method)
+ assert.Equal(t, "/path2", req.URL.Path)
+ w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "hola!")
}))
w := performRequest(router, "POST", "/path")
- assert.Equal(t, w.Code, 500)
- assert.Equal(t, w.Body.String(), "hello")
+ assert.Equal(t, http.StatusInternalServerError, w.Code)
+ assert.Equal(t, "hello", w.Body.String())
w = performRequest(router, "GET", "/path2")
- assert.Equal(t, w.Code, 400)
- assert.Equal(t, w.Body.String(), "hola!")
+ assert.Equal(t, http.StatusBadRequest, w.Code)
+ assert.Equal(t, "hola!", w.Body.String())
}
func TestLastChar(t *testing.T) {
- assert.Equal(t, lastChar("hola"), uint8('a'))
- assert.Equal(t, lastChar("adios"), uint8('s'))
+ assert.Equal(t, uint8('a'), lastChar("hola"))
+ assert.Equal(t, uint8('s'), lastChar("adios"))
assert.Panics(t, func() { lastChar("") })
}
func TestParseAccept(t *testing.T) {
parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
assert.Len(t, parts, 4)
- assert.Equal(t, parts[0], "text/html")
- assert.Equal(t, parts[1], "application/xhtml+xml")
- assert.Equal(t, parts[2], "application/xml")
- assert.Equal(t, parts[3], "*/*")
+ assert.Equal(t, "text/html", parts[0])
+ assert.Equal(t, "application/xhtml+xml", parts[1])
+ assert.Equal(t, "application/xml", parts[2])
+ assert.Equal(t, "*/*", parts[3])
}
func TestChooseData(t *testing.T) {
A := "a"
B := "b"
- assert.Equal(t, chooseData(A, B), A)
- assert.Equal(t, chooseData(nil, B), B)
+ assert.Equal(t, A, chooseData(A, B))
+ assert.Equal(t, B, chooseData(nil, B))
assert.Panics(t, func() { chooseData(nil, nil) })
}
func TestFilterFlags(t *testing.T) {
result := filterFlags("text/html ")
- assert.Equal(t, result, "text/html")
+ assert.Equal(t, "text/html", result)
result = filterFlags("text/html;")
- assert.Equal(t, result, "text/html")
+ assert.Equal(t, "text/html", result)
}
func TestFunctionName(t *testing.T) {
@@ -86,16 +88,16 @@ func somefunction() {
}
func TestJoinPaths(t *testing.T) {
- assert.Equal(t, joinPaths("", ""), "")
- assert.Equal(t, joinPaths("", "/"), "/")
- assert.Equal(t, joinPaths("/a", ""), "/a")
- assert.Equal(t, joinPaths("/a/", ""), "/a/")
- assert.Equal(t, joinPaths("/a/", "/"), "/a/")
- assert.Equal(t, joinPaths("/a", "/"), "/a/")
- assert.Equal(t, joinPaths("/a", "/hola"), "/a/hola")
- assert.Equal(t, joinPaths("/a/", "/hola"), "/a/hola")
- assert.Equal(t, joinPaths("/a/", "/hola/"), "/a/hola/")
- assert.Equal(t, joinPaths("/a/", "/hola//"), "/a/hola/")
+ assert.Equal(t, "", joinPaths("", ""))
+ assert.Equal(t, "/", joinPaths("", "/"))
+ assert.Equal(t, "/a", joinPaths("/a", ""))
+ assert.Equal(t, "/a/", joinPaths("/a/", ""))
+ assert.Equal(t, "/a/", joinPaths("/a/", "/"))
+ assert.Equal(t, "/a/", joinPaths("/a", "/"))
+ assert.Equal(t, "/a/hola", joinPaths("/a", "/hola"))
+ assert.Equal(t, "/a/hola", joinPaths("/a/", "/hola"))
+ assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola/"))
+ assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola//"))
}
type bindTestStruct struct {
@@ -113,8 +115,8 @@ func TestBindMiddleware(t *testing.T) {
})
performRequest(router, "GET", "/?foo=hola&bar=10")
assert.True(t, called)
- assert.Equal(t, value.Foo, "hola")
- assert.Equal(t, value.Bar, 10)
+ assert.Equal(t, "hola", value.Foo)
+ assert.Equal(t, 10, value.Bar)
called = false
performRequest(router, "GET", "/?foo=hola&bar=1")
@@ -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)
+}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index bcefee10..e6d038a4 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -1,19 +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"
- },
- {
- "checksumSHA1": "7c3FuEadBInl/4ExSrB7iJMXpe4=",
- "path": "github.com/dustin/go-broadcast",
- "revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1",
- "revisionTime": "2014-06-27T04:00:55Z"
+ "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73",
+ "revisionTime": "2018-02-21T22:46:20Z"
},
{
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
@@ -22,66 +15,62 @@
"revisionTime": "2017-01-09T09:34:21Z"
},
{
- "checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=",
- "path": "github.com/gin-gonic/autotls",
- "revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d",
- "revisionTime": "2017-04-16T09:39:34Z"
- },
- {
- "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": "9if9IBLsxkarJ804NPWAzgskIAk=",
- "path": "github.com/manucorporat/stats",
- "revision": "8f2d6ace262eba462e9beb552382c98be51d807b",
- "revisionTime": "2015-05-31T20:46:25Z"
- },
- {
- "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": "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=",
- "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",
+ "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"
- },
- {
- "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"
+ "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab",
+ "revisionTime": "2018-04-07T10:07:33Z",
+ "version": "v1.1.1",
+ "versionExact": "v1.1.1"
},
{
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
@@ -91,24 +80,26 @@
"revisionTime": "2016-10-18T08:54:36Z"
},
{
- "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
+ "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=",
"path": "golang.org/x/sys/unix",
- "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97",
- "revisionTime": "2017-03-08T15:04:45Z"
+ "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded",
+ "revisionTime": "2018-08-15T07:37:39Z"
},
{
- "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=",
- "comment": "v8.18.1",
+ "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
"path": "gopkg.in/go-playground/validator.v8",
- "revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c",
- "revisionTime": "2016-07-18T13:41:25Z"
+ "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf",
+ "revisionTime": "2017-07-30T05:02:35Z",
+ "version": "v8.18.2",
+ "versionExact": "v8.18.2"
},
{
- "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=",
- "comment": "v2",
+ "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"