Merge branch 'master' into file-attachments

This commit is contained in:
Emmanuel Goh 2018-09-10 13:31:52 +08:00 committed by GitHub
commit f506c13560
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 1723 additions and 522 deletions

13
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -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.
- go version:
- gin version (or commit ref):
- operating system:
## Description
## Screenshots

7
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -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.

View File

@ -6,7 +6,7 @@ go:
- 1.8.x - 1.8.x
- 1.9.x - 1.9.x
- 1.10.x - 1.10.x
- master - 1.11.x
git: git:
depth: 10 depth: 10

View File

@ -1,8 +1,12 @@
List of all the awesome people working to make Gin the best Web Framework in Go. 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 ## 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. People and companies, who have contributed, in alphabetical order.

View File

@ -1,6 +1,28 @@
# CHANGELOG # 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] Switch from godeps to govendor
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls - [NEW] Add support for Let's Encrypt via gin-gonic/autotls

View File

@ -1,5 +1,6 @@
GOFMT ?= gofmt "-s" GOFMT ?= gofmt "-s"
PACKAGES ?= $(shell go list ./... | grep -v /vendor/) 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/*") GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
all: install all: install
@ -26,7 +27,7 @@ fmt-check:
fi; fi;
vet: vet:
go vet $(PACKAGES) go vet $(VETPACKAGES)
deps: deps:
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \

366
README.md
View File

@ -7,6 +7,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin)
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. 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.
@ -15,10 +16,11 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
## Contents ## Contents
- [Installation](#installation)
- [Prerequisite](#prerequisite)
- [Quick start](#quick-start) - [Quick start](#quick-start)
- [Benchmarks](#benchmarks) - [Benchmarks](#benchmarks)
- [Gin v1.stable](#gin-v1-stable) - [Gin v1.stable](#gin-v1-stable)
- [Start using it](#start-using-it)
- [Build with jsoniter](#build-with-jsoniter) - [Build with jsoniter](#build-with-jsoniter)
- [API Examples](#api-examples) - [API Examples](#api-examples)
- [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
@ -26,6 +28,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Querystring parameters](#querystring-parameters) - [Querystring parameters](#querystring-parameters)
- [Multipart/Urlencoded Form](#multiparturlencoded-form) - [Multipart/Urlencoded Form](#multiparturlencoded-form)
- [Another example: query + post form](#another-example-query--post-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) - [Upload files](#upload-files)
- [Grouping routes](#grouping-routes) - [Grouping routes](#grouping-routes)
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
@ -37,7 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Query String or Post Data](#bind-query-string-or-post-data)
- [Bind HTML checkboxes](#bind-html-checkboxes) - [Bind HTML checkboxes](#bind-html-checkboxes)
- [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
- [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
- [JSONP rendering](#jsonp) - [JSONP rendering](#jsonp)
- [Serving static files](#serving-static-files) - [Serving static files](#serving-static-files)
- [Serving data from reader](#serving-data-from-reader) - [Serving data from reader](#serving-data-from-reader)
@ -54,8 +57,67 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Build a single binary with templates](#build-a-single-binary-with-templates) - [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) - [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) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
- [http2 server push](#http2-server-push)
- [Testing](#testing) - [Testing](#testing)
- [Users](#users--) - [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 ## Quick start
@ -134,61 +196,9 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894
- [x] Battle tested - [x] Battle tested
- [x] API frozen, new releases will not break your code. - [x] API frozen, new releases will not break your code.
## Start using it
1. Download and install it:
```sh
$ go get github.com/gin-gonic/gin
```
2. Import it in your code:
```go
import "github.com/gin-gonic/gin"
```
3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
```go
import "net/http"
```
### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)
1. `go get` govendor
```sh
$ go get github.com/kardianos/govendor
```
2. Create your project folder and `cd` inside
```sh
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
```
3. Vendor init your project and add gin
```sh
$ govendor init
$ govendor fetch github.com/gin-gonic/gin@v1.2
```
4. Copy a starting template inside your project
```sh
$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
```
5. Run your project
```sh
$ go run main.go
```
## Build with [jsoniter](https://github.com/json-iterator/go) ## 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 ```sh
$ go build -tags=jsoniter . $ go build -tags=jsoniter .
@ -228,7 +238,7 @@ func main() {
func main() { func main() {
router := gin.Default() 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) { router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name") name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name) c.String(http.StatusOK, "Hello %s", name)
@ -315,6 +325,34 @@ func main() {
id: 1234; page: 1; name: manu; message: this_is_great 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 ### Upload files
#### Single file #### Single file
@ -496,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: Also, Gin provides two sets of methods for binding:
- **Type** - Must bind - **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. - **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 - **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. - **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`. 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`.
@ -509,8 +547,8 @@ You can also specify that specific fields are required. If a field is decorated
```go ```go
// Binding from JSON // Binding from JSON
type Login struct { type Login struct {
User string `form:"user" json:"user" binding:"required"` User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"`
} }
func main() { func main() {
@ -519,30 +557,55 @@ func main() {
// Example for binding JSON ({"user": "manu", "password": "123"}) // Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) { router.POST("/loginJSON", func(c *gin.Context) {
var json Login var json Login
if err := c.ShouldBindJSON(&json); err == nil { 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 {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 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 (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>user</user>
// <password>123</user>
// </root>)
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) // Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) { router.POST("/loginForm", func(c *gin.Context) {
var form Login var form Login
// This will infer what binder to use depending on the content-type header. // This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err == nil { 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 {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 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 // Listen and serve on 0.0.0.0:8080
@ -572,6 +635,10 @@ $ curl -v -X POST \
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} {"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 ### Custom Validators
It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go).
@ -637,7 +704,7 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
``` ```
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more.
### Only Bind Query String ### Only Bind Query String
@ -683,9 +750,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco
```go ```go
package main package main
import "log" import (
import "github.com/gin-gonic/gin" "log"
import "time" "time"
"github.com/gin-gonic/gin"
)
type Person struct { type Person struct {
Name string `form:"name"` Name string `form:"name"`
@ -804,7 +874,7 @@ Test it with:
$ curl -v --form user=user --form password=password http://localhost:8080/login $ curl -v --form user=user --form password=password http://localhost:8080/login
``` ```
### XML, JSON and YAML rendering ### XML, JSON, YAML and ProtoBuf rendering
```go ```go
func main() { func main() {
@ -838,6 +908,19 @@ func main() {
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) 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 // Listen and serve on 0.0.0.0:8080
r.Run(":8080") r.Run(":8080")
} }
@ -888,6 +971,57 @@ func main() {
} }
``` ```
#### AsciiJSON
Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters.
```go
func main() {
r := gin.Default()
r.GET("/someJSON", func(c *gin.Context) {
data := map[string]interface{}{
"lang": "GO语言",
"tag": "<br>",
}
// 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
Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead.
This feature is unavailable in Go 1.6 and lower.
```go
func main() {
r := gin.Default()
// Serves unicode entities
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// Serves literal characters
r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// listen and serve on 0.0.0.0:8080
r.Run(":8080)
}
```
### Serving static files ### Serving static files
```go ```go
@ -1052,7 +1186,7 @@ func main() {
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate, "formatAsDate": formatAsDate,
}) })
router.LoadHTMLFiles("./fixtures/basic/raw.tmpl") router.LoadHTMLFiles("./testdata/template/raw.tmpl")
router.GET("/raw", func(c *gin.Context) { router.GET("/raw", func(c *gin.Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
@ -1082,14 +1216,26 @@ Gin allow by default use only one html.Template. Check [a multitemplate render](
### Redirects ### Redirects
Issuing a HTTP redirect is easy: Issuing a HTTP redirect is easy. Both internal and external locations are supported.
```go ```go
r.GET("/test", func(c *gin.Context) { r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") 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 ### Custom Middleware
@ -1640,6 +1786,55 @@ enough to call binding at once.
can be called by `c.ShouldBind()` multiple times without any damage to can be called by `c.ShouldBind()` multiple times without any damage to
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). 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(`
<html>
<head>
<title>Https Test</title>
<script src="/assets/app.js"></script>
</head>
<body>
<h1 style="color:red;">Welcome, Ginner!</h1>
</body>
</html>
`))
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 ## Testing
The `net/http/httptest` package is preferable way for HTTP testing. The `net/http/httptest` package is preferable way for HTTP testing.
@ -1686,9 +1881,10 @@ func TestPingRoute(t *testing.T) {
} }
``` ```
## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) ## Users
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go * [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go.
* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.

View File

@ -7,6 +7,7 @@ package gin
import ( import (
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
"net/http"
"strconv" "strconv"
) )
@ -51,7 +52,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
if !found { if !found {
// Credentials doesn't match, we return 401 and abort handlers chain. // Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm) c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(401) c.AbortWithStatus(http.StatusUnauthorized)
return return
} }

View File

@ -93,7 +93,7 @@ func TestBasicAuthSucceed(t *testing.T) {
router := New() router := New()
router.Use(BasicAuth(accounts)) router.Use(BasicAuth(accounts))
router.GET("/login", func(c *Context) { router.GET("/login", func(c *Context) {
c.String(200, c.MustGet(AuthUserKey).(string)) c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
}) })
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -101,7 +101,7 @@ func TestBasicAuthSucceed(t *testing.T) {
req.Header.Set("Authorization", authorizationHeader("admin", "password")) req.Header.Set("Authorization", authorizationHeader("admin", "password"))
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "admin", w.Body.String()) assert.Equal(t, "admin", w.Body.String())
} }
@ -112,7 +112,7 @@ func TestBasicAuth401(t *testing.T) {
router.Use(BasicAuth(accounts)) router.Use(BasicAuth(accounts))
router.GET("/login", func(c *Context) { router.GET("/login", func(c *Context) {
called = true called = true
c.String(200, c.MustGet(AuthUserKey).(string)) c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
}) })
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -121,7 +121,7 @@ func TestBasicAuth401(t *testing.T) {
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.False(t, called) 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")) 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.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\""))
router.GET("/login", func(c *Context) { router.GET("/login", func(c *Context) {
called = true called = true
c.String(200, c.MustGet(AuthUserKey).(string)) c.String(http.StatusOK, c.MustGet(AuthUserKey).(string))
}) })
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) {
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.False(t, called) 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")) assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate"))
} }

View File

@ -54,7 +54,7 @@ func BenchmarkOneRouteJSON(B *testing.B) {
Status string `json:"status"` Status string `json:"status"`
}{"ok"} }{"ok"}
router.GET("/json", func(c *Context) { router.GET("/json", func(c *Context) {
c.JSON(200, data) c.JSON(http.StatusOK, data)
}) })
runRequest(B, router, "GET", "/json") runRequest(B, router, "GET", "/json")
} }
@ -66,7 +66,7 @@ func BenchmarkOneRouteHTML(B *testing.B) {
router.SetHTMLTemplate(t) router.SetHTMLTemplate(t)
router.GET("/html", func(c *Context) { router.GET("/html", func(c *Context) {
c.HTML(200, "index", "hola") c.HTML(http.StatusOK, "index", "hola")
}) })
runRequest(B, router, "GET", "/html") runRequest(B, router, "GET", "/html")
} }
@ -82,7 +82,7 @@ func BenchmarkOneRouteSet(B *testing.B) {
func BenchmarkOneRouteString(B *testing.B) { func BenchmarkOneRouteString(B *testing.B) {
router := New() router := New()
router.GET("/text", func(c *Context) { 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") runRequest(B, router, "GET", "/text")
} }

View File

@ -4,10 +4,9 @@
package binding package binding
import ( import "net/http"
"net/http"
)
// Content-Type MIME of the most common data formats.
const ( const (
MIMEJSON = "application/json" MIMEJSON = "application/json"
MIMEHTML = "text/html" MIMEHTML = "text/html"

View File

@ -5,7 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"testing" "testing"
"github.com/gin-gonic/gin/binding/example" "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
@ -55,12 +55,12 @@ func msgPackBody(t *testing.T) string {
} }
func TestBindingBodyProto(t *testing.T) { func TestBindingBodyProto(t *testing.T) {
test := example.Test{ test := protoexample.Test{
Label: proto.String("FOO"), Label: proto.String("FOO"),
} }
data, _ := proto.Marshal(&test) data, _ := proto.Marshal(&test)
req := requestWithBody("POST", "/", string(data)) req := requestWithBody("POST", "/", string(data))
form := example.Test{} form := protoexample.Test{}
body, _ := ioutil.ReadAll(req.Body) body, _ := ioutil.ReadAll(req.Body)
assert.NoError(t, ProtoBuf.BindBody(body, &form)) assert.NoError(t, ProtoBuf.BindBody(body, &form))
assert.Equal(t, test, form) assert.Equal(t, test, form)

View File

@ -14,7 +14,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/gin-gonic/gin/binding/example" "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
@ -562,7 +562,7 @@ func TestBindingFormMultipartFail(t *testing.T) {
} }
func TestBindingProtoBuf(t *testing.T) { func TestBindingProtoBuf(t *testing.T) {
test := &example.Test{ test := &protoexample.Test{
Label: proto.String("yes"), Label: proto.String("yes"),
} }
data, _ := proto.Marshal(test) data, _ := proto.Marshal(test)
@ -574,7 +574,7 @@ func TestBindingProtoBuf(t *testing.T) {
} }
func TestBindingProtoBufFail(t *testing.T) { func TestBindingProtoBufFail(t *testing.T) {
test := &example.Test{ test := &protoexample.Test{
Label: proto.String("yes"), Label: proto.String("yes"),
} }
data, _ := proto.Marshal(test) data, _ := proto.Marshal(test)
@ -1156,14 +1156,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad
func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := example.Test{} obj := protoexample.Test{}
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", MIMEPROTOBUF)
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "yes", *obj.Label) assert.Equal(t, "yes", *obj.Label)
obj = example.Test{} obj = protoexample.Test{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", MIMEPROTOBUF)
err = ProtoBuf.Bind(req, &obj) err = ProtoBuf.Bind(req, &obj)
@ -1179,7 +1179,7 @@ func (h hook) Read([]byte) (int, error) {
func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := example.Test{} obj := protoexample.Test{}
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
req.Body = ioutil.NopCloser(&hook{}) req.Body = ioutil.NopCloser(&hook{})
@ -1187,7 +1187,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
obj = example.Test{} obj = protoexample.Test{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", MIMEPROTOBUF)
err = ProtoBuf.Bind(req, &obj) err = ProtoBuf.Bind(req, &obj)

View File

@ -18,11 +18,17 @@ type defaultValidator struct {
var _ StructValidator = &defaultValidator{} 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 { 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() v.lazyinit()
if err := v.validate.Struct(obj); err != nil { if err := v.validate.Struct(obj); err != nil {
return error(err) return err
} }
} }
return nil return nil
@ -43,12 +49,3 @@ func (v *defaultValidator) lazyinit() {
v.validate = validator.New(config) 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
}

View File

@ -74,7 +74,8 @@ func mapForm(ptr interface{}, form map[string][]string) error {
} }
} }
val.Field(i).Set(slice) val.Field(i).Set(slice)
} else { continue
}
if _, isTime := structField.Interface().(time.Time); isTime { if _, isTime := structField.Interface().(time.Time); isTime {
if err := setTimeField(inputValue[0], typeField, structField); err != nil { if err := setTimeField(inputValue[0], typeField, structField); err != nil {
return err return err
@ -85,7 +86,6 @@ func mapForm(ptr interface{}, form map[string][]string) error {
return err return err
} }
} }
}
return nil return nil
} }
@ -178,7 +178,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error {
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
timeFormat := structField.Tag.Get("time_format") timeFormat := structField.Tag.Get("time_format")
if timeFormat == "" { if timeFormat == "" {
return errors.New("Blank time format") timeFormat = time.RFC3339
} }
if val == "" { if val == "" {

View File

@ -9,7 +9,7 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/gin-gonic/gin/json" "github.com/gin-gonic/gin/internal/json"
) )
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON // EnableDecoderUseNumber is used to call the UseNumber method on the JSON

120
context.go Executable file → Normal file
View File

@ -160,16 +160,15 @@ func (c *Context) Error(err error) *Error {
if err == nil { if err == nil {
panic("err is nil") panic("err is nil")
} }
var parsedError *Error
switch err.(type) { parsedError, ok := err.(*Error)
case *Error: if !ok {
parsedError = err.(*Error)
default:
parsedError = &Error{ parsedError = &Error{
Err: err, Err: err,
Type: ErrorTypePrivate, Type: ErrorTypePrivate,
} }
} }
c.Errors = append(c.Errors, parsedError) c.Errors = append(c.Errors, parsedError)
return parsedError return parsedError
} }
@ -362,6 +361,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) {
return []string{}, false 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 // PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`. // when it exists, otherwise it returns an empty string `("")`.
func (c *Context) PostForm(key string) string { func (c *Context) PostForm(key string) string {
@ -417,6 +428,42 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
return []string{}, false 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. // FormFile returns the first file for the provided form key.
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
_, fh, err := c.Request.FormFile(name) _, fh, err := c.Request.FormFile(name)
@ -465,6 +512,11 @@ func (c *Context) BindJSON(obj interface{}) error {
return c.MustBindWith(obj, binding.JSON) 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). // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) BindQuery(obj interface{}) error { func (c *Context) BindQuery(obj interface{}) error {
return c.MustBindWith(obj, binding.Query) return c.MustBindWith(obj, binding.Query)
@ -475,7 +527,7 @@ func (c *Context) BindQuery(obj interface{}) error {
// See the binding package. // See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
if err = c.ShouldBindWith(obj, b); err != nil { if err = c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(400, err).SetType(ErrorTypeBind) c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)
} }
return return
@ -499,6 +551,11 @@ func (c *Context) ShouldBindJSON(obj interface{}) error {
return c.ShouldBindWith(obj, binding.JSON) 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). // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj interface{}) error { func (c *Context) ShouldBindQuery(obj interface{}) error {
return c.ShouldBindWith(obj, binding.Query) return c.ShouldBindWith(obj, binding.Query)
@ -540,14 +597,10 @@ func (c *Context) ShouldBindBodyWith(
func (c *Context) ClientIP() string { func (c *Context) ClientIP() string {
if c.engine.ForwardedByClientIP { if c.engine.ForwardedByClientIP {
clientIP := c.requestHeader("X-Forwarded-For") clientIP := c.requestHeader("X-Forwarded-For")
if index := strings.IndexByte(clientIP, ','); index >= 0 { clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0])
clientIP = clientIP[0:index] if clientIP == "" {
}
clientIP = strings.TrimSpace(clientIP)
if clientIP != "" {
return clientIP
}
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
}
if clientIP != "" { if clientIP != "" {
return clientIP return clientIP
} }
@ -594,9 +647,9 @@ func bodyAllowedForStatus(status int) bool {
switch { switch {
case status >= 100 && status <= 199: case status >= 100 && status <= 199:
return false return false
case status == 204: case status == http.StatusNoContent:
return false return false
case status == 304: case status == http.StatusNotModified:
return false return false
} }
return true return true
@ -613,9 +666,9 @@ func (c *Context) Status(code int) {
func (c *Context) Header(key, value string) { func (c *Context) Header(key, value string) {
if value == "" { if value == "" {
c.Writer.Header().Del(key) c.Writer.Header().Del(key)
} else { return
c.Writer.Header().Set(key, value)
} }
c.Writer.Header().Set(key, value)
} }
// GetHeader returns value from request headers. // GetHeader returns value from request headers.
@ -700,7 +753,12 @@ func (c *Context) SecureJSON(code int, obj interface{}) {
// It add padding to response body to request data from a server residing in a different domain than the client. // It 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". // It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj interface{}) { func (c *Context) JSONP(code int, obj interface{}) {
c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) callback := c.DefaultQuery("callback", "")
if callback == "" {
c.Render(code, render.JSON{Data: obj})
return
}
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
} }
// JSON serializes the given struct as JSON into the response body. // JSON serializes the given struct as JSON into the response body.
@ -709,6 +767,12 @@ func (c *Context) JSON(code int, obj interface{}) {
c.Render(code, render.JSON{Data: obj}) 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. // XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml". // It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) { func (c *Context) XML(code int, obj interface{}) {
@ -720,6 +784,11 @@ func (c *Context) YAML(code int, obj interface{}) {
c.Render(code, render.YAML{Data: obj}) 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. // String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...interface{}) { func (c *Context) String(code int, format string, values ...interface{}) {
c.Render(code, render.String{Format: format, Data: values}) c.Render(code, render.String{Format: format, Data: values})
@ -848,18 +917,33 @@ func (c *Context) SetAccepted(formats ...string) {
/***** GOLANG.ORG/X/NET/CONTEXT *****/ /***** 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) { func (c *Context) Deadline() (deadline time.Time, ok bool) {
return 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{} { func (c *Context) Done() <-chan struct{} {
return nil 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 { func (c *Context) Err() error {
return nil 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{} { func (c *Context) Value(key interface{}) interface{} {
if key == 0 { if key == 0 {
return c.Request return c.Request

17
context_17.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build go1.7
package gin
import (
"github.com/gin-gonic/gin/render"
)
// PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
func (c *Context) PureJSON(code int, obj interface{}) {
c.Render(code, render.PureJSON{Data: obj})
}

27
context_17_test.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build go1.7
package gin
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
// Tests that the response is serialized as JSON
// and Content-Type is set to application/json
// and special HTML characters are preserved
func TestContextRenderPureJSON(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
}

View File

@ -9,6 +9,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
"io"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -19,8 +20,11 @@ import (
"github.com/gin-contrib/sse" "github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context" "golang.org/x/net/context"
testdata "github.com/gin-gonic/gin/testdata/protoexample"
) )
var _ context.Context = &Context{} var _ context.Context = &Context{}
@ -47,6 +51,8 @@ func createMultipartRequest() *http.Request {
must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_local", "31/12/2016 14:55"))
must(mw.WriteField("time_utc", "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("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) req, err := http.NewRequest("POST", "/", body)
must(err) must(err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
@ -371,7 +377,8 @@ func TestContextQuery(t *testing.T) {
func TestContextQueryAndPostForm(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") 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) c.Request.Header.Add("Content-Type", MIMEPOSTForm)
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none")) assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
@ -439,6 +446,30 @@ func TestContextQueryAndPostForm(t *testing.T) {
values = c.QueryArray("both") values = c.QueryArray("both")
assert.Equal(t, 1, len(values)) assert.Equal(t, 1, len(values))
assert.Equal(t, "GET", values[0]) 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) { func TestContextPostFormMultipart(t *testing.T) {
@ -515,6 +546,22 @@ func TestContextPostFormMultipart(t *testing.T) {
values = c.PostFormArray("foo") values = c.PostFormArray("foo")
assert.Equal(t, 1, len(values)) assert.Equal(t, 1, len(values))
assert.Equal(t, "bar", values[0]) 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) { func TestContextSetCookie(t *testing.T) {
@ -541,10 +588,11 @@ func TestContextGetCookie(t *testing.T) {
} }
func TestContextBodyAllowedForStatus(t *testing.T) { func TestContextBodyAllowedForStatus(t *testing.T) {
// todo(thinkerou): go1.6 not support StatusProcessing
assert.False(t, false, bodyAllowedForStatus(102)) assert.False(t, false, bodyAllowedForStatus(102))
assert.False(t, false, bodyAllowedForStatus(204)) assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
assert.False(t, false, bodyAllowedForStatus(304)) assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
assert.True(t, true, bodyAllowedForStatus(500)) assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
} }
type TestPanicRender struct { type TestPanicRender struct {
@ -571,14 +619,15 @@ func TestContextRenderPanicIfErr(t *testing.T) {
// Tests that the response is serialized as JSON // Tests that the response is serialized as JSON
// and Content-Type is set to application/json // and Content-Type is set to application/json
// and special HTML characters are escaped
func TestContextRenderJSON(t *testing.T) { func TestContextRenderJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.JSON(201, H{"foo": "bar"}) c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -589,21 +638,35 @@ func TestContextRenderJSONP(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil)
c.JSONP(201, H{"foo": "bar"}) c.JSONP(http.StatusCreated, H{"foo": "bar"})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String())
assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w.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 // Tests that no JSON is rendered if code is 204
func TestContextRenderNoContentJSON(t *testing.T) { func TestContextRenderNoContentJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.JSON(204, H{"foo": "bar"}) c.JSON(http.StatusNoContent, H{"foo": "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -615,9 +678,9 @@ func TestContextRenderAPIJSON(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Header("Content-Type", "application/vnd.api+json") 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, "{\"foo\":\"bar\"}", w.Body.String())
assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type"))
} }
@ -628,9 +691,9 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Header("Content-Type", "application/vnd.api+json") c.Header("Content-Type", "application/vnd.api+json")
c.JSON(204, H{"foo": "bar"}) c.JSON(http.StatusNoContent, H{"foo": "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json")
} }
@ -641,9 +704,9 @@ func TestContextRenderIndentedJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -653,9 +716,9 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -667,9 +730,9 @@ func TestContextRenderSecureJSON(t *testing.T) {
c, router := CreateTestContext(w) c, router := CreateTestContext(w)
router.SecureJsonPrefix("&&&START&&&") router.SecureJsonPrefix("&&&START&&&")
c.SecureJSON(201, []string{"foo", "bar"}) c.SecureJSON(http.StatusCreated, []string{"foo", "bar"})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -679,13 +742,24 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.SecureJSON(204, []string{"foo", "bar"}) c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.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 // Tests that the response executes the templates
// and responds with Content-Type set to text/html // and responds with Content-Type set to text/html
func TestContextRenderHTML(t *testing.T) { func TestContextRenderHTML(t *testing.T) {
@ -695,9 +769,9 @@ func TestContextRenderHTML(t *testing.T) {
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
c.HTML(201, "t", H{"name": "alexandernyquist"}) c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "Hello alexandernyquist", w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -719,9 +793,9 @@ func TestContextRenderHTML2(t *testing.T) {
assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String()) assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String())
c.HTML(201, "t", H{"name": "alexandernyquist"}) c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "Hello alexandernyquist", w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -733,9 +807,9 @@ func TestContextRenderNoContentHTML(t *testing.T) {
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
c.HTML(204, "t", H{"name": "alexandernyquist"}) c.HTML(http.StatusNoContent, "t", H{"name": "alexandernyquist"})
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -746,9 +820,9 @@ func TestContextRenderXML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.XML(201, H{"foo": "bar"}) c.XML(http.StatusCreated, H{"foo": "bar"})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String()) assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -758,9 +832,9 @@ func TestContextRenderNoContentXML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.XML(204, H{"foo": "bar"}) c.XML(http.StatusNoContent, H{"foo": "bar"})
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -771,9 +845,9 @@ func TestContextRenderString(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.String(201, "test %s %d", "string", 2) c.String(http.StatusCreated, "test %s %d", "string", 2)
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "test string 2", w.Body.String()) assert.Equal(t, "test string 2", w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -783,9 +857,9 @@ func TestContextRenderNoContentString(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.String(204, "test %s %d", "string", 2) c.String(http.StatusNoContent, "test %s %d", "string", 2)
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -797,9 +871,9 @@ func TestContextRenderHTMLString(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Header("Content-Type", "text/html; charset=utf-8") c.Header("Content-Type", "text/html; charset=utf-8")
c.String(201, "<html>%s %d</html>", "string", 3) c.String(http.StatusCreated, "<html>%s %d</html>", "string", 3)
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "<html>string 3</html>", w.Body.String()) assert.Equal(t, "<html>string 3</html>", w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -810,9 +884,9 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Header("Content-Type", "text/html; charset=utf-8") c.Header("Content-Type", "text/html; charset=utf-8")
c.String(204, "<html>%s %d</html>", "string", 3) c.String(http.StatusNoContent, "<html>%s %d</html>", "string", 3)
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -823,9 +897,9 @@ func TestContextRenderData(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Data(201, "text/csv", []byte(`foo,bar`)) c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`))
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "foo,bar", w.Body.String()) assert.Equal(t, "foo,bar", w.Body.String())
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
} }
@ -835,9 +909,9 @@ func TestContextRenderNoContentData(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Data(204, "text/csv", []byte(`foo,bar`)) c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`))
assert.Equal(t, 204, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
assert.Empty(t, w.Body.String()) assert.Empty(t, w.Body.String())
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
} }
@ -866,7 +940,7 @@ func TestContextRenderFile(t *testing.T) {
c.Request, _ = http.NewRequest("GET", "/", nil) c.Request, _ = http.NewRequest("GET", "/", nil)
c.File("./gin.go") c.File("./gin.go")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Contains(t, w.Body.String(), "func New() *Engine {")
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -890,13 +964,37 @@ func TestContextRenderYAML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.YAML(201, H{"foo": "bar"}) c.YAML(http.StatusCreated, H{"foo": "bar"})
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "foo: bar\n", w.Body.String()) assert.Equal(t, "foo: bar\n", w.Body.String())
assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/x-yaml; charset=utf-8", w.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) { func TestContextHeaders(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Header("Content-Type", "text/plain") c.Header("Content-Type", "text/plain")
@ -922,9 +1020,9 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(299, "/new_path") })
assert.Panics(t, func() { c.Redirect(309, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") })
c.Redirect(301, "/path") c.Redirect(http.StatusMovedPermanently, "/path")
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
assert.Equal(t, 301, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, "/path", w.Header().Get("Location"))
} }
@ -933,10 +1031,10 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "http://example.com", nil) 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() c.Writer.WriteHeaderNow()
assert.Equal(t, 302, w.Code) assert.Equal(t, http.StatusFound, w.Code)
assert.Equal(t, "http://google.com", w.Header().Get("Location")) assert.Equal(t, "http://google.com", w.Header().Get("Location"))
} }
@ -945,21 +1043,23 @@ func TestContextRenderRedirectWith201(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
c.Redirect(201, "/resource") c.Redirect(http.StatusCreated, "/resource")
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
assert.Equal(t, 201, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "/resource", w.Header().Get("Location")) assert.Equal(t, "/resource", w.Header().Get("Location"))
} }
func TestContextRenderRedirectAll(t *testing.T) { func TestContextRenderRedirectAll(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "http://example.com", nil) c.Request, _ = http.NewRequest("POST", "http://example.com", nil)
assert.Panics(t, func() { c.Redirect(200, "/resource") }) assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") })
assert.Panics(t, func() { c.Redirect(202, "/resource") }) assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") })
assert.Panics(t, func() { c.Redirect(299, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") })
assert.Panics(t, func() { c.Redirect(309, "/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") }) assert.NotPanics(t, func() { c.Redirect(308, "/resource") })
} }
@ -968,12 +1068,12 @@ func TestContextNegotiationWithJSON(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(200, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEJSON, MIMEXML}, Offered: []string{MIMEJSON, MIMEXML},
Data: H{"foo": "bar"}, 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, "{\"foo\":\"bar\"}", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -983,12 +1083,12 @@ func TestContextNegotiationWithXML(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(200, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEXML, MIMEJSON}, Offered: []string{MIMEXML, MIMEJSON},
Data: H{"foo": "bar"}, Data: H{"foo": "bar"},
}) })
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String()) assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -1000,13 +1100,13 @@ func TestContextNegotiationWithHTML(t *testing.T) {
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
c.Negotiate(200, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEHTML}, Offered: []string{MIMEHTML},
Data: H{"name": "gin"}, Data: H{"name": "gin"},
HTMLName: "t", 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, "Hello gin", w.Body.String())
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -1016,11 +1116,11 @@ func TestContextNegotiationNotSupport(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(200, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEPOSTForm}, Offered: []string{MIMEPOSTForm},
}) })
assert.Equal(t, 406, w.Code) assert.Equal(t, http.StatusNotAcceptable, w.Code)
assert.Equal(t, c.index, abortIndex) assert.Equal(t, c.index, abortIndex)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }
@ -1078,11 +1178,11 @@ func TestContextAbortWithStatus(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.index = 4 c.index = 4
c.AbortWithStatus(401) c.AbortWithStatus(http.StatusUnauthorized)
assert.Equal(t, abortIndex, c.index) assert.Equal(t, abortIndex, c.index)
assert.Equal(t, 401, c.Writer.Status()) assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
assert.Equal(t, 401, w.Code) assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }
@ -1100,11 +1200,11 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
in.Bar = "barValue" in.Bar = "barValue"
in.Foo = "fooValue" in.Foo = "fooValue"
c.AbortWithStatusJSON(415, in) c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in)
assert.Equal(t, abortIndex, c.index) assert.Equal(t, abortIndex, c.index)
assert.Equal(t, 415, c.Writer.Status()) assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status())
assert.Equal(t, 415, w.Code) assert.Equal(t, http.StatusUnsupportedMediaType, w.Code)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
contentType := w.Header().Get("Content-Type") contentType := w.Header().Get("Content-Type")
@ -1167,9 +1267,9 @@ func TestContextAbortWithError(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input")
assert.Equal(t, 401, w.Code) assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, abortIndex, c.index) assert.Equal(t, abortIndex, c.index)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }
@ -1243,6 +1343,26 @@ func TestContextBindWithJSON(t *testing.T) {
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len()) 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(`<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>FOO</foo>
<bar>BAR</bar>
</root>`))
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) { func TestContextBindWithQuery(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -1277,7 +1397,7 @@ func TestContextBadAutoBind(t *testing.T) {
assert.Empty(t, obj.Bar) assert.Empty(t, obj.Bar)
assert.Empty(t, obj.Foo) assert.Empty(t, obj.Foo)
assert.Equal(t, 400, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }
@ -1313,6 +1433,27 @@ func TestContextShouldBindWithJSON(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) 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(`<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>FOO</foo>
<bar>BAR</bar>
</root>`))
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) { func TestContextShouldBindWithQuery(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
@ -1503,3 +1644,58 @@ func TestContextRenderDataFromReader(t *testing.T) {
assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length"))
assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) assert.Equal(t, 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())
}

View File

@ -4,7 +4,7 @@ set -e
echo "mode: count" > coverage.out echo "mode: count" > coverage.out
for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do
go test -v -covermode=count -coverprofile=profile.out $d go test -v -covermode=count -coverprofile=profile.out $d
if [ -f profile.out ]; then if [ -f profile.out ]; then
cat profile.out | grep -v "mode:" >> coverage.out cat profile.out | grep -v "mode:" >> coverage.out

View File

@ -47,6 +47,9 @@ func debugPrint(format string, values ...interface{}) {
} }
func debugPrintWARNINGDefault() { 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. debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`) `)

View File

@ -72,7 +72,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) {
setup(&w) setup(&w)
defer teardown() 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) debugPrintLoadTemplate(templ)
assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) 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() defer teardown()
debugPrintWARNINGDefault() 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) { func TestDebugPrintWARNINGNew(t *testing.T) {

View File

@ -9,7 +9,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/gin-gonic/gin/json" "github.com/gin-gonic/gin/internal/json"
) )
type ErrorType uint64 type ErrorType uint64

View File

@ -8,7 +8,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/gin-gonic/gin/json" "github.com/gin-gonic/gin/internal/json"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -19,17 +19,17 @@ func TestError(t *testing.T) {
Type: ErrorTypePrivate, Type: ErrorTypePrivate,
} }
assert.Equal(t, err.Error(), baseError.Error()) 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.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.SetMeta("some data"), err)
assert.Equal(t, err.Meta, "some data") assert.Equal(t, "some data", err.Meta)
assert.Equal(t, err.JSON(), H{ assert.Equal(t, H{
"error": baseError.Error(), "error": baseError.Error(),
"meta": "some data", "meta": "some data",
}) }, err.JSON())
jsonBytes, _ := json.Marshal(err) jsonBytes, _ := json.Marshal(err)
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
@ -38,22 +38,22 @@ func TestError(t *testing.T) {
"status": "200", "status": "200",
"data": "some data", "data": "some data",
}) })
assert.Equal(t, err.JSON(), H{ assert.Equal(t, H{
"error": baseError.Error(), "error": baseError.Error(),
"status": "200", "status": "200",
"data": "some data", "data": "some data",
}) }, err.JSON())
err.SetMeta(H{ err.SetMeta(H{
"error": "custom error", "error": "custom error",
"status": "200", "status": "200",
"data": "some data", "data": "some data",
}) })
assert.Equal(t, err.JSON(), H{ assert.Equal(t, H{
"error": "custom error", "error": "custom error",
"status": "200", "status": "200",
"data": "some data", "data": "some data",
}) }, err.JSON())
type customError struct { type customError struct {
status string status string

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -13,7 +15,7 @@ func setupRouter() *gin.Engine {
// Ping test // Ping test
r.GET("/ping", func(c *gin.Context) { r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") c.String(http.StatusOK, "pong")
}) })
// Get user value // Get user value
@ -21,9 +23,9 @@ func setupRouter() *gin.Engine {
user := c.Params.ByName("name") user := c.Params.ByName("name")
value, ok := DB[user] value, ok := DB[user]
if ok { if ok {
c.JSON(200, gin.H{"user": user, "value": value}) c.JSON(http.StatusOK, gin.H{"user": user, "value": value})
} else { } else {
c.JSON(200, gin.H{"user": user, "status": "no value"}) c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"})
} }
}) })
@ -49,7 +51,7 @@ func setupRouter() *gin.Engine {
if c.Bind(&json) == nil { if c.Bind(&json) == nil {
DB[user] = json.Value DB[user] = json.Value
c.JSON(200, gin.H{"status": "ok"}) c.JSON(http.StatusOK, gin.H{"status": "ok"})
} }
}) })

View File

@ -15,6 +15,6 @@ func TestPingRoute(t *testing.T) {
req, _ := http.NewRequest("GET", "/ping", nil) req, _ := http.NewRequest("GET", "/ping", nil)
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "pong", w.Body.String()) assert.Equal(t, "pong", w.Body.String())
} }

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/thinkerou/favicon" "github.com/thinkerou/favicon"
) )
@ -9,7 +11,7 @@ func main() {
app := gin.Default() app := gin.Default()
app.Use(favicon.New("./favicon.ico")) app.Use(favicon.New("./favicon.ico"))
app.GET("/ping", func(c *gin.Context) { app.GET("/ping", func(c *gin.Context) {
c.String(200, "Hello favicon.") c.String(http.StatusOK, "Hello favicon.")
}) })
app.Run(":8080") app.Run(":8080")
} }

19
examples/grpc/README.md Normal file
View File

@ -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'
```

46
examples/grpc/gin/main.go Normal file
View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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,
}

View File

@ -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;
}

View File

@ -0,0 +1 @@
console.log("http2 pusher");

View File

@ -0,0 +1,41 @@
package main
import (
"html/template"
"log"
"github.com/gin-gonic/gin"
)
var html = template.Must(template.New("https").Parse(`
<html>
<head>
<title>Https Test</title>
<script src="/assets/app.js"></script>
</head>
<body>
<h1 style="color:red;">Welcome, Ginner!</h1>
</body>
</html>
`))
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")
}

15
examples/http-pusher/testdata/ca.pem vendored Normal file
View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -3,6 +3,7 @@ package main
import ( import (
"html/template" "html/template"
"log" "log"
"net/http"
"os" "os"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -27,7 +28,7 @@ func main() {
r.SetHTMLTemplate(html) r.SetHTMLTemplate(html)
r.GET("/welcome", func(c *gin.Context) { r.GET("/welcome", func(c *gin.Context) {
c.HTML(200, "https", gin.H{ c.HTML(http.StatusOK, "https", gin.H{
"status": "success", "status": "success",
}) })
}) })

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"html" "html"
"io" "io"
"net/http"
"strings" "strings"
"time" "time"
@ -21,12 +22,12 @@ func rateLimit(c *gin.Context) {
fmt.Println("ip blocked") fmt.Println("ip blocked")
} }
c.Abort() c.Abort()
c.String(503, "you were automatically banned :)") c.String(http.StatusServiceUnavailable, "you were automatically banned :)")
} }
} }
func index(c *gin.Context) { func index(c *gin.Context) {
c.Redirect(301, "/room/hn") c.Redirect(http.StatusMovedPermanently, "/room/hn")
} }
func roomGET(c *gin.Context) { func roomGET(c *gin.Context) {
@ -38,7 +39,7 @@ func roomGET(c *gin.Context) {
if len(nick) > 13 { if len(nick) > 13 {
nick = nick[0:12] + "..." 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, "roomid": roomid,
"nick": nick, "nick": nick,
"timestamp": time.Now().Unix(), "timestamp": time.Now().Unix(),
@ -55,7 +56,7 @@ func roomPOST(c *gin.Context) {
validMessage := len(message) > 1 && len(message) < 200 validMessage := len(message) > 1 && len(message) < 200
validNick := len(nick) > 1 && len(nick) < 14 validNick := len(nick) > 1 && len(nick) < 14
if !validMessage || !validNick { if !validMessage || !validNick {
c.JSON(400, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"status": "failed", "status": "failed",
"error": "the message or nickname is too long", "error": "the message or nickname is too long",
}) })
@ -68,7 +69,7 @@ func roomPOST(c *gin.Context) {
} }
messages.Add("inbound", 1) messages.Add("inbound", 1)
room(roomid).Submit(post) room(roomid).Submit(post)
c.JSON(200, post) c.JSON(http.StatusOK, post)
} }
func streamRoom(c *gin.Context) { func streamRoom(c *gin.Context) {

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -34,7 +35,7 @@ func stream(c *gin.Context) {
func roomGET(c *gin.Context) { func roomGET(c *gin.Context) {
roomid := c.Param("roomid") roomid := c.Param("roomid")
userid := fmt.Sprint(rand.Int31()) userid := fmt.Sprint(rand.Int31())
c.HTML(200, "chat_room", gin.H{ c.HTML(http.StatusOK, "chat_room", gin.H{
"roomid": roomid, "roomid": roomid,
"userid": userid, "userid": userid,
}) })
@ -46,7 +47,7 @@ func roomPOST(c *gin.Context) {
message := c.PostForm("message") message := c.PostForm("message")
room(roomid).Submit(userid + ": " + message) room(roomid).Submit(userid + ": " + message)
c.JSON(200, gin.H{ c.JSON(http.StatusOK, gin.H{
"status": "success", "status": "success",
"message": message, "message": message,
}) })

View File

@ -20,7 +20,7 @@ func main() {
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate, "formatAsDate": formatAsDate,
}) })
router.LoadHTMLFiles("../../fixtures/basic/raw.tmpl") router.LoadHTMLFiles("../../testdata/template/raw.tmpl")
router.GET("/raw", func(c *gin.Context) { router.GET("/raw", func(c *gin.Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{

49
gin.go
View File

@ -16,7 +16,7 @@ import (
const ( const (
// Version is Framework's version. // Version is Framework's version.
Version = "v1.2" Version = "v1.3.0"
defaultMultipartMemory = 32 << 20 // 32 MB defaultMultipartMemory = 32 << 20 // 32 MB
) )
@ -171,14 +171,14 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
func (engine *Engine) LoadHTMLGlob(pattern string) { func (engine *Engine) LoadHTMLGlob(pattern string) {
left := engine.delims.Left left := engine.delims.Left
right := engine.delims.Right right := engine.delims.Right
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
if IsDebugging() { 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} engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
return return
} }
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
engine.SetHTMLTemplate(templ) engine.SetHTMLTemplate(templ)
} }
@ -329,7 +329,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
// HandleContext re-enter a context that has been rewritten. // 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. // Disclaimer: You can loop yourself to death with this, use wisely.
func (engine *Engine) HandleContext(c *Context) { func (engine *Engine) HandleContext(c *Context) {
c.reset() c.reset()
@ -349,7 +349,9 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
// Find root of the tree for the given HTTP method // Find root of the tree for the given HTTP method
t := engine.trees t := engine.trees
for i, tl := 0, len(t); i < tl; i++ { for i, tl := 0, len(t); i < tl; i++ {
if t[i].method == httpMethod { if t[i].method != httpMethod {
continue
}
root := t[i].root root := t[i].root
// Find route in tree // Find route in tree
handlers, params, tsr := root.getValue(path, c.Params, unescape) handlers, params, tsr := root.getValue(path, c.Params, unescape)
@ -371,21 +373,21 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
} }
break break
} }
}
if engine.HandleMethodNotAllowed { if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees { for _, tree := range engine.trees {
if tree.method != httpMethod { if tree.method == httpMethod {
continue
}
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
c.handlers = engine.allNoMethod c.handlers = engine.allNoMethod
serveError(c, 405, default405Body) serveError(c, http.StatusMethodNotAllowed, default405Body)
return return
} }
} }
} }
}
c.handlers = engine.allNoRoute c.handlers = engine.allNoRoute
serveError(c, 404, default404Body) serveError(c, http.StatusNotFound, default404Body)
} }
var mimePlain = []string{MIMEPlain} var mimePlain = []string{MIMEPlain}
@ -393,28 +395,29 @@ var mimePlain = []string{MIMEPlain}
func serveError(c *Context, code int, defaultMessage []byte) { func serveError(c *Context, code int, defaultMessage []byte) {
c.writermem.status = code c.writermem.status = code
c.Next() c.Next()
if !c.writermem.Written() { if c.writermem.Written() {
return
}
if c.writermem.Status() == code { if c.writermem.Status() == code {
c.writermem.Header()["Content-Type"] = mimePlain c.writermem.Header()["Content-Type"] = mimePlain
c.Writer.Write(defaultMessage) c.Writer.Write(defaultMessage)
} else { return
}
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()
} return
}
} }
func redirectTrailingSlash(c *Context) { func redirectTrailingSlash(c *Context) {
req := c.Request req := c.Request
path := req.URL.Path 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" { if req.Method != "GET" {
code = 307 code = http.StatusTemporaryRedirect
} }
req.URL.Path = path + "/"
if length := len(path); length > 1 && path[length-1] == '/' { if length := len(path); length > 1 && path[length-1] == '/' {
req.URL.Path = path[:length-1] req.URL.Path = path[:length-1]
} else {
req.URL.Path = path + "/"
} }
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
http.Redirect(c.Writer, req, req.URL.String(), code) http.Redirect(c.Writer, req, req.URL.String(), code)
@ -425,14 +428,10 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
req := c.Request req := c.Request
path := req.URL.Path path := req.URL.Path
fixedPath, found := root.findCaseInsensitivePath( if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok {
cleanPath(path), code := http.StatusMovedPermanently // Permanent redirect, request with GET method
trailingSlash,
)
if found {
code := 301 // Permanent redirect, request with GET method
if req.Method != "GET" { if req.Method != "GET" {
code = 307 code = http.StatusTemporaryRedirect
} }
req.URL.Path = string(fixedPath) req.URL.Path = string(fixedPath)
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())

View File

@ -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. // 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) // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
// Note: this method will block the calling goroutine undefinitelly unless an error happens. // 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) return engine().RunTLS(addr, certFile, keyFile)
} }

View File

@ -30,7 +30,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate, "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) { router.GET("/test", func(c *Context) {
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
}) })
@ -41,7 +41,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
}) })
if tls { if tls {
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")
} else { } else {
router.Run(":8888") router.Run(":8888")
} }
@ -59,7 +59,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
router.SetFuncMap(template.FuncMap{ router.SetFuncMap(template.FuncMap{
"formatAsDate": formatAsDate, "formatAsDate": formatAsDate,
}) })
router.LoadHTMLGlob("./fixtures/basic/*") router.LoadHTMLGlob("./testdata/template/*")
router.GET("/test", func(c *Context) { router.GET("/test", func(c *Context) {
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
}) })
@ -70,7 +70,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
}) })
if tls { if tls {
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")
} else { } else {
router.Run(":8888") router.Run(":8888")
} }

View File

@ -293,7 +293,7 @@ func githubConfigRouter(router *Engine) {
for _, param := range c.Params { for _, param := range c.Params {
output[param.Key] = param.Value output[param.Key] = param.Value
} }
c.JSON(200, output) c.JSON(http.StatusOK, output)
}) })
} }
} }

View File

@ -12,4 +12,5 @@ var (
Marshal = json.Marshal Marshal = json.Marshal
MarshalIndent = json.MarshalIndent MarshalIndent = json.MarshalIndent
NewDecoder = json.NewDecoder NewDecoder = json.NewDecoder
NewEncoder = json.NewEncoder
) )

View File

@ -13,4 +13,5 @@ var (
Marshal = json.Marshal Marshal = json.Marshal
MarshalIndent = json.MarshalIndent MarshalIndent = json.MarshalIndent
NewDecoder = json.NewDecoder NewDecoder = json.NewDecoder
NewEncoder = json.NewEncoder
) )

View File

@ -7,6 +7,7 @@ package gin
import ( import (
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"time" "time"
@ -118,11 +119,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
func colorForStatus(code int) string { func colorForStatus(code int) string {
switch { switch {
case code >= 200 && code < 300: case code >= http.StatusOK && code < http.StatusMultipleChoices:
return green return green
case code >= 300 && code < 400: case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
return white return white
case code >= 400 && code < 500: case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
return yellow return yellow
default: default:
return red return red

View File

@ -7,6 +7,7 @@ package gin
import ( import (
"bytes" "bytes"
"errors" "errors"
"net/http"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -93,9 +94,9 @@ func TestColorForMethod(t *testing.T) {
} }
func TestColorForStatus(t *testing.T) { func TestColorForStatus(t *testing.T) {
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green") assert.Equal(t, string([]byte{27, 91, 57, 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(301), "3xx should be white") 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(404), "4xx should be yellow") 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") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red")
} }
@ -106,23 +107,23 @@ func TestErrorLogger(t *testing.T) {
c.Error(errors.New("this is an error")) c.Error(errors.New("this is an error"))
}) })
router.GET("/abort", func(c *Context) { 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) { router.GET("/print", func(c *Context) {
c.Error(errors.New("this is an error")) c.Error(errors.New("this is an error"))
c.String(500, "hola!") c.String(http.StatusInternalServerError, "hola!")
}) })
w := performRequest(router, "GET", "/error") 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()) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
w = performRequest(router, "GET", "/abort") 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()) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
w = performRequest(router, "GET", "/print") 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()) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
} }

View File

@ -6,6 +6,7 @@ package gin
import ( import (
"errors" "errors"
"net/http"
"strings" "strings"
"testing" "testing"
@ -37,7 +38,7 @@ func TestMiddlewareGeneralCase(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "ACDB", signature) assert.Equal(t, "ACDB", signature)
} }
@ -73,7 +74,7 @@ func TestMiddlewareNoRoute(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
assert.Equal(t, "ACEGHFDB", signature) assert.Equal(t, "ACEGHFDB", signature)
} }
@ -110,7 +111,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, 405, w.Code) assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
assert.Equal(t, "ACEGHFDB", signature) assert.Equal(t, "ACEGHFDB", signature)
} }
@ -147,7 +148,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
assert.Equal(t, "AC X DB", signature) assert.Equal(t, "AC X DB", signature)
} }
@ -159,7 +160,7 @@ func TestMiddlewareAbort(t *testing.T) {
}) })
router.Use(func(c *Context) { router.Use(func(c *Context) {
signature += "C" signature += "C"
c.AbortWithStatus(401) c.AbortWithStatus(http.StatusUnauthorized)
c.Next() c.Next()
signature += "D" signature += "D"
}) })
@ -173,7 +174,7 @@ func TestMiddlewareAbort(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, 401, w.Code) assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Equal(t, "ACD", signature) assert.Equal(t, "ACD", signature)
} }
@ -183,7 +184,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
router.Use(func(c *Context) { router.Use(func(c *Context) {
signature += "A" signature += "A"
c.Next() c.Next()
c.AbortWithStatus(410) c.AbortWithStatus(http.StatusGone)
signature += "B" signature += "B"
}) })
@ -195,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, 410, w.Code) assert.Equal(t, http.StatusGone, w.Code)
assert.Equal(t, "ACB", signature) assert.Equal(t, "ACB", signature)
} }
@ -207,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
router := New() router := New()
router.Use(func(context *Context) { router.Use(func(context *Context) {
signature += "A" signature += "A"
context.AbortWithError(500, errors.New("foo")) context.AbortWithError(http.StatusInternalServerError, errors.New("foo"))
}) })
router.Use(func(context *Context) { router.Use(func(context *Context) {
signature += "B" signature += "B"
@ -218,25 +219,25 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
// TEST // TEST
assert.Equal(t, 500, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "A", signature) assert.Equal(t, "A", signature)
} }
func TestMiddlewareWrite(t *testing.T) { func TestMiddlewareWrite(t *testing.T) {
router := New() router := New()
router.Use(func(c *Context) { router.Use(func(c *Context) {
c.String(400, "hola\n") c.String(http.StatusBadRequest, "hola\n")
}) })
router.Use(func(c *Context) { router.Use(func(c *Context) {
c.XML(400, H{"foo": "bar"}) c.XML(http.StatusBadRequest, H{"foo": "bar"})
}) })
router.Use(func(c *Context) { router.Use(func(c *Context) {
c.JSON(400, H{"foo": "bar"}) c.JSON(http.StatusBadRequest, H{"foo": "bar"})
}) })
router.GET("/", func(c *Context) { router.GET("/", func(c *Context) {
c.JSON(400, H{"foo": "bar"}) c.JSON(http.StatusBadRequest, H{"foo": "bar"})
}, func(c *Context) { }, func(c *Context) {
c.Render(400, sse.Event{ c.Render(http.StatusBadRequest, sse.Event{
Event: "test", Event: "test",
Data: "message", Data: "message",
}) })
@ -244,6 +245,6 @@ func TestMiddlewareWrite(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
assert.Equal(t, 400, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
} }

View File

@ -41,7 +41,7 @@ func cleanPath(p string) string {
buf[0] = '/' 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 // 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 // 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] == '/': case p[r] == '.' && p[r+1] == '/':
// . element // . element
r++ r += 2
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
// .. element: remove to last / // .. element: remove to last /
r += 2 r += 3
if w > 1 { if w > 1 {
// can backtrack // can backtrack

View File

@ -24,6 +24,7 @@ var cleanTests = []struct {
// missing root // missing root
{"", "/"}, {"", "/"},
{"a/", "/a/"},
{"abc", "/abc"}, {"abc", "/abc"},
{"abc/def", "/abc/def"}, {"abc/def", "/abc/def"},
{"a/b/c", "/a/b/c"}, {"a/b/c", "/a/b/c"},

View File

@ -10,6 +10,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http"
"net/http/httputil" "net/http/httputil"
"runtime" "runtime"
"time" "time"
@ -41,7 +42,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
httprequest, _ := httputil.DumpRequest(c.Request, false) httprequest, _ := httputil.DumpRequest(c.Request, false)
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) 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() c.Next()

View File

@ -6,6 +6,7 @@ package gin
import ( import (
"bytes" "bytes"
"net/http"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -22,7 +23,7 @@ func TestPanicInHandler(t *testing.T) {
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := performRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, 500, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "GET /recovery")
assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem")
assert.Contains(t, buffer.String(), "TestPanicInHandler") assert.Contains(t, buffer.String(), "TestPanicInHandler")
@ -33,11 +34,31 @@ func TestPanicWithAbort(t *testing.T) {
router := New() router := New()
router.Use(RecoveryWithWriter(nil)) router.Use(RecoveryWithWriter(nil))
router.GET("/recovery", func(c *Context) { router.GET("/recovery", func(c *Context) {
c.AbortWithStatus(400) c.AbortWithStatus(http.StatusBadRequest)
panic("Oupps, Houston, we have a problem") panic("Oupps, Houston, we have a problem")
}) })
// RUN // RUN
w := performRequest(router, "GET", "/recovery") w := performRequest(router, "GET", "/recovery")
// TEST // TEST
assert.Equal(t, 400, w.Code) 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)
} }

32
render/json.go Executable file → Normal file
View File

@ -6,10 +6,11 @@ package render
import ( import (
"bytes" "bytes"
"fmt"
"html/template" "html/template"
"net/http" "net/http"
"github.com/gin-gonic/gin/json" "github.com/gin-gonic/gin/internal/json"
) )
type JSON struct { type JSON struct {
@ -30,10 +31,15 @@ type JsonpJSON struct {
Data interface{} Data interface{}
} }
type AsciiJSON struct {
Data interface{}
}
type SecureJSONPrefix string type SecureJSONPrefix string
var jsonContentType = []string{"application/json; charset=utf-8"} var jsonContentType = []string{"application/json; charset=utf-8"}
var jsonpContentType = []string{"application/javascript; 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) { func (r JSON) Render(w http.ResponseWriter) (err error) {
if err = WriteJSON(w, r.Data); err != nil { if err = WriteJSON(w, r.Data); err != nil {
@ -112,3 +118,27 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonpContentType) 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 := string(r)
if r >= 128 {
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)
}

28
render/json_17.go Normal file
View File

@ -0,0 +1,28 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build go1.7
package render
import (
"net/http"
"github.com/gin-gonic/gin/internal/json"
)
type PureJSON struct {
Data interface{}
}
func (r PureJSON) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false)
return encoder.Encode(r.Data)
}
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}

View File

@ -26,6 +26,6 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, msgpackContentType) writeContentType(w, msgpackContentType)
var h codec.Handle = new(codec.MsgpackHandle) var mh codec.MsgpackHandle
return codec.NewEncoder(w, h).Encode(obj) return codec.NewEncoder(w, &mh).Encode(obj)
} }

33
render/protobuf.go Normal file
View File

@ -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)
}

View File

@ -1,3 +1,7 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package render package render
import ( import (

View File

@ -16,6 +16,8 @@ type Redirect struct {
} }
func (r Redirect) Render(w http.ResponseWriter) error { 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 { if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
} }

2
render/render.go Executable file → Normal file
View File

@ -26,6 +26,8 @@ var (
_ Render = YAML{} _ Render = YAML{}
_ Render = MsgPack{} _ Render = MsgPack{}
_ Render = Reader{} _ Render = Reader{}
_ Render = AsciiJSON{}
_ Render = ProtoBuf{}
) )
func writeContentType(w http.ResponseWriter, value []string) { func writeContentType(w http.ResponseWriter, value []string) {

26
render/render_17_test.go Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build go1.7
package render
import (
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRenderPureJSON(t *testing.T) {
w := httptest.NewRecorder()
data := map[string]interface{}{
"foo": "bar",
"html": "<b>",
}
err := (PureJSON{data}).Render(w)
assert.NoError(t, err)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
}

87
render/render_test.go Executable file → Normal file
View File

@ -15,8 +15,11 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
testdata "github.com/gin-gonic/gin/testdata/protoexample"
) )
// TODO unit tests // TODO unit tests
@ -50,6 +53,7 @@ func TestRenderJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]interface{}{
"foo": "bar", "foo": "bar",
"html": "<b>",
} }
(JSON{data}).WriteContentType(w) (JSON{data}).WriteContentType(w)
@ -58,7 +62,7 @@ func TestRenderJSON(t *testing.T) {
err := (JSON{data}).Render(w) err := (JSON{data}).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
} }
@ -158,6 +162,21 @@ func TestRenderJsonpJSON(t *testing.T) {
assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) 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) { func TestRenderJsonpJSONFail(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := make(chan int) data := make(chan int)
@ -167,6 +186,35 @@ func TestRenderJsonpJSONFail(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }
func TestRenderAsciiJSON(t *testing.T) {
w1 := httptest.NewRecorder()
data1 := map[string]interface{}{
"lang": "GO语言",
"tag": "<br>",
}
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{} type xmlmap map[string]interface{}
// Allows type H to be used with xml.Marshal // Allows type H to be used with xml.Marshal
@ -221,6 +269,35 @@ func TestRenderYAMLFail(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }
// test Protobuf rendering
func TestRenderProtoBuf(t *testing.T) {
w := httptest.NewRecorder()
reps := []int64{int64(1), int64(2)}
label := "test"
data := &testdata.Test{
Label: &label,
Reps: reps,
}
(ProtoBuf{data}).WriteContentType(w)
protoData, err := proto.Marshal(data)
assert.NoError(t, err)
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
err = (ProtoBuf{data}).Render(w)
assert.NoError(t, err)
assert.Equal(t, string(protoData[:]), w.Body.String())
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
}
func TestRenderProtoBufFail(t *testing.T) {
w := httptest.NewRecorder()
data := &testdata.Test{}
err := (ProtoBuf{data}).Render(w)
assert.Error(t, err)
}
func TestRenderXML(t *testing.T) { func TestRenderXML(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := xmlmap{ data := xmlmap{
@ -242,7 +319,7 @@ func TestRenderRedirect(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
data1 := Redirect{ data1 := Redirect{
Code: 301, Code: http.StatusMovedPermanently,
Request: req, Request: req,
Location: "/new/location", Location: "/new/location",
} }
@ -252,7 +329,7 @@ func TestRenderRedirect(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
data2 := Redirect{ data2 := Redirect{
Code: 200, Code: http.StatusOK,
Request: req, Request: req,
Location: "/new/location", Location: "/new/location",
} }
@ -344,7 +421,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"}, htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"},
Glob: "", Glob: "",
Delims: Delims{Left: "{[{", Right: "}]}"}, Delims: Delims{Left: "{[{", Right: "}]}"},
FuncMap: nil, FuncMap: nil,
@ -363,7 +440,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
func TestRenderHTMLDebugGlob(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
htmlRender := HTMLDebug{Files: nil, htmlRender := HTMLDebug{Files: nil,
Glob: "../fixtures/basic/hello*", Glob: "../testdata/template/hello*",
Delims: Delims{Left: "{[{", Right: "}]}"}, Delims: Delims{Left: "{[{", Right: "}]}"},
FuncMap: nil, FuncMap: nil,
} }

View File

@ -30,7 +30,7 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) {
writeContentType(w, plainContentType) writeContentType(w, plainContentType)
if len(data) > 0 { if len(data) > 0 {
fmt.Fprintf(w, format, data...) fmt.Fprintf(w, format, data...)
} else { return
}
io.WriteString(w, format) io.WriteString(w, format)
} }
}

View File

@ -13,10 +13,10 @@ import (
const ( const (
noWritten = -1 noWritten = -1
defaultStatus = 200 defaultStatus = http.StatusOK
) )
type ResponseWriter interface { type responseWriterBase interface {
http.ResponseWriter http.ResponseWriter
http.Hijacker http.Hijacker
http.Flusher http.Flusher
@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool {
// Flush implements the http.Flush interface. // Flush implements the http.Flush interface.
func (w *responseWriter) Flush() { func (w *responseWriter) Flush() {
w.WriteHeaderNow()
w.ResponseWriter.(http.Flusher).Flush() w.ResponseWriter.(http.Flusher).Flush()
} }

12
response_writer_1.7.go Normal file
View File

@ -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
}

25
response_writer_1.8.go Normal file
View File

@ -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
}

View File

@ -35,10 +35,10 @@ func TestResponseWriterReset(t *testing.T) {
writer.reset(testWritter) writer.reset(testWritter)
assert.Equal(t, -1, writer.size) 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, testWritter, writer.ResponseWriter)
assert.Equal(t, -1, w.Size()) assert.Equal(t, -1, w.Size())
assert.Equal(t, 200, w.Status()) assert.Equal(t, http.StatusOK, w.Status())
assert.False(t, w.Written()) assert.False(t, w.Written())
} }
@ -48,13 +48,13 @@ func TestResponseWriterWriteHeader(t *testing.T) {
writer.reset(testWritter) writer.reset(testWritter)
w := ResponseWriter(writer) w := ResponseWriter(writer)
w.WriteHeader(300) w.WriteHeader(http.StatusMultipleChoices)
assert.False(t, w.Written()) assert.False(t, w.Written())
assert.Equal(t, 300, w.Status()) assert.Equal(t, http.StatusMultipleChoices, w.Status())
assert.NotEqual(t, testWritter.Code, 300) assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code)
w.WriteHeader(-1) w.WriteHeader(-1)
assert.Equal(t, 300, w.Status()) assert.Equal(t, http.StatusMultipleChoices, w.Status())
} }
func TestResponseWriterWriteHeadersNow(t *testing.T) { func TestResponseWriterWriteHeadersNow(t *testing.T) {
@ -63,12 +63,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
writer.reset(testWritter) writer.reset(testWritter)
w := ResponseWriter(writer) w := ResponseWriter(writer)
w.WriteHeader(300) w.WriteHeader(http.StatusMultipleChoices)
w.WriteHeaderNow() w.WriteHeaderNow()
assert.True(t, w.Written()) assert.True(t, w.Written())
assert.Equal(t, 0, w.Size()) assert.Equal(t, 0, w.Size())
assert.Equal(t, 300, testWritter.Code) assert.Equal(t, http.StatusMultipleChoices, testWritter.Code)
writer.size = 10 writer.size = 10
w.WriteHeaderNow() w.WriteHeaderNow()
@ -84,8 +84,8 @@ func TestResponseWriterWrite(t *testing.T) {
n, err := w.Write([]byte("hola")) n, err := w.Write([]byte("hola"))
assert.Equal(t, 4, n) assert.Equal(t, 4, n)
assert.Equal(t, 4, w.Size()) assert.Equal(t, 4, w.Size())
assert.Equal(t, 200, w.Status()) assert.Equal(t, http.StatusOK, w.Status())
assert.Equal(t, 200, testWritter.Code) assert.Equal(t, http.StatusOK, testWritter.Code)
assert.Equal(t, "hola", testWritter.Body.String()) assert.Equal(t, "hola", testWritter.Body.String())
assert.NoError(t, err) assert.NoError(t, err)
@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) {
w.Flush() 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)
}

View File

@ -139,7 +139,7 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou
return group.returnObj() 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") // router.StaticFile("favicon.ico", "./resources/favicon.ico")
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
@ -184,7 +184,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
_, nolisting := fs.(*onlyfilesFS) _, nolisting := fs.(*onlyfilesFS)
return func(c *Context) { return func(c *Context) {
if nolisting { if nolisting {
c.Writer.WriteHeader(404) c.Writer.WriteHeader(http.StatusNotFound)
} }
fileServer.ServeHTTP(c.Writer, c.Request) fileServer.ServeHTTP(c.Writer, c.Request)
} }

View File

@ -5,6 +5,7 @@
package gin package gin
import ( import (
"net/http"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -50,7 +51,7 @@ func performRequestInGroup(t *testing.T, method string) {
assert.Equal(t, "/v1/login/", login.BasePath()) assert.Equal(t, "/v1/login/", login.BasePath())
handler := func(c *Context) { 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 { switch method {
@ -80,11 +81,11 @@ func performRequestInGroup(t *testing.T, method string) {
} }
w := performRequest(router, method, "/v1/login/test") w := performRequest(router, method, "/v1/login/test")
assert.Equal(t, 400, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
w = performRequest(router, method, "/v1/test") w = performRequest(router, method, "/v1/test")
assert.Equal(t, 400, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
} }

View File

@ -80,20 +80,20 @@ func testRouteNotOK2(method string, t *testing.T) {
func TestRouterMethod(t *testing.T) { func TestRouterMethod(t *testing.T) {
router := New() router := New()
router.PUT("/hey2", func(c *Context) { router.PUT("/hey2", func(c *Context) {
c.String(200, "sup2") c.String(http.StatusOK, "sup2")
}) })
router.PUT("/hey", func(c *Context) { router.PUT("/hey", func(c *Context) {
c.String(200, "called") c.String(http.StatusOK, "called")
}) })
router.PUT("/hey3", func(c *Context) { router.PUT("/hey3", func(c *Context) {
c.String(200, "sup3") c.String(http.StatusOK, "sup3")
}) })
w := performRequest(router, "PUT", "/hey") w := performRequest(router, "PUT", "/hey")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "called", w.Body.String()) assert.Equal(t, "called", w.Body.String())
} }
@ -144,42 +144,42 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
w := performRequest(router, "GET", "/path/") w := performRequest(router, "GET", "/path/")
assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, 301, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, "/path2/", w.Header().Get("Location")) assert.Equal(t, "/path2/", w.Header().Get("Location"))
assert.Equal(t, 301, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "POST", "/path3/") w = performRequest(router, "POST", "/path3/")
assert.Equal(t, "/path3", w.Header().Get("Location")) assert.Equal(t, "/path3", w.Header().Get("Location"))
assert.Equal(t, 307, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, "PUT", "/path4") w = performRequest(router, "PUT", "/path4")
assert.Equal(t, "/path4/", w.Header().Get("Location")) assert.Equal(t, "/path4/", w.Header().Get("Location"))
assert.Equal(t, 307, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, "GET", "/path") w = performRequest(router, "GET", "/path")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, "GET", "/path2/") w = performRequest(router, "GET", "/path2/")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, "POST", "/path3") w = performRequest(router, "POST", "/path3")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
w = performRequest(router, "PUT", "/path4/") w = performRequest(router, "PUT", "/path4/")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
router.RedirectTrailingSlash = false router.RedirectTrailingSlash = false
w = performRequest(router, "GET", "/path/") w = performRequest(router, "GET", "/path/")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, "POST", "/path3/") w = performRequest(router, "POST", "/path3/")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
w = performRequest(router, "PUT", "/path4") w = performRequest(router, "PUT", "/path4")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
} }
func TestRouteRedirectFixedPath(t *testing.T) { func TestRouteRedirectFixedPath(t *testing.T) {
@ -194,19 +194,19 @@ func TestRouteRedirectFixedPath(t *testing.T) {
w := performRequest(router, "GET", "/PATH") w := performRequest(router, "GET", "/PATH")
assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, 301, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, "/Path2", w.Header().Get("Location")) assert.Equal(t, "/Path2", w.Header().Get("Location"))
assert.Equal(t, 301, w.Code) assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = performRequest(router, "POST", "/path3") w = performRequest(router, "POST", "/path3")
assert.Equal(t, "/PATH3", w.Header().Get("Location")) assert.Equal(t, "/PATH3", w.Header().Get("Location"))
assert.Equal(t, 307, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
w = performRequest(router, "POST", "/path4") w = performRequest(router, "POST", "/path4")
assert.Equal(t, "/Path4/", w.Header().Get("Location")) assert.Equal(t, "/Path4/", w.Header().Get("Location"))
assert.Equal(t, 307, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
} }
// TestContextParamsGet tests that a parameter can be parsed from the URL. // TestContextParamsGet tests that a parameter can be parsed from the URL.
@ -236,7 +236,7 @@ func TestRouteParamsByName(t *testing.T) {
w := performRequest(router, "GET", "/test/john/smith/is/super/great") w := performRequest(router, "GET", "/test/john/smith/is/super/great")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "john", name) assert.Equal(t, "john", name)
assert.Equal(t, "smith", lastName) assert.Equal(t, "smith", lastName)
assert.Equal(t, "/is/super/great", wild) assert.Equal(t, "/is/super/great", wild)
@ -265,7 +265,7 @@ func TestRouteStaticFile(t *testing.T) {
w2 := performRequest(router, "GET", "/result") w2 := performRequest(router, "GET", "/result")
assert.Equal(t, w, w2) assert.Equal(t, w, w2)
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "Gin Web Framework", w.Body.String())
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
@ -273,7 +273,7 @@ func TestRouteStaticFile(t *testing.T) {
w4 := performRequest(router, "HEAD", "/result") w4 := performRequest(router, "HEAD", "/result")
assert.Equal(t, w3, w4) assert.Equal(t, w3, w4)
assert.Equal(t, 200, w3.Code) assert.Equal(t, http.StatusOK, w3.Code)
} }
// TestHandleStaticDir - ensure the root/sub dir handles properly // TestHandleStaticDir - ensure the root/sub dir handles properly
@ -283,7 +283,7 @@ func TestRouteStaticListingDir(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "gin.go") assert.Contains(t, w.Body.String(), "gin.go")
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
} }
@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) {
w := performRequest(router, "GET", "/") w := performRequest(router, "GET", "/")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
assert.NotContains(t, w.Body.String(), "gin.go") assert.NotContains(t, w.Body.String(), "gin.go")
} }
@ -310,7 +310,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
w := performRequest(router, "GET", "/gin.go") w := performRequest(router, "GET", "/gin.go")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "package gin") assert.Contains(t, w.Body.String(), "package gin")
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) 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.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
@ -333,19 +333,29 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
assert.Equal(t, http.StatusTeapot, w.Code) 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) { func TestRouteNotAllowedDisabled(t *testing.T) {
router := New() router := New()
router.HandleMethodNotAllowed = false router.HandleMethodNotAllowed = false
router.POST("/path", func(c *Context) {}) router.POST("/path", func(c *Context) {})
w := performRequest(router, "GET", "/path") w := performRequest(router, "GET", "/path")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
router.NoMethod(func(c *Context) { router.NoMethod(func(c *Context) {
c.String(http.StatusTeapot, "responseText") c.String(http.StatusTeapot, "responseText")
}) })
w = performRequest(router, "GET", "/path") w = performRequest(router, "GET", "/path")
assert.Equal(t, "404 page not found", w.Body.String()) assert.Equal(t, "404 page not found", w.Body.String())
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
} }
func TestRouterNotFound(t *testing.T) { func TestRouterNotFound(t *testing.T) {
@ -360,20 +370,20 @@ func TestRouterNotFound(t *testing.T) {
code int code int
location string location string
}{ }{
{"/path/", 301, "/path"}, // TSR -/ {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
{"/dir", 301, "/dir/"}, // TSR +/ {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
{"", 301, "/"}, // TSR +/ {"", http.StatusMovedPermanently, "/"}, // TSR +/
{"/PATH", 301, "/path"}, // Fixed Case {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
{"/DIR/", 301, "/dir/"}, // Fixed Case {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
{"/PATH/", 301, "/path"}, // Fixed Case -/ {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
{"/DIR", 301, "/dir/"}, // Fixed Case +/ {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
{"/../path", 301, "/path"}, // CleanPath {"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
{"/nope", 404, ""}, // NotFound {"/nope", http.StatusNotFound, ""}, // NotFound
} }
for _, tr := range testRoutes { for _, tr := range testRoutes {
w := performRequest(router, "GET", tr.route) w := performRequest(router, "GET", tr.route)
assert.Equal(t, tr.code, w.Code) assert.Equal(t, tr.code, w.Code)
if w.Code != 404 { if w.Code != http.StatusNotFound {
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
} }
} }
@ -381,24 +391,24 @@ func TestRouterNotFound(t *testing.T) {
// Test custom not found handler // Test custom not found handler
var notFound bool var notFound bool
router.NoRoute(func(c *Context) { router.NoRoute(func(c *Context) {
c.AbortWithStatus(404) c.AbortWithStatus(http.StatusNotFound)
notFound = true notFound = true
}) })
w := performRequest(router, "GET", "/nope") w := performRequest(router, "GET", "/nope")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
assert.True(t, notFound) assert.True(t, notFound)
// Test other method than GET (want 307 instead of 301) // Test other method than GET (want 307 instead of 301)
router.PATCH("/path", func(c *Context) {}) router.PATCH("/path", func(c *Context) {})
w = performRequest(router, "PATCH", "/path/") w = performRequest(router, "PATCH", "/path/")
assert.Equal(t, 307, w.Code) assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
// Test special case where no node for the prefix "/" exists // Test special case where no node for the prefix "/" exists
router = New() router = New()
router.GET("/a", func(c *Context) {}) router.GET("/a", func(c *Context) {})
w = performRequest(router, "GET", "/") w = performRequest(router, "GET", "/")
assert.Equal(t, 404, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
} }
func TestRouteRawPath(t *testing.T) { func TestRouteRawPath(t *testing.T) {
@ -417,7 +427,7 @@ func TestRouteRawPath(t *testing.T) {
}) })
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} }
func TestRouteRawPathNoUnescape(t *testing.T) { func TestRouteRawPathNoUnescape(t *testing.T) {
@ -437,7 +447,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
}) })
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
assert.Equal(t, 200, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} }
func TestRouteServeErrorWithWriteHeader(t *testing.T) { func TestRouteServeErrorWithWriteHeader(t *testing.T) {

View File

@ -4,9 +4,7 @@
package gin package gin
import ( import "net/http"
"net/http"
)
// CreateTestContext returns a fresh engine and context for testing purposes // CreateTestContext returns a fresh engine and context for testing purposes
func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {

View File

@ -3,7 +3,7 @@
// DO NOT EDIT! // 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: It is generated from these files:
test.proto test.proto
@ -11,7 +11,7 @@ It is generated from these files:
It has these top-level messages: It has these top-level messages:
Test Test
*/ */
package example package protoexample
import proto "github.com/golang/protobuf/proto" import proto "github.com/golang/protobuf/proto"
import math "math" import math "math"
@ -109,5 +109,5 @@ func (m *Test_OptionalGroup) GetRequiredField() string {
} }
func init() { func init() {
proto.RegisterEnum("example.FOO", FOO_name, FOO_value) proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value)
} }

View File

@ -1,4 +1,4 @@
package example; package protoexample;
enum FOO {X=17;}; enum FOO {X=17;};

View File

@ -103,10 +103,7 @@ func parseAccept(acceptHeader string) []string {
parts := strings.Split(acceptHeader, ",") parts := strings.Split(acceptHeader, ",")
out := make([]string, 0, len(parts)) out := make([]string, 0, len(parts))
for _, part := range parts { for _, part := range parts {
if index := strings.IndexByte(part, ';'); index >= 0 { if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" {
part = part[0:index]
}
if part = strings.TrimSpace(part); part != "" {
out = append(out, part) out = append(out, part)
} }
} }

View File

@ -5,6 +5,8 @@
package gin package gin
import ( import (
"bytes"
"encoding/xml"
"fmt" "fmt"
"net/http" "net/http"
"testing" "testing"
@ -23,7 +25,7 @@ type testStruct struct {
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
assert.Equal(t.T, "POST", req.Method) assert.Equal(t.T, "POST", req.Method)
assert.Equal(t.T, "/path", req.URL.Path) assert.Equal(t.T, "/path", req.URL.Path)
w.WriteHeader(500) w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "hello") fmt.Fprint(w, "hello")
} }
@ -33,16 +35,16 @@ func TestWrap(t *testing.T) {
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
assert.Equal(t, "GET", req.Method) assert.Equal(t, "GET", req.Method)
assert.Equal(t, "/path2", req.URL.Path) assert.Equal(t, "/path2", req.URL.Path)
w.WriteHeader(400) w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "hola!") fmt.Fprint(w, "hola!")
})) }))
w := performRequest(router, "POST", "/path") w := performRequest(router, "POST", "/path")
assert.Equal(t, 500, w.Code) assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, "hello", w.Body.String()) assert.Equal(t, "hello", w.Body.String())
w = performRequest(router, "GET", "/path2") w = performRequest(router, "GET", "/path2")
assert.Equal(t, 400, w.Code) assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Equal(t, "hola!", w.Body.String()) assert.Equal(t, "hola!", w.Body.String())
} }
@ -124,3 +126,14 @@ func TestBindMiddleware(t *testing.T) {
Bind(&bindTestStruct{}) 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)
}

129
vendor/vendor.json vendored
View File

@ -1,19 +1,12 @@
{ {
"comment": "v1.2", "comment": "v1.3.0",
"ignore": "test", "ignore": "test",
"package": [ "package": [
{ {
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=",
"comment": "v1.1.0",
"path": "github.com/davecgh/go-spew/spew", "path": "github.com/davecgh/go-spew/spew",
"revision": "346938d642f2ec3594ed81d874461961cd0faa76", "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73",
"revisionTime": "2016-10-29T20:57:26Z" "revisionTime": "2018-02-21T22:46:20Z"
},
{
"checksumSHA1": "7c3FuEadBInl/4ExSrB7iJMXpe4=",
"path": "github.com/dustin/go-broadcast",
"revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1",
"revisionTime": "2014-06-27T04:00:55Z"
}, },
{ {
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
@ -22,78 +15,44 @@
"revisionTime": "2017-01-09T09:34:21Z" "revisionTime": "2017-01-09T09:34:21Z"
}, },
{ {
"checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=", "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=",
"path": "github.com/gin-gonic/autotls",
"revision": "8ca25fbde72bb72a00466215b94b489c71fcb815",
"revisionTime": "2017-09-16T16:54:15Z"
},
{
"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=",
"path": "github.com/golang/protobuf/proto", "path": "github.com/golang/protobuf/proto",
"revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265",
"revisionTime": "2017-06-01T23:02:30Z" "revisionTime": "2018-04-30T18:52:41Z",
"version": "v1.1.0",
"versionExact": "v1.1.0"
}, },
{ {
"checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=", "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=",
"path": "github.com/jessevdk/go-assets",
"revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5",
"revisionTime": "2016-09-21T14:41:39Z"
},
{
"checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=",
"path": "github.com/json-iterator/go", "path": "github.com/json-iterator/go",
"revision": "36b14963da70d11297d313183d7e6388c8510e1e", "revision": "1624edc4454b8682399def8740d46db5e4362ba4",
"revisionTime": "2017-08-29T15:58:51Z" "revisionTime": "2018-08-06T06:07:27Z",
"version": "v1.1",
"versionExact": "v1.1.5"
}, },
{ {
"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=",
"path": "github.com/manucorporat/stats",
"revision": "8f2d6ace262eba462e9beb552382c98be51d807b",
"revisionTime": "2015-05-31T20:46:25Z"
},
{
"checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=",
"path": "github.com/mattn/go-isatty", "path": "github.com/mattn/go-isatty",
"revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", "revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39",
"revisionTime": "2017-03-07T16:30:44Z" "revisionTime": "2017-09-25T05:34:41Z",
"version": "v0.0.3",
"versionExact": "v0.0.3"
}, },
{ {
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
"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",
"path": "github.com/stretchr/testify/assert", "path": "github.com/stretchr/testify/assert",
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686",
"revisionTime": "2016-09-25T22:06:09Z" "revisionTime": "2018-05-06T18:05:49Z",
"version": "v1.2.2",
"versionExact": "v1.2.2"
}, },
{ {
"checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=", "checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=",
"path": "github.com/thinkerou/favicon",
"revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93",
"revisionTime": "2017-07-10T14:05:20Z"
},
{
"checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=",
"path": "github.com/ugorji/go/codec", "path": "github.com/ugorji/go/codec",
"revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab",
"revisionTime": "2017-02-15T20:11:44Z" "revisionTime": "2018-04-07T10:07:33Z",
}, "version": "v1.1.1",
{ "versionExact": "v1.1.1"
"checksumSHA1": "W0j4I7QpxXlChjyhAojZmFjU6Bg=",
"path": "golang.org/x/crypto/acme",
"revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d",
"revisionTime": "2017-06-19T06:03:41Z"
},
{
"checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=",
"path": "golang.org/x/crypto/acme/autocert",
"revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d",
"revisionTime": "2017-06-19T06:03:41Z"
}, },
{ {
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
@ -103,30 +62,20 @@
"revisionTime": "2016-10-18T08:54:36Z" "revisionTime": "2016-10-18T08:54:36Z"
}, },
{ {
"checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=", "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
"path": "golang.org/x/sync/errgroup",
"revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690",
"revisionTime": "2017-07-19T03:38:01Z"
},
{
"checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
"path": "golang.org/x/sys/unix",
"revision": "99f16d856c9836c42d24e7ab64ea72916925fa97",
"revisionTime": "2017-03-08T15:04:45Z"
},
{
"checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=",
"comment": "v8.18.1",
"path": "gopkg.in/go-playground/validator.v8", "path": "gopkg.in/go-playground/validator.v8",
"revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c", "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf",
"revisionTime": "2016-07-18T13:41:25Z" "revisionTime": "2017-07-30T05:02:35Z",
"version": "v8.18.2",
"versionExact": "v8.18.2"
}, },
{ {
"checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=",
"comment": "v2",
"path": "gopkg.in/yaml.v2", "path": "gopkg.in/yaml.v2",
"revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183",
"revisionTime": "2016-09-28T15:37:09Z" "revisionTime": "2018-03-28T19:50:20Z",
"version": "v2.2.1",
"versionExact": "v2.2.1"
} }
], ],
"rootPath": "github.com/gin-gonic/gin" "rootPath": "github.com/gin-gonic/gin"