mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-24 02:32:17 +08:00
commit
0fda834942
@ -4,6 +4,7 @@ go:
|
|||||||
- 1.6.x
|
- 1.6.x
|
||||||
- 1.7.x
|
- 1.7.x
|
||||||
- 1.8.x
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
- master
|
- master
|
||||||
|
|
||||||
git:
|
git:
|
||||||
|
300
README.md
300
README.md
@ -74,10 +74,10 @@ BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104
|
|||||||
BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848
|
BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848
|
||||||
BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609
|
BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609
|
||||||
|
|
||||||
(1): Total Repetitions achieved in constant time, higher means more confident result
|
- (1): Total Repetitions achieved in constant time, higher means more confident result
|
||||||
(2): Single Repetition Duration (ns/op), lower is better
|
- (2): Single Repetition Duration (ns/op), lower is better
|
||||||
(3): Heap Memory (B/op), lower is better
|
- (3): Heap Memory (B/op), lower is better
|
||||||
(4): Average Allocations per Repetition (allocs/op), lower is better
|
- (4): Average Allocations per Repetition (allocs/op), lower is better
|
||||||
|
|
||||||
## Gin v1. stable
|
## Gin v1. stable
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ $ go get github.com/kardianos/govendor
|
|||||||
2. Create your project folder and `cd` inside
|
2. Create your project folder and `cd` inside
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_"
|
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Vendor init your project and add gin
|
3. Vendor init your project and add gin
|
||||||
@ -277,6 +277,8 @@ References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail
|
|||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
||||||
|
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||||
router.POST("/upload", func(c *gin.Context) {
|
router.POST("/upload", func(c *gin.Context) {
|
||||||
// single file
|
// single file
|
||||||
file, _ := c.FormFile("file")
|
file, _ := c.FormFile("file")
|
||||||
@ -306,6 +308,8 @@ See the detail [example code](examples/upload-file/multiple).
|
|||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
||||||
|
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||||
router.POST("/upload", func(c *gin.Context) {
|
router.POST("/upload", func(c *gin.Context) {
|
||||||
// Multipart form
|
// Multipart form
|
||||||
form, _ := c.MultipartForm()
|
form, _ := c.MultipartForm()
|
||||||
@ -381,7 +385,8 @@ func main() {
|
|||||||
r := gin.New()
|
r := gin.New()
|
||||||
|
|
||||||
// Global middleware
|
// Global middleware
|
||||||
// Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. By default gin.DefaultWriter = os.Stdout
|
// Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
|
||||||
|
// By default gin.DefaultWriter = os.Stdout
|
||||||
r.Use(gin.Logger())
|
r.Use(gin.Logger())
|
||||||
|
|
||||||
// Recovery middleware recovers from any panics and writes a 500 if there was one.
|
// Recovery middleware recovers from any panics and writes a 500 if there was one.
|
||||||
@ -412,6 +417,28 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### How to write log file
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// Disable Console Color, you don't need console color when writing the logs to file.
|
||||||
|
gin.DisableConsoleColor()
|
||||||
|
|
||||||
|
// Logging to a file.
|
||||||
|
f, _ := os.Create("gin.log")
|
||||||
|
gin.DefaultWriter = io.MultiWriter(f)
|
||||||
|
|
||||||
|
// Use the following code if you need to write the logs to file and console at the same time.
|
||||||
|
// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
router.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(200, "pong")
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Model binding and validation
|
### Model binding and validation
|
||||||
|
|
||||||
To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).
|
To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).
|
||||||
@ -420,9 +447,17 @@ Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/valid
|
|||||||
|
|
||||||
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
|
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
|
||||||
|
|
||||||
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 BindWith.
|
Also, Gin provides two sets of methods for binding:
|
||||||
|
- **Type** - Must bind
|
||||||
|
- **Methods** - `Bind`, `BindJSON`, `BindQuery`
|
||||||
|
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
|
||||||
|
- **Type** - Should bind
|
||||||
|
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery`
|
||||||
|
- **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.
|
||||||
|
|
||||||
You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, the current request will fail with an error.
|
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`.
|
||||||
|
|
||||||
|
You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Binding from JSON
|
// Binding from JSON
|
||||||
@ -437,12 +472,14 @@ 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 c.BindJSON(&json) == nil {
|
if err := c.ShouldBindJSON(&json); err == nil {
|
||||||
if json.User == "manu" && json.Password == "123" {
|
if json.User == "manu" && json.Password == "123" {
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -450,12 +487,14 @@ func main() {
|
|||||||
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 c.Bind(&form) == nil {
|
if err := c.ShouldBind(&form); err == nil {
|
||||||
if form.User == "manu" && form.Password == "123" {
|
if form.User == "manu" && form.Password == "123" {
|
||||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -464,9 +503,92 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Sample request**
|
||||||
|
```shell
|
||||||
|
$ curl -v -X POST \
|
||||||
|
http://localhost:8080/loginJSON \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{ "user": "manu" }'
|
||||||
|
> POST /loginJSON HTTP/1.1
|
||||||
|
> Host: localhost:8080
|
||||||
|
> User-Agent: curl/7.51.0
|
||||||
|
> Accept: */*
|
||||||
|
> content-type: application/json
|
||||||
|
> Content-Length: 18
|
||||||
|
>
|
||||||
|
* upload completely sent off: 18 out of 18 bytes
|
||||||
|
< HTTP/1.1 400 Bad Request
|
||||||
|
< Content-Type: application/json; charset=utf-8
|
||||||
|
< Date: Fri, 04 Aug 2017 03:51:31 GMT
|
||||||
|
< Content-Length: 100
|
||||||
|
<
|
||||||
|
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Validators
|
||||||
|
|
||||||
|
It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go).
|
||||||
|
|
||||||
|
[embedmd]:# (examples/custom-validation/server.go go)
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"gopkg.in/go-playground/validator.v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Booking struct {
|
||||||
|
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
|
||||||
|
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func bookableDate(
|
||||||
|
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
|
||||||
|
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
|
||||||
|
) bool {
|
||||||
|
if date, ok := field.Interface().(time.Time); ok {
|
||||||
|
today := time.Now()
|
||||||
|
if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
binding.Validator.RegisterValidation("bookabledate", bookableDate)
|
||||||
|
route.GET("/bookable", getBookable)
|
||||||
|
route.Run(":8085")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBookable(c *gin.Context) {
|
||||||
|
var b Booking
|
||||||
|
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ curl "localhost:8085/bookable?check_in=2017-08-16&check_out=2017-08-17"
|
||||||
|
{"message":"Booking dates are valid!"}
|
||||||
|
|
||||||
|
$ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16"
|
||||||
|
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
|
||||||
|
```
|
||||||
|
|
||||||
### Only Bind Query String
|
### Only Bind Query String
|
||||||
|
|
||||||
`BindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
|
`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
@ -490,7 +612,7 @@ func main() {
|
|||||||
|
|
||||||
func startPage(c *gin.Context) {
|
func startPage(c *gin.Context) {
|
||||||
var person Person
|
var person Person
|
||||||
if c.BindQuery(&person) == nil {
|
if c.ShouldBindQuery(&person) == nil {
|
||||||
log.Println("====== Only Bind By Query String ======")
|
log.Println("====== Only Bind By Query String ======")
|
||||||
log.Println(person.Name)
|
log.Println(person.Name)
|
||||||
log.Println(person.Address)
|
log.Println(person.Address)
|
||||||
@ -509,10 +631,12 @@ package main
|
|||||||
|
|
||||||
import "log"
|
import "log"
|
||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
|
import "time"
|
||||||
|
|
||||||
type Person struct {
|
type Person struct {
|
||||||
Name string `form:"name"`
|
Name string `form:"name"`
|
||||||
Address string `form:"address"`
|
Address string `form:"address"`
|
||||||
|
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -526,15 +650,21 @@ func startPage(c *gin.Context) {
|
|||||||
// If `GET`, only `Form` binding engine (`query`) used.
|
// If `GET`, only `Form` binding engine (`query`) used.
|
||||||
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
|
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
|
||||||
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
|
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
|
||||||
if c.Bind(&person) == nil {
|
if c.ShouldBind(&person) == nil {
|
||||||
log.Println(person.Name)
|
log.Println(person.Name)
|
||||||
log.Println(person.Address)
|
log.Println(person.Address)
|
||||||
|
log.Println(person.Birthday)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.String(200, "Success")
|
c.String(200, "Success")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Test it with:
|
||||||
|
```sh
|
||||||
|
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
|
||||||
|
```
|
||||||
|
|
||||||
### Bind HTML checkboxes
|
### Bind HTML checkboxes
|
||||||
|
|
||||||
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
|
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
|
||||||
@ -552,7 +682,7 @@ type myForm struct {
|
|||||||
|
|
||||||
func formHandler(c *gin.Context) {
|
func formHandler(c *gin.Context) {
|
||||||
var fakeForm myForm
|
var fakeForm myForm
|
||||||
c.Bind(&fakeForm)
|
c.ShouldBind(&fakeForm)
|
||||||
c.JSON(200, gin.H{"color": fakeForm.Colors})
|
c.JSON(200, gin.H{"color": fakeForm.Colors})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,11 +729,11 @@ func main() {
|
|||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.POST("/login", func(c *gin.Context) {
|
router.POST("/login", func(c *gin.Context) {
|
||||||
// you can bind multipart form with explicit binding declaration:
|
// you can bind multipart form with explicit binding declaration:
|
||||||
// c.MustBindWith(&form, binding.Form)
|
// c.ShouldBindWith(&form, binding.Form)
|
||||||
// or you can simply use autobinding with Bind method:
|
// or you can simply use autobinding with ShouldBind method:
|
||||||
var form LoginForm
|
var form LoginForm
|
||||||
// in this case proper binding will be automatically selected
|
// in this case proper binding will be automatically selected
|
||||||
if c.Bind(&form) == nil {
|
if c.ShouldBind(&form) == nil {
|
||||||
if form.User == "user" && form.Password == "password" {
|
if form.User == "user" && form.Password == "password" {
|
||||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||||
} else {
|
} else {
|
||||||
@ -941,7 +1071,7 @@ func main() {
|
|||||||
|
|
||||||
### Goroutines inside a middleware
|
### Goroutines inside a middleware
|
||||||
|
|
||||||
When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
|
When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func main() {
|
func main() {
|
||||||
@ -1003,7 +1133,7 @@ func main() {
|
|||||||
|
|
||||||
example for 1-line LetsEncrypt HTTPS servers.
|
example for 1-line LetsEncrypt HTTPS servers.
|
||||||
|
|
||||||
[embedmd]:# (examples/auto-tls/example1.go go)
|
[embedmd]:# (examples/auto-tls/example1/main.go go)
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@ -1028,7 +1158,7 @@ func main() {
|
|||||||
|
|
||||||
example for custom autocert manager.
|
example for custom autocert manager.
|
||||||
|
|
||||||
[embedmd]:# (examples/auto-tls/example2.go go)
|
[embedmd]:# (examples/auto-tls/example2/main.go go)
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
@ -1058,6 +1188,88 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Run multiple service using Gin
|
||||||
|
|
||||||
|
See the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling example:
|
||||||
|
|
||||||
|
[embedmd]:# (examples/multiple-service/main.go go)
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
g errgroup.Group
|
||||||
|
)
|
||||||
|
|
||||||
|
func router01() http.Handler {
|
||||||
|
e := gin.New()
|
||||||
|
e.Use(gin.Recovery())
|
||||||
|
e.GET("/", func(c *gin.Context) {
|
||||||
|
c.JSON(
|
||||||
|
http.StatusOK,
|
||||||
|
gin.H{
|
||||||
|
"code": http.StatusOK,
|
||||||
|
"error": "Welcome server 01",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func router02() http.Handler {
|
||||||
|
e := gin.New()
|
||||||
|
e.Use(gin.Recovery())
|
||||||
|
e.GET("/", func(c *gin.Context) {
|
||||||
|
c.JSON(
|
||||||
|
http.StatusOK,
|
||||||
|
gin.H{
|
||||||
|
"code": http.StatusOK,
|
||||||
|
"error": "Welcome server 02",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server01 := &http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
Handler: router01(),
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
server02 := &http.Server{
|
||||||
|
Addr: ":8081",
|
||||||
|
Handler: router02(),
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
return server01.ListenAndServe()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
return server02.ListenAndServe()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Graceful restart or stop
|
### Graceful restart or stop
|
||||||
|
|
||||||
Do you want to graceful restart or stop your web server?
|
Do you want to graceful restart or stop your web server?
|
||||||
@ -1128,7 +1340,53 @@ func main() {
|
|||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
log.Fatal("Server Shutdown:", err)
|
log.Fatal("Server Shutdown:", err)
|
||||||
}
|
}
|
||||||
log.Println("Server exist")
|
log.Println("Server exiting")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The `net/http/httptest` package is preferable way for HTTP testing.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
func setupRouter() *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.String(200, "pong")
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := setupRouter()
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test for code example above:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPingRoute(t *testing.T) {
|
||||||
|
router := setupRouter()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
assert.Equal(t, "pong", w.Body.String())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
18
auth.go
18
auth.go
@ -17,19 +17,19 @@ const AuthUserKey = "user"
|
|||||||
type Accounts map[string]string
|
type Accounts map[string]string
|
||||||
|
|
||||||
type authPair struct {
|
type authPair struct {
|
||||||
Value string
|
value string
|
||||||
User string
|
user string
|
||||||
}
|
}
|
||||||
|
|
||||||
type authPairs []authPair
|
type authPairs []authPair
|
||||||
|
|
||||||
func (a authPairs) searchCredential(authValue string) (string, bool) {
|
func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||||
if len(authValue) == 0 {
|
if authValue == "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
for _, pair := range a {
|
for _, pair := range a {
|
||||||
if pair.Value == authValue {
|
if pair.value == authValue {
|
||||||
return pair.User, true
|
return pair.user, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
@ -47,7 +47,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
|
|||||||
pairs := processAccounts(accounts)
|
pairs := processAccounts(accounts)
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
// Search user in the slice of allowed credentials
|
// Search user in the slice of allowed credentials
|
||||||
user, found := pairs.searchCredential(c.Request.Header.Get("Authorization"))
|
user, found := pairs.searchCredential(c.requestHeader("Authorization"))
|
||||||
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)
|
||||||
@ -71,11 +71,11 @@ func processAccounts(accounts Accounts) authPairs {
|
|||||||
assert1(len(accounts) > 0, "Empty list of authorized credentials")
|
assert1(len(accounts) > 0, "Empty list of authorized credentials")
|
||||||
pairs := make(authPairs, 0, len(accounts))
|
pairs := make(authPairs, 0, len(accounts))
|
||||||
for user, password := range accounts {
|
for user, password := range accounts {
|
||||||
assert1(len(user) > 0, "User can not be empty")
|
assert1(user != "", "User can not be empty")
|
||||||
value := authorizationHeader(user, password)
|
value := authorizationHeader(user, password)
|
||||||
pairs = append(pairs, authPair{
|
pairs = append(pairs, authPair{
|
||||||
Value: value,
|
value: value,
|
||||||
User: user,
|
user: user,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return pairs
|
return pairs
|
||||||
|
12
auth_test.go
12
auth_test.go
@ -22,16 +22,16 @@ func TestBasicAuth(t *testing.T) {
|
|||||||
|
|
||||||
assert.Len(t, pairs, 3)
|
assert.Len(t, pairs, 3)
|
||||||
assert.Contains(t, pairs, authPair{
|
assert.Contains(t, pairs, authPair{
|
||||||
User: "bar",
|
user: "bar",
|
||||||
Value: "Basic YmFyOmZvbw==",
|
value: "Basic YmFyOmZvbw==",
|
||||||
})
|
})
|
||||||
assert.Contains(t, pairs, authPair{
|
assert.Contains(t, pairs, authPair{
|
||||||
User: "foo",
|
user: "foo",
|
||||||
Value: "Basic Zm9vOmJhcg==",
|
value: "Basic Zm9vOmJhcg==",
|
||||||
})
|
})
|
||||||
assert.Contains(t, pairs, authPair{
|
assert.Contains(t, pairs, authPair{
|
||||||
User: "admin",
|
user: "admin",
|
||||||
Value: "Basic YWRtaW46cGFzc3dvcmQ=",
|
value: "Basic YWRtaW46cGFzc3dvcmQ=",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +59,6 @@ func BenchmarkOneRouteJSON(B *testing.B) {
|
|||||||
runRequest(B, router, "GET", "/json")
|
runRequest(B, router, "GET", "/json")
|
||||||
}
|
}
|
||||||
|
|
||||||
var htmlContentType = []string{"text/html; charset=utf-8"}
|
|
||||||
|
|
||||||
func BenchmarkOneRouteHTML(B *testing.B) {
|
func BenchmarkOneRouteHTML(B *testing.B) {
|
||||||
router := New()
|
router := New()
|
||||||
t := template.Must(template.New("index").Parse(`
|
t := template.Must(template.New("index").Parse(`
|
||||||
|
@ -4,7 +4,11 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/go-playground/validator.v8"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MIMEJSON = "application/json"
|
MIMEJSON = "application/json"
|
||||||
@ -31,6 +35,11 @@ type StructValidator interface {
|
|||||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
// Otherwise nil must be returned.
|
// Otherwise nil must be returned.
|
||||||
ValidateStruct(interface{}) error
|
ValidateStruct(interface{}) error
|
||||||
|
|
||||||
|
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
|
||||||
|
// NOTE: if the key already exists, the previous validation function will be replaced.
|
||||||
|
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||||
|
RegisterValidation(string, validator.Func) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var Validator StructValidator = &defaultValidator{}
|
var Validator StructValidator = &defaultValidator{}
|
||||||
|
@ -28,6 +28,11 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error {
|
||||||
|
v.lazyinit()
|
||||||
|
return v.validate.RegisterValidation(key, fn)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *defaultValidator) lazyinit() {
|
func (v *defaultValidator) lazyinit() {
|
||||||
v.once.Do(func() {
|
v.once.Do(func() {
|
||||||
config := &validator.Config{TagName: "binding"}
|
config := &validator.Config{TagName: "binding"}
|
||||||
|
@ -6,6 +6,8 @@ package binding
|
|||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
|
const defaultMemory = 32 * 1024 * 1024
|
||||||
|
|
||||||
type formBinding struct{}
|
type formBinding struct{}
|
||||||
type formPostBinding struct{}
|
type formPostBinding struct{}
|
||||||
type formMultipartBinding struct{}
|
type formMultipartBinding struct{}
|
||||||
@ -18,7 +20,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
|||||||
if err := req.ParseForm(); err != nil {
|
if err := req.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.ParseMultipartForm(32 << 10) // 32 MB
|
req.ParseMultipartForm(defaultMemory)
|
||||||
if err := mapForm(obj, req.Form); err != nil {
|
if err := mapForm(obj, req.Form); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -44,7 +46,7 @@ func (formMultipartBinding) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
if err := req.ParseMultipartForm(32 << 10); err != nil {
|
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := mapForm(obj, req.MultipartForm.Value); err != nil {
|
if err := mapForm(obj, req.MultipartForm.Value); err != nil {
|
||||||
|
@ -163,6 +163,14 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
l = time.UTC
|
l = time.UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if locTag := structField.Tag.Get("time_location"); locTag != "" {
|
||||||
|
loc, err := time.LoadLocation(locTag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l = loc
|
||||||
|
}
|
||||||
|
|
||||||
t, err := time.ParseInLocation(timeFormat, val, l)
|
t, err := time.ParseInLocation(timeFormat, val, l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -171,12 +179,3 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
value.Set(reflect.ValueOf(t))
|
value.Set(reflect.ValueOf(t))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't pass in pointers to bind to. Can lead to bugs. See:
|
|
||||||
// https://github.com/codegangsta/martini-contrib/issues/40
|
|
||||||
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
|
|
||||||
func ensureNotPointer(obj interface{}) {
|
|
||||||
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
|
|
||||||
panic("Pointers are not accepted as binding models")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -10,9 +10,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin/json"
|
"github.com/gin-gonic/gin/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var EnableDecoderUseNumber = false
|
||||||
EnableDecoderUseNumber = false
|
|
||||||
)
|
|
||||||
|
|
||||||
type jsonBinding struct{}
|
type jsonBinding struct{}
|
||||||
|
|
||||||
|
@ -4,9 +4,7 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import "net/http"
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type queryBinding struct{}
|
type queryBinding struct{}
|
||||||
|
|
||||||
|
@ -6,10 +6,12 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/go-playground/validator.v8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testInterface interface {
|
type testInterface interface {
|
||||||
@ -190,3 +192,42 @@ func TestValidatePrimitives(t *testing.T) {
|
|||||||
assert.NoError(t, validate(&str))
|
assert.NoError(t, validate(&str))
|
||||||
assert.Equal(t, str, "value")
|
assert.Equal(t, str, "value")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// structCustomValidation is a helper struct we use to check that
|
||||||
|
// custom validation can be registered on it.
|
||||||
|
// The `notone` binding directive is for custom validation and registered later.
|
||||||
|
type structCustomValidation struct {
|
||||||
|
Integer int `binding:"notone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// notOne is a custom validator meant to be used with `validator.v8` library.
|
||||||
|
// The method signature for `v9` is significantly different and this function
|
||||||
|
// would need to be changed for tests to pass after upgrade.
|
||||||
|
// See https://github.com/gin-gonic/gin/pull/1015.
|
||||||
|
func notOne(
|
||||||
|
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
|
||||||
|
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
|
||||||
|
) bool {
|
||||||
|
if val, ok := field.Interface().(int); ok {
|
||||||
|
return val != 1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterValidation(t *testing.T) {
|
||||||
|
// This validates that the function `notOne` matches
|
||||||
|
// the expected function signature by `defaultValidator`
|
||||||
|
// and by extension the validator library.
|
||||||
|
err := Validator.RegisterValidation("notone", notOne)
|
||||||
|
// Check that we can register custom validation without error
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Create an instance which will fail validation
|
||||||
|
withOne := structCustomValidation{Integer: 1}
|
||||||
|
errs := validate(withOne)
|
||||||
|
|
||||||
|
// Check that we got back non-nil errs
|
||||||
|
assert.NotNil(t, errs)
|
||||||
|
// Check that the error matches expactation
|
||||||
|
assert.Error(t, errs, "", "", "notone")
|
||||||
|
}
|
||||||
|
59
context.go
59
context.go
@ -33,10 +33,7 @@ const (
|
|||||||
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const abortIndex int8 = math.MaxInt8 / 2
|
||||||
defaultMemory = 32 << 20 // 32 MB
|
|
||||||
abortIndex int8 = math.MaxInt8 / 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
||||||
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
||||||
@ -106,8 +103,7 @@ func (c *Context) Handler() HandlerFunc {
|
|||||||
// See example in GitHub.
|
// See example in GitHub.
|
||||||
func (c *Context) Next() {
|
func (c *Context) Next() {
|
||||||
c.index++
|
c.index++
|
||||||
s := int8(len(c.handlers))
|
for s := int8(len(c.handlers)); c.index < s; c.index++ {
|
||||||
for ; c.index < s; c.index++ {
|
|
||||||
c.handlers[c.index](c)
|
c.handlers[c.index](c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -407,7 +403,7 @@ func (c *Context) PostFormArray(key string) []string {
|
|||||||
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||||
req := c.Request
|
req := c.Request
|
||||||
req.ParseForm()
|
req.ParseForm()
|
||||||
req.ParseMultipartForm(defaultMemory)
|
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||||
if values := req.PostForm[key]; len(values) > 0 {
|
if values := req.PostForm[key]; len(values) > 0 {
|
||||||
return values, true
|
return values, true
|
||||||
}
|
}
|
||||||
@ -427,7 +423,7 @@ func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
|
|||||||
|
|
||||||
// MultipartForm is the parsed multipart form, including file uploads.
|
// MultipartForm is the parsed multipart form, including file uploads.
|
||||||
func (c *Context) MultipartForm() (*multipart.Form, error) {
|
func (c *Context) MultipartForm() (*multipart.Form, error) {
|
||||||
err := c.Request.ParseMultipartForm(defaultMemory)
|
err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||||
return c.Request.MultipartForm, err
|
return c.Request.MultipartForm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,10 +449,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
|
|||||||
// Depending the "Content-Type" header different bindings are used:
|
// Depending the "Content-Type" header different bindings are used:
|
||||||
// "application/json" --> JSON binding
|
// "application/json" --> JSON binding
|
||||||
// "application/xml" --> XML binding
|
// "application/xml" --> XML binding
|
||||||
// otherwise --> returns an error
|
// otherwise --> returns an error.
|
||||||
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||||
// It decodes the json payload into the struct specified as a pointer.
|
// It decodes the json payload into the struct specified as a pointer.
|
||||||
// It will writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
|
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
|
||||||
func (c *Context) Bind(obj interface{}) error {
|
func (c *Context) Bind(obj interface{}) error {
|
||||||
b := binding.Default(c.Request.Method, c.ContentType())
|
b := binding.Default(c.Request.Method, c.ContentType())
|
||||||
return c.MustBindWith(obj, b)
|
return c.MustBindWith(obj, b)
|
||||||
@ -483,6 +479,29 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBind checks the Content-Type to select a binding engine automatically,
|
||||||
|
// Depending the "Content-Type" header different bindings are used:
|
||||||
|
// "application/json" --> JSON binding
|
||||||
|
// "application/xml" --> XML binding
|
||||||
|
// otherwise --> returns an error
|
||||||
|
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||||
|
// It decodes the json payload into the struct specified as a pointer.
|
||||||
|
// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.
|
||||||
|
func (c *Context) ShouldBind(obj interface{}) error {
|
||||||
|
b := binding.Default(c.Request.Method, c.ContentType())
|
||||||
|
return c.ShouldBindWith(obj, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
|
||||||
|
func (c *Context) ShouldBindJSON(obj interface{}) error {
|
||||||
|
return c.ShouldBindWith(obj, binding.JSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
|
||||||
|
func (c *Context) ShouldBindQuery(obj interface{}) error {
|
||||||
|
return c.ShouldBindWith(obj, binding.Query)
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
// ShouldBindWith binds the passed struct pointer using the specified binding engine.
|
||||||
// See the binding package.
|
// See the binding package.
|
||||||
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
||||||
@ -499,17 +518,17 @@ func (c *Context) ClientIP() string {
|
|||||||
clientIP = clientIP[0:index]
|
clientIP = clientIP[0:index]
|
||||||
}
|
}
|
||||||
clientIP = strings.TrimSpace(clientIP)
|
clientIP = strings.TrimSpace(clientIP)
|
||||||
if len(clientIP) > 0 {
|
if clientIP != "" {
|
||||||
return clientIP
|
return clientIP
|
||||||
}
|
}
|
||||||
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
||||||
if len(clientIP) > 0 {
|
if clientIP != "" {
|
||||||
return clientIP
|
return clientIP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.engine.AppEngine {
|
if c.engine.AppEngine {
|
||||||
if addr := c.Request.Header.Get("X-Appengine-Remote-Addr"); addr != "" {
|
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -537,10 +556,7 @@ func (c *Context) IsWebsocket() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) requestHeader(key string) string {
|
func (c *Context) requestHeader(key string) string {
|
||||||
if values, _ := c.Request.Header[key]; len(values) > 0 {
|
return c.Request.Header.Get(key)
|
||||||
return values[0]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
@ -569,7 +585,7 @@ func (c *Context) Status(code int) {
|
|||||||
// It writes a header in the response.
|
// It writes a header in the response.
|
||||||
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
||||||
func (c *Context) Header(key, value string) {
|
func (c *Context) Header(key, value string) {
|
||||||
if len(value) == 0 {
|
if value == "" {
|
||||||
c.Writer.Header().Del(key)
|
c.Writer.Header().Del(key)
|
||||||
} else {
|
} else {
|
||||||
c.Writer.Header().Set(key, value)
|
c.Writer.Header().Set(key, value)
|
||||||
@ -586,6 +602,9 @@ func (c *Context) GetRawData() ([]byte, error) {
|
|||||||
return ioutil.ReadAll(c.Request.Body)
|
return ioutil.ReadAll(c.Request.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
|
||||||
|
// The provided cookie must have a valid Name. Invalid cookies may be
|
||||||
|
// silently dropped.
|
||||||
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
|
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = "/"
|
path = "/"
|
||||||
@ -601,6 +620,10 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cookie returns the named cookie provided in the request or
|
||||||
|
// ErrNoCookie if not found. And return the named cookie is unescaped.
|
||||||
|
// If multiple cookies match the given name, only one cookie will
|
||||||
|
// be returned.
|
||||||
func (c *Context) Cookie(name string) (string, error) {
|
func (c *Context) Cookie(name string) (string, error) {
|
||||||
cookie, err := c.Request.Cookie(name)
|
cookie, err := c.Request.Cookie(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
369
context_test.go
369
context_test.go
@ -45,6 +45,7 @@ func createMultipartRequest() *http.Request {
|
|||||||
must(mw.WriteField("id", ""))
|
must(mw.WriteField("id", ""))
|
||||||
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"))
|
||||||
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)
|
||||||
@ -179,14 +180,14 @@ func TestContextSetGet(t *testing.T) {
|
|||||||
c.Set("foo", "bar")
|
c.Set("foo", "bar")
|
||||||
|
|
||||||
value, err := c.Get("foo")
|
value, err := c.Get("foo")
|
||||||
assert.Equal(t, value, "bar")
|
assert.Equal(t, "bar", value)
|
||||||
assert.True(t, err)
|
assert.True(t, err)
|
||||||
|
|
||||||
value, err = c.Get("foo2")
|
value, err = c.Get("foo2")
|
||||||
assert.Nil(t, value)
|
assert.Nil(t, value)
|
||||||
assert.False(t, err)
|
assert.False(t, err)
|
||||||
|
|
||||||
assert.Equal(t, c.MustGet("foo"), "bar")
|
assert.Equal(t, "bar", c.MustGet("foo"))
|
||||||
assert.Panics(t, func() { c.MustGet("no_exist") })
|
assert.Panics(t, func() { c.MustGet("no_exist") })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +221,7 @@ func TestContextGetString(t *testing.T) {
|
|||||||
func TestContextSetGetBool(t *testing.T) {
|
func TestContextSetGetBool(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Set("bool", true)
|
c.Set("bool", true)
|
||||||
assert.Equal(t, true, c.GetBool("bool"))
|
assert.True(t, c.GetBool("bool"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextGetInt(t *testing.T) {
|
func TestContextGetInt(t *testing.T) {
|
||||||
@ -337,26 +338,26 @@ func TestContextQuery(t *testing.T) {
|
|||||||
|
|
||||||
value, ok := c.GetQuery("foo")
|
value, ok := c.GetQuery("foo")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, value, "bar")
|
assert.Equal(t, "bar", value)
|
||||||
assert.Equal(t, c.DefaultQuery("foo", "none"), "bar")
|
assert.Equal(t, "bar", c.DefaultQuery("foo", "none"))
|
||||||
assert.Equal(t, c.Query("foo"), "bar")
|
assert.Equal(t, "bar", c.Query("foo"))
|
||||||
|
|
||||||
value, ok = c.GetQuery("page")
|
value, ok = c.GetQuery("page")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, value, "10")
|
assert.Equal(t, "10", value)
|
||||||
assert.Equal(t, c.DefaultQuery("page", "0"), "10")
|
assert.Equal(t, "10", c.DefaultQuery("page", "0"))
|
||||||
assert.Equal(t, c.Query("page"), "10")
|
assert.Equal(t, "10", c.Query("page"))
|
||||||
|
|
||||||
value, ok = c.GetQuery("id")
|
value, ok = c.GetQuery("id")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Empty(t, value)
|
assert.Empty(t, value)
|
||||||
assert.Equal(t, c.DefaultQuery("id", "nada"), "")
|
assert.Empty(t, c.DefaultQuery("id", "nada"))
|
||||||
assert.Empty(t, c.Query("id"))
|
assert.Empty(t, c.Query("id"))
|
||||||
|
|
||||||
value, ok = c.GetQuery("NoKey")
|
value, ok = c.GetQuery("NoKey")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.Empty(t, value)
|
assert.Empty(t, value)
|
||||||
assert.Equal(t, c.DefaultQuery("NoKey", "nada"), "nada")
|
assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada"))
|
||||||
assert.Empty(t, c.Query("NoKey"))
|
assert.Empty(t, c.Query("NoKey"))
|
||||||
|
|
||||||
// postform should not mess
|
// postform should not mess
|
||||||
@ -372,29 +373,29 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
|||||||
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", body)
|
||||||
c.Request.Header.Add("Content-Type", MIMEPOSTForm)
|
c.Request.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
|
|
||||||
assert.Equal(t, c.DefaultPostForm("foo", "none"), "bar")
|
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
||||||
assert.Equal(t, c.PostForm("foo"), "bar")
|
assert.Equal(t, "bar", c.PostForm("foo"))
|
||||||
assert.Empty(t, c.Query("foo"))
|
assert.Empty(t, c.Query("foo"))
|
||||||
|
|
||||||
value, ok := c.GetPostForm("page")
|
value, ok := c.GetPostForm("page")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, value, "11")
|
assert.Equal(t, "11", value)
|
||||||
assert.Equal(t, c.DefaultPostForm("page", "0"), "11")
|
assert.Equal(t, "11", c.DefaultPostForm("page", "0"))
|
||||||
assert.Equal(t, c.PostForm("page"), "11")
|
assert.Equal(t, "11", c.PostForm("page"))
|
||||||
assert.Equal(t, c.Query("page"), "")
|
assert.Empty(t, c.Query("page"))
|
||||||
|
|
||||||
value, ok = c.GetPostForm("both")
|
value, ok = c.GetPostForm("both")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Empty(t, value)
|
assert.Empty(t, value)
|
||||||
assert.Empty(t, c.PostForm("both"))
|
assert.Empty(t, c.PostForm("both"))
|
||||||
assert.Equal(t, c.DefaultPostForm("both", "nothing"), "")
|
assert.Empty(t, c.DefaultPostForm("both", "nothing"))
|
||||||
assert.Equal(t, c.Query("both"), "GET")
|
assert.Equal(t, "GET", c.Query("both"), "GET")
|
||||||
|
|
||||||
value, ok = c.GetQuery("id")
|
value, ok = c.GetQuery("id")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, value, "main")
|
assert.Equal(t, "main", value)
|
||||||
assert.Equal(t, c.DefaultPostForm("id", "000"), "000")
|
assert.Equal(t, "000", c.DefaultPostForm("id", "000"))
|
||||||
assert.Equal(t, c.Query("id"), "main")
|
assert.Equal(t, "main", c.Query("id"))
|
||||||
assert.Empty(t, c.PostForm("id"))
|
assert.Empty(t, c.PostForm("id"))
|
||||||
|
|
||||||
value, ok = c.GetQuery("NoKey")
|
value, ok = c.GetQuery("NoKey")
|
||||||
@ -403,8 +404,8 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
|||||||
value, ok = c.GetPostForm("NoKey")
|
value, ok = c.GetPostForm("NoKey")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.Empty(t, value)
|
assert.Empty(t, value)
|
||||||
assert.Equal(t, c.DefaultPostForm("NoKey", "nada"), "nada")
|
assert.Equal(t, "nada", c.DefaultPostForm("NoKey", "nada"))
|
||||||
assert.Equal(t, c.DefaultQuery("NoKey", "nothing"), "nothing")
|
assert.Equal(t, "nothing", c.DefaultQuery("NoKey", "nothing"))
|
||||||
assert.Empty(t, c.PostForm("NoKey"))
|
assert.Empty(t, c.PostForm("NoKey"))
|
||||||
assert.Empty(t, c.Query("NoKey"))
|
assert.Empty(t, c.Query("NoKey"))
|
||||||
|
|
||||||
@ -416,11 +417,11 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
|||||||
Array []string `form:"array[]"`
|
Array []string `form:"array[]"`
|
||||||
}
|
}
|
||||||
assert.NoError(t, c.Bind(&obj))
|
assert.NoError(t, c.Bind(&obj))
|
||||||
assert.Equal(t, obj.Foo, "bar")
|
assert.Equal(t, "bar", obj.Foo, "bar")
|
||||||
assert.Equal(t, obj.ID, "main")
|
assert.Equal(t, "main", obj.ID, "main")
|
||||||
assert.Equal(t, obj.Page, 11)
|
assert.Equal(t, 11, obj.Page, 11)
|
||||||
assert.Equal(t, obj.Both, "")
|
assert.Empty(t, obj.Both)
|
||||||
assert.Equal(t, obj.Array, []string{"first", "second"})
|
assert.Equal(t, []string{"first", "second"}, obj.Array)
|
||||||
|
|
||||||
values, ok := c.GetQueryArray("array[]")
|
values, ok := c.GetQueryArray("array[]")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
@ -451,37 +452,41 @@ func TestContextPostFormMultipart(t *testing.T) {
|
|||||||
ID string `form:"id"`
|
ID string `form:"id"`
|
||||||
TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"`
|
TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"`
|
||||||
TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"`
|
TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"`
|
||||||
|
TimeLocation time.Time `form:"time_location" time_format:"02/01/2006 15:04" time_location:"Asia/Tokyo"`
|
||||||
BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"`
|
BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"`
|
||||||
}
|
}
|
||||||
assert.NoError(t, c.Bind(&obj))
|
assert.NoError(t, c.Bind(&obj))
|
||||||
assert.Equal(t, obj.Foo, "bar")
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
assert.Equal(t, obj.Bar, "10")
|
assert.Equal(t, "10", obj.Bar)
|
||||||
assert.Equal(t, obj.BarAsInt, 10)
|
assert.Equal(t, 10, obj.BarAsInt)
|
||||||
assert.Equal(t, obj.Array, []string{"first", "second"})
|
assert.Equal(t, []string{"first", "second"}, obj.Array)
|
||||||
assert.Equal(t, obj.ID, "")
|
assert.Empty(t, obj.ID)
|
||||||
assert.Equal(t, obj.TimeLocal.Format("02/01/2006 15:04"), "31/12/2016 14:55")
|
assert.Equal(t, "31/12/2016 14:55", obj.TimeLocal.Format("02/01/2006 15:04"))
|
||||||
assert.Equal(t, obj.TimeLocal.Location(), time.Local)
|
assert.Equal(t, time.Local, obj.TimeLocal.Location())
|
||||||
assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55")
|
assert.Equal(t, "31/12/2016 14:55", obj.TimeUTC.Format("02/01/2006 15:04"))
|
||||||
assert.Equal(t, obj.TimeUTC.Location(), time.UTC)
|
assert.Equal(t, time.UTC, obj.TimeUTC.Location())
|
||||||
|
loc, _ := time.LoadLocation("Asia/Tokyo")
|
||||||
|
assert.Equal(t, "31/12/2016 14:55", obj.TimeLocation.Format("02/01/2006 15:04"))
|
||||||
|
assert.Equal(t, loc, obj.TimeLocation.Location())
|
||||||
assert.True(t, obj.BlankTime.IsZero())
|
assert.True(t, obj.BlankTime.IsZero())
|
||||||
|
|
||||||
value, ok := c.GetQuery("foo")
|
value, ok := c.GetQuery("foo")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.Empty(t, value)
|
assert.Empty(t, value)
|
||||||
assert.Empty(t, c.Query("bar"))
|
assert.Empty(t, c.Query("bar"))
|
||||||
assert.Equal(t, c.DefaultQuery("id", "nothing"), "nothing")
|
assert.Equal(t, "nothing", c.DefaultQuery("id", "nothing"))
|
||||||
|
|
||||||
value, ok = c.GetPostForm("foo")
|
value, ok = c.GetPostForm("foo")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, value, "bar")
|
assert.Equal(t, "bar", value)
|
||||||
assert.Equal(t, c.PostForm("foo"), "bar")
|
assert.Equal(t, "bar", c.PostForm("foo"))
|
||||||
|
|
||||||
value, ok = c.GetPostForm("array")
|
value, ok = c.GetPostForm("array")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, value, "first")
|
assert.Equal(t, "first", value)
|
||||||
assert.Equal(t, c.PostForm("array"), "first")
|
assert.Equal(t, "first", c.PostForm("array"))
|
||||||
|
|
||||||
assert.Equal(t, c.DefaultPostForm("bar", "nothing"), "10")
|
assert.Equal(t, "10", c.DefaultPostForm("bar", "nothing"))
|
||||||
|
|
||||||
value, ok = c.GetPostForm("id")
|
value, ok = c.GetPostForm("id")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
@ -492,7 +497,7 @@ func TestContextPostFormMultipart(t *testing.T) {
|
|||||||
value, ok = c.GetPostForm("nokey")
|
value, ok = c.GetPostForm("nokey")
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
assert.Empty(t, value)
|
assert.Empty(t, value)
|
||||||
assert.Equal(t, c.DefaultPostForm("nokey", "nothing"), "nothing")
|
assert.Equal(t, "nothing", c.DefaultPostForm("nokey", "nothing"))
|
||||||
|
|
||||||
values, ok := c.GetPostFormArray("array")
|
values, ok := c.GetPostFormArray("array")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
@ -514,13 +519,13 @@ func TestContextPostFormMultipart(t *testing.T) {
|
|||||||
func TestContextSetCookie(t *testing.T) {
|
func TestContextSetCookie(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.SetCookie("user", "gin", 1, "/", "localhost", true, true)
|
c.SetCookie("user", "gin", 1, "/", "localhost", true, true)
|
||||||
assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
|
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextSetCookiePathEmpty(t *testing.T) {
|
func TestContextSetCookiePathEmpty(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.SetCookie("user", "gin", 1, "", "localhost", true, true)
|
c.SetCookie("user", "gin", 1, "", "localhost", true, true)
|
||||||
assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
|
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextGetCookie(t *testing.T) {
|
func TestContextGetCookie(t *testing.T) {
|
||||||
@ -528,17 +533,17 @@ func TestContextGetCookie(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("GET", "/get", nil)
|
c.Request, _ = http.NewRequest("GET", "/get", nil)
|
||||||
c.Request.Header.Set("Cookie", "user=gin")
|
c.Request.Header.Set("Cookie", "user=gin")
|
||||||
cookie, _ := c.Cookie("user")
|
cookie, _ := c.Cookie("user")
|
||||||
assert.Equal(t, cookie, "gin")
|
assert.Equal(t, "gin", cookie)
|
||||||
|
|
||||||
_, err := c.Cookie("nokey")
|
_, err := c.Cookie("nokey")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextBodyAllowedForStatus(t *testing.T) {
|
func TestContextBodyAllowedForStatus(t *testing.T) {
|
||||||
assert.Equal(t, false, bodyAllowedForStatus(102))
|
assert.False(t, false, bodyAllowedForStatus(102))
|
||||||
assert.Equal(t, false, bodyAllowedForStatus(204))
|
assert.False(t, false, bodyAllowedForStatus(204))
|
||||||
assert.Equal(t, false, bodyAllowedForStatus(304))
|
assert.False(t, false, bodyAllowedForStatus(304))
|
||||||
assert.Equal(t, true, bodyAllowedForStatus(500))
|
assert.True(t, true, bodyAllowedForStatus(500))
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestPanicRender struct {
|
type TestPanicRender struct {
|
||||||
@ -584,7 +589,7 @@ func TestContextRenderNoContentJSON(t *testing.T) {
|
|||||||
c.JSON(204, H{"foo": "bar"})
|
c.JSON(204, H{"foo": "bar"})
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(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"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,7 +616,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
|
|||||||
c.JSON(204, H{"foo": "bar"})
|
c.JSON(204, H{"foo": "bar"})
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(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")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -623,7 +628,7 @@ func TestContextRenderIndentedJSON(t *testing.T) {
|
|||||||
|
|
||||||
c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, 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"))
|
||||||
}
|
}
|
||||||
@ -636,7 +641,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
|
|||||||
c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}})
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(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"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -649,9 +654,9 @@ func TestContextRenderSecureJSON(t *testing.T) {
|
|||||||
router.SecureJsonPrefix("&&&START&&&")
|
router.SecureJsonPrefix("&&&START&&&")
|
||||||
c.SecureJSON(201, []string{"foo", "bar"})
|
c.SecureJSON(201, []string{"foo", "bar"})
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]")
|
assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that no Custom JSON is rendered if code is 204
|
// Tests that no Custom JSON is rendered if code is 204
|
||||||
@ -662,8 +667,8 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
|||||||
c.SecureJSON(204, []string{"foo", "bar"})
|
c.SecureJSON(204, []string{"foo", "bar"})
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Empty(t, w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the response executes the templates
|
// Tests that the response executes the templates
|
||||||
@ -671,14 +676,39 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
|||||||
func TestContextRenderHTML(t *testing.T) {
|
func TestContextRenderHTML(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, router := CreateTestContext(w)
|
c, router := CreateTestContext(w)
|
||||||
|
|
||||||
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(201, "t", H{"name": "alexandernyquist"})
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
|
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextRenderHTML2(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, router := CreateTestContext(w)
|
||||||
|
|
||||||
|
// print debug warning log when Engine.trees > 0
|
||||||
|
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
||||||
|
assert.Len(t, router.trees, 1)
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
setup(&b)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||||
|
router.SetHTMLTemplate(templ)
|
||||||
|
|
||||||
|
assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String())
|
||||||
|
|
||||||
|
c.HTML(201, "t", H{"name": "alexandernyquist"})
|
||||||
|
|
||||||
|
assert.Equal(t, 201, w.Code)
|
||||||
|
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||||
|
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that no HTML is rendered if code is 204
|
// Tests that no HTML is rendered if code is 204
|
||||||
@ -691,8 +721,8 @@ func TestContextRenderNoContentHTML(t *testing.T) {
|
|||||||
c.HTML(204, "t", H{"name": "alexandernyquist"})
|
c.HTML(204, "t", H{"name": "alexandernyquist"})
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Empty(t, w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextXML tests that the response is serialized as XML
|
// TestContextXML tests that the response is serialized as XML
|
||||||
@ -703,9 +733,9 @@ func TestContextRenderXML(t *testing.T) {
|
|||||||
|
|
||||||
c.XML(201, H{"foo": "bar"})
|
c.XML(201, H{"foo": "bar"})
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "<map><foo>bar</foo></map>")
|
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8")
|
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that no XML is rendered if code is 204
|
// Tests that no XML is rendered if code is 204
|
||||||
@ -716,8 +746,8 @@ func TestContextRenderNoContentXML(t *testing.T) {
|
|||||||
c.XML(204, H{"foo": "bar"})
|
c.XML(204, H{"foo": "bar"})
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Empty(t, w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8")
|
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextString tests that the response is returned
|
// TestContextString tests that the response is returned
|
||||||
@ -728,9 +758,9 @@ func TestContextRenderString(t *testing.T) {
|
|||||||
|
|
||||||
c.String(201, "test %s %d", "string", 2)
|
c.String(201, "test %s %d", "string", 2)
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "test string 2")
|
assert.Equal(t, "test string 2", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that no String is rendered if code is 204
|
// Tests that no String is rendered if code is 204
|
||||||
@ -741,8 +771,8 @@ func TestContextRenderNoContentString(t *testing.T) {
|
|||||||
c.String(204, "test %s %d", "string", 2)
|
c.String(204, "test %s %d", "string", 2)
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Empty(t, w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextString tests that the response is returned
|
// TestContextString tests that the response is returned
|
||||||
@ -754,9 +784,9 @@ func TestContextRenderHTMLString(t *testing.T) {
|
|||||||
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(201, "<html>%s %d</html>", "string", 3)
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "<html>string 3</html>")
|
assert.Equal(t, "<html>string 3</html>", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that no HTML String is rendered if code is 204
|
// Tests that no HTML String is rendered if code is 204
|
||||||
@ -768,8 +798,8 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
|
|||||||
c.String(204, "<html>%s %d</html>", "string", 3)
|
c.String(204, "<html>%s %d</html>", "string", 3)
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Empty(t, w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextData tests that the response can be written from `bytesting`
|
// TestContextData tests that the response can be written from `bytesting`
|
||||||
@ -780,9 +810,9 @@ func TestContextRenderData(t *testing.T) {
|
|||||||
|
|
||||||
c.Data(201, "text/csv", []byte(`foo,bar`))
|
c.Data(201, "text/csv", []byte(`foo,bar`))
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "foo,bar")
|
assert.Equal(t, "foo,bar", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv")
|
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that no Custom Data is rendered if code is 204
|
// Tests that no Custom Data is rendered if code is 204
|
||||||
@ -793,8 +823,8 @@ func TestContextRenderNoContentData(t *testing.T) {
|
|||||||
c.Data(204, "text/csv", []byte(`foo,bar`))
|
c.Data(204, "text/csv", []byte(`foo,bar`))
|
||||||
|
|
||||||
assert.Equal(t, 204, w.Code)
|
assert.Equal(t, 204, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Empty(t, w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv")
|
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextRenderSSE(t *testing.T) {
|
func TestContextRenderSSE(t *testing.T) {
|
||||||
@ -821,9 +851,9 @@ 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, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextRenderYAML tests that the response is serialized as YAML
|
// TestContextRenderYAML tests that the response is serialized as YAML
|
||||||
@ -834,9 +864,9 @@ func TestContextRenderYAML(t *testing.T) {
|
|||||||
|
|
||||||
c.YAML(201, H{"foo": "bar"})
|
c.YAML(201, H{"foo": "bar"})
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "foo: bar\n")
|
assert.Equal(t, "foo: bar\n", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/x-yaml; charset=utf-8")
|
assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextHeaders(t *testing.T) {
|
func TestContextHeaders(t *testing.T) {
|
||||||
@ -844,13 +874,13 @@ func TestContextHeaders(t *testing.T) {
|
|||||||
c.Header("Content-Type", "text/plain")
|
c.Header("Content-Type", "text/plain")
|
||||||
c.Header("X-Custom", "value")
|
c.Header("X-Custom", "value")
|
||||||
|
|
||||||
assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/plain")
|
assert.Equal(t, "text/plain", c.Writer.Header().Get("Content-Type"))
|
||||||
assert.Equal(t, c.Writer.Header().Get("X-Custom"), "value")
|
assert.Equal(t, "value", c.Writer.Header().Get("X-Custom"))
|
||||||
|
|
||||||
c.Header("Content-Type", "text/html")
|
c.Header("Content-Type", "text/html")
|
||||||
c.Header("X-Custom", "")
|
c.Header("X-Custom", "")
|
||||||
|
|
||||||
assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/html")
|
assert.Equal(t, "text/html", c.Writer.Header().Get("Content-Type"))
|
||||||
_, exist := c.Writer.Header()["X-Custom"]
|
_, exist := c.Writer.Header()["X-Custom"]
|
||||||
assert.False(t, exist)
|
assert.False(t, exist)
|
||||||
}
|
}
|
||||||
@ -866,8 +896,8 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
|
|||||||
|
|
||||||
c.Redirect(301, "/path")
|
c.Redirect(301, "/path")
|
||||||
c.Writer.WriteHeaderNow()
|
c.Writer.WriteHeaderNow()
|
||||||
assert.Equal(t, w.Code, 301)
|
assert.Equal(t, 301, w.Code)
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/path")
|
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
||||||
@ -878,8 +908,8 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
|||||||
c.Redirect(302, "http://google.com")
|
c.Redirect(302, "http://google.com")
|
||||||
c.Writer.WriteHeaderNow()
|
c.Writer.WriteHeaderNow()
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 302)
|
assert.Equal(t, 302, w.Code)
|
||||||
assert.Equal(t, w.Header().Get("Location"), "http://google.com")
|
assert.Equal(t, "http://google.com", w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextRenderRedirectWith201(t *testing.T) {
|
func TestContextRenderRedirectWith201(t *testing.T) {
|
||||||
@ -890,8 +920,8 @@ func TestContextRenderRedirectWith201(t *testing.T) {
|
|||||||
c.Redirect(201, "/resource")
|
c.Redirect(201, "/resource")
|
||||||
c.Writer.WriteHeaderNow()
|
c.Writer.WriteHeaderNow()
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 201)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/resource")
|
assert.Equal(t, "/resource", w.Header().Get("Location"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextRenderRedirectAll(t *testing.T) {
|
func TestContextRenderRedirectAll(t *testing.T) {
|
||||||
@ -972,8 +1002,8 @@ func TestContextNegotiationFormat(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
assert.Panics(t, func() { c.NegotiateFormat() })
|
assert.Panics(t, func() { c.NegotiateFormat() })
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON)
|
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML)
|
assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextNegotiationFormatWithAccept(t *testing.T) {
|
func TestContextNegotiationFormatWithAccept(t *testing.T) {
|
||||||
@ -981,9 +1011,9 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||||
|
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEXML)
|
assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML))
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEHTML)
|
assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML))
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON), "")
|
assert.Empty(t, c.NegotiateFormat(MIMEJSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextNegotiationFormatCustum(t *testing.T) {
|
func TestContextNegotiationFormatCustum(t *testing.T) {
|
||||||
@ -994,9 +1024,9 @@ func TestContextNegotiationFormatCustum(t *testing.T) {
|
|||||||
c.Accepted = nil
|
c.Accepted = nil
|
||||||
c.SetAccepted(MIMEJSON, MIMEXML)
|
c.SetAccepted(MIMEJSON, MIMEXML)
|
||||||
|
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON)
|
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEXML)
|
assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML))
|
||||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON)
|
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextIsAborted(t *testing.T) {
|
func TestContextIsAborted(t *testing.T) {
|
||||||
@ -1022,9 +1052,9 @@ func TestContextAbortWithStatus(t *testing.T) {
|
|||||||
c.index = 4
|
c.index = 4
|
||||||
c.AbortWithStatus(401)
|
c.AbortWithStatus(401)
|
||||||
|
|
||||||
assert.Equal(t, c.index, abortIndex)
|
assert.Equal(t, abortIndex, c.index)
|
||||||
assert.Equal(t, c.Writer.Status(), 401)
|
assert.Equal(t, 401, c.Writer.Status())
|
||||||
assert.Equal(t, w.Code, 401)
|
assert.Equal(t, 401, w.Code)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1044,13 +1074,13 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
|||||||
|
|
||||||
c.AbortWithStatusJSON(415, in)
|
c.AbortWithStatusJSON(415, in)
|
||||||
|
|
||||||
assert.Equal(t, c.index, abortIndex)
|
assert.Equal(t, abortIndex, c.index)
|
||||||
assert.Equal(t, c.Writer.Status(), 415)
|
assert.Equal(t, 415, c.Writer.Status())
|
||||||
assert.Equal(t, w.Code, 415)
|
assert.Equal(t, 415, w.Code)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
|
|
||||||
contentType := w.Header().Get("Content-Type")
|
contentType := w.Header().Get("Content-Type")
|
||||||
assert.Equal(t, contentType, "application/json; charset=utf-8")
|
assert.Equal(t, "application/json; charset=utf-8", contentType)
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
buf.ReadFrom(w.Body)
|
buf.ReadFrom(w.Body)
|
||||||
@ -1064,7 +1094,7 @@ func TestContextError(t *testing.T) {
|
|||||||
|
|
||||||
c.Error(errors.New("first error"))
|
c.Error(errors.New("first error"))
|
||||||
assert.Len(t, c.Errors, 1)
|
assert.Len(t, c.Errors, 1)
|
||||||
assert.Equal(t, c.Errors.String(), "Error #01: first error\n")
|
assert.Equal(t, "Error #01: first error\n", c.Errors.String())
|
||||||
|
|
||||||
c.Error(&Error{
|
c.Error(&Error{
|
||||||
Err: errors.New("second error"),
|
Err: errors.New("second error"),
|
||||||
@ -1073,13 +1103,13 @@ func TestContextError(t *testing.T) {
|
|||||||
})
|
})
|
||||||
assert.Len(t, c.Errors, 2)
|
assert.Len(t, c.Errors, 2)
|
||||||
|
|
||||||
assert.Equal(t, c.Errors[0].Err, errors.New("first error"))
|
assert.Equal(t, errors.New("first error"), c.Errors[0].Err)
|
||||||
assert.Nil(t, c.Errors[0].Meta)
|
assert.Nil(t, c.Errors[0].Meta)
|
||||||
assert.Equal(t, c.Errors[0].Type, ErrorTypePrivate)
|
assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type)
|
||||||
|
|
||||||
assert.Equal(t, c.Errors[1].Err, errors.New("second error"))
|
assert.Equal(t, errors.New("second error"), c.Errors[1].Err)
|
||||||
assert.Equal(t, c.Errors[1].Meta, "some data 2")
|
assert.Equal(t, "some data 2", c.Errors[1].Meta)
|
||||||
assert.Equal(t, c.Errors[1].Type, ErrorTypePublic)
|
assert.Equal(t, ErrorTypePublic, c.Errors[1].Type)
|
||||||
|
|
||||||
assert.Equal(t, c.Errors.Last(), c.Errors[1])
|
assert.Equal(t, c.Errors.Last(), c.Errors[1])
|
||||||
|
|
||||||
@ -1097,12 +1127,12 @@ func TestContextTypedError(t *testing.T) {
|
|||||||
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
|
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
|
||||||
|
|
||||||
for _, err := range c.Errors.ByType(ErrorTypePublic) {
|
for _, err := range c.Errors.ByType(ErrorTypePublic) {
|
||||||
assert.Equal(t, err.Type, ErrorTypePublic)
|
assert.Equal(t, ErrorTypePublic, err.Type)
|
||||||
}
|
}
|
||||||
for _, err := range c.Errors.ByType(ErrorTypePrivate) {
|
for _, err := range c.Errors.ByType(ErrorTypePrivate) {
|
||||||
assert.Equal(t, err.Type, ErrorTypePrivate)
|
assert.Equal(t, ErrorTypePrivate, err.Type)
|
||||||
}
|
}
|
||||||
assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "interno 0"})
|
assert.Equal(t, []string{"externo 0", "interno 0"}, c.Errors.Errors())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextAbortWithError(t *testing.T) {
|
func TestContextAbortWithError(t *testing.T) {
|
||||||
@ -1111,8 +1141,8 @@ func TestContextAbortWithError(t *testing.T) {
|
|||||||
|
|
||||||
c.AbortWithError(401, errors.New("bad input")).SetMeta("some input")
|
c.AbortWithError(401, errors.New("bad input")).SetMeta("some input")
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 401)
|
assert.Equal(t, 401, w.Code)
|
||||||
assert.Equal(t, c.index, abortIndex)
|
assert.Equal(t, abortIndex, c.index)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1143,7 +1173,7 @@ func TestContextClientIP(t *testing.T) {
|
|||||||
|
|
||||||
// no port
|
// no port
|
||||||
c.Request.RemoteAddr = "50.50.50.50"
|
c.Request.RemoteAddr = "50.50.50.50"
|
||||||
assert.Equal(t, "", c.ClientIP())
|
assert.Empty(t, c.ClientIP())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextContentType(t *testing.T) {
|
func TestContextContentType(t *testing.T) {
|
||||||
@ -1151,7 +1181,7 @@ func TestContextContentType(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
c.Request.Header.Set("Content-Type", "application/json; charset=utf-8")
|
c.Request.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
assert.Equal(t, c.ContentType(), "application/json")
|
assert.Equal(t, "application/json", c.ContentType())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextAutoBindJSON(t *testing.T) {
|
func TestContextAutoBindJSON(t *testing.T) {
|
||||||
@ -1164,8 +1194,8 @@ func TestContextAutoBindJSON(t *testing.T) {
|
|||||||
Bar string `json:"bar"`
|
Bar string `json:"bar"`
|
||||||
}
|
}
|
||||||
assert.NoError(t, c.Bind(&obj))
|
assert.NoError(t, c.Bind(&obj))
|
||||||
assert.Equal(t, obj.Bar, "foo")
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
assert.Equal(t, obj.Foo, "bar")
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
assert.Empty(t, c.Errors)
|
assert.Empty(t, c.Errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1181,9 +1211,9 @@ func TestContextBindWithJSON(t *testing.T) {
|
|||||||
Bar string `json:"bar"`
|
Bar string `json:"bar"`
|
||||||
}
|
}
|
||||||
assert.NoError(t, c.BindJSON(&obj))
|
assert.NoError(t, c.BindJSON(&obj))
|
||||||
assert.Equal(t, obj.Bar, "foo")
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
assert.Equal(t, obj.Foo, "bar")
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
assert.Equal(t, w.Body.Len(), 0)
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextBindWithQuery(t *testing.T) {
|
func TestContextBindWithQuery(t *testing.T) {
|
||||||
@ -1219,10 +1249,77 @@ 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, w.Code, 400)
|
assert.Equal(t, 400, w.Code)
|
||||||
assert.True(t, c.IsAborted())
|
assert.True(t, c.IsAborted())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextAutoShouldBindJSON(t *testing.T) {
|
||||||
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEJSON)
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
assert.NoError(t, c.ShouldBind(&obj))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Empty(t, c.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindWithJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
assert.NoError(t, c.ShouldBindJSON(&obj))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindWithQuery(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `form:"foo"`
|
||||||
|
Bar string `form:"bar"`
|
||||||
|
}
|
||||||
|
assert.NoError(t, c.ShouldBindQuery(&obj))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextBadAutoShouldBind(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEJSON)
|
||||||
|
var obj struct {
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
Bar string `json:"bar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, c.IsAborted())
|
||||||
|
assert.Error(t, c.ShouldBind(&obj))
|
||||||
|
|
||||||
|
assert.Empty(t, obj.Bar)
|
||||||
|
assert.Empty(t, obj.Foo)
|
||||||
|
assert.False(t, c.IsAborted())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextGolangContext(t *testing.T) {
|
func TestContextGolangContext(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
@ -1235,7 +1332,7 @@ func TestContextGolangContext(t *testing.T) {
|
|||||||
assert.Nil(t, c.Value("foo"))
|
assert.Nil(t, c.Value("foo"))
|
||||||
|
|
||||||
c.Set("foo", "bar")
|
c.Set("foo", "bar")
|
||||||
assert.Equal(t, c.Value("foo"), "bar")
|
assert.Equal(t, "bar", c.Value("foo"))
|
||||||
assert.Nil(t, c.Value(1))
|
assert.Nil(t, c.Value(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1267,7 +1364,7 @@ func TestGetRequestHeaderValue(t *testing.T) {
|
|||||||
c.Request.Header.Set("Gin-Version", "1.0.0")
|
c.Request.Header.Set("Gin-Version", "1.0.0")
|
||||||
|
|
||||||
assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version"))
|
assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version"))
|
||||||
assert.Equal(t, "", c.GetHeader("Connection"))
|
assert.Empty(t, c.GetHeader("Connection"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextGetRawData(t *testing.T) {
|
func TestContextGetRawData(t *testing.T) {
|
||||||
|
8
debug.go
8
debug.go
@ -15,7 +15,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.Release) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
func IsDebugging() bool {
|
func IsDebugging() bool {
|
||||||
return ginMode == debugCode
|
return ginMode == debugCode
|
||||||
}
|
}
|
||||||
@ -46,6 +46,12 @@ func debugPrint(format string, values ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func debugPrintWARNINGDefault() {
|
||||||
|
debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
func debugPrintWARNINGNew() {
|
func debugPrintWARNINGNew() {
|
||||||
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
||||||
- using env: export GIN_MODE=release
|
- using env: export GIN_MODE=release
|
||||||
|
@ -86,6 +86,24 @@ func TestDebugPrintWARNINGSetHTMLTemplate(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", w.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", w.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||||
|
var w bytes.Buffer
|
||||||
|
setup(&w)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
debugPrintWARNINGDefault()
|
||||||
|
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugPrintWARNINGNew(t *testing.T) {
|
||||||
|
var w bytes.Buffer
|
||||||
|
setup(&w)
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
debugPrintWARNINGNew()
|
||||||
|
assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", w.String())
|
||||||
|
}
|
||||||
|
|
||||||
func setup(w io.Writer) {
|
func setup(w io.Writer) {
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
log.SetOutput(w)
|
log.SetOutput(w)
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
||||||
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
|
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
|
||||||
be deprecated, please check issue #662 and either use MustBindWith() if you
|
be deprecated, please check issue #662 and either use MustBindWith() if you
|
||||||
want HTTP 400 to be automatically returned if any error occur, of use
|
want HTTP 400 to be automatically returned if any error occur, or use
|
||||||
ShouldBindWith() if you need to manage the error.`)
|
ShouldBindWith() if you need to manage the error.`)
|
||||||
return c.MustBindWith(obj, b)
|
return c.MustBindWith(obj, b)
|
||||||
}
|
}
|
||||||
|
31
deprecated_test.go
Normal file
31
deprecated_test.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBindWith(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
|
||||||
|
|
||||||
|
var obj struct {
|
||||||
|
Foo string `form:"foo"`
|
||||||
|
Bar string `form:"bar"`
|
||||||
|
}
|
||||||
|
assert.NoError(t, c.BindWith(&obj, binding.Form))
|
||||||
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
@ -148,7 +148,7 @@ func (a errorMsgs) String() string {
|
|||||||
}
|
}
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
for i, msg := range a {
|
for i, msg := range a {
|
||||||
fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err)
|
fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
|
||||||
if msg.Meta != nil {
|
if msg.Meta != nil {
|
||||||
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
|
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
var DB = make(map[string]string)
|
var DB = make(map[string]string)
|
||||||
|
|
||||||
func main() {
|
func setupRouter() *gin.Engine {
|
||||||
// Disable Console Color
|
// Disable Console Color
|
||||||
// gin.DisableConsoleColor()
|
// gin.DisableConsoleColor()
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
@ -53,6 +53,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := setupRouter()
|
||||||
// Listen and Server in 0.0.0.0:8080
|
// Listen and Server in 0.0.0.0:8080
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
}
|
}
|
||||||
|
20
examples/basic/main_test.go
Normal file
20
examples/basic/main_test.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPingRoute(t *testing.T) {
|
||||||
|
router := setupRouter()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/ping", nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
assert.Equal(t, "pong", w.Body.String())
|
||||||
|
}
|
45
examples/custom-validation/server.go
Normal file
45
examples/custom-validation/server.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"gopkg.in/go-playground/validator.v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Booking struct {
|
||||||
|
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
|
||||||
|
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func bookableDate(
|
||||||
|
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
|
||||||
|
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
|
||||||
|
) bool {
|
||||||
|
if date, ok := field.Interface().(time.Time); ok {
|
||||||
|
today := time.Now()
|
||||||
|
if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
binding.Validator.RegisterValidation("bookabledate", bookableDate)
|
||||||
|
route.GET("/bookable", getBookable)
|
||||||
|
route.Run(":8085")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBookable(c *gin.Context) {
|
||||||
|
var b Booking
|
||||||
|
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
@ -41,5 +41,5 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Server exist")
|
log.Println("Server exiting")
|
||||||
}
|
}
|
||||||
|
@ -44,5 +44,5 @@ func main() {
|
|||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
log.Fatal("Server Shutdown:", err)
|
log.Fatal("Server Shutdown:", err)
|
||||||
}
|
}
|
||||||
log.Println("Server exist")
|
log.Println("Server exiting")
|
||||||
}
|
}
|
||||||
|
74
examples/multiple-service/main.go
Normal file
74
examples/multiple-service/main.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
g errgroup.Group
|
||||||
|
)
|
||||||
|
|
||||||
|
func router01() http.Handler {
|
||||||
|
e := gin.New()
|
||||||
|
e.Use(gin.Recovery())
|
||||||
|
e.GET("/", func(c *gin.Context) {
|
||||||
|
c.JSON(
|
||||||
|
http.StatusOK,
|
||||||
|
gin.H{
|
||||||
|
"code": http.StatusOK,
|
||||||
|
"error": "Welcome server 01",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func router02() http.Handler {
|
||||||
|
e := gin.New()
|
||||||
|
e.Use(gin.Recovery())
|
||||||
|
e.GET("/", func(c *gin.Context) {
|
||||||
|
c.JSON(
|
||||||
|
http.StatusOK,
|
||||||
|
gin.H{
|
||||||
|
"code": http.StatusOK,
|
||||||
|
"error": "Welcome server 02",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server01 := &http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
Handler: router01(),
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
server02 := &http.Server{
|
||||||
|
Addr: ":8081",
|
||||||
|
Handler: router02(),
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
return server01.ListenAndServe()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
return server02.ListenAndServe()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -29,8 +29,8 @@ func statsWorker() {
|
|||||||
"timestamp": uint64(time.Now().Unix()),
|
"timestamp": uint64(time.Now().Unix()),
|
||||||
"HeapInuse": stats.HeapInuse,
|
"HeapInuse": stats.HeapInuse,
|
||||||
"StackInuse": stats.StackInuse,
|
"StackInuse": stats.StackInuse,
|
||||||
"Mallocs": (stats.Mallocs - lastMallocs),
|
"Mallocs": stats.Mallocs - lastMallocs,
|
||||||
"Frees": (stats.Frees - lastFrees),
|
"Frees": stats.Frees - lastFrees,
|
||||||
"Inbound": uint64(messages.Get("inbound")),
|
"Inbound": uint64(messages.Get("inbound")),
|
||||||
"Outbound": uint64(messages.Get("outbound")),
|
"Outbound": uint64(messages.Get("outbound")),
|
||||||
"Connected": connectedUsers(),
|
"Connected": connectedUsers(),
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
||||||
|
router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||||
router.Static("/", "./public")
|
router.Static("/", "./public")
|
||||||
router.POST("/upload", func(c *gin.Context) {
|
router.POST("/upload", func(c *gin.Context) {
|
||||||
name := c.PostForm("name")
|
name := c.PostForm("name")
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
||||||
|
router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||||
router.Static("/", "./public")
|
router.Static("/", "./public")
|
||||||
router.POST("/upload", func(c *gin.Context) {
|
router.POST("/upload", func(c *gin.Context) {
|
||||||
name := c.PostForm("name")
|
name := c.PostForm("name")
|
||||||
|
18
fixtures/testdata/cert.pem
vendored
Normal file
18
fixtures/testdata/cert.pem
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC9DCCAdygAwIBAgIQUNSK+OxWHYYFxHVJV0IlpDANBgkqhkiG9w0BAQsFADAS
|
||||||
|
MRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MTExNjEyMDA0N1oXDTE4MTExNjEyMDA0
|
||||||
|
N1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||||
|
AQoCggEBAKmyj/YZpD59Bpy4w3qf6VzMw9uUBsWp+IP4kl7z5cmGHYUHn/YopTLH
|
||||||
|
vR23GAB12p6Km5QWzCBuJF4j61PJXHfg3/rjShZ77JcQ3kzxuy1iKDI+DNKN7Klz
|
||||||
|
rdjJ49QD0lczZHeBvvCL7JsJFKFjGy62rnStuW8LaIEdtjXT+GUZTxJh6G7yPYfD
|
||||||
|
MS1IsdMQGOdbGwNa+qogMuQPh0TzHw+C73myKrjY6pREijknMC/rnIMz9dLPt6Kl
|
||||||
|
xXy4br443dpY6dYGIhDuKhROT+vZ05HKasuuQUFhY7v/KoUpEZMB9rfUSzjQ5fak
|
||||||
|
eDUAMniXRcd+DmwvboG2TI6ixmuPK+ECAwEAAaNGMEQwDgYDVR0PAQH/BAQDAgWg
|
||||||
|
MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocE
|
||||||
|
fwAAATANBgkqhkiG9w0BAQsFAAOCAQEAMXOLvj7BFsxdbcfRPBd0OFrH/8lI7vPV
|
||||||
|
LRcJ6r5iv0cnNvZXXbIOQLbg4clJAWjoE08nRm1KvNXhKdns0ELEV86YN2S6jThq
|
||||||
|
rIGrBqKtaJLB3M9BtDSiQ6SGPLYrWvmhj3Avi8PbSGy51bpGbqopd16j6LYU7Cp2
|
||||||
|
TefMRlOAFtHojpCVon1CMpqcNxS0WNlQ3lUBSrw3HB0o12x++roja2ibF54tSHXB
|
||||||
|
KUuadoEzN+mMBwenEBychmAGzdiG4GQHRmhigh85+mtW6UMGiqyCZHs0EgE9FCLL
|
||||||
|
sRrsTI/VOzLz6lluygXkOsXrP+PP0SvmE3eylWjj9e2nj/u/Cy2YKg==
|
||||||
|
-----END CERTIFICATE-----
|
27
fixtures/testdata/key.pem
vendored
Normal file
27
fixtures/testdata/key.pem
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAqbKP9hmkPn0GnLjDep/pXMzD25QGxan4g/iSXvPlyYYdhQef
|
||||||
|
9iilMse9HbcYAHXanoqblBbMIG4kXiPrU8lcd+Df+uNKFnvslxDeTPG7LWIoMj4M
|
||||||
|
0o3sqXOt2Mnj1APSVzNkd4G+8IvsmwkUoWMbLraudK25bwtogR22NdP4ZRlPEmHo
|
||||||
|
bvI9h8MxLUix0xAY51sbA1r6qiAy5A+HRPMfD4LvebIquNjqlESKOScwL+ucgzP1
|
||||||
|
0s+3oqXFfLhuvjjd2ljp1gYiEO4qFE5P69nTkcpqy65BQWFju/8qhSkRkwH2t9RL
|
||||||
|
ONDl9qR4NQAyeJdFx34ObC9ugbZMjqLGa48r4QIDAQABAoIBAD5mhd+GMEo2KU9J
|
||||||
|
9b/Ku8I/HapJtW/L/7Fvn0tBPncrVQGM+zpGWfDhV95sbGwG6lwwNeNvuqIWPlNL
|
||||||
|
vAY0XkdKrrIQEDdSXH50WnpKzXxzwrou7QIj5Cmvevbjzl4xBZDBOilj0XWczmV4
|
||||||
|
IljyG5XC4UXQeAaoWEZaSZ1jk8yAt2Zq1Hgg7HqhHsK/arWXBgax+4K5nV/s9gZx
|
||||||
|
yjKU9mXTIs7k/aNnZqwQKqcZF+l3mvbZttOaFwsP14H0I8OFWhnM9hie54Dejqxi
|
||||||
|
f4/llNxDqUs6lqJfP3qNxtORLcFe75M+Yl8v7g2hkjtLdZBakPzSTEx3TAK/UHgi
|
||||||
|
aM8DdxECgYEA3fmg/PI4EgUEj0C3SCmQXR/CnQLMUQgb54s0asp4akvp+M7YCcr1
|
||||||
|
pQd3HFUpBwhBcJg5LeSe87vLupY7pHCKk56cl9WY6hse0b9sP/7DWJuGiO62m0E0
|
||||||
|
vNjQ2jpG99oR2ROIHHeWsGCpGLmrRT/kY+vR3M+AOLZniXlOCw8k0aUCgYEAw7WL
|
||||||
|
XFWLxgZYQYilywqrQmfv1MBfaUCvykO6oWB+f6mmnihSFjecI+nDw/b3yXVYGEgy
|
||||||
|
0ebkuw0jP8suC8wBqX9WuXj+9nZNomJRssJyOMiEhDEqUiTztFPSp9pdruoakLTh
|
||||||
|
Wk1p9NralOqGPUmxpXlFKVmYRTUbluikVxDypI0CgYBn6sqEQH0hann0+o4TWWn9
|
||||||
|
PrYkPUAbm1k8771tVTZERR/W3Dbldr/DL5iCihe39BR2urziEEqdvkglJNntJMar
|
||||||
|
TzDuIBADYQjvltb9qq4XGFBGYMLaMg+XbUVxNKEuvUdnwa4R7aZ9EfN34MwekkfA
|
||||||
|
w5Cu9/GGG1ajVEfGA6PwBQKBgA3o71jGs8KFXOx7e90sivOTU5Z5fc6LTHNB0Rf7
|
||||||
|
NcJ5GmCPWRY/KZfb25AoE4B8GKDRMNt+X69zxZeZJ1KrU0rqxA02rlhyHB54gnoE
|
||||||
|
G/4xMkn6/JkOC0w70PMhMBtohC7YzFOQwQEoNPT0nkno3Pl33xSLS6lPlwBo1JVj
|
||||||
|
nPtZAoGACXNLXYkR5vexE+w6FGl59r4RQhu1XU8Mr5DIHeB7kXPN3RKbS201M+Tb
|
||||||
|
SB5jbu0iDV477XkzSNmhaksFf2wM9MT6CaE+8n3UU5tMa+MmBGgwYTp/i9HkqVh5
|
||||||
|
jjpJifn1VWBINd4cpNzwCg9LXoo0tbtUPWwGzqVeyo/YE5GIHGo=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
98
gin.go
98
gin.go
@ -14,12 +14,17 @@ import (
|
|||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
// Version is Framework's version.
|
// Version is Framework's version.
|
||||||
const Version = "v1.2"
|
Version = "v1.2"
|
||||||
|
defaultMultipartMemory = 32 << 20 // 32 MB
|
||||||
|
)
|
||||||
|
|
||||||
var default404Body = []byte("404 page not found")
|
var (
|
||||||
var default405Body = []byte("405 method not allowed")
|
default404Body = []byte("404 page not found")
|
||||||
var defaultAppEngine bool
|
default405Body = []byte("405 method not allowed")
|
||||||
|
defaultAppEngine bool
|
||||||
|
)
|
||||||
|
|
||||||
type HandlerFunc func(*Context)
|
type HandlerFunc func(*Context)
|
||||||
type HandlersChain []HandlerFunc
|
type HandlersChain []HandlerFunc
|
||||||
@ -44,16 +49,6 @@ type RoutesInfo []RouteInfo
|
|||||||
// Create an instance of Engine, by using New() or Default()
|
// Create an instance of Engine, by using New() or Default()
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
RouterGroup
|
RouterGroup
|
||||||
delims render.Delims
|
|
||||||
secureJsonPrefix string
|
|
||||||
HTMLRender render.HTMLRender
|
|
||||||
FuncMap template.FuncMap
|
|
||||||
allNoRoute HandlersChain
|
|
||||||
allNoMethod HandlersChain
|
|
||||||
noRoute HandlersChain
|
|
||||||
noMethod HandlersChain
|
|
||||||
pool sync.Pool
|
|
||||||
trees methodTrees
|
|
||||||
|
|
||||||
// Enables automatic redirection if the current route can't be matched but a
|
// Enables automatic redirection if the current route can't be matched but a
|
||||||
// handler for the path with (without) the trailing slash exists.
|
// handler for the path with (without) the trailing slash exists.
|
||||||
@ -88,10 +83,26 @@ type Engine struct {
|
|||||||
|
|
||||||
// If enabled, the url.RawPath will be used to find parameters.
|
// If enabled, the url.RawPath will be used to find parameters.
|
||||||
UseRawPath bool
|
UseRawPath bool
|
||||||
|
|
||||||
// If true, the path value will be unescaped.
|
// If true, the path value will be unescaped.
|
||||||
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
||||||
// as url.Path gonna be used, which is already unescaped.
|
// as url.Path gonna be used, which is already unescaped.
|
||||||
UnescapePathValues bool
|
UnescapePathValues bool
|
||||||
|
|
||||||
|
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
||||||
|
// method call.
|
||||||
|
MaxMultipartMemory int64
|
||||||
|
|
||||||
|
delims render.Delims
|
||||||
|
secureJsonPrefix string
|
||||||
|
HTMLRender render.HTMLRender
|
||||||
|
FuncMap template.FuncMap
|
||||||
|
allNoRoute HandlersChain
|
||||||
|
allNoMethod HandlersChain
|
||||||
|
noRoute HandlersChain
|
||||||
|
noMethod HandlersChain
|
||||||
|
pool sync.Pool
|
||||||
|
trees methodTrees
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ IRouter = &Engine{}
|
var _ IRouter = &Engine{}
|
||||||
@ -120,6 +131,7 @@ func New() *Engine {
|
|||||||
AppEngine: defaultAppEngine,
|
AppEngine: defaultAppEngine,
|
||||||
UseRawPath: false,
|
UseRawPath: false,
|
||||||
UnescapePathValues: true,
|
UnescapePathValues: true,
|
||||||
|
MaxMultipartMemory: defaultMultipartMemory,
|
||||||
trees: make(methodTrees, 0, 9),
|
trees: make(methodTrees, 0, 9),
|
||||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||||
secureJsonPrefix: "while(1);",
|
secureJsonPrefix: "while(1);",
|
||||||
@ -133,6 +145,7 @@ func New() *Engine {
|
|||||||
|
|
||||||
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
func Default() *Engine {
|
func Default() *Engine {
|
||||||
|
debugPrintWARNINGDefault()
|
||||||
engine := New()
|
engine := New()
|
||||||
engine.Use(Logger(), Recovery())
|
engine.Use(Logger(), Recovery())
|
||||||
return engine
|
return engine
|
||||||
@ -147,22 +160,30 @@ func (engine *Engine) Delims(left, right string) *Engine {
|
|||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
|
||||||
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
||||||
engine.secureJsonPrefix = prefix
|
engine.secureJsonPrefix = prefix
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadHTMLGlob loads HTML files identified by glob pattern
|
||||||
|
// and associates the result with HTML renderer.
|
||||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
|
left := engine.delims.Left
|
||||||
|
right := engine.delims.Right
|
||||||
|
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)))
|
debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)))
|
||||||
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(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
||||||
engine.SetHTMLTemplate(templ)
|
engine.SetHTMLTemplate(templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadHTMLFiles loads a slice of HTML files
|
||||||
|
// and associates the result with HTML renderer.
|
||||||
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
|
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||||
@ -173,6 +194,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
|
|||||||
engine.SetHTMLTemplate(templ)
|
engine.SetHTMLTemplate(templ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHTMLTemplate associate a template with HTML renderer.
|
||||||
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
||||||
if len(engine.trees) > 0 {
|
if len(engine.trees) > 0 {
|
||||||
debugPrintWARNINGSetHTMLTemplate()
|
debugPrintWARNINGSetHTMLTemplate()
|
||||||
@ -181,6 +203,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
|||||||
engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
|
engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetFuncMap sets the FuncMap used for template.FuncMap.
|
||||||
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
|
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
|
||||||
engine.FuncMap = funcMap
|
engine.FuncMap = funcMap
|
||||||
}
|
}
|
||||||
@ -217,7 +240,7 @@ func (engine *Engine) rebuild405Handlers() {
|
|||||||
|
|
||||||
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||||
assert1(path[0] == '/', "path must begin with '/'")
|
assert1(path[0] == '/', "path must begin with '/'")
|
||||||
assert1(len(method) > 0, "HTTP method can not be empty")
|
assert1(method != "", "HTTP method can not be empty")
|
||||||
assert1(len(handlers) > 0, "there must be at least one handler")
|
assert1(len(handlers) > 0, "there must be at least one handler")
|
||||||
|
|
||||||
debugPrintRoute(method, path, handlers)
|
debugPrintRoute(method, path, handlers)
|
||||||
@ -314,16 +337,13 @@ func (engine *Engine) HandleContext(c *Context) {
|
|||||||
engine.pool.Put(c)
|
engine.pool.Put(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) handleHTTPRequest(context *Context) {
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
httpMethod := context.Request.Method
|
httpMethod := c.Request.Method
|
||||||
var path string
|
path := c.Request.URL.Path
|
||||||
var unescape bool
|
unescape := false
|
||||||
if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 {
|
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
||||||
path = context.Request.URL.RawPath
|
path = c.Request.URL.RawPath
|
||||||
unescape = engine.UnescapePathValues
|
unescape = engine.UnescapePathValues
|
||||||
} else {
|
|
||||||
path = context.Request.URL.Path
|
|
||||||
unescape = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find root of the tree for the given HTTP method
|
// Find root of the tree for the given HTTP method
|
||||||
@ -332,20 +352,20 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
|
|||||||
if t[i].method == httpMethod {
|
if t[i].method == httpMethod {
|
||||||
root := t[i].root
|
root := t[i].root
|
||||||
// Find route in tree
|
// Find route in tree
|
||||||
handlers, params, tsr := root.getValue(path, context.Params, unescape)
|
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
||||||
if handlers != nil {
|
if handlers != nil {
|
||||||
context.handlers = handlers
|
c.handlers = handlers
|
||||||
context.Params = params
|
c.Params = params
|
||||||
context.Next()
|
c.Next()
|
||||||
context.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if httpMethod != "CONNECT" && path != "/" {
|
if httpMethod != "CONNECT" && path != "/" {
|
||||||
if tsr && engine.RedirectTrailingSlash {
|
if tsr && engine.RedirectTrailingSlash {
|
||||||
redirectTrailingSlash(context)
|
redirectTrailingSlash(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) {
|
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -357,15 +377,15 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
|
|||||||
for _, tree := range engine.trees {
|
for _, tree := range engine.trees {
|
||||||
if tree.method != httpMethod {
|
if tree.method != httpMethod {
|
||||||
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
||||||
context.handlers = engine.allNoMethod
|
c.handlers = engine.allNoMethod
|
||||||
serveError(context, 405, default405Body)
|
serveError(c, 405, default405Body)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.handlers = engine.allNoRoute
|
c.handlers = engine.allNoRoute
|
||||||
serveError(context, 404, default404Body)
|
serveError(c, 404, default404Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
var mimePlain = []string{MIMEPlain}
|
var mimePlain = []string{MIMEPlain}
|
||||||
@ -391,8 +411,8 @@ func redirectTrailingSlash(c *Context) {
|
|||||||
code = 307
|
code = 307
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
if length := len(path); length > 1 && path[length-1] == '/' {
|
||||||
req.URL.Path = path[:len(path)-1]
|
req.URL.Path = path[:length-1]
|
||||||
} else {
|
} else {
|
||||||
req.URL.Path = path + "/"
|
req.URL.Path = path + "/"
|
||||||
}
|
}
|
||||||
|
40
ginS/gins.go
40
ginS/gins.go
@ -9,15 +9,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
. "github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var once sync.Once
|
var once sync.Once
|
||||||
var internalEngine *Engine
|
var internalEngine *gin.Engine
|
||||||
|
|
||||||
func engine() *Engine {
|
func engine() *gin.Engine {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
internalEngine = Default()
|
internalEngine = gin.Default()
|
||||||
})
|
})
|
||||||
return internalEngine
|
return internalEngine
|
||||||
}
|
}
|
||||||
@ -35,65 +35,65 @@ func SetHTMLTemplate(templ *template.Template) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
||||||
func NoRoute(handlers ...HandlerFunc) {
|
func NoRoute(handlers ...gin.HandlerFunc) {
|
||||||
engine().NoRoute(handlers...)
|
engine().NoRoute(handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoMethod sets the handlers called when... TODO
|
// NoMethod sets the handlers called when... TODO
|
||||||
func NoMethod(handlers ...HandlerFunc) {
|
func NoMethod(handlers ...gin.HandlerFunc) {
|
||||||
engine().NoMethod(handlers...)
|
engine().NoMethod(handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
|
// Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
|
||||||
// For example, all the routes that use a common middlware for authorization could be grouped.
|
// For example, all the routes that use a common middlware for authorization could be grouped.
|
||||||
func Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
|
func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
|
||||||
return engine().Group(relativePath, handlers...)
|
return engine().Group(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
|
func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().Handle(httpMethod, relativePath, handlers...)
|
return engine().Handle(httpMethod, relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST is a shortcut for router.Handle("POST", path, handle)
|
// POST is a shortcut for router.Handle("POST", path, handle)
|
||||||
func POST(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().POST(relativePath, handlers...)
|
return engine().POST(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET is a shortcut for router.Handle("GET", path, handle)
|
// GET is a shortcut for router.Handle("GET", path, handle)
|
||||||
func GET(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().GET(relativePath, handlers...)
|
return engine().GET(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
|
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
|
||||||
func DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().DELETE(relativePath, handlers...)
|
return engine().DELETE(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
|
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
|
||||||
func PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().PATCH(relativePath, handlers...)
|
return engine().PATCH(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT is a shortcut for router.Handle("PUT", path, handle)
|
// PUT is a shortcut for router.Handle("PUT", path, handle)
|
||||||
func PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().PUT(relativePath, handlers...)
|
return engine().PUT(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
|
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
|
||||||
func OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().OPTIONS(relativePath, handlers...)
|
return engine().OPTIONS(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
|
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
|
||||||
func HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().HEAD(relativePath, handlers...)
|
return engine().HEAD(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().Any(relativePath, handlers...)
|
return engine().Any(relativePath, handlers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StaticFile(relativePath, filepath string) IRoutes {
|
func StaticFile(relativePath, filepath string) gin.IRoutes {
|
||||||
return engine().StaticFile(relativePath, filepath)
|
return engine().StaticFile(relativePath, filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,18 +103,18 @@ func StaticFile(relativePath, filepath string) IRoutes {
|
|||||||
// To use the operating system's file system implementation,
|
// To use the operating system's file system implementation,
|
||||||
// use :
|
// use :
|
||||||
// router.Static("/static", "/var/www")
|
// router.Static("/static", "/var/www")
|
||||||
func Static(relativePath, root string) IRoutes {
|
func Static(relativePath, root string) gin.IRoutes {
|
||||||
return engine().Static(relativePath, root)
|
return engine().Static(relativePath, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StaticFS(relativePath string, fs http.FileSystem) IRoutes {
|
func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
|
||||||
return engine().StaticFS(relativePath, fs)
|
return engine().StaticFS(relativePath, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use attachs a global middleware to the router. ie. the middlewares attached though Use() will be
|
// Use attachs a global middleware to the router. ie. the middlewares attached though Use() will be
|
||||||
// included in the handlers chain for every single request. Even 404, 405, static files...
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||||
// For example, this is the right place for a logger or error management middleware.
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
func Use(middlewares ...HandlerFunc) IRoutes {
|
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().Use(middlewares...)
|
return engine().Use(middlewares...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ func TestUnixSocket(t *testing.T) {
|
|||||||
c, err := net.Dial("unix", "/tmp/unix_unit_test")
|
c, err := net.Dial("unix", "/tmp/unix_unit_test")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||||
scanner := bufio.NewScanner(c)
|
scanner := bufio.NewScanner(c)
|
||||||
var response string
|
var response string
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
|
128
gin_test.go
128
gin_test.go
@ -5,6 +5,7 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -21,9 +22,9 @@ func formatAsDate(t time.Time) string {
|
|||||||
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
|
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHTMLFiles(t *testing.T) func() {
|
func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
|
||||||
go func() {
|
go func() {
|
||||||
SetMode(TestMode)
|
SetMode(mode)
|
||||||
router := New()
|
router := New()
|
||||||
router.Delims("{[{", "}]}")
|
router.Delims("{[{", "}]}")
|
||||||
router.SetFuncMap(template.FuncMap{
|
router.SetFuncMap(template.FuncMap{
|
||||||
@ -38,16 +39,21 @@ func setupHTMLFiles(t *testing.T) func() {
|
|||||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
if tls {
|
||||||
|
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
|
||||||
|
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
||||||
|
} else {
|
||||||
router.Run(":8888")
|
router.Run(":8888")
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
t.Log("waiting 1 second for server startup")
|
t.Log("waiting 1 second for server startup")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
return func() {}
|
return func() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHTMLGlob(t *testing.T) func() {
|
func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
|
||||||
go func() {
|
go func() {
|
||||||
SetMode(DebugMode)
|
SetMode(mode)
|
||||||
router := New()
|
router := New()
|
||||||
router.Delims("{[{", "}]}")
|
router.Delims("{[{", "}]}")
|
||||||
router.SetFuncMap(template.FuncMap{
|
router.SetFuncMap(template.FuncMap{
|
||||||
@ -62,16 +68,20 @@ func setupHTMLGlob(t *testing.T) func() {
|
|||||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
if tls {
|
||||||
|
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
|
||||||
|
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
||||||
|
} else {
|
||||||
router.Run(":8888")
|
router.Run(":8888")
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
t.Log("waiting 1 second for server startup")
|
t.Log("waiting 1 second for server startup")
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
return func() {}
|
return func() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
|
||||||
func TestLoadHTMLGlob(t *testing.T) {
|
func TestLoadHTMLGlob(t *testing.T) {
|
||||||
td := setupHTMLGlob(t)
|
td := setupHTMLGlob(t, DebugMode, false)
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@ -83,9 +93,55 @@ func TestLoadHTMLGlob(t *testing.T) {
|
|||||||
td()
|
td()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadHTMLGlob2(t *testing.T) {
|
||||||
|
td := setupHTMLGlob(t, TestMode, false)
|
||||||
|
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||||
|
|
||||||
|
td()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadHTMLGlob3(t *testing.T) {
|
||||||
|
td := setupHTMLGlob(t, ReleaseMode, false)
|
||||||
|
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||||
|
|
||||||
|
td()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadHTMLGlobUsingTLS(t *testing.T) {
|
||||||
|
td := setupHTMLGlob(t, DebugMode, true)
|
||||||
|
// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
res, err := client.Get("https://127.0.0.1:9999/test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||||
|
|
||||||
|
td()
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
||||||
time.Now()
|
time.Now()
|
||||||
td := setupHTMLGlob(t)
|
td := setupHTMLGlob(t, DebugMode, false)
|
||||||
res, err := http.Get("http://127.0.0.1:8888/raw")
|
res, err := http.Get("http://127.0.0.1:8888/raw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@ -97,9 +153,6 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
|||||||
td()
|
td()
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (engine *Engine) LoadHTMLFiles(files ...string) {
|
|
||||||
// func (engine *Engine) RunTLS(addr string, cert string, key string) error {
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
}
|
}
|
||||||
@ -117,17 +170,17 @@ func TestCreateEngine(t *testing.T) {
|
|||||||
// router.LoadHTMLGlob("*.testtmpl")
|
// router.LoadHTMLGlob("*.testtmpl")
|
||||||
// r := router.HTMLRender.(render.HTMLDebug)
|
// r := router.HTMLRender.(render.HTMLDebug)
|
||||||
// assert.Empty(t, r.Files)
|
// assert.Empty(t, r.Files)
|
||||||
// assert.Equal(t, r.Glob, "*.testtmpl")
|
// assert.Equal(t, "*.testtmpl", r.Glob)
|
||||||
//
|
//
|
||||||
// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl")
|
// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl")
|
||||||
// r = router.HTMLRender.(render.HTMLDebug)
|
// r = router.HTMLRender.(render.HTMLDebug)
|
||||||
// assert.Empty(t, r.Glob)
|
// assert.Empty(t, r.Glob)
|
||||||
// assert.Equal(t, r.Files, []string{"index.html", "login.html"})
|
// assert.Equal(t, []string{"index.html", "login.html"}, r.Files)
|
||||||
// SetMode(TestMode)
|
// SetMode(TestMode)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func TestLoadHTMLFiles(t *testing.T) {
|
func TestLoadHTMLFiles(t *testing.T) {
|
||||||
td := setupHTMLFiles(t)
|
td := setupHTMLFiles(t, TestMode, false)
|
||||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@ -138,9 +191,52 @@ func TestLoadHTMLFiles(t *testing.T) {
|
|||||||
td()
|
td()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadHTMLFiles2(t *testing.T) {
|
||||||
|
td := setupHTMLFiles(t, DebugMode, false)
|
||||||
|
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||||
|
td()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadHTMLFiles3(t *testing.T) {
|
||||||
|
td := setupHTMLFiles(t, ReleaseMode, false)
|
||||||
|
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||||
|
td()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadHTMLFilesUsingTLS(t *testing.T) {
|
||||||
|
td := setupHTMLFiles(t, TestMode, true)
|
||||||
|
// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
res, err := client.Get("https://127.0.0.1:9999/test")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := ioutil.ReadAll(res.Body)
|
||||||
|
assert.Equal(t, "<h1>Hello world</h1>", string(resp[:]))
|
||||||
|
td()
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
||||||
time.Now()
|
time.Now()
|
||||||
td := setupHTMLFiles(t)
|
td := setupHTMLFiles(t, TestMode, false)
|
||||||
res, err := http.Get("http://127.0.0.1:8888/raw")
|
res, err := http.Get("http://127.0.0.1:8888/raw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@ -152,10 +248,6 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
|||||||
td()
|
td()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadHTMLReleaseMode(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddRoute(t *testing.T) {
|
func TestAddRoute(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
||||||
|
@ -6,9 +6,7 @@
|
|||||||
|
|
||||||
package json
|
package json
|
||||||
|
|
||||||
import (
|
import "encoding/json"
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Marshal = json.Marshal
|
Marshal = json.Marshal
|
||||||
|
@ -6,9 +6,7 @@
|
|||||||
|
|
||||||
package json
|
package json
|
||||||
|
|
||||||
import (
|
import "github.com/json-iterator/go"
|
||||||
"github.com/json-iterator/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
@ -91,10 +91,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
|||||||
clientIP := c.ClientIP()
|
clientIP := c.ClientIP()
|
||||||
method := c.Request.Method
|
method := c.Request.Method
|
||||||
statusCode := c.Writer.Status()
|
statusCode := c.Writer.Status()
|
||||||
var statusColor, methodColor string
|
var statusColor, methodColor, resetColor string
|
||||||
if isTerm {
|
if isTerm {
|
||||||
statusColor = colorForStatus(statusCode)
|
statusColor = colorForStatus(statusCode)
|
||||||
methodColor = colorForMethod(method)
|
methodColor = colorForMethod(method)
|
||||||
|
resetColor = reset
|
||||||
}
|
}
|
||||||
comment := c.Errors.ByType(ErrorTypePrivate).String()
|
comment := c.Errors.ByType(ErrorTypePrivate).String()
|
||||||
|
|
||||||
@ -104,10 +105,10 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
|||||||
|
|
||||||
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
|
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
|
||||||
end.Format("2006/01/02 - 15:04:05"),
|
end.Format("2006/01/02 - 15:04:05"),
|
||||||
statusColor, statusCode, reset,
|
statusColor, statusCode, resetColor,
|
||||||
latency,
|
latency,
|
||||||
clientIP,
|
clientIP,
|
||||||
methodColor, method, reset,
|
methodColor, method, resetColor,
|
||||||
path,
|
path,
|
||||||
comment,
|
comment,
|
||||||
)
|
)
|
||||||
|
@ -82,21 +82,21 @@ func TestLogger(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestColorForMethod(t *testing.T) {
|
func TestColorForMethod(t *testing.T) {
|
||||||
assert.Equal(t, colorForMethod("GET"), string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), "get should be blue")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue")
|
||||||
assert.Equal(t, colorForMethod("POST"), string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), "post should be cyan")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan")
|
||||||
assert.Equal(t, colorForMethod("PUT"), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "put should be yellow")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow")
|
||||||
assert.Equal(t, colorForMethod("DELETE"), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "delete should be red")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red")
|
||||||
assert.Equal(t, colorForMethod("PATCH"), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "patch should be green")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green")
|
||||||
assert.Equal(t, colorForMethod("HEAD"), string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), "head should be magenta")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta")
|
||||||
assert.Equal(t, colorForMethod("OPTIONS"), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "options should be white")
|
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white")
|
||||||
assert.Equal(t, colorForMethod("TRACE"), string([]byte{27, 91, 48, 109}), "trace is not defined and should be the reset color")
|
assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorForStatus(t *testing.T) {
|
func TestColorForStatus(t *testing.T) {
|
||||||
assert.Equal(t, colorForStatus(200), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "2xx should be green")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green")
|
||||||
assert.Equal(t, colorForStatus(301), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "3xx should be white")
|
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white")
|
||||||
assert.Equal(t, colorForStatus(404), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "4xx should be yellow")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow")
|
||||||
assert.Equal(t, colorForStatus(2), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "other things should be red")
|
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorLogger(t *testing.T) {
|
func TestErrorLogger(t *testing.T) {
|
||||||
|
12
mode.go
12
mode.go
@ -14,9 +14,9 @@ import (
|
|||||||
const ENV_GIN_MODE = "GIN_MODE"
|
const ENV_GIN_MODE = "GIN_MODE"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DebugMode string = "debug"
|
DebugMode = "debug"
|
||||||
ReleaseMode string = "release"
|
ReleaseMode = "release"
|
||||||
TestMode string = "test"
|
TestMode = "test"
|
||||||
)
|
)
|
||||||
const (
|
const (
|
||||||
debugCode = iota
|
debugCode = iota
|
||||||
@ -39,16 +39,12 @@ var modeName = DebugMode
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mode := os.Getenv(ENV_GIN_MODE)
|
mode := os.Getenv(ENV_GIN_MODE)
|
||||||
if len(mode) == 0 {
|
|
||||||
SetMode(DebugMode)
|
|
||||||
} else {
|
|
||||||
SetMode(mode)
|
SetMode(mode)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func SetMode(value string) {
|
func SetMode(value string) {
|
||||||
switch value {
|
switch value {
|
||||||
case DebugMode:
|
case DebugMode, "":
|
||||||
ginMode = debugCode
|
ginMode = debugCode
|
||||||
case ReleaseMode:
|
case ReleaseMode:
|
||||||
ginMode = releaseCode
|
ginMode = releaseCode
|
||||||
|
16
mode_test.go
16
mode_test.go
@ -17,21 +17,21 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetMode(t *testing.T) {
|
func TestSetMode(t *testing.T) {
|
||||||
assert.Equal(t, ginMode, testCode)
|
assert.Equal(t, testCode, ginMode)
|
||||||
assert.Equal(t, Mode(), TestMode)
|
assert.Equal(t, TestMode, Mode())
|
||||||
os.Unsetenv(ENV_GIN_MODE)
|
os.Unsetenv(ENV_GIN_MODE)
|
||||||
|
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
assert.Equal(t, ginMode, debugCode)
|
assert.Equal(t, debugCode, ginMode)
|
||||||
assert.Equal(t, Mode(), DebugMode)
|
assert.Equal(t, DebugMode, Mode())
|
||||||
|
|
||||||
SetMode(ReleaseMode)
|
SetMode(ReleaseMode)
|
||||||
assert.Equal(t, ginMode, releaseCode)
|
assert.Equal(t, releaseCode, ginMode)
|
||||||
assert.Equal(t, Mode(), ReleaseMode)
|
assert.Equal(t, ReleaseMode, Mode())
|
||||||
|
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
assert.Equal(t, ginMode, testCode)
|
assert.Equal(t, testCode, ginMode)
|
||||||
assert.Equal(t, Mode(), TestMode)
|
assert.Equal(t, TestMode, Mode())
|
||||||
|
|
||||||
assert.Panics(t, func() { SetMode("unknown") })
|
assert.Panics(t, func() { SetMode("unknown") })
|
||||||
}
|
}
|
||||||
|
@ -67,8 +67,8 @@ var cleanTests = []struct {
|
|||||||
|
|
||||||
func TestPathClean(t *testing.T) {
|
func TestPathClean(t *testing.T) {
|
||||||
for _, test := range cleanTests {
|
for _, test := range cleanTests {
|
||||||
assert.Equal(t, cleanPath(test.path), test.result)
|
assert.Equal(t, test.result, cleanPath(test.path))
|
||||||
assert.Equal(t, cleanPath(test.result), test.result)
|
assert.Equal(t, test.result, cleanPath(test.result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ func Recovery() HandlerFunc {
|
|||||||
return RecoveryWithWriter(DefaultErrorWriter)
|
return RecoveryWithWriter(DefaultErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
|
||||||
func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||||
var logger *log.Logger
|
var logger *log.Logger
|
||||||
if out != nil {
|
if out != nil {
|
||||||
@ -46,7 +47,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stack returns a nicely formated stack frame, skipping skip frames.
|
// stack returns a nicely formatted stack frame, skipping skip frames.
|
||||||
func stack(skip int) []byte {
|
func stack(skip int) []byte {
|
||||||
buf := new(bytes.Buffer) // the returned data
|
buf := new(bytes.Buffer) // the returned data
|
||||||
// As we loop, we open files and read them. These variables record the currently
|
// As we loop, we open files and read them. These variables record the currently
|
||||||
|
@ -22,7 +22,7 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := performRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, w.Code, 500)
|
assert.Equal(t, 500, 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")
|
||||||
@ -39,5 +39,5 @@ func TestPanicWithAbort(t *testing.T) {
|
|||||||
// RUN
|
// RUN
|
||||||
w := performRequest(router, "GET", "/recovery")
|
w := performRequest(router, "GET", "/recovery")
|
||||||
// TEST
|
// TEST
|
||||||
assert.Equal(t, w.Code, 400)
|
assert.Equal(t, 400, w.Code)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ func (r HTMLDebug) loadTemplate() *template.Template {
|
|||||||
if len(r.Files) > 0 {
|
if len(r.Files) > 0 {
|
||||||
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
|
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
|
||||||
}
|
}
|
||||||
if len(r.Glob) > 0 {
|
if r.Glob != "" {
|
||||||
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
|
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
|
||||||
}
|
}
|
||||||
panic("the HTML debug render was created without files or glob pattern")
|
panic("the HTML debug render was created without files or glob pattern")
|
||||||
@ -69,7 +69,7 @@ func (r HTMLDebug) loadTemplate() *template.Template {
|
|||||||
func (r HTML) Render(w http.ResponseWriter) error {
|
func (r HTML) Render(w http.ResponseWriter) error {
|
||||||
r.WriteContentType(w)
|
r.WriteContentType(w)
|
||||||
|
|
||||||
if len(r.Name) == 0 {
|
if r.Name == "" {
|
||||||
return r.Template.Execute(w, r.Data)
|
return r.Template.Execute(w, r.Data)
|
||||||
}
|
}
|
||||||
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
||||||
|
@ -111,10 +111,8 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
|
|
||||||
return err
|
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderXML(t *testing.T) {
|
func TestRenderXML(t *testing.T) {
|
||||||
|
@ -20,15 +20,15 @@ func TestRouterGroupBasic(t *testing.T) {
|
|||||||
group.Use(func(c *Context) {})
|
group.Use(func(c *Context) {})
|
||||||
|
|
||||||
assert.Len(t, group.Handlers, 2)
|
assert.Len(t, group.Handlers, 2)
|
||||||
assert.Equal(t, group.BasePath(), "/hola")
|
assert.Equal(t, "/hola", group.BasePath())
|
||||||
assert.Equal(t, group.engine, router)
|
assert.Equal(t, router, group.engine)
|
||||||
|
|
||||||
group2 := group.Group("manu")
|
group2 := group.Group("manu")
|
||||||
group2.Use(func(c *Context) {}, func(c *Context) {})
|
group2.Use(func(c *Context) {}, func(c *Context) {})
|
||||||
|
|
||||||
assert.Len(t, group2.Handlers, 4)
|
assert.Len(t, group2.Handlers, 4)
|
||||||
assert.Equal(t, group2.BasePath(), "/hola/manu")
|
assert.Equal(t, "/hola/manu", group2.BasePath())
|
||||||
assert.Equal(t, group2.engine, router)
|
assert.Equal(t, router, group2.engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterGroupBasicHandle(t *testing.T) {
|
func TestRouterGroupBasicHandle(t *testing.T) {
|
||||||
@ -44,10 +44,10 @@ func TestRouterGroupBasicHandle(t *testing.T) {
|
|||||||
func performRequestInGroup(t *testing.T, method string) {
|
func performRequestInGroup(t *testing.T, method string) {
|
||||||
router := New()
|
router := New()
|
||||||
v1 := router.Group("v1", func(c *Context) {})
|
v1 := router.Group("v1", func(c *Context) {})
|
||||||
assert.Equal(t, v1.BasePath(), "/v1")
|
assert.Equal(t, "/v1", v1.BasePath())
|
||||||
|
|
||||||
login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {})
|
login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {})
|
||||||
assert.Equal(t, login.BasePath(), "/v1/login/")
|
assert.Equal(t, "/v1/login/", login.BasePath())
|
||||||
|
|
||||||
handler := func(c *Context) {
|
handler := func(c *Context) {
|
||||||
c.String(400, "the method was %s and index %d", c.Request.Method, c.index)
|
c.String(400, "the method was %s and index %d", c.Request.Method, c.index)
|
||||||
@ -80,12 +80,12 @@ func performRequestInGroup(t *testing.T, method string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w := performRequest(router, method, "/v1/login/test")
|
w := performRequest(router, method, "/v1/login/test")
|
||||||
assert.Equal(t, w.Code, 400)
|
assert.Equal(t, 400, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 3")
|
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, w.Code, 400)
|
assert.Equal(t, 400, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 1")
|
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterGroupInvalidStatic(t *testing.T) {
|
func TestRouterGroupInvalidStatic(t *testing.T) {
|
||||||
|
124
routes_test.go
124
routes_test.go
@ -36,7 +36,7 @@ func testRouteOK(method string, t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(r, method, "/test")
|
w := performRequest(r, method, "/test")
|
||||||
assert.True(t, passed)
|
assert.True(t, passed)
|
||||||
assert.Equal(t, w.Code, http.StatusOK)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
performRequest(r, method, "/test2")
|
performRequest(r, method, "/test2")
|
||||||
assert.True(t, passedAny)
|
assert.True(t, passedAny)
|
||||||
@ -53,7 +53,7 @@ func testRouteNotOK(method string, t *testing.T) {
|
|||||||
w := performRequest(router, method, "/test")
|
w := performRequest(router, method, "/test")
|
||||||
|
|
||||||
assert.False(t, passed)
|
assert.False(t, passed)
|
||||||
assert.Equal(t, w.Code, http.StatusNotFound)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSingleRouteOK tests that POST route is correctly invoked.
|
// TestSingleRouteOK tests that POST route is correctly invoked.
|
||||||
@ -74,7 +74,7 @@ func testRouteNotOK2(method string, t *testing.T) {
|
|||||||
w := performRequest(router, method, "/test")
|
w := performRequest(router, method, "/test")
|
||||||
|
|
||||||
assert.False(t, passed)
|
assert.False(t, passed)
|
||||||
assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
|
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterMethod(t *testing.T) {
|
func TestRouterMethod(t *testing.T) {
|
||||||
@ -93,8 +93,8 @@ func TestRouterMethod(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "PUT", "/hey")
|
w := performRequest(router, "PUT", "/hey")
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "called")
|
assert.Equal(t, "called", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterGroupRouteOK(t *testing.T) {
|
func TestRouterGroupRouteOK(t *testing.T) {
|
||||||
@ -143,43 +143,43 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
|||||||
router.PUT("/path4/", func(c *Context) {})
|
router.PUT("/path4/", func(c *Context) {})
|
||||||
|
|
||||||
w := performRequest(router, "GET", "/path/")
|
w := performRequest(router, "GET", "/path/")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/path")
|
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 301)
|
assert.Equal(t, 301, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path2")
|
w = performRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/path2/")
|
assert.Equal(t, "/path2/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 301)
|
assert.Equal(t, 301, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "POST", "/path3/")
|
w = performRequest(router, "POST", "/path3/")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/path3")
|
assert.Equal(t, "/path3", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 307)
|
assert.Equal(t, 307, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "PUT", "/path4")
|
w = performRequest(router, "PUT", "/path4")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/path4/")
|
assert.Equal(t, "/path4/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 307)
|
assert.Equal(t, 307, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path")
|
w = performRequest(router, "GET", "/path")
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path2/")
|
w = performRequest(router, "GET", "/path2/")
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "POST", "/path3")
|
w = performRequest(router, "POST", "/path3")
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "PUT", "/path4/")
|
w = performRequest(router, "PUT", "/path4/")
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
router.RedirectTrailingSlash = false
|
router.RedirectTrailingSlash = false
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path/")
|
w = performRequest(router, "GET", "/path/")
|
||||||
assert.Equal(t, w.Code, 404)
|
assert.Equal(t, 404, w.Code)
|
||||||
w = performRequest(router, "GET", "/path2")
|
w = performRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, w.Code, 404)
|
assert.Equal(t, 404, w.Code)
|
||||||
w = performRequest(router, "POST", "/path3/")
|
w = performRequest(router, "POST", "/path3/")
|
||||||
assert.Equal(t, w.Code, 404)
|
assert.Equal(t, 404, w.Code)
|
||||||
w = performRequest(router, "PUT", "/path4")
|
w = performRequest(router, "PUT", "/path4")
|
||||||
assert.Equal(t, w.Code, 404)
|
assert.Equal(t, 404, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteRedirectFixedPath(t *testing.T) {
|
func TestRouteRedirectFixedPath(t *testing.T) {
|
||||||
@ -193,20 +193,20 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
|||||||
router.POST("/Path4/", func(c *Context) {})
|
router.POST("/Path4/", func(c *Context) {})
|
||||||
|
|
||||||
w := performRequest(router, "GET", "/PATH")
|
w := performRequest(router, "GET", "/PATH")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/path")
|
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 301)
|
assert.Equal(t, 301, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path2")
|
w = performRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/Path2")
|
assert.Equal(t, "/Path2", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 301)
|
assert.Equal(t, 301, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "POST", "/path3")
|
w = performRequest(router, "POST", "/path3")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/PATH3")
|
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 307)
|
assert.Equal(t, 307, w.Code)
|
||||||
|
|
||||||
w = performRequest(router, "POST", "/path4")
|
w = performRequest(router, "POST", "/path4")
|
||||||
assert.Equal(t, w.Header().Get("Location"), "/Path4/")
|
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
|
||||||
assert.Equal(t, w.Code, 307)
|
assert.Equal(t, 307, 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,10 +236,10 @@ 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, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, name, "john")
|
assert.Equal(t, "john", name)
|
||||||
assert.Equal(t, lastName, "smith")
|
assert.Equal(t, "smith", lastName)
|
||||||
assert.Equal(t, wild, "/is/super/great")
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleStaticFile - ensure the static file handles properly
|
// TestHandleStaticFile - ensure the static file handles properly
|
||||||
@ -265,15 +265,15 @@ 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, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "Gin Web Framework")
|
assert.Equal(t, "Gin Web Framework", w.Body.String())
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
|
|
||||||
w3 := performRequest(router, "HEAD", "/using_static/"+filename)
|
w3 := performRequest(router, "HEAD", "/using_static/"+filename)
|
||||||
w4 := performRequest(router, "HEAD", "/result")
|
w4 := performRequest(router, "HEAD", "/result")
|
||||||
|
|
||||||
assert.Equal(t, w3, w4)
|
assert.Equal(t, w3, w4)
|
||||||
assert.Equal(t, w3.Code, 200)
|
assert.Equal(t, 200, w3.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleStaticDir - ensure the root/sub dir handles properly
|
// TestHandleStaticDir - ensure the root/sub dir handles properly
|
||||||
@ -283,9 +283,9 @@ func TestRouteStaticListingDir(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "gin.go")
|
assert.Contains(t, w.Body.String(), "gin.go")
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestHandleHeadToDir - ensure the root/sub dir handles properly
|
// TestHandleHeadToDir - ensure the root/sub dir handles properly
|
||||||
@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 404)
|
assert.Equal(t, 404, w.Code)
|
||||||
assert.NotContains(t, w.Body.String(), "gin.go")
|
assert.NotContains(t, w.Body.String(), "gin.go")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,12 +310,12 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/gin.go")
|
w := performRequest(router, "GET", "/gin.go")
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Contains(t, w.Body.String(), "package gin")
|
assert.Contains(t, w.Body.String(), "package gin")
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||||
assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
||||||
assert.Equal(t, w.HeaderMap.Get("Expires"), "Mon, 02 Jan 2006 15:04:05 MST")
|
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.HeaderMap.Get("Expires"))
|
||||||
assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework")
|
assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteNotAllowedEnabled(t *testing.T) {
|
func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||||
@ -323,14 +323,14 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
|||||||
router.HandleMethodNotAllowed = true
|
router.HandleMethodNotAllowed = true
|
||||||
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, w.Code, http.StatusMethodNotAllowed)
|
assert.Equal(t, http.StatusMethodNotAllowed, 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, w.Body.String(), "responseText")
|
assert.Equal(t, "responseText", w.Body.String())
|
||||||
assert.Equal(t, w.Code, http.StatusTeapot)
|
assert.Equal(t, http.StatusTeapot, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteNotAllowedDisabled(t *testing.T) {
|
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||||
@ -338,14 +338,14 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
|
|||||||
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, w.Code, 404)
|
assert.Equal(t, 404, 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, w.Body.String(), "404 page not found")
|
assert.Equal(t, "404 page not found", w.Body.String())
|
||||||
assert.Equal(t, w.Code, 404)
|
assert.Equal(t, 404, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterNotFound(t *testing.T) {
|
func TestRouterNotFound(t *testing.T) {
|
||||||
@ -372,9 +372,9 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tr := range testRoutes {
|
for _, tr := range testRoutes {
|
||||||
w := performRequest(router, "GET", tr.route)
|
w := performRequest(router, "GET", tr.route)
|
||||||
assert.Equal(t, w.Code, tr.code)
|
assert.Equal(t, tr.code, w.Code)
|
||||||
if w.Code != 404 {
|
if w.Code != 404 {
|
||||||
assert.Equal(t, fmt.Sprint(w.Header().Get("Location")), tr.location)
|
assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,20 +385,20 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
notFound = true
|
notFound = true
|
||||||
})
|
})
|
||||||
w := performRequest(router, "GET", "/nope")
|
w := performRequest(router, "GET", "/nope")
|
||||||
assert.Equal(t, w.Code, 404)
|
assert.Equal(t, 404, 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, w.Code, 307)
|
assert.Equal(t, 307, w.Code)
|
||||||
assert.Equal(t, fmt.Sprint(w.Header()), "map[Location:[/path]]")
|
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, w.Code, 404)
|
assert.Equal(t, 404, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteRawPath(t *testing.T) {
|
func TestRouteRawPath(t *testing.T) {
|
||||||
@ -409,15 +409,15 @@ func TestRouteRawPath(t *testing.T) {
|
|||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
num := c.Params.ByName("num")
|
num := c.Params.ByName("num")
|
||||||
|
|
||||||
assert.Equal(t, c.Param("name"), name)
|
assert.Equal(t, name, c.Param("name"))
|
||||||
assert.Equal(t, c.Param("num"), num)
|
assert.Equal(t, num, c.Param("num"))
|
||||||
|
|
||||||
assert.Equal(t, "Some/Other/Project", name)
|
assert.Equal(t, "Some/Other/Project", name)
|
||||||
assert.Equal(t, "222", num)
|
assert.Equal(t, "222", num)
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
|
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteRawPathNoUnescape(t *testing.T) {
|
func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||||
@ -429,15 +429,15 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
|||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
num := c.Params.ByName("num")
|
num := c.Params.ByName("num")
|
||||||
|
|
||||||
assert.Equal(t, c.Param("name"), name)
|
assert.Equal(t, name, c.Param("name"))
|
||||||
assert.Equal(t, c.Param("num"), num)
|
assert.Equal(t, num, c.Param("num"))
|
||||||
|
|
||||||
assert.Equal(t, "Some%2FOther%2FProject", name)
|
assert.Equal(t, "Some%2FOther%2FProject", name)
|
||||||
assert.Equal(t, "333", num)
|
assert.Equal(t, "333", num)
|
||||||
})
|
})
|
||||||
|
|
||||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
|
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
|
||||||
assert.Equal(t, w.Code, 200)
|
assert.Equal(t, 200, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||||
|
14
tree.go
14
tree.go
@ -87,13 +87,13 @@ const (
|
|||||||
|
|
||||||
type node struct {
|
type node struct {
|
||||||
path string
|
path string
|
||||||
wildChild bool
|
|
||||||
nType nodeType
|
|
||||||
maxParams uint8
|
|
||||||
indices string
|
indices string
|
||||||
children []*node
|
children []*node
|
||||||
handlers HandlersChain
|
handlers HandlersChain
|
||||||
priority uint32
|
priority uint32
|
||||||
|
nType nodeType
|
||||||
|
maxParams uint8
|
||||||
|
wildChild bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// increments priority of the given child and reorders if necessary.
|
// increments priority of the given child and reorders if necessary.
|
||||||
@ -384,7 +384,7 @@ walk: // Outer loop for walking the tree
|
|||||||
// Nothing found.
|
// Nothing found.
|
||||||
// We can recommend to redirect to the same URL without a
|
// We can recommend to redirect to the same URL without a
|
||||||
// trailing slash if a leaf exists for that path.
|
// trailing slash if a leaf exists for that path.
|
||||||
tsr = (path == "/" && n.handlers != nil)
|
tsr = path == "/" && n.handlers != nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,7 +424,7 @@ walk: // Outer loop for walking the tree
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ... but we can't
|
// ... but we can't
|
||||||
tsr = (len(path) == end+1)
|
tsr = len(path) == end+1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,7 +435,7 @@ walk: // Outer loop for walking the tree
|
|||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
// trailing slash exists for TSR recommendation
|
// trailing slash exists for TSR recommendation
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
tsr = (n.path == "/" && n.handlers != nil)
|
tsr = n.path == "/" && n.handlers != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -530,7 +530,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
|||||||
|
|
||||||
// Nothing found. We can recommend to redirect to the same URL
|
// Nothing found. We can recommend to redirect to the same URL
|
||||||
// without a trailing slash if a leaf exists for that path
|
// without a trailing slash if a leaf exists for that path
|
||||||
found = (fixTrailingSlash && path == "/" && n.handlers != nil)
|
found = fixTrailingSlash && path == "/" && n.handlers != nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
tree_test.go
11
tree_test.go
@ -5,22 +5,11 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func printChildren(n *node, prefix string) {
|
|
||||||
fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handlers, n.wildChild, n.nType)
|
|
||||||
for l := len(n.path); l > 0; l-- {
|
|
||||||
prefix += " "
|
|
||||||
}
|
|
||||||
for _, child := range n.children {
|
|
||||||
printChildren(child, prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used as a workaround since we can't compare functions or their addressses
|
// Used as a workaround since we can't compare functions or their addressses
|
||||||
var fakeHandlerValue string
|
var fakeHandlerValue string
|
||||||
|
|
||||||
|
22
utils.go
22
utils.go
@ -33,18 +33,23 @@ func Bind(val interface{}) HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WrapF is a helper function for wrapping http.HandlerFunc
|
||||||
|
// Returns a Gin middleware
|
||||||
func WrapF(f http.HandlerFunc) HandlerFunc {
|
func WrapF(f http.HandlerFunc) HandlerFunc {
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
f(c.Writer, c.Request)
|
f(c.Writer, c.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WrapH is a helper function for wrapping http.Handler
|
||||||
|
// Returns a Gin middleware
|
||||||
func WrapH(h http.Handler) HandlerFunc {
|
func WrapH(h http.Handler) HandlerFunc {
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
h.ServeHTTP(c.Writer, c.Request)
|
h.ServeHTTP(c.Writer, c.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// H is a shortcup for map[string]interface{}
|
||||||
type H map[string]interface{}
|
type H map[string]interface{}
|
||||||
|
|
||||||
// MarshalXML allows type H to be used with xml.Marshal.
|
// MarshalXML allows type H to be used with xml.Marshal.
|
||||||
@ -65,10 +70,8 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
|
|
||||||
return err
|
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func assert1(guard bool, text string) {
|
func assert1(guard bool, text string) {
|
||||||
@ -103,7 +106,7 @@ func parseAccept(acceptHeader string) []string {
|
|||||||
if index := strings.IndexByte(part, ';'); index >= 0 {
|
if index := strings.IndexByte(part, ';'); index >= 0 {
|
||||||
part = part[0:index]
|
part = part[0:index]
|
||||||
}
|
}
|
||||||
if part = strings.TrimSpace(part); len(part) > 0 {
|
if part = strings.TrimSpace(part); part != "" {
|
||||||
out = append(out, part)
|
out = append(out, part)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,11 +114,10 @@ func parseAccept(acceptHeader string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func lastChar(str string) uint8 {
|
func lastChar(str string) uint8 {
|
||||||
size := len(str)
|
if str == "" {
|
||||||
if size == 0 {
|
|
||||||
panic("The length of the string can't be 0")
|
panic("The length of the string can't be 0")
|
||||||
}
|
}
|
||||||
return str[size-1]
|
return str[len(str)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func nameOfFunction(f interface{}) string {
|
func nameOfFunction(f interface{}) string {
|
||||||
@ -123,7 +125,7 @@ func nameOfFunction(f interface{}) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func joinPaths(absolutePath, relativePath string) string {
|
func joinPaths(absolutePath, relativePath string) string {
|
||||||
if len(relativePath) == 0 {
|
if relativePath == "" {
|
||||||
return absolutePath
|
return absolutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +140,7 @@ func joinPaths(absolutePath, relativePath string) string {
|
|||||||
func resolveAddress(addr []string) string {
|
func resolveAddress(addr []string) string {
|
||||||
switch len(addr) {
|
switch len(addr) {
|
||||||
case 0:
|
case 0:
|
||||||
if port := os.Getenv("PORT"); len(port) > 0 {
|
if port := os.Getenv("PORT"); port != "" {
|
||||||
debugPrint("Environment variable PORT=\"%s\"", port)
|
debugPrint("Environment variable PORT=\"%s\"", port)
|
||||||
return ":" + port
|
return ":" + port
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@ 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, req.Method, "POST")
|
assert.Equal(t.T, "POST", req.Method)
|
||||||
assert.Equal(t.T, req.URL.Path, "/path")
|
assert.Equal(t.T, "/path", req.URL.Path)
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
fmt.Fprint(w, "hello")
|
fmt.Fprint(w, "hello")
|
||||||
}
|
}
|
||||||
@ -31,50 +31,50 @@ func TestWrap(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.POST("/path", WrapH(&testStruct{t}))
|
router.POST("/path", WrapH(&testStruct{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, req.Method, "GET")
|
assert.Equal(t, "GET", req.Method)
|
||||||
assert.Equal(t, req.URL.Path, "/path2")
|
assert.Equal(t, "/path2", req.URL.Path)
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
fmt.Fprint(w, "hola!")
|
fmt.Fprint(w, "hola!")
|
||||||
}))
|
}))
|
||||||
|
|
||||||
w := performRequest(router, "POST", "/path")
|
w := performRequest(router, "POST", "/path")
|
||||||
assert.Equal(t, w.Code, 500)
|
assert.Equal(t, 500, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "hello")
|
assert.Equal(t, "hello", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/path2")
|
w = performRequest(router, "GET", "/path2")
|
||||||
assert.Equal(t, w.Code, 400)
|
assert.Equal(t, 400, w.Code)
|
||||||
assert.Equal(t, w.Body.String(), "hola!")
|
assert.Equal(t, "hola!", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLastChar(t *testing.T) {
|
func TestLastChar(t *testing.T) {
|
||||||
assert.Equal(t, lastChar("hola"), uint8('a'))
|
assert.Equal(t, uint8('a'), lastChar("hola"))
|
||||||
assert.Equal(t, lastChar("adios"), uint8('s'))
|
assert.Equal(t, uint8('s'), lastChar("adios"))
|
||||||
assert.Panics(t, func() { lastChar("") })
|
assert.Panics(t, func() { lastChar("") })
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseAccept(t *testing.T) {
|
func TestParseAccept(t *testing.T) {
|
||||||
parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
|
parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
|
||||||
assert.Len(t, parts, 4)
|
assert.Len(t, parts, 4)
|
||||||
assert.Equal(t, parts[0], "text/html")
|
assert.Equal(t, "text/html", parts[0])
|
||||||
assert.Equal(t, parts[1], "application/xhtml+xml")
|
assert.Equal(t, "application/xhtml+xml", parts[1])
|
||||||
assert.Equal(t, parts[2], "application/xml")
|
assert.Equal(t, "application/xml", parts[2])
|
||||||
assert.Equal(t, parts[3], "*/*")
|
assert.Equal(t, "*/*", parts[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChooseData(t *testing.T) {
|
func TestChooseData(t *testing.T) {
|
||||||
A := "a"
|
A := "a"
|
||||||
B := "b"
|
B := "b"
|
||||||
assert.Equal(t, chooseData(A, B), A)
|
assert.Equal(t, A, chooseData(A, B))
|
||||||
assert.Equal(t, chooseData(nil, B), B)
|
assert.Equal(t, B, chooseData(nil, B))
|
||||||
assert.Panics(t, func() { chooseData(nil, nil) })
|
assert.Panics(t, func() { chooseData(nil, nil) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilterFlags(t *testing.T) {
|
func TestFilterFlags(t *testing.T) {
|
||||||
result := filterFlags("text/html ")
|
result := filterFlags("text/html ")
|
||||||
assert.Equal(t, result, "text/html")
|
assert.Equal(t, "text/html", result)
|
||||||
|
|
||||||
result = filterFlags("text/html;")
|
result = filterFlags("text/html;")
|
||||||
assert.Equal(t, result, "text/html")
|
assert.Equal(t, "text/html", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFunctionName(t *testing.T) {
|
func TestFunctionName(t *testing.T) {
|
||||||
@ -86,16 +86,16 @@ func somefunction() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestJoinPaths(t *testing.T) {
|
func TestJoinPaths(t *testing.T) {
|
||||||
assert.Equal(t, joinPaths("", ""), "")
|
assert.Equal(t, "", joinPaths("", ""))
|
||||||
assert.Equal(t, joinPaths("", "/"), "/")
|
assert.Equal(t, "/", joinPaths("", "/"))
|
||||||
assert.Equal(t, joinPaths("/a", ""), "/a")
|
assert.Equal(t, "/a", joinPaths("/a", ""))
|
||||||
assert.Equal(t, joinPaths("/a/", ""), "/a/")
|
assert.Equal(t, "/a/", joinPaths("/a/", ""))
|
||||||
assert.Equal(t, joinPaths("/a/", "/"), "/a/")
|
assert.Equal(t, "/a/", joinPaths("/a/", "/"))
|
||||||
assert.Equal(t, joinPaths("/a", "/"), "/a/")
|
assert.Equal(t, "/a/", joinPaths("/a", "/"))
|
||||||
assert.Equal(t, joinPaths("/a", "/hola"), "/a/hola")
|
assert.Equal(t, "/a/hola", joinPaths("/a", "/hola"))
|
||||||
assert.Equal(t, joinPaths("/a/", "/hola"), "/a/hola")
|
assert.Equal(t, "/a/hola", joinPaths("/a/", "/hola"))
|
||||||
assert.Equal(t, joinPaths("/a/", "/hola/"), "/a/hola/")
|
assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola/"))
|
||||||
assert.Equal(t, joinPaths("/a/", "/hola//"), "/a/hola/")
|
assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola//"))
|
||||||
}
|
}
|
||||||
|
|
||||||
type bindTestStruct struct {
|
type bindTestStruct struct {
|
||||||
@ -113,8 +113,8 @@ func TestBindMiddleware(t *testing.T) {
|
|||||||
})
|
})
|
||||||
performRequest(router, "GET", "/?foo=hola&bar=10")
|
performRequest(router, "GET", "/?foo=hola&bar=10")
|
||||||
assert.True(t, called)
|
assert.True(t, called)
|
||||||
assert.Equal(t, value.Foo, "hola")
|
assert.Equal(t, "hola", value.Foo)
|
||||||
assert.Equal(t, value.Bar, 10)
|
assert.Equal(t, 10, value.Bar)
|
||||||
|
|
||||||
called = false
|
called = false
|
||||||
performRequest(router, "GET", "/?foo=hola&bar=1")
|
performRequest(router, "GET", "/?foo=hola&bar=1")
|
||||||
|
24
vendor/vendor.json
vendored
24
vendor/vendor.json
vendored
@ -22,10 +22,10 @@
|
|||||||
"revisionTime": "2017-01-09T09:34:21Z"
|
"revisionTime": "2017-01-09T09:34:21Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=",
|
"checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=",
|
||||||
"path": "github.com/gin-gonic/autotls",
|
"path": "github.com/gin-gonic/autotls",
|
||||||
"revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d",
|
"revision": "8ca25fbde72bb72a00466215b94b489c71fcb815",
|
||||||
"revisionTime": "2017-04-16T09:39:34Z"
|
"revisionTime": "2017-09-16T16:54:15Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=",
|
"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=",
|
||||||
@ -34,10 +34,10 @@
|
|||||||
"revisionTime": "2017-06-01T23:02:30Z"
|
"revisionTime": "2017-06-01T23:02:30Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "0e59uuETpidkmpaRwipQ8auqwhM=",
|
"checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=",
|
||||||
"path": "github.com/json-iterator/go",
|
"path": "github.com/json-iterator/go",
|
||||||
"revision": "6b6938829d6156d7b9825f83eec757f0f571c981",
|
"revision": "36b14963da70d11297d313183d7e6388c8510e1e",
|
||||||
"revisionTime": "2017-07-18T14:19:52Z"
|
"revisionTime": "2017-08-29T15:58:51Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=",
|
"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=",
|
||||||
@ -65,6 +65,12 @@
|
|||||||
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
|
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
|
||||||
"revisionTime": "2016-09-25T22:06:09Z"
|
"revisionTime": "2016-09-25T22:06:09Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=",
|
||||||
|
"path": "github.com/thinkerou/favicon",
|
||||||
|
"revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93",
|
||||||
|
"revisionTime": "2017-07-10T14:05:20Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=",
|
"checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=",
|
||||||
"path": "github.com/ugorji/go/codec",
|
"path": "github.com/ugorji/go/codec",
|
||||||
@ -90,6 +96,12 @@
|
|||||||
"revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a",
|
"revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a",
|
||||||
"revisionTime": "2016-10-18T08:54:36Z"
|
"revisionTime": "2016-10-18T08:54:36Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=",
|
||||||
|
"path": "golang.org/x/sync/errgroup",
|
||||||
|
"revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690",
|
||||||
|
"revisionTime": "2017-07-19T03:38:01Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
|
"checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
|
||||||
"path": "golang.org/x/sys/unix",
|
"path": "golang.org/x/sys/unix",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user