Merge pull request #1 from gin-gonic/master

pull request
This commit is contained in:
Sergey Cheung 2018-01-19 00:11:41 +08:00 committed by GitHub
commit 0fda834942
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1287 additions and 503 deletions

View File

@ -4,6 +4,7 @@ go:
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- master
git:

322
README.md
View File

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

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

View File

@ -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=",
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,9 +10,7 @@ import (
"github.com/gin-gonic/gin/json"
)
var (
EnableDecoderUseNumber = false
)
var EnableDecoderUseNumber = false
type jsonBinding struct{}

View File

@ -4,9 +4,7 @@
package binding
import (
"net/http"
)
import "net/http"
type queryBinding struct{}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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())
}

View 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()})
}
}

View File

@ -41,5 +41,5 @@ func main() {
}
}
log.Println("Server exist")
log.Println("Server exiting")
}

View File

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

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

View File

@ -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(),

View File

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

View File

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

@ -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 + "/"
}

View File

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

View File

@ -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() {

View File

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

View File

@ -6,9 +6,7 @@
package json
import (
"encoding/json"
)
import "encoding/json"
var (
Marshal = json.Marshal

View File

@ -6,9 +6,7 @@
package json
import (
"github.com/json-iterator/go"
)
import "github.com/json-iterator/go"
var (
json = jsoniter.ConfigCompatibleWithStandardLibrary

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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