mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-23 18:22:23 +08:00
commit
0fda834942
@ -4,6 +4,7 @@ go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- master
|
||||
|
||||
git:
|
||||
|
322
README.md
322
README.md
@ -40,7 +40,7 @@ $ go run example.go
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
|
||||
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
|
||||
|
||||
[See all benchmarks](/BENCHMARKS.md)
|
||||
|
||||
@ -74,10 +74,10 @@ BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104
|
||||
BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848
|
||||
BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609
|
||||
|
||||
(1): Total Repetitions achieved in constant time, higher means more confident result
|
||||
(2): Single Repetition Duration (ns/op), lower is better
|
||||
(3): Heap Memory (B/op), lower is better
|
||||
(4): Average Allocations per Repetition (allocs/op), lower is better
|
||||
- (1): Total Repetitions achieved in constant time, higher means more confident result
|
||||
- (2): Single Repetition Duration (ns/op), lower is better
|
||||
- (3): Heap Memory (B/op), lower is better
|
||||
- (4): Average Allocations per Repetition (allocs/op), lower is better
|
||||
|
||||
## Gin v1. stable
|
||||
|
||||
@ -117,7 +117,7 @@ $ go get github.com/kardianos/govendor
|
||||
2. Create your project folder and `cd` inside
|
||||
|
||||
```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
|
||||
@ -277,14 +277,16 @@ References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail
|
||||
```go
|
||||
func main() {
|
||||
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) {
|
||||
// single file
|
||||
file, _ := c.FormFile("file")
|
||||
log.Println(file.Filename)
|
||||
|
||||
|
||||
// Upload the file to specific dst.
|
||||
// c.SaveUploadedFile(file, dst)
|
||||
|
||||
// c.SaveUploadedFile(file, dst)
|
||||
|
||||
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
|
||||
})
|
||||
router.Run(":8080")
|
||||
@ -306,6 +308,8 @@ See the detail [example code](examples/upload-file/multiple).
|
||||
```go
|
||||
func main() {
|
||||
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) {
|
||||
// Multipart form
|
||||
form, _ := c.MultipartForm()
|
||||
@ -313,9 +317,9 @@ func main() {
|
||||
|
||||
for _, file := range files {
|
||||
log.Println(file.Filename)
|
||||
|
||||
|
||||
// Upload the file to specific dst.
|
||||
// c.SaveUploadedFile(file, dst)
|
||||
// c.SaveUploadedFile(file, dst)
|
||||
}
|
||||
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
|
||||
})
|
||||
@ -381,9 +385,10 @@ func main() {
|
||||
r := gin.New()
|
||||
|
||||
// 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())
|
||||
|
||||
|
||||
// Recovery middleware recovers from any panics and writes a 500 if there was one.
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
@ -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
|
||||
|
||||
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"`.
|
||||
|
||||
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
|
||||
// Binding from JSON
|
||||
@ -437,12 +472,14 @@ func main() {
|
||||
// Example for binding JSON ({"user": "manu", "password": "123"})
|
||||
router.POST("/loginJSON", func(c *gin.Context) {
|
||||
var json Login
|
||||
if c.BindJSON(&json) == nil {
|
||||
if err := c.ShouldBindJSON(&json); err == nil {
|
||||
if json.User == "manu" && json.Password == "123" {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
}
|
||||
})
|
||||
|
||||
@ -450,12 +487,14 @@ func main() {
|
||||
router.POST("/loginForm", func(c *gin.Context) {
|
||||
var form Login
|
||||
// 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" {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
}
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
`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
|
||||
package main
|
||||
@ -490,7 +612,7 @@ func main() {
|
||||
|
||||
func startPage(c *gin.Context) {
|
||||
var person Person
|
||||
if c.BindQuery(&person) == nil {
|
||||
if c.ShouldBindQuery(&person) == nil {
|
||||
log.Println("====== Only Bind By Query String ======")
|
||||
log.Println(person.Name)
|
||||
log.Println(person.Address)
|
||||
@ -509,10 +631,12 @@ package main
|
||||
|
||||
import "log"
|
||||
import "github.com/gin-gonic/gin"
|
||||
import "time"
|
||||
|
||||
type Person struct {
|
||||
Name string `form:"name"`
|
||||
Address string `form:"address"`
|
||||
Name string `form:"name"`
|
||||
Address string `form:"address"`
|
||||
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -526,15 +650,21 @@ func startPage(c *gin.Context) {
|
||||
// If `GET`, only `Form` binding engine (`query`) used.
|
||||
// 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
|
||||
if c.Bind(&person) == nil {
|
||||
if c.ShouldBind(&person) == nil {
|
||||
log.Println(person.Name)
|
||||
log.Println(person.Address)
|
||||
log.Println(person.Birthday)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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) {
|
||||
var fakeForm myForm
|
||||
c.Bind(&fakeForm)
|
||||
c.ShouldBind(&fakeForm)
|
||||
c.JSON(200, gin.H{"color": fakeForm.Colors})
|
||||
}
|
||||
|
||||
@ -599,11 +729,11 @@ func main() {
|
||||
router := gin.Default()
|
||||
router.POST("/login", func(c *gin.Context) {
|
||||
// you can bind multipart form with explicit binding declaration:
|
||||
// c.MustBindWith(&form, binding.Form)
|
||||
// or you can simply use autobinding with Bind method:
|
||||
// c.ShouldBindWith(&form, binding.Form)
|
||||
// or you can simply use autobinding with ShouldBind method:
|
||||
var form LoginForm
|
||||
// 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" {
|
||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
@ -680,7 +810,7 @@ func main() {
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### Serving static files
|
||||
|
||||
@ -791,7 +921,7 @@ You may use custom delims
|
||||
r := gin.Default()
|
||||
r.Delims("{[{", "}]}")
|
||||
r.LoadHTMLGlob("/path/to/templates"))
|
||||
```
|
||||
```
|
||||
|
||||
#### Custom Template Funcs
|
||||
|
||||
@ -941,7 +1071,7 @@ func main() {
|
||||
|
||||
### Goroutines inside a middleware
|
||||
|
||||
When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
|
||||
When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
@ -1003,7 +1133,7 @@ func main() {
|
||||
|
||||
example for 1-line LetsEncrypt HTTPS servers.
|
||||
|
||||
[embedmd]:# (examples/auto-tls/example1.go go)
|
||||
[embedmd]:# (examples/auto-tls/example1/main.go go)
|
||||
```go
|
||||
package main
|
||||
|
||||
@ -1028,7 +1158,7 @@ func main() {
|
||||
|
||||
example for custom autocert manager.
|
||||
|
||||
[embedmd]:# (examples/auto-tls/example2.go go)
|
||||
[embedmd]:# (examples/auto-tls/example2/main.go go)
|
||||
```go
|
||||
package main
|
||||
|
||||
@ -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
|
||||
|
||||
Do you want to graceful restart or stop your web server?
|
||||
@ -1128,7 +1340,53 @@ func main() {
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
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 authPair struct {
|
||||
Value string
|
||||
User string
|
||||
value string
|
||||
user string
|
||||
}
|
||||
|
||||
type authPairs []authPair
|
||||
|
||||
func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||
if len(authValue) == 0 {
|
||||
if authValue == "" {
|
||||
return "", false
|
||||
}
|
||||
for _, pair := range a {
|
||||
if pair.Value == authValue {
|
||||
return pair.User, true
|
||||
if pair.value == authValue {
|
||||
return pair.user, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
@ -47,7 +47,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
|
||||
pairs := processAccounts(accounts)
|
||||
return func(c *Context) {
|
||||
// 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 {
|
||||
// Credentials doesn't match, we return 401 and abort handlers chain.
|
||||
c.Header("WWW-Authenticate", realm)
|
||||
@ -71,11 +71,11 @@ func processAccounts(accounts Accounts) authPairs {
|
||||
assert1(len(accounts) > 0, "Empty list of authorized credentials")
|
||||
pairs := make(authPairs, 0, len(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)
|
||||
pairs = append(pairs, authPair{
|
||||
Value: value,
|
||||
User: user,
|
||||
value: value,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
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.Contains(t, pairs, authPair{
|
||||
User: "bar",
|
||||
Value: "Basic YmFyOmZvbw==",
|
||||
user: "bar",
|
||||
value: "Basic YmFyOmZvbw==",
|
||||
})
|
||||
assert.Contains(t, pairs, authPair{
|
||||
User: "foo",
|
||||
Value: "Basic Zm9vOmJhcg==",
|
||||
user: "foo",
|
||||
value: "Basic Zm9vOmJhcg==",
|
||||
})
|
||||
assert.Contains(t, pairs, authPair{
|
||||
User: "admin",
|
||||
Value: "Basic YWRtaW46cGFzc3dvcmQ=",
|
||||
user: "admin",
|
||||
value: "Basic YWRtaW46cGFzc3dvcmQ=",
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -59,8 +59,6 @@ func BenchmarkOneRouteJSON(B *testing.B) {
|
||||
runRequest(B, router, "GET", "/json")
|
||||
}
|
||||
|
||||
var htmlContentType = []string{"text/html; charset=utf-8"}
|
||||
|
||||
func BenchmarkOneRouteHTML(B *testing.B) {
|
||||
router := New()
|
||||
t := template.Must(template.New("index").Parse(`
|
||||
|
@ -4,7 +4,11 @@
|
||||
|
||||
package binding
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
const (
|
||||
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.
|
||||
// Otherwise nil must be returned.
|
||||
ValidateStruct(interface{}) error
|
||||
|
||||
// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
|
||||
// NOTE: if the key already exists, the previous validation function will be replaced.
|
||||
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
||||
RegisterValidation(string, validator.Func) error
|
||||
}
|
||||
|
||||
var Validator StructValidator = &defaultValidator{}
|
||||
|
@ -28,6 +28,11 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error {
|
||||
v.lazyinit()
|
||||
return v.validate.RegisterValidation(key, fn)
|
||||
}
|
||||
|
||||
func (v *defaultValidator) lazyinit() {
|
||||
v.once.Do(func() {
|
||||
config := &validator.Config{TagName: "binding"}
|
||||
|
@ -6,6 +6,8 @@ package binding
|
||||
|
||||
import "net/http"
|
||||
|
||||
const defaultMemory = 32 * 1024 * 1024
|
||||
|
||||
type formBinding struct{}
|
||||
type formPostBinding struct{}
|
||||
type formMultipartBinding struct{}
|
||||
@ -18,7 +20,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
req.ParseMultipartForm(32 << 10) // 32 MB
|
||||
req.ParseMultipartForm(defaultMemory)
|
||||
if err := mapForm(obj, req.Form); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -44,7 +46,7 @@ func (formMultipartBinding) Name() string {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -171,12 +179,3 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
||||
value.Set(reflect.ValueOf(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't pass in pointers to bind to. Can lead to bugs. See:
|
||||
// https://github.com/codegangsta/martini-contrib/issues/40
|
||||
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
|
||||
func ensureNotPointer(obj interface{}) {
|
||||
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
|
||||
panic("Pointers are not accepted as binding models")
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,7 @@ import (
|
||||
"github.com/gin-gonic/gin/json"
|
||||
)
|
||||
|
||||
var (
|
||||
EnableDecoderUseNumber = false
|
||||
)
|
||||
var EnableDecoderUseNumber = false
|
||||
|
||||
type jsonBinding struct{}
|
||||
|
||||
|
@ -4,9 +4,7 @@
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
import "net/http"
|
||||
|
||||
type queryBinding struct{}
|
||||
|
||||
|
@ -6,10 +6,12 @@ package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
)
|
||||
|
||||
type testInterface interface {
|
||||
@ -190,3 +192,42 @@ func TestValidatePrimitives(t *testing.T) {
|
||||
assert.NoError(t, validate(&str))
|
||||
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
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMemory = 32 << 20 // 32 MB
|
||||
abortIndex int8 = math.MaxInt8 / 2
|
||||
)
|
||||
const abortIndex int8 = math.MaxInt8 / 2
|
||||
|
||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
||||
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
||||
@ -106,8 +103,7 @@ func (c *Context) Handler() HandlerFunc {
|
||||
// See example in GitHub.
|
||||
func (c *Context) Next() {
|
||||
c.index++
|
||||
s := int8(len(c.handlers))
|
||||
for ; c.index < s; c.index++ {
|
||||
for s := int8(len(c.handlers)); c.index < s; c.index++ {
|
||||
c.handlers[c.index](c)
|
||||
}
|
||||
}
|
||||
@ -407,7 +403,7 @@ func (c *Context) PostFormArray(key string) []string {
|
||||
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||
req := c.Request
|
||||
req.ParseForm()
|
||||
req.ParseMultipartForm(defaultMemory)
|
||||
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||
if values := req.PostForm[key]; len(values) > 0 {
|
||||
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.
|
||||
func (c *Context) MultipartForm() (*multipart.Form, error) {
|
||||
err := c.Request.ParseMultipartForm(defaultMemory)
|
||||
err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||
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:
|
||||
// "application/json" --> JSON binding
|
||||
// "application/xml" --> XML binding
|
||||
// otherwise --> returns an error
|
||||
// otherwise --> returns an error.
|
||||
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||
// It decodes the json payload into the struct specified as a pointer.
|
||||
// It will writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
|
||||
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
|
||||
func (c *Context) Bind(obj interface{}) error {
|
||||
b := binding.Default(c.Request.Method, c.ContentType())
|
||||
return c.MustBindWith(obj, b)
|
||||
@ -483,6 +479,29 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
|
||||
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.
|
||||
// See the binding package.
|
||||
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
|
||||
@ -499,17 +518,17 @@ func (c *Context) ClientIP() string {
|
||||
clientIP = clientIP[0:index]
|
||||
}
|
||||
clientIP = strings.TrimSpace(clientIP)
|
||||
if len(clientIP) > 0 {
|
||||
if clientIP != "" {
|
||||
return clientIP
|
||||
}
|
||||
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
||||
if len(clientIP) > 0 {
|
||||
if clientIP != "" {
|
||||
return clientIP
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -537,10 +556,7 @@ func (c *Context) IsWebsocket() bool {
|
||||
}
|
||||
|
||||
func (c *Context) requestHeader(key string) string {
|
||||
if values, _ := c.Request.Header[key]; len(values) > 0 {
|
||||
return values[0]
|
||||
}
|
||||
return ""
|
||||
return c.Request.Header.Get(key)
|
||||
}
|
||||
|
||||
/************************************/
|
||||
@ -569,7 +585,7 @@ func (c *Context) Status(code int) {
|
||||
// It writes a header in the response.
|
||||
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
||||
func (c *Context) Header(key, value string) {
|
||||
if len(value) == 0 {
|
||||
if value == "" {
|
||||
c.Writer.Header().Del(key)
|
||||
} else {
|
||||
c.Writer.Header().Set(key, value)
|
||||
@ -586,6 +602,9 @@ func (c *Context) GetRawData() ([]byte, error) {
|
||||
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) {
|
||||
if 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) {
|
||||
cookie, err := c.Request.Cookie(name)
|
||||
if err != nil {
|
||||
|
385
context_test.go
385
context_test.go
@ -45,6 +45,7 @@ func createMultipartRequest() *http.Request {
|
||||
must(mw.WriteField("id", ""))
|
||||
must(mw.WriteField("time_local", "31/12/2016 14:55"))
|
||||
must(mw.WriteField("time_utc", "31/12/2016 14:55"))
|
||||
must(mw.WriteField("time_location", "31/12/2016 14:55"))
|
||||
req, err := http.NewRequest("POST", "/", body)
|
||||
must(err)
|
||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||
@ -179,14 +180,14 @@ func TestContextSetGet(t *testing.T) {
|
||||
c.Set("foo", "bar")
|
||||
|
||||
value, err := c.Get("foo")
|
||||
assert.Equal(t, value, "bar")
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.True(t, err)
|
||||
|
||||
value, err = c.Get("foo2")
|
||||
assert.Nil(t, value)
|
||||
assert.False(t, err)
|
||||
|
||||
assert.Equal(t, c.MustGet("foo"), "bar")
|
||||
assert.Equal(t, "bar", c.MustGet("foo"))
|
||||
assert.Panics(t, func() { c.MustGet("no_exist") })
|
||||
}
|
||||
|
||||
@ -220,7 +221,7 @@ func TestContextGetString(t *testing.T) {
|
||||
func TestContextSetGetBool(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("bool", true)
|
||||
assert.Equal(t, true, c.GetBool("bool"))
|
||||
assert.True(t, c.GetBool("bool"))
|
||||
}
|
||||
|
||||
func TestContextGetInt(t *testing.T) {
|
||||
@ -337,26 +338,26 @@ func TestContextQuery(t *testing.T) {
|
||||
|
||||
value, ok := c.GetQuery("foo")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, value, "bar")
|
||||
assert.Equal(t, c.DefaultQuery("foo", "none"), "bar")
|
||||
assert.Equal(t, c.Query("foo"), "bar")
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.Equal(t, "bar", c.DefaultQuery("foo", "none"))
|
||||
assert.Equal(t, "bar", c.Query("foo"))
|
||||
|
||||
value, ok = c.GetQuery("page")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, value, "10")
|
||||
assert.Equal(t, c.DefaultQuery("page", "0"), "10")
|
||||
assert.Equal(t, c.Query("page"), "10")
|
||||
assert.Equal(t, "10", value)
|
||||
assert.Equal(t, "10", c.DefaultQuery("page", "0"))
|
||||
assert.Equal(t, "10", c.Query("page"))
|
||||
|
||||
value, ok = c.GetQuery("id")
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, value)
|
||||
assert.Equal(t, c.DefaultQuery("id", "nada"), "")
|
||||
assert.Empty(t, c.DefaultQuery("id", "nada"))
|
||||
assert.Empty(t, c.Query("id"))
|
||||
|
||||
value, ok = c.GetQuery("NoKey")
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, value)
|
||||
assert.Equal(t, c.DefaultQuery("NoKey", "nada"), "nada")
|
||||
assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada"))
|
||||
assert.Empty(t, c.Query("NoKey"))
|
||||
|
||||
// postform should not mess
|
||||
@ -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.Header.Add("Content-Type", MIMEPOSTForm)
|
||||
|
||||
assert.Equal(t, c.DefaultPostForm("foo", "none"), "bar")
|
||||
assert.Equal(t, c.PostForm("foo"), "bar")
|
||||
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
|
||||
assert.Equal(t, "bar", c.PostForm("foo"))
|
||||
assert.Empty(t, c.Query("foo"))
|
||||
|
||||
value, ok := c.GetPostForm("page")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, value, "11")
|
||||
assert.Equal(t, c.DefaultPostForm("page", "0"), "11")
|
||||
assert.Equal(t, c.PostForm("page"), "11")
|
||||
assert.Equal(t, c.Query("page"), "")
|
||||
assert.Equal(t, "11", value)
|
||||
assert.Equal(t, "11", c.DefaultPostForm("page", "0"))
|
||||
assert.Equal(t, "11", c.PostForm("page"))
|
||||
assert.Empty(t, c.Query("page"))
|
||||
|
||||
value, ok = c.GetPostForm("both")
|
||||
assert.True(t, ok)
|
||||
assert.Empty(t, value)
|
||||
assert.Empty(t, c.PostForm("both"))
|
||||
assert.Equal(t, c.DefaultPostForm("both", "nothing"), "")
|
||||
assert.Equal(t, c.Query("both"), "GET")
|
||||
assert.Empty(t, c.DefaultPostForm("both", "nothing"))
|
||||
assert.Equal(t, "GET", c.Query("both"), "GET")
|
||||
|
||||
value, ok = c.GetQuery("id")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, value, "main")
|
||||
assert.Equal(t, c.DefaultPostForm("id", "000"), "000")
|
||||
assert.Equal(t, c.Query("id"), "main")
|
||||
assert.Equal(t, "main", value)
|
||||
assert.Equal(t, "000", c.DefaultPostForm("id", "000"))
|
||||
assert.Equal(t, "main", c.Query("id"))
|
||||
assert.Empty(t, c.PostForm("id"))
|
||||
|
||||
value, ok = c.GetQuery("NoKey")
|
||||
@ -403,8 +404,8 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
||||
value, ok = c.GetPostForm("NoKey")
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, value)
|
||||
assert.Equal(t, c.DefaultPostForm("NoKey", "nada"), "nada")
|
||||
assert.Equal(t, c.DefaultQuery("NoKey", "nothing"), "nothing")
|
||||
assert.Equal(t, "nada", c.DefaultPostForm("NoKey", "nada"))
|
||||
assert.Equal(t, "nothing", c.DefaultQuery("NoKey", "nothing"))
|
||||
assert.Empty(t, c.PostForm("NoKey"))
|
||||
assert.Empty(t, c.Query("NoKey"))
|
||||
|
||||
@ -416,11 +417,11 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
||||
Array []string `form:"array[]"`
|
||||
}
|
||||
assert.NoError(t, c.Bind(&obj))
|
||||
assert.Equal(t, obj.Foo, "bar")
|
||||
assert.Equal(t, obj.ID, "main")
|
||||
assert.Equal(t, obj.Page, 11)
|
||||
assert.Equal(t, obj.Both, "")
|
||||
assert.Equal(t, obj.Array, []string{"first", "second"})
|
||||
assert.Equal(t, "bar", obj.Foo, "bar")
|
||||
assert.Equal(t, "main", obj.ID, "main")
|
||||
assert.Equal(t, 11, obj.Page, 11)
|
||||
assert.Empty(t, obj.Both)
|
||||
assert.Equal(t, []string{"first", "second"}, obj.Array)
|
||||
|
||||
values, ok := c.GetQueryArray("array[]")
|
||||
assert.True(t, ok)
|
||||
@ -444,44 +445,48 @@ func TestContextPostFormMultipart(t *testing.T) {
|
||||
c.Request = createMultipartRequest()
|
||||
|
||||
var obj struct {
|
||||
Foo string `form:"foo"`
|
||||
Bar string `form:"bar"`
|
||||
BarAsInt int `form:"bar"`
|
||||
Array []string `form:"array"`
|
||||
ID string `form:"id"`
|
||||
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"`
|
||||
BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"`
|
||||
Foo string `form:"foo"`
|
||||
Bar string `form:"bar"`
|
||||
BarAsInt int `form:"bar"`
|
||||
Array []string `form:"array"`
|
||||
ID string `form:"id"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
assert.NoError(t, c.Bind(&obj))
|
||||
assert.Equal(t, obj.Foo, "bar")
|
||||
assert.Equal(t, obj.Bar, "10")
|
||||
assert.Equal(t, obj.BarAsInt, 10)
|
||||
assert.Equal(t, obj.Array, []string{"first", "second"})
|
||||
assert.Equal(t, obj.ID, "")
|
||||
assert.Equal(t, obj.TimeLocal.Format("02/01/2006 15:04"), "31/12/2016 14:55")
|
||||
assert.Equal(t, obj.TimeLocal.Location(), time.Local)
|
||||
assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55")
|
||||
assert.Equal(t, obj.TimeUTC.Location(), time.UTC)
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, "10", obj.Bar)
|
||||
assert.Equal(t, 10, obj.BarAsInt)
|
||||
assert.Equal(t, []string{"first", "second"}, obj.Array)
|
||||
assert.Empty(t, obj.ID)
|
||||
assert.Equal(t, "31/12/2016 14:55", obj.TimeLocal.Format("02/01/2006 15:04"))
|
||||
assert.Equal(t, time.Local, obj.TimeLocal.Location())
|
||||
assert.Equal(t, "31/12/2016 14:55", obj.TimeUTC.Format("02/01/2006 15:04"))
|
||||
assert.Equal(t, time.UTC, obj.TimeUTC.Location())
|
||||
loc, _ := time.LoadLocation("Asia/Tokyo")
|
||||
assert.Equal(t, "31/12/2016 14:55", obj.TimeLocation.Format("02/01/2006 15:04"))
|
||||
assert.Equal(t, loc, obj.TimeLocation.Location())
|
||||
assert.True(t, obj.BlankTime.IsZero())
|
||||
|
||||
value, ok := c.GetQuery("foo")
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, value)
|
||||
assert.Empty(t, c.Query("bar"))
|
||||
assert.Equal(t, c.DefaultQuery("id", "nothing"), "nothing")
|
||||
assert.Equal(t, "nothing", c.DefaultQuery("id", "nothing"))
|
||||
|
||||
value, ok = c.GetPostForm("foo")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, value, "bar")
|
||||
assert.Equal(t, c.PostForm("foo"), "bar")
|
||||
assert.Equal(t, "bar", value)
|
||||
assert.Equal(t, "bar", c.PostForm("foo"))
|
||||
|
||||
value, ok = c.GetPostForm("array")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, value, "first")
|
||||
assert.Equal(t, c.PostForm("array"), "first")
|
||||
assert.Equal(t, "first", value)
|
||||
assert.Equal(t, "first", c.PostForm("array"))
|
||||
|
||||
assert.Equal(t, c.DefaultPostForm("bar", "nothing"), "10")
|
||||
assert.Equal(t, "10", c.DefaultPostForm("bar", "nothing"))
|
||||
|
||||
value, ok = c.GetPostForm("id")
|
||||
assert.True(t, ok)
|
||||
@ -492,7 +497,7 @@ func TestContextPostFormMultipart(t *testing.T) {
|
||||
value, ok = c.GetPostForm("nokey")
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, value)
|
||||
assert.Equal(t, c.DefaultPostForm("nokey", "nothing"), "nothing")
|
||||
assert.Equal(t, "nothing", c.DefaultPostForm("nokey", "nothing"))
|
||||
|
||||
values, ok := c.GetPostFormArray("array")
|
||||
assert.True(t, ok)
|
||||
@ -514,13 +519,13 @@ func TestContextPostFormMultipart(t *testing.T) {
|
||||
func TestContextSetCookie(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.SetCookie("user", "gin", 1, "/", "localhost", true, true)
|
||||
assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
|
||||
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
|
||||
}
|
||||
|
||||
func TestContextSetCookiePathEmpty(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.SetCookie("user", "gin", 1, "", "localhost", true, true)
|
||||
assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
|
||||
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie"))
|
||||
}
|
||||
|
||||
func TestContextGetCookie(t *testing.T) {
|
||||
@ -528,17 +533,17 @@ func TestContextGetCookie(t *testing.T) {
|
||||
c.Request, _ = http.NewRequest("GET", "/get", nil)
|
||||
c.Request.Header.Set("Cookie", "user=gin")
|
||||
cookie, _ := c.Cookie("user")
|
||||
assert.Equal(t, cookie, "gin")
|
||||
assert.Equal(t, "gin", cookie)
|
||||
|
||||
_, err := c.Cookie("nokey")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestContextBodyAllowedForStatus(t *testing.T) {
|
||||
assert.Equal(t, false, bodyAllowedForStatus(102))
|
||||
assert.Equal(t, false, bodyAllowedForStatus(204))
|
||||
assert.Equal(t, false, bodyAllowedForStatus(304))
|
||||
assert.Equal(t, true, bodyAllowedForStatus(500))
|
||||
assert.False(t, false, bodyAllowedForStatus(102))
|
||||
assert.False(t, false, bodyAllowedForStatus(204))
|
||||
assert.False(t, false, bodyAllowedForStatus(304))
|
||||
assert.True(t, true, bodyAllowedForStatus(500))
|
||||
}
|
||||
|
||||
type TestPanicRender struct {
|
||||
@ -584,7 +589,7 @@ func TestContextRenderNoContentJSON(t *testing.T) {
|
||||
c.JSON(204, H{"foo": "bar"})
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
@ -611,7 +616,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) {
|
||||
c.JSON(204, H{"foo": "bar"})
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@ -623,7 +628,7 @@ func TestContextRenderIndentedJSON(t *testing.T) {
|
||||
|
||||
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, "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"}})
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
@ -649,9 +654,9 @@ func TestContextRenderSecureJSON(t *testing.T) {
|
||||
router.SecureJsonPrefix("&&&START&&&")
|
||||
c.SecureJSON(201, []string{"foo", "bar"})
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that no Custom JSON is rendered if code is 204
|
||||
@ -662,8 +667,8 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
||||
c.SecureJSON(204, []string{"foo", "bar"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that the response executes the templates
|
||||
@ -671,14 +676,39 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
||||
func TestContextRenderHTML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, router := CreateTestContext(w)
|
||||
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
||||
c.HTML(201, "t", H{"name": "alexandernyquist"})
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Body.String(), "Hello alexandernyquist")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "Hello alexandernyquist", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestContextRenderHTML2(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, router := CreateTestContext(w)
|
||||
|
||||
// print debug warning log when Engine.trees > 0
|
||||
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
||||
assert.Len(t, router.trees, 1)
|
||||
|
||||
var b bytes.Buffer
|
||||
setup(&b)
|
||||
defer teardown()
|
||||
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
router.SetHTMLTemplate(templ)
|
||||
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String())
|
||||
|
||||
c.HTML(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
|
||||
@ -691,8 +721,8 @@ func TestContextRenderNoContentHTML(t *testing.T) {
|
||||
c.HTML(204, "t", H{"name": "alexandernyquist"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestContextXML tests that the response is serialized as XML
|
||||
@ -703,9 +733,9 @@ func TestContextRenderXML(t *testing.T) {
|
||||
|
||||
c.XML(201, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Body.String(), "<map><foo>bar</foo></map>")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
|
||||
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that no XML is rendered if code is 204
|
||||
@ -716,8 +746,8 @@ func TestContextRenderNoContentXML(t *testing.T) {
|
||||
c.XML(204, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8")
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestContextString tests that the response is returned
|
||||
@ -728,9 +758,9 @@ func TestContextRenderString(t *testing.T) {
|
||||
|
||||
c.String(201, "test %s %d", "string", 2)
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Body.String(), "test string 2")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "test string 2", w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that no String is rendered if code is 204
|
||||
@ -741,8 +771,8 @@ func TestContextRenderNoContentString(t *testing.T) {
|
||||
c.String(204, "test %s %d", "string", 2)
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestContextString tests that the response is returned
|
||||
@ -754,9 +784,9 @@ func TestContextRenderHTMLString(t *testing.T) {
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.String(201, "<html>%s %d</html>", "string", 3)
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Body.String(), "<html>string 3</html>")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "<html>string 3</html>", w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that no HTML String is rendered if code is 204
|
||||
@ -768,8 +798,8 @@ func TestContextRenderNoContentHTMLString(t *testing.T) {
|
||||
c.String(204, "<html>%s %d</html>", "string", 3)
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestContextData tests that the response can be written from `bytesting`
|
||||
@ -780,9 +810,9 @@ func TestContextRenderData(t *testing.T) {
|
||||
|
||||
c.Data(201, "text/csv", []byte(`foo,bar`))
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Body.String(), "foo,bar")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "foo,bar", w.Body.String())
|
||||
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// Tests that no Custom Data is rendered if code is 204
|
||||
@ -793,8 +823,8 @@ func TestContextRenderNoContentData(t *testing.T) {
|
||||
c.Data(204, "text/csv", []byte(`foo,bar`))
|
||||
|
||||
assert.Equal(t, 204, w.Code)
|
||||
assert.Equal(t, "", w.Body.String())
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv")
|
||||
assert.Empty(t, w.Body.String())
|
||||
assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestContextRenderSSE(t *testing.T) {
|
||||
@ -821,9 +851,9 @@ func TestContextRenderFile(t *testing.T) {
|
||||
c.Request, _ = http.NewRequest("GET", "/", nil)
|
||||
c.File("./gin.go")
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "func New() *Engine {")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestContextRenderYAML tests that the response is serialized as YAML
|
||||
@ -834,9 +864,9 @@ func TestContextRenderYAML(t *testing.T) {
|
||||
|
||||
c.YAML(201, H{"foo": "bar"})
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Body.String(), "foo: bar\n")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/x-yaml; charset=utf-8")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "foo: bar\n", w.Body.String())
|
||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func TestContextHeaders(t *testing.T) {
|
||||
@ -844,13 +874,13 @@ func TestContextHeaders(t *testing.T) {
|
||||
c.Header("Content-Type", "text/plain")
|
||||
c.Header("X-Custom", "value")
|
||||
|
||||
assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/plain")
|
||||
assert.Equal(t, c.Writer.Header().Get("X-Custom"), "value")
|
||||
assert.Equal(t, "text/plain", c.Writer.Header().Get("Content-Type"))
|
||||
assert.Equal(t, "value", c.Writer.Header().Get("X-Custom"))
|
||||
|
||||
c.Header("Content-Type", "text/html")
|
||||
c.Header("X-Custom", "")
|
||||
|
||||
assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/html")
|
||||
assert.Equal(t, "text/html", c.Writer.Header().Get("Content-Type"))
|
||||
_, exist := c.Writer.Header()["X-Custom"]
|
||||
assert.False(t, exist)
|
||||
}
|
||||
@ -866,8 +896,8 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) {
|
||||
|
||||
c.Redirect(301, "/path")
|
||||
c.Writer.WriteHeaderNow()
|
||||
assert.Equal(t, w.Code, 301)
|
||||
assert.Equal(t, w.Header().Get("Location"), "/path")
|
||||
assert.Equal(t, 301, w.Code)
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
||||
@ -878,8 +908,8 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) {
|
||||
c.Redirect(302, "http://google.com")
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, w.Code, 302)
|
||||
assert.Equal(t, w.Header().Get("Location"), "http://google.com")
|
||||
assert.Equal(t, 302, w.Code)
|
||||
assert.Equal(t, "http://google.com", w.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestContextRenderRedirectWith201(t *testing.T) {
|
||||
@ -890,8 +920,8 @@ func TestContextRenderRedirectWith201(t *testing.T) {
|
||||
c.Redirect(201, "/resource")
|
||||
c.Writer.WriteHeaderNow()
|
||||
|
||||
assert.Equal(t, w.Code, 201)
|
||||
assert.Equal(t, w.Header().Get("Location"), "/resource")
|
||||
assert.Equal(t, 201, w.Code)
|
||||
assert.Equal(t, "/resource", w.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestContextRenderRedirectAll(t *testing.T) {
|
||||
@ -972,8 +1002,8 @@ func TestContextNegotiationFormat(t *testing.T) {
|
||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||
|
||||
assert.Panics(t, func() { c.NegotiateFormat() })
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON)
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML)
|
||||
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
|
||||
assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON))
|
||||
}
|
||||
|
||||
func TestContextNegotiationFormatWithAccept(t *testing.T) {
|
||||
@ -981,9 +1011,9 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) {
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEXML)
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEHTML)
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON), "")
|
||||
assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML))
|
||||
assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML))
|
||||
assert.Empty(t, c.NegotiateFormat(MIMEJSON))
|
||||
}
|
||||
|
||||
func TestContextNegotiationFormatCustum(t *testing.T) {
|
||||
@ -994,9 +1024,9 @@ func TestContextNegotiationFormatCustum(t *testing.T) {
|
||||
c.Accepted = nil
|
||||
c.SetAccepted(MIMEJSON, MIMEXML)
|
||||
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON)
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEXML)
|
||||
assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON)
|
||||
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML))
|
||||
assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML))
|
||||
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON))
|
||||
}
|
||||
|
||||
func TestContextIsAborted(t *testing.T) {
|
||||
@ -1022,9 +1052,9 @@ func TestContextAbortWithStatus(t *testing.T) {
|
||||
c.index = 4
|
||||
c.AbortWithStatus(401)
|
||||
|
||||
assert.Equal(t, c.index, abortIndex)
|
||||
assert.Equal(t, c.Writer.Status(), 401)
|
||||
assert.Equal(t, w.Code, 401)
|
||||
assert.Equal(t, abortIndex, c.index)
|
||||
assert.Equal(t, 401, c.Writer.Status())
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.True(t, c.IsAborted())
|
||||
}
|
||||
|
||||
@ -1044,13 +1074,13 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
||||
|
||||
c.AbortWithStatusJSON(415, in)
|
||||
|
||||
assert.Equal(t, c.index, abortIndex)
|
||||
assert.Equal(t, c.Writer.Status(), 415)
|
||||
assert.Equal(t, w.Code, 415)
|
||||
assert.Equal(t, abortIndex, c.index)
|
||||
assert.Equal(t, 415, c.Writer.Status())
|
||||
assert.Equal(t, 415, w.Code)
|
||||
assert.True(t, c.IsAborted())
|
||||
|
||||
contentType := w.Header().Get("Content-Type")
|
||||
assert.Equal(t, contentType, "application/json; charset=utf-8")
|
||||
assert.Equal(t, "application/json; charset=utf-8", contentType)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(w.Body)
|
||||
@ -1064,7 +1094,7 @@ func TestContextError(t *testing.T) {
|
||||
|
||||
c.Error(errors.New("first error"))
|
||||
assert.Len(t, c.Errors, 1)
|
||||
assert.Equal(t, c.Errors.String(), "Error #01: first error\n")
|
||||
assert.Equal(t, "Error #01: first error\n", c.Errors.String())
|
||||
|
||||
c.Error(&Error{
|
||||
Err: errors.New("second error"),
|
||||
@ -1073,13 +1103,13 @@ func TestContextError(t *testing.T) {
|
||||
})
|
||||
assert.Len(t, c.Errors, 2)
|
||||
|
||||
assert.Equal(t, c.Errors[0].Err, errors.New("first error"))
|
||||
assert.Equal(t, errors.New("first error"), c.Errors[0].Err)
|
||||
assert.Nil(t, c.Errors[0].Meta)
|
||||
assert.Equal(t, c.Errors[0].Type, ErrorTypePrivate)
|
||||
assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type)
|
||||
|
||||
assert.Equal(t, c.Errors[1].Err, errors.New("second error"))
|
||||
assert.Equal(t, c.Errors[1].Meta, "some data 2")
|
||||
assert.Equal(t, c.Errors[1].Type, ErrorTypePublic)
|
||||
assert.Equal(t, errors.New("second error"), c.Errors[1].Err)
|
||||
assert.Equal(t, "some data 2", c.Errors[1].Meta)
|
||||
assert.Equal(t, ErrorTypePublic, c.Errors[1].Type)
|
||||
|
||||
assert.Equal(t, c.Errors.Last(), c.Errors[1])
|
||||
|
||||
@ -1097,12 +1127,12 @@ func TestContextTypedError(t *testing.T) {
|
||||
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
|
||||
|
||||
for _, err := range c.Errors.ByType(ErrorTypePublic) {
|
||||
assert.Equal(t, err.Type, ErrorTypePublic)
|
||||
assert.Equal(t, ErrorTypePublic, err.Type)
|
||||
}
|
||||
for _, err := range c.Errors.ByType(ErrorTypePrivate) {
|
||||
assert.Equal(t, err.Type, ErrorTypePrivate)
|
||||
assert.Equal(t, ErrorTypePrivate, err.Type)
|
||||
}
|
||||
assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "interno 0"})
|
||||
assert.Equal(t, []string{"externo 0", "interno 0"}, c.Errors.Errors())
|
||||
}
|
||||
|
||||
func TestContextAbortWithError(t *testing.T) {
|
||||
@ -1111,8 +1141,8 @@ func TestContextAbortWithError(t *testing.T) {
|
||||
|
||||
c.AbortWithError(401, errors.New("bad input")).SetMeta("some input")
|
||||
|
||||
assert.Equal(t, w.Code, 401)
|
||||
assert.Equal(t, c.index, abortIndex)
|
||||
assert.Equal(t, 401, w.Code)
|
||||
assert.Equal(t, abortIndex, c.index)
|
||||
assert.True(t, c.IsAborted())
|
||||
}
|
||||
|
||||
@ -1143,7 +1173,7 @@ func TestContextClientIP(t *testing.T) {
|
||||
|
||||
// no port
|
||||
c.Request.RemoteAddr = "50.50.50.50"
|
||||
assert.Equal(t, "", c.ClientIP())
|
||||
assert.Empty(t, c.ClientIP())
|
||||
}
|
||||
|
||||
func TestContextContentType(t *testing.T) {
|
||||
@ -1151,7 +1181,7 @@ func TestContextContentType(t *testing.T) {
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request.Header.Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
assert.Equal(t, c.ContentType(), "application/json")
|
||||
assert.Equal(t, "application/json", c.ContentType())
|
||||
}
|
||||
|
||||
func TestContextAutoBindJSON(t *testing.T) {
|
||||
@ -1164,8 +1194,8 @@ func TestContextAutoBindJSON(t *testing.T) {
|
||||
Bar string `json:"bar"`
|
||||
}
|
||||
assert.NoError(t, c.Bind(&obj))
|
||||
assert.Equal(t, obj.Bar, "foo")
|
||||
assert.Equal(t, obj.Foo, "bar")
|
||||
assert.Equal(t, "foo", obj.Bar)
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Empty(t, c.Errors)
|
||||
}
|
||||
|
||||
@ -1181,9 +1211,9 @@ func TestContextBindWithJSON(t *testing.T) {
|
||||
Bar string `json:"bar"`
|
||||
}
|
||||
assert.NoError(t, c.BindJSON(&obj))
|
||||
assert.Equal(t, obj.Bar, "foo")
|
||||
assert.Equal(t, obj.Foo, "bar")
|
||||
assert.Equal(t, w.Body.Len(), 0)
|
||||
assert.Equal(t, "foo", obj.Bar)
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
func TestContextBindWithQuery(t *testing.T) {
|
||||
@ -1219,10 +1249,77 @@ func TestContextBadAutoBind(t *testing.T) {
|
||||
|
||||
assert.Empty(t, obj.Bar)
|
||||
assert.Empty(t, obj.Foo)
|
||||
assert.Equal(t, w.Code, 400)
|
||||
assert.Equal(t, 400, w.Code)
|
||||
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) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
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"))
|
||||
|
||||
c.Set("foo", "bar")
|
||||
assert.Equal(t, c.Value("foo"), "bar")
|
||||
assert.Equal(t, "bar", c.Value("foo"))
|
||||
assert.Nil(t, c.Value(1))
|
||||
}
|
||||
|
||||
@ -1267,7 +1364,7 @@ func TestGetRequestHeaderValue(t *testing.T) {
|
||||
c.Request.Header.Set("Gin-Version", "1.0.0")
|
||||
|
||||
assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version"))
|
||||
assert.Equal(t, "", c.GetHeader("Connection"))
|
||||
assert.Empty(t, c.GetHeader("Connection"))
|
||||
}
|
||||
|
||||
func TestContextGetRawData(t *testing.T) {
|
||||
|
8
debug.go
8
debug.go
@ -15,7 +15,7 @@ func init() {
|
||||
}
|
||||
|
||||
// IsDebugging returns true if the framework is running in debug mode.
|
||||
// Use SetMode(gin.Release) to disable debug mode.
|
||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||
func IsDebugging() bool {
|
||||
return ginMode == debugCode
|
||||
}
|
||||
@ -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() {
|
||||
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
||||
- 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())
|
||||
}
|
||||
|
||||
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) {
|
||||
SetMode(DebugMode)
|
||||
log.SetOutput(w)
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
||||
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
|
||||
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.`)
|
||||
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
|
||||
for i, msg := range a {
|
||||
fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err)
|
||||
fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
|
||||
if msg.Meta != nil {
|
||||
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
var DB = make(map[string]string)
|
||||
|
||||
func main() {
|
||||
func setupRouter() *gin.Engine {
|
||||
// Disable Console Color
|
||||
// gin.DisableConsoleColor()
|
||||
r := gin.Default()
|
||||
@ -53,6 +53,11 @@ func main() {
|
||||
}
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := setupRouter()
|
||||
// Listen and Server in 0.0.0.0: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 {
|
||||
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()),
|
||||
"HeapInuse": stats.HeapInuse,
|
||||
"StackInuse": stats.StackInuse,
|
||||
"Mallocs": (stats.Mallocs - lastMallocs),
|
||||
"Frees": (stats.Frees - lastFrees),
|
||||
"Mallocs": stats.Mallocs - lastMallocs,
|
||||
"Frees": stats.Frees - lastFrees,
|
||||
"Inbound": uint64(messages.Get("inbound")),
|
||||
"Outbound": uint64(messages.Get("outbound")),
|
||||
"Connected": connectedUsers(),
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
|
||||
func main() {
|
||||
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.POST("/upload", func(c *gin.Context) {
|
||||
name := c.PostForm("name")
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
|
||||
func main() {
|
||||
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.POST("/upload", func(c *gin.Context) {
|
||||
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-----
|
100
gin.go
100
gin.go
@ -14,12 +14,17 @@ import (
|
||||
"github.com/gin-gonic/gin/render"
|
||||
)
|
||||
|
||||
// Version is Framework's version.
|
||||
const Version = "v1.2"
|
||||
const (
|
||||
// Version is Framework's version.
|
||||
Version = "v1.2"
|
||||
defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
)
|
||||
|
||||
var default404Body = []byte("404 page not found")
|
||||
var default405Body = []byte("405 method not allowed")
|
||||
var defaultAppEngine bool
|
||||
var (
|
||||
default404Body = []byte("404 page not found")
|
||||
default405Body = []byte("405 method not allowed")
|
||||
defaultAppEngine bool
|
||||
)
|
||||
|
||||
type HandlerFunc func(*Context)
|
||||
type HandlersChain []HandlerFunc
|
||||
@ -44,16 +49,6 @@ type RoutesInfo []RouteInfo
|
||||
// Create an instance of Engine, by using New() or Default()
|
||||
type Engine struct {
|
||||
RouterGroup
|
||||
delims render.Delims
|
||||
secureJsonPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
FuncMap template.FuncMap
|
||||
allNoRoute HandlersChain
|
||||
allNoMethod HandlersChain
|
||||
noRoute HandlersChain
|
||||
noMethod HandlersChain
|
||||
pool sync.Pool
|
||||
trees methodTrees
|
||||
|
||||
// Enables automatic redirection if the current route can't be matched but a
|
||||
// handler for the path with (without) the trailing slash exists.
|
||||
@ -88,10 +83,26 @@ type Engine struct {
|
||||
|
||||
// If enabled, the url.RawPath will be used to find parameters.
|
||||
UseRawPath bool
|
||||
|
||||
// If true, the path value will be unescaped.
|
||||
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
||||
// as url.Path gonna be used, which is already unescaped.
|
||||
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{}
|
||||
@ -120,6 +131,7 @@ func New() *Engine {
|
||||
AppEngine: defaultAppEngine,
|
||||
UseRawPath: false,
|
||||
UnescapePathValues: true,
|
||||
MaxMultipartMemory: defaultMultipartMemory,
|
||||
trees: make(methodTrees, 0, 9),
|
||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||
secureJsonPrefix: "while(1);",
|
||||
@ -133,6 +145,7 @@ func New() *Engine {
|
||||
|
||||
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||
func Default() *Engine {
|
||||
debugPrintWARNINGDefault()
|
||||
engine := New()
|
||||
engine.Use(Logger(), Recovery())
|
||||
return engine
|
||||
@ -147,22 +160,30 @@ func (engine *Engine) Delims(left, right string) *Engine {
|
||||
return engine
|
||||
}
|
||||
|
||||
// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
|
||||
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
||||
engine.secureJsonPrefix = prefix
|
||||
return engine
|
||||
}
|
||||
|
||||
// LoadHTMLGlob loads HTML files identified by glob pattern
|
||||
// and associates the result with HTML renderer.
|
||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||
left := engine.delims.Left
|
||||
right := engine.delims.Right
|
||||
|
||||
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}
|
||||
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)
|
||||
}
|
||||
|
||||
// LoadHTMLFiles loads a slice of HTML files
|
||||
// and associates the result with HTML renderer.
|
||||
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||
if IsDebugging() {
|
||||
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||
@ -173,6 +194,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||
engine.SetHTMLTemplate(templ)
|
||||
}
|
||||
|
||||
// SetHTMLTemplate associate a template with HTML renderer.
|
||||
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
||||
if len(engine.trees) > 0 {
|
||||
debugPrintWARNINGSetHTMLTemplate()
|
||||
@ -181,6 +203,7 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
||||
engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
|
||||
}
|
||||
|
||||
// SetFuncMap sets the FuncMap used for template.FuncMap.
|
||||
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
|
||||
engine.FuncMap = funcMap
|
||||
}
|
||||
@ -217,7 +240,7 @@ func (engine *Engine) rebuild405Handlers() {
|
||||
|
||||
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||
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")
|
||||
|
||||
debugPrintRoute(method, path, handlers)
|
||||
@ -314,16 +337,13 @@ func (engine *Engine) HandleContext(c *Context) {
|
||||
engine.pool.Put(c)
|
||||
}
|
||||
|
||||
func (engine *Engine) handleHTTPRequest(context *Context) {
|
||||
httpMethod := context.Request.Method
|
||||
var path string
|
||||
var unescape bool
|
||||
if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 {
|
||||
path = context.Request.URL.RawPath
|
||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
httpMethod := c.Request.Method
|
||||
path := c.Request.URL.Path
|
||||
unescape := false
|
||||
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
||||
path = c.Request.URL.RawPath
|
||||
unescape = engine.UnescapePathValues
|
||||
} else {
|
||||
path = context.Request.URL.Path
|
||||
unescape = false
|
||||
}
|
||||
|
||||
// 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 {
|
||||
root := t[i].root
|
||||
// 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 {
|
||||
context.handlers = handlers
|
||||
context.Params = params
|
||||
context.Next()
|
||||
context.writermem.WriteHeaderNow()
|
||||
c.handlers = handlers
|
||||
c.Params = params
|
||||
c.Next()
|
||||
c.writermem.WriteHeaderNow()
|
||||
return
|
||||
}
|
||||
if httpMethod != "CONNECT" && path != "/" {
|
||||
if tsr && engine.RedirectTrailingSlash {
|
||||
redirectTrailingSlash(context)
|
||||
redirectTrailingSlash(c)
|
||||
return
|
||||
}
|
||||
if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) {
|
||||
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -357,15 +377,15 @@ func (engine *Engine) handleHTTPRequest(context *Context) {
|
||||
for _, tree := range engine.trees {
|
||||
if tree.method != httpMethod {
|
||||
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
||||
context.handlers = engine.allNoMethod
|
||||
serveError(context, 405, default405Body)
|
||||
c.handlers = engine.allNoMethod
|
||||
serveError(c, 405, default405Body)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
context.handlers = engine.allNoRoute
|
||||
serveError(context, 404, default404Body)
|
||||
c.handlers = engine.allNoRoute
|
||||
serveError(c, 404, default404Body)
|
||||
}
|
||||
|
||||
var mimePlain = []string{MIMEPlain}
|
||||
@ -391,8 +411,8 @@ func redirectTrailingSlash(c *Context) {
|
||||
code = 307
|
||||
}
|
||||
|
||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||
req.URL.Path = path[:len(path)-1]
|
||||
if length := len(path); length > 1 && path[length-1] == '/' {
|
||||
req.URL.Path = path[:length-1]
|
||||
} else {
|
||||
req.URL.Path = path + "/"
|
||||
}
|
||||
|
40
ginS/gins.go
40
ginS/gins.go
@ -9,15 +9,15 @@ import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
. "github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var internalEngine *Engine
|
||||
var internalEngine *gin.Engine
|
||||
|
||||
func engine() *Engine {
|
||||
func engine() *gin.Engine {
|
||||
once.Do(func() {
|
||||
internalEngine = Default()
|
||||
internalEngine = gin.Default()
|
||||
})
|
||||
return internalEngine
|
||||
}
|
||||
@ -35,65 +35,65 @@ func SetHTMLTemplate(templ *template.Template) {
|
||||
}
|
||||
|
||||
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
||||
func NoRoute(handlers ...HandlerFunc) {
|
||||
func NoRoute(handlers ...gin.HandlerFunc) {
|
||||
engine().NoRoute(handlers...)
|
||||
}
|
||||
|
||||
// NoMethod sets the handlers called when... TODO
|
||||
func NoMethod(handlers ...HandlerFunc) {
|
||||
func NoMethod(handlers ...gin.HandlerFunc) {
|
||||
engine().NoMethod(handlers...)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
|
||||
func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
|
||||
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...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
func Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
|
||||
return engine().Any(relativePath, handlers...)
|
||||
}
|
||||
|
||||
func StaticFile(relativePath, filepath string) IRoutes {
|
||||
func StaticFile(relativePath, filepath string) gin.IRoutes {
|
||||
return engine().StaticFile(relativePath, filepath)
|
||||
}
|
||||
|
||||
@ -103,18 +103,18 @@ func StaticFile(relativePath, filepath string) IRoutes {
|
||||
// To use the operating system's file system implementation,
|
||||
// use :
|
||||
// router.Static("/static", "/var/www")
|
||||
func Static(relativePath, root string) IRoutes {
|
||||
func Static(relativePath, root string) gin.IRoutes {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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...
|
||||
// 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...)
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ func TestUnixSocket(t *testing.T) {
|
||||
c, err := net.Dial("unix", "/tmp/unix_unit_test")
|
||||
assert.NoError(t, err)
|
||||
|
||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
var response string
|
||||
for scanner.Scan() {
|
||||
|
132
gin_test.go
132
gin_test.go
@ -5,6 +5,7 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
@ -21,9 +22,9 @@ func formatAsDate(t time.Time) string {
|
||||
return fmt.Sprintf("%d/%02d/%02d", year, month, day)
|
||||
}
|
||||
|
||||
func setupHTMLFiles(t *testing.T) func() {
|
||||
func setupHTMLFiles(t *testing.T, mode string, tls bool) func() {
|
||||
go func() {
|
||||
SetMode(TestMode)
|
||||
SetMode(mode)
|
||||
router := New()
|
||||
router.Delims("{[{", "}]}")
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
@ -38,16 +39,21 @@ func setupHTMLFiles(t *testing.T) func() {
|
||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
})
|
||||
router.Run(":8888")
|
||||
if tls {
|
||||
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
|
||||
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
||||
} else {
|
||||
router.Run(":8888")
|
||||
}
|
||||
}()
|
||||
t.Log("waiting 1 second for server startup")
|
||||
time.Sleep(1 * time.Second)
|
||||
return func() {}
|
||||
}
|
||||
|
||||
func setupHTMLGlob(t *testing.T) func() {
|
||||
func setupHTMLGlob(t *testing.T, mode string, tls bool) func() {
|
||||
go func() {
|
||||
SetMode(DebugMode)
|
||||
SetMode(mode)
|
||||
router := New()
|
||||
router.Delims("{[{", "}]}")
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
@ -62,16 +68,20 @@ func setupHTMLGlob(t *testing.T) func() {
|
||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
})
|
||||
router.Run(":8888")
|
||||
if tls {
|
||||
// these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1`
|
||||
router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem")
|
||||
} else {
|
||||
router.Run(":8888")
|
||||
}
|
||||
}()
|
||||
t.Log("waiting 1 second for server startup")
|
||||
time.Sleep(1 * time.Second)
|
||||
return func() {}
|
||||
}
|
||||
|
||||
//TODO
|
||||
func TestLoadHTMLGlob(t *testing.T) {
|
||||
td := setupHTMLGlob(t)
|
||||
td := setupHTMLGlob(t, DebugMode, false)
|
||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@ -83,9 +93,55 @@ func TestLoadHTMLGlob(t *testing.T) {
|
||||
td()
|
||||
}
|
||||
|
||||
func TestLoadHTMLGlob2(t *testing.T) {
|
||||
td := setupHTMLGlob(t, TestMode, false)
|
||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<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) {
|
||||
time.Now()
|
||||
td := setupHTMLGlob(t)
|
||||
td := setupHTMLGlob(t, DebugMode, false)
|
||||
res, err := http.Get("http://127.0.0.1:8888/raw")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@ -97,9 +153,6 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
||||
td()
|
||||
}
|
||||
|
||||
// func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||
// func (engine *Engine) RunTLS(addr string, cert string, key string) error {
|
||||
|
||||
func init() {
|
||||
SetMode(TestMode)
|
||||
}
|
||||
@ -117,17 +170,17 @@ func TestCreateEngine(t *testing.T) {
|
||||
// router.LoadHTMLGlob("*.testtmpl")
|
||||
// r := router.HTMLRender.(render.HTMLDebug)
|
||||
// assert.Empty(t, r.Files)
|
||||
// assert.Equal(t, r.Glob, "*.testtmpl")
|
||||
// assert.Equal(t, "*.testtmpl", r.Glob)
|
||||
//
|
||||
// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl")
|
||||
// r = router.HTMLRender.(render.HTMLDebug)
|
||||
// assert.Empty(t, r.Glob)
|
||||
// assert.Equal(t, r.Files, []string{"index.html", "login.html"})
|
||||
// assert.Equal(t, []string{"index.html", "login.html"}, r.Files)
|
||||
// SetMode(TestMode)
|
||||
// }
|
||||
|
||||
func TestLoadHTMLFiles(t *testing.T) {
|
||||
td := setupHTMLFiles(t)
|
||||
td := setupHTMLFiles(t, TestMode, false)
|
||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@ -138,9 +191,52 @@ func TestLoadHTMLFiles(t *testing.T) {
|
||||
td()
|
||||
}
|
||||
|
||||
func TestLoadHTMLFiles2(t *testing.T) {
|
||||
td := setupHTMLFiles(t, DebugMode, false)
|
||||
res, err := http.Get("http://127.0.0.1:8888/test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "<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) {
|
||||
time.Now()
|
||||
td := setupHTMLFiles(t)
|
||||
td := setupHTMLFiles(t, TestMode, false)
|
||||
res, err := http.Get("http://127.0.0.1:8888/raw")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@ -152,10 +248,6 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
||||
td()
|
||||
}
|
||||
|
||||
func TestLoadHTMLReleaseMode(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestAddRoute(t *testing.T) {
|
||||
router := New()
|
||||
router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}})
|
||||
|
@ -6,9 +6,7 @@
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
import "encoding/json"
|
||||
|
||||
var (
|
||||
Marshal = json.Marshal
|
||||
|
@ -6,9 +6,7 @@
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"github.com/json-iterator/go"
|
||||
)
|
||||
import "github.com/json-iterator/go"
|
||||
|
||||
var (
|
||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
@ -91,10 +91,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
statusCode := c.Writer.Status()
|
||||
var statusColor, methodColor string
|
||||
var statusColor, methodColor, resetColor string
|
||||
if isTerm {
|
||||
statusColor = colorForStatus(statusCode)
|
||||
methodColor = colorForMethod(method)
|
||||
resetColor = reset
|
||||
}
|
||||
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",
|
||||
end.Format("2006/01/02 - 15:04:05"),
|
||||
statusColor, statusCode, reset,
|
||||
statusColor, statusCode, resetColor,
|
||||
latency,
|
||||
clientIP,
|
||||
methodColor, method, reset,
|
||||
methodColor, method, resetColor,
|
||||
path,
|
||||
comment,
|
||||
)
|
||||
|
@ -82,21 +82,21 @@ func TestLogger(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestColorForMethod(t *testing.T) {
|
||||
assert.Equal(t, colorForMethod("GET"), string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), "get should be blue")
|
||||
assert.Equal(t, colorForMethod("POST"), string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), "post should be cyan")
|
||||
assert.Equal(t, colorForMethod("PUT"), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "put should be yellow")
|
||||
assert.Equal(t, colorForMethod("DELETE"), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "delete should be red")
|
||||
assert.Equal(t, colorForMethod("PATCH"), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "patch should be green")
|
||||
assert.Equal(t, colorForMethod("HEAD"), string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), "head should be magenta")
|
||||
assert.Equal(t, colorForMethod("OPTIONS"), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "options should be white")
|
||||
assert.Equal(t, colorForMethod("TRACE"), string([]byte{27, 91, 48, 109}), "trace is not defined and should be the reset color")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white")
|
||||
assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color")
|
||||
}
|
||||
|
||||
func TestColorForStatus(t *testing.T) {
|
||||
assert.Equal(t, colorForStatus(200), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "2xx should be green")
|
||||
assert.Equal(t, colorForStatus(301), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "3xx should be white")
|
||||
assert.Equal(t, colorForStatus(404), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "4xx should be yellow")
|
||||
assert.Equal(t, colorForStatus(2), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "other things should be red")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow")
|
||||
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red")
|
||||
}
|
||||
|
||||
func TestErrorLogger(t *testing.T) {
|
||||
|
14
mode.go
14
mode.go
@ -14,9 +14,9 @@ import (
|
||||
const ENV_GIN_MODE = "GIN_MODE"
|
||||
|
||||
const (
|
||||
DebugMode string = "debug"
|
||||
ReleaseMode string = "release"
|
||||
TestMode string = "test"
|
||||
DebugMode = "debug"
|
||||
ReleaseMode = "release"
|
||||
TestMode = "test"
|
||||
)
|
||||
const (
|
||||
debugCode = iota
|
||||
@ -39,16 +39,12 @@ var modeName = DebugMode
|
||||
|
||||
func init() {
|
||||
mode := os.Getenv(ENV_GIN_MODE)
|
||||
if len(mode) == 0 {
|
||||
SetMode(DebugMode)
|
||||
} else {
|
||||
SetMode(mode)
|
||||
}
|
||||
SetMode(mode)
|
||||
}
|
||||
|
||||
func SetMode(value string) {
|
||||
switch value {
|
||||
case DebugMode:
|
||||
case DebugMode, "":
|
||||
ginMode = debugCode
|
||||
case ReleaseMode:
|
||||
ginMode = releaseCode
|
||||
|
16
mode_test.go
16
mode_test.go
@ -17,21 +17,21 @@ func init() {
|
||||
}
|
||||
|
||||
func TestSetMode(t *testing.T) {
|
||||
assert.Equal(t, ginMode, testCode)
|
||||
assert.Equal(t, Mode(), TestMode)
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
os.Unsetenv(ENV_GIN_MODE)
|
||||
|
||||
SetMode(DebugMode)
|
||||
assert.Equal(t, ginMode, debugCode)
|
||||
assert.Equal(t, Mode(), DebugMode)
|
||||
assert.Equal(t, debugCode, ginMode)
|
||||
assert.Equal(t, DebugMode, Mode())
|
||||
|
||||
SetMode(ReleaseMode)
|
||||
assert.Equal(t, ginMode, releaseCode)
|
||||
assert.Equal(t, Mode(), ReleaseMode)
|
||||
assert.Equal(t, releaseCode, ginMode)
|
||||
assert.Equal(t, ReleaseMode, Mode())
|
||||
|
||||
SetMode(TestMode)
|
||||
assert.Equal(t, ginMode, testCode)
|
||||
assert.Equal(t, Mode(), TestMode)
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
|
||||
assert.Panics(t, func() { SetMode("unknown") })
|
||||
}
|
||||
|
@ -67,8 +67,8 @@ var cleanTests = []struct {
|
||||
|
||||
func TestPathClean(t *testing.T) {
|
||||
for _, test := range cleanTests {
|
||||
assert.Equal(t, cleanPath(test.path), test.result)
|
||||
assert.Equal(t, cleanPath(test.result), test.result)
|
||||
assert.Equal(t, test.result, cleanPath(test.path))
|
||||
assert.Equal(t, test.result, cleanPath(test.result))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ func Recovery() HandlerFunc {
|
||||
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 {
|
||||
var logger *log.Logger
|
||||
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 {
|
||||
buf := new(bytes.Buffer) // the returned data
|
||||
// As we loop, we open files and read them. These variables record the currently
|
||||
|
@ -22,7 +22,7 @@ func TestPanicInHandler(t *testing.T) {
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
// 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(), "Oupps, Houston, we have a problem")
|
||||
assert.Contains(t, buffer.String(), "TestPanicInHandler")
|
||||
@ -39,5 +39,5 @@ func TestPanicWithAbort(t *testing.T) {
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/recovery")
|
||||
// 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 {
|
||||
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))
|
||||
}
|
||||
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 {
|
||||
r.WriteContentType(w)
|
||||
|
||||
if len(r.Name) == 0 {
|
||||
if r.Name == "" {
|
||||
return r.Template.Execute(w, 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
|
||||
}
|
||||
}
|
||||
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||
}
|
||||
|
||||
func TestRenderXML(t *testing.T) {
|
||||
|
@ -20,15 +20,15 @@ func TestRouterGroupBasic(t *testing.T) {
|
||||
group.Use(func(c *Context) {})
|
||||
|
||||
assert.Len(t, group.Handlers, 2)
|
||||
assert.Equal(t, group.BasePath(), "/hola")
|
||||
assert.Equal(t, group.engine, router)
|
||||
assert.Equal(t, "/hola", group.BasePath())
|
||||
assert.Equal(t, router, group.engine)
|
||||
|
||||
group2 := group.Group("manu")
|
||||
group2.Use(func(c *Context) {}, func(c *Context) {})
|
||||
|
||||
assert.Len(t, group2.Handlers, 4)
|
||||
assert.Equal(t, group2.BasePath(), "/hola/manu")
|
||||
assert.Equal(t, group2.engine, router)
|
||||
assert.Equal(t, "/hola/manu", group2.BasePath())
|
||||
assert.Equal(t, router, group2.engine)
|
||||
}
|
||||
|
||||
func TestRouterGroupBasicHandle(t *testing.T) {
|
||||
@ -44,10 +44,10 @@ func TestRouterGroupBasicHandle(t *testing.T) {
|
||||
func performRequestInGroup(t *testing.T, method string) {
|
||||
router := New()
|
||||
v1 := router.Group("v1", func(c *Context) {})
|
||||
assert.Equal(t, v1.BasePath(), "/v1")
|
||||
assert.Equal(t, "/v1", v1.BasePath())
|
||||
|
||||
login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {})
|
||||
assert.Equal(t, login.BasePath(), "/v1/login/")
|
||||
assert.Equal(t, "/v1/login/", login.BasePath())
|
||||
|
||||
handler := func(c *Context) {
|
||||
c.String(400, "the method was %s and index %d", c.Request.Method, c.index)
|
||||
@ -80,12 +80,12 @@ func performRequestInGroup(t *testing.T, method string) {
|
||||
}
|
||||
|
||||
w := performRequest(router, method, "/v1/login/test")
|
||||
assert.Equal(t, w.Code, 400)
|
||||
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 3")
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, "the method was "+method+" and index 3", w.Body.String())
|
||||
|
||||
w = performRequest(router, method, "/v1/test")
|
||||
assert.Equal(t, w.Code, 400)
|
||||
assert.Equal(t, w.Body.String(), "the method was "+method+" and index 1")
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, "the method was "+method+" and index 1", w.Body.String())
|
||||
}
|
||||
|
||||
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")
|
||||
assert.True(t, passed)
|
||||
assert.Equal(t, w.Code, http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
performRequest(r, method, "/test2")
|
||||
assert.True(t, passedAny)
|
||||
@ -53,7 +53,7 @@ func testRouteNotOK(method string, t *testing.T) {
|
||||
w := performRequest(router, method, "/test")
|
||||
|
||||
assert.False(t, passed)
|
||||
assert.Equal(t, w.Code, http.StatusNotFound)
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
// TestSingleRouteOK tests that POST route is correctly invoked.
|
||||
@ -74,7 +74,7 @@ func testRouteNotOK2(method string, t *testing.T) {
|
||||
w := performRequest(router, method, "/test")
|
||||
|
||||
assert.False(t, passed)
|
||||
assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
}
|
||||
|
||||
func TestRouterMethod(t *testing.T) {
|
||||
@ -93,8 +93,8 @@ func TestRouterMethod(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "PUT", "/hey")
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Body.String(), "called")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "called", w.Body.String())
|
||||
}
|
||||
|
||||
func TestRouterGroupRouteOK(t *testing.T) {
|
||||
@ -143,43 +143,43 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
|
||||
router.PUT("/path4/", func(c *Context) {})
|
||||
|
||||
w := performRequest(router, "GET", "/path/")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/path")
|
||||
assert.Equal(t, w.Code, 301)
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/path2/")
|
||||
assert.Equal(t, w.Code, 301)
|
||||
assert.Equal(t, "/path2/", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path3/")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/path3")
|
||||
assert.Equal(t, w.Code, 307)
|
||||
assert.Equal(t, "/path3", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
|
||||
w = performRequest(router, "PUT", "/path4")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/path4/")
|
||||
assert.Equal(t, w.Code, 307)
|
||||
assert.Equal(t, "/path4/", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path2/")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path3")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
w = performRequest(router, "PUT", "/path4/")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
|
||||
router.RedirectTrailingSlash = false
|
||||
|
||||
w = performRequest(router, "GET", "/path/")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
w = performRequest(router, "POST", "/path3/")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
w = performRequest(router, "PUT", "/path4")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteRedirectFixedPath(t *testing.T) {
|
||||
@ -193,20 +193,20 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
||||
router.POST("/Path4/", func(c *Context) {})
|
||||
|
||||
w := performRequest(router, "GET", "/PATH")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/path")
|
||||
assert.Equal(t, w.Code, 301)
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/Path2")
|
||||
assert.Equal(t, w.Code, 301)
|
||||
assert.Equal(t, "/Path2", w.Header().Get("Location"))
|
||||
assert.Equal(t, 301, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path3")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/PATH3")
|
||||
assert.Equal(t, w.Code, 307)
|
||||
assert.Equal(t, "/PATH3", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
|
||||
w = performRequest(router, "POST", "/path4")
|
||||
assert.Equal(t, w.Header().Get("Location"), "/Path4/")
|
||||
assert.Equal(t, w.Code, 307)
|
||||
assert.Equal(t, "/Path4/", w.Header().Get("Location"))
|
||||
assert.Equal(t, 307, w.Code)
|
||||
}
|
||||
|
||||
// TestContextParamsGet tests that a parameter can be parsed from the URL.
|
||||
@ -236,10 +236,10 @@ func TestRouteParamsByName(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/test/john/smith/is/super/great")
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, name, "john")
|
||||
assert.Equal(t, lastName, "smith")
|
||||
assert.Equal(t, wild, "/is/super/great")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "john", name)
|
||||
assert.Equal(t, "smith", lastName)
|
||||
assert.Equal(t, "/is/super/great", wild)
|
||||
}
|
||||
|
||||
// TestHandleStaticFile - ensure the static file handles properly
|
||||
@ -265,15 +265,15 @@ func TestRouteStaticFile(t *testing.T) {
|
||||
w2 := performRequest(router, "GET", "/result")
|
||||
|
||||
assert.Equal(t, w, w2)
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Body.String(), "Gin Web Framework")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, "Gin Web Framework", w.Body.String())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
|
||||
w3 := performRequest(router, "HEAD", "/using_static/"+filename)
|
||||
w4 := performRequest(router, "HEAD", "/result")
|
||||
|
||||
assert.Equal(t, w3, w4)
|
||||
assert.Equal(t, w3.Code, 200)
|
||||
assert.Equal(t, 200, w3.Code)
|
||||
}
|
||||
|
||||
// TestHandleStaticDir - ensure the root/sub dir handles properly
|
||||
@ -283,9 +283,9 @@ func TestRouteStaticListingDir(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "gin.go")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8")
|
||||
assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestHandleHeadToDir - ensure the root/sub dir handles properly
|
||||
@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.NotContains(t, w.Body.String(), "gin.go")
|
||||
}
|
||||
|
||||
@ -310,12 +310,12 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
||||
|
||||
w := performRequest(router, "GET", "/gin.go")
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "package gin")
|
||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
|
||||
assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type"))
|
||||
assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST")
|
||||
assert.Equal(t, w.HeaderMap.Get("Expires"), "Mon, 02 Jan 2006 15:04:05 MST")
|
||||
assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework")
|
||||
assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.HeaderMap.Get("Expires"))
|
||||
assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN"))
|
||||
}
|
||||
|
||||
func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||
@ -323,14 +323,14 @@ func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||
router.HandleMethodNotAllowed = true
|
||||
router.POST("/path", func(c *Context) {})
|
||||
w := performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
|
||||
|
||||
router.NoMethod(func(c *Context) {
|
||||
c.String(http.StatusTeapot, "responseText")
|
||||
})
|
||||
w = performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, w.Body.String(), "responseText")
|
||||
assert.Equal(t, w.Code, http.StatusTeapot)
|
||||
assert.Equal(t, "responseText", w.Body.String())
|
||||
assert.Equal(t, http.StatusTeapot, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||
@ -338,14 +338,14 @@ func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||
router.HandleMethodNotAllowed = false
|
||||
router.POST("/path", func(c *Context) {})
|
||||
w := performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
|
||||
router.NoMethod(func(c *Context) {
|
||||
c.String(http.StatusTeapot, "responseText")
|
||||
})
|
||||
w = performRequest(router, "GET", "/path")
|
||||
assert.Equal(t, w.Body.String(), "404 page not found")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, "404 page not found", w.Body.String())
|
||||
assert.Equal(t, 404, w.Code)
|
||||
}
|
||||
|
||||
func TestRouterNotFound(t *testing.T) {
|
||||
@ -372,9 +372,9 @@ func TestRouterNotFound(t *testing.T) {
|
||||
}
|
||||
for _, tr := range testRoutes {
|
||||
w := performRequest(router, "GET", tr.route)
|
||||
assert.Equal(t, w.Code, tr.code)
|
||||
assert.Equal(t, tr.code, w.Code)
|
||||
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
|
||||
})
|
||||
w := performRequest(router, "GET", "/nope")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
assert.True(t, notFound)
|
||||
|
||||
// Test other method than GET (want 307 instead of 301)
|
||||
router.PATCH("/path", func(c *Context) {})
|
||||
w = performRequest(router, "PATCH", "/path/")
|
||||
assert.Equal(t, w.Code, 307)
|
||||
assert.Equal(t, fmt.Sprint(w.Header()), "map[Location:[/path]]")
|
||||
assert.Equal(t, 307, w.Code)
|
||||
assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header()))
|
||||
|
||||
// Test special case where no node for the prefix "/" exists
|
||||
router = New()
|
||||
router.GET("/a", func(c *Context) {})
|
||||
w = performRequest(router, "GET", "/")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
assert.Equal(t, 404, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteRawPath(t *testing.T) {
|
||||
@ -409,15 +409,15 @@ func TestRouteRawPath(t *testing.T) {
|
||||
name := c.Params.ByName("name")
|
||||
num := c.Params.ByName("num")
|
||||
|
||||
assert.Equal(t, c.Param("name"), name)
|
||||
assert.Equal(t, c.Param("num"), num)
|
||||
assert.Equal(t, name, c.Param("name"))
|
||||
assert.Equal(t, num, c.Param("num"))
|
||||
|
||||
assert.Equal(t, "Some/Other/Project", name)
|
||||
assert.Equal(t, "222", num)
|
||||
})
|
||||
|
||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||
@ -429,15 +429,15 @@ func TestRouteRawPathNoUnescape(t *testing.T) {
|
||||
name := c.Params.ByName("name")
|
||||
num := c.Params.ByName("num")
|
||||
|
||||
assert.Equal(t, c.Param("name"), name)
|
||||
assert.Equal(t, c.Param("num"), num)
|
||||
assert.Equal(t, name, c.Param("name"))
|
||||
assert.Equal(t, num, c.Param("num"))
|
||||
|
||||
assert.Equal(t, "Some%2FOther%2FProject", name)
|
||||
assert.Equal(t, "333", num)
|
||||
})
|
||||
|
||||
w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, 200, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteServeErrorWithWriteHeader(t *testing.T) {
|
||||
|
14
tree.go
14
tree.go
@ -87,13 +87,13 @@ const (
|
||||
|
||||
type node struct {
|
||||
path string
|
||||
wildChild bool
|
||||
nType nodeType
|
||||
maxParams uint8
|
||||
indices string
|
||||
children []*node
|
||||
handlers HandlersChain
|
||||
priority uint32
|
||||
nType nodeType
|
||||
maxParams uint8
|
||||
wildChild bool
|
||||
}
|
||||
|
||||
// increments priority of the given child and reorders if necessary.
|
||||
@ -384,7 +384,7 @@ walk: // Outer loop for walking the tree
|
||||
// Nothing found.
|
||||
// We can recommend to redirect to the same URL without a
|
||||
// trailing slash if a leaf exists for that path.
|
||||
tsr = (path == "/" && n.handlers != nil)
|
||||
tsr = path == "/" && n.handlers != nil
|
||||
return
|
||||
}
|
||||
|
||||
@ -424,7 +424,7 @@ walk: // Outer loop for walking the tree
|
||||
}
|
||||
|
||||
// ... but we can't
|
||||
tsr = (len(path) == end+1)
|
||||
tsr = len(path) == end+1
|
||||
return
|
||||
}
|
||||
|
||||
@ -435,7 +435,7 @@ walk: // Outer loop for walking the tree
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists for TSR recommendation
|
||||
n = n.children[0]
|
||||
tsr = (n.path == "/" && n.handlers != nil)
|
||||
tsr = n.path == "/" && n.handlers != nil
|
||||
}
|
||||
|
||||
return
|
||||
@ -530,7 +530,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||
|
||||
// Nothing found. We can recommend to redirect to the same URL
|
||||
// without a trailing slash if a leaf exists for that path
|
||||
found = (fixTrailingSlash && path == "/" && n.handlers != nil)
|
||||
found = fixTrailingSlash && path == "/" && n.handlers != nil
|
||||
return
|
||||
}
|
||||
|
||||
|
11
tree_test.go
11
tree_test.go
@ -5,22 +5,11 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func printChildren(n *node, prefix string) {
|
||||
fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handlers, n.wildChild, n.nType)
|
||||
for l := len(n.path); l > 0; l-- {
|
||||
prefix += " "
|
||||
}
|
||||
for _, child := range n.children {
|
||||
printChildren(child, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Used as a workaround since we can't compare functions or their addressses
|
||||
var fakeHandlerValue string
|
||||
|
||||
|
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 {
|
||||
return func(c *Context) {
|
||||
f(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// WrapH is a helper function for wrapping http.Handler
|
||||
// Returns a Gin middleware
|
||||
func WrapH(h http.Handler) HandlerFunc {
|
||||
return func(c *Context) {
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// H is a shortcup for map[string]interface{}
|
||||
type H map[string]interface{}
|
||||
|
||||
// MarshalXML allows type H to be used with xml.Marshal.
|
||||
@ -65,10 +70,8 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return e.EncodeToken(xml.EndElement{Name: start.Name})
|
||||
}
|
||||
|
||||
func assert1(guard bool, text string) {
|
||||
@ -103,7 +106,7 @@ func parseAccept(acceptHeader string) []string {
|
||||
if index := strings.IndexByte(part, ';'); index >= 0 {
|
||||
part = part[0:index]
|
||||
}
|
||||
if part = strings.TrimSpace(part); len(part) > 0 {
|
||||
if part = strings.TrimSpace(part); part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
@ -111,11 +114,10 @@ func parseAccept(acceptHeader string) []string {
|
||||
}
|
||||
|
||||
func lastChar(str string) uint8 {
|
||||
size := len(str)
|
||||
if size == 0 {
|
||||
if str == "" {
|
||||
panic("The length of the string can't be 0")
|
||||
}
|
||||
return str[size-1]
|
||||
return str[len(str)-1]
|
||||
}
|
||||
|
||||
func nameOfFunction(f interface{}) string {
|
||||
@ -123,7 +125,7 @@ func nameOfFunction(f interface{}) string {
|
||||
}
|
||||
|
||||
func joinPaths(absolutePath, relativePath string) string {
|
||||
if len(relativePath) == 0 {
|
||||
if relativePath == "" {
|
||||
return absolutePath
|
||||
}
|
||||
|
||||
@ -138,7 +140,7 @@ func joinPaths(absolutePath, relativePath string) string {
|
||||
func resolveAddress(addr []string) string {
|
||||
switch len(addr) {
|
||||
case 0:
|
||||
if port := os.Getenv("PORT"); len(port) > 0 {
|
||||
if port := os.Getenv("PORT"); port != "" {
|
||||
debugPrint("Environment variable PORT=\"%s\"", port)
|
||||
return ":" + port
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ type testStruct struct {
|
||||
}
|
||||
|
||||
func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
assert.Equal(t.T, req.Method, "POST")
|
||||
assert.Equal(t.T, req.URL.Path, "/path")
|
||||
assert.Equal(t.T, "POST", req.Method)
|
||||
assert.Equal(t.T, "/path", req.URL.Path)
|
||||
w.WriteHeader(500)
|
||||
fmt.Fprint(w, "hello")
|
||||
}
|
||||
@ -31,50 +31,50 @@ func TestWrap(t *testing.T) {
|
||||
router := New()
|
||||
router.POST("/path", WrapH(&testStruct{t}))
|
||||
router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) {
|
||||
assert.Equal(t, req.Method, "GET")
|
||||
assert.Equal(t, req.URL.Path, "/path2")
|
||||
assert.Equal(t, "GET", req.Method)
|
||||
assert.Equal(t, "/path2", req.URL.Path)
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprint(w, "hola!")
|
||||
}))
|
||||
|
||||
w := performRequest(router, "POST", "/path")
|
||||
assert.Equal(t, w.Code, 500)
|
||||
assert.Equal(t, w.Body.String(), "hello")
|
||||
assert.Equal(t, 500, w.Code)
|
||||
assert.Equal(t, "hello", w.Body.String())
|
||||
|
||||
w = performRequest(router, "GET", "/path2")
|
||||
assert.Equal(t, w.Code, 400)
|
||||
assert.Equal(t, w.Body.String(), "hola!")
|
||||
assert.Equal(t, 400, w.Code)
|
||||
assert.Equal(t, "hola!", w.Body.String())
|
||||
}
|
||||
|
||||
func TestLastChar(t *testing.T) {
|
||||
assert.Equal(t, lastChar("hola"), uint8('a'))
|
||||
assert.Equal(t, lastChar("adios"), uint8('s'))
|
||||
assert.Equal(t, uint8('a'), lastChar("hola"))
|
||||
assert.Equal(t, uint8('s'), lastChar("adios"))
|
||||
assert.Panics(t, func() { lastChar("") })
|
||||
}
|
||||
|
||||
func TestParseAccept(t *testing.T) {
|
||||
parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
|
||||
assert.Len(t, parts, 4)
|
||||
assert.Equal(t, parts[0], "text/html")
|
||||
assert.Equal(t, parts[1], "application/xhtml+xml")
|
||||
assert.Equal(t, parts[2], "application/xml")
|
||||
assert.Equal(t, parts[3], "*/*")
|
||||
assert.Equal(t, "text/html", parts[0])
|
||||
assert.Equal(t, "application/xhtml+xml", parts[1])
|
||||
assert.Equal(t, "application/xml", parts[2])
|
||||
assert.Equal(t, "*/*", parts[3])
|
||||
}
|
||||
|
||||
func TestChooseData(t *testing.T) {
|
||||
A := "a"
|
||||
B := "b"
|
||||
assert.Equal(t, chooseData(A, B), A)
|
||||
assert.Equal(t, chooseData(nil, B), B)
|
||||
assert.Equal(t, A, chooseData(A, B))
|
||||
assert.Equal(t, B, chooseData(nil, B))
|
||||
assert.Panics(t, func() { chooseData(nil, nil) })
|
||||
}
|
||||
|
||||
func TestFilterFlags(t *testing.T) {
|
||||
result := filterFlags("text/html ")
|
||||
assert.Equal(t, result, "text/html")
|
||||
assert.Equal(t, "text/html", result)
|
||||
|
||||
result = filterFlags("text/html;")
|
||||
assert.Equal(t, result, "text/html")
|
||||
assert.Equal(t, "text/html", result)
|
||||
}
|
||||
|
||||
func TestFunctionName(t *testing.T) {
|
||||
@ -86,16 +86,16 @@ func somefunction() {
|
||||
}
|
||||
|
||||
func TestJoinPaths(t *testing.T) {
|
||||
assert.Equal(t, joinPaths("", ""), "")
|
||||
assert.Equal(t, joinPaths("", "/"), "/")
|
||||
assert.Equal(t, joinPaths("/a", ""), "/a")
|
||||
assert.Equal(t, joinPaths("/a/", ""), "/a/")
|
||||
assert.Equal(t, joinPaths("/a/", "/"), "/a/")
|
||||
assert.Equal(t, joinPaths("/a", "/"), "/a/")
|
||||
assert.Equal(t, joinPaths("/a", "/hola"), "/a/hola")
|
||||
assert.Equal(t, joinPaths("/a/", "/hola"), "/a/hola")
|
||||
assert.Equal(t, joinPaths("/a/", "/hola/"), "/a/hola/")
|
||||
assert.Equal(t, joinPaths("/a/", "/hola//"), "/a/hola/")
|
||||
assert.Equal(t, "", joinPaths("", ""))
|
||||
assert.Equal(t, "/", joinPaths("", "/"))
|
||||
assert.Equal(t, "/a", joinPaths("/a", ""))
|
||||
assert.Equal(t, "/a/", joinPaths("/a/", ""))
|
||||
assert.Equal(t, "/a/", joinPaths("/a/", "/"))
|
||||
assert.Equal(t, "/a/", joinPaths("/a", "/"))
|
||||
assert.Equal(t, "/a/hola", joinPaths("/a", "/hola"))
|
||||
assert.Equal(t, "/a/hola", joinPaths("/a/", "/hola"))
|
||||
assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola/"))
|
||||
assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola//"))
|
||||
}
|
||||
|
||||
type bindTestStruct struct {
|
||||
@ -113,8 +113,8 @@ func TestBindMiddleware(t *testing.T) {
|
||||
})
|
||||
performRequest(router, "GET", "/?foo=hola&bar=10")
|
||||
assert.True(t, called)
|
||||
assert.Equal(t, value.Foo, "hola")
|
||||
assert.Equal(t, value.Bar, 10)
|
||||
assert.Equal(t, "hola", value.Foo)
|
||||
assert.Equal(t, 10, value.Bar)
|
||||
|
||||
called = false
|
||||
performRequest(router, "GET", "/?foo=hola&bar=1")
|
||||
|
24
vendor/vendor.json
vendored
24
vendor/vendor.json
vendored
@ -22,10 +22,10 @@
|
||||
"revisionTime": "2017-01-09T09:34:21Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=",
|
||||
"checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=",
|
||||
"path": "github.com/gin-gonic/autotls",
|
||||
"revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d",
|
||||
"revisionTime": "2017-04-16T09:39:34Z"
|
||||
"revision": "8ca25fbde72bb72a00466215b94b489c71fcb815",
|
||||
"revisionTime": "2017-09-16T16:54:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=",
|
||||
@ -34,10 +34,10 @@
|
||||
"revisionTime": "2017-06-01T23:02:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "0e59uuETpidkmpaRwipQ8auqwhM=",
|
||||
"checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=",
|
||||
"path": "github.com/json-iterator/go",
|
||||
"revision": "6b6938829d6156d7b9825f83eec757f0f571c981",
|
||||
"revisionTime": "2017-07-18T14:19:52Z"
|
||||
"revision": "36b14963da70d11297d313183d7e6388c8510e1e",
|
||||
"revisionTime": "2017-08-29T15:58:51Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=",
|
||||
@ -65,6 +65,12 @@
|
||||
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
|
||||
"revisionTime": "2016-09-25T22:06:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=",
|
||||
"path": "github.com/thinkerou/favicon",
|
||||
"revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93",
|
||||
"revisionTime": "2017-07-10T14:05:20Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=",
|
||||
"path": "github.com/ugorji/go/codec",
|
||||
@ -90,6 +96,12 @@
|
||||
"revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a",
|
||||
"revisionTime": "2016-10-18T08:54:36Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=",
|
||||
"path": "golang.org/x/sync/errgroup",
|
||||
"revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690",
|
||||
"revisionTime": "2017-07-19T03:38:01Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=",
|
||||
"path": "golang.org/x/sys/unix",
|
||||
|
Loading…
x
Reference in New Issue
Block a user