mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-21 16:58:08 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
7dfeb6d03e
15
.travis.yml
15
.travis.yml
@ -1,22 +1,17 @@
|
|||||||
language: go
|
language: go
|
||||||
sudo: false
|
|
||||||
go:
|
|
||||||
- 1.6.x
|
|
||||||
- 1.7.x
|
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
|
||||||
- master
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
|
- go: 1.8.x
|
||||||
|
- go: 1.9.x
|
||||||
|
- go: 1.10.x
|
||||||
- go: 1.11.x
|
- go: 1.11.x
|
||||||
env: GO111MODULE=on
|
env: GO111MODULE=on
|
||||||
- go: 1.12.x
|
- go: 1.12.x
|
||||||
env: GO111MODULE=on
|
env: GO111MODULE=on
|
||||||
|
- go: master
|
||||||
|
env: GO111MODULE=on
|
||||||
|
|
||||||
git:
|
git:
|
||||||
depth: 10
|
depth: 10
|
||||||
|
58
CHANGELOG.md
58
CHANGELOG.md
@ -1,4 +1,60 @@
|
|||||||
# CHANGELOG
|
|
||||||
|
### Gin 1.4.0
|
||||||
|
|
||||||
|
- [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569)
|
||||||
|
- [NEW] Refactor of form mapping multipart requesta [#1829](https://github.com/gin-gonic/gin/pull/1829)
|
||||||
|
- [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830)
|
||||||
|
- [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802)
|
||||||
|
- [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264)
|
||||||
|
- [NEW] Add support for mapping arrays [#1797](https://github.com/gin-gonic/gin/pull/1797)
|
||||||
|
- [FIX] Readme updates [#1793](https://github.com/gin-gonic/gin/pull/1793) [#1788](https://github.com/gin-gonic/gin/pull/1788) [1789](https://github.com/gin-gonic/gin/pull/1789)
|
||||||
|
- [FIX] StaticFS: Fixed Logging two log lines on 404. [#1805](https://github.com/gin-gonic/gin/pull/1805), [#1804](https://github.com/gin-gonic/gin/pull/1804)
|
||||||
|
- [NEW] Make context.Keys available as LogFormatterParams [#1779](https://github.com/gin-gonic/gin/pull/1779)
|
||||||
|
- [NEW] Use internal/json for Marshal/Unmarshal [#1791](https://github.com/gin-gonic/gin/pull/1791)
|
||||||
|
- [NEW] Support mapping time.Duration [#1794](https://github.com/gin-gonic/gin/pull/1794)
|
||||||
|
- [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749)
|
||||||
|
- [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252)
|
||||||
|
- [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775)
|
||||||
|
- [NEW] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260)
|
||||||
|
- [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112)
|
||||||
|
- [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238)
|
||||||
|
- [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020)
|
||||||
|
- [NEW] Add context.HandlerNames() [#1729](https://github.com/gin-gonic/gin/pull/1729)
|
||||||
|
- [FIX] Change color methods to public in the defaultLogger. [#1771](https://github.com/gin-gonic/gin/pull/1771)
|
||||||
|
- [FIX] Update writeHeaders method to use http.Header.Set [#1722](https://github.com/gin-gonic/gin/pull/1722)
|
||||||
|
- [NEW] Add response size to LogFormatterParams [#1752](https://github.com/gin-gonic/gin/pull/1752)
|
||||||
|
- [NEW] Allow ignoring field on form mapping [#1733](https://github.com/gin-gonic/gin/pull/1733)
|
||||||
|
- [NEW] Add a function to force color in console output. [#1724](https://github.com/gin-gonic/gin/pull/1724)
|
||||||
|
- [FIX] Context.Next() - recheck len of handlers on every iteration. [#1745](https://github.com/gin-gonic/gin/pull/1745)
|
||||||
|
- [FIX] Fix all errcheck warnings [#1739](https://github.com/gin-gonic/gin/pull/1739) [#1653](https://github.com/gin-gonic/gin/pull/1653)
|
||||||
|
- [NEW] context: inherits context cancellation and deadline from http.Request context for Go>=1.7 [#1690](https://github.com/gin-gonic/gin/pull/1690)
|
||||||
|
- [NEW] Binding for URL Params [#1694](https://github.com/gin-gonic/gin/pull/1694)
|
||||||
|
- [NEW] Add LoggerWithFormatter method [#1677](https://github.com/gin-gonic/gin/pull/1677)
|
||||||
|
- [FIX] CI testing updates [#1671](https://github.com/gin-gonic/gin/pull/1671) [#1670](https://github.com/gin-gonic/gin/pull/1670) [#1682](https://github.com/gin-gonic/gin/pull/1682) [#1669](https://github.com/gin-gonic/gin/pull/1669)
|
||||||
|
- [FIX] StaticFS(): Send 404 when path does not exist [#1663](https://github.com/gin-gonic/gin/pull/1663)
|
||||||
|
- [FIX] Handle nil body for JSON binding [#1638](https://github.com/gin-gonic/gin/pull/1638)
|
||||||
|
- [FIX] Support bind uri param [#1612](https://github.com/gin-gonic/gin/pull/1612)
|
||||||
|
- [FIX] recovery: fix issue with syscall import on google app engine [#1640](https://github.com/gin-gonic/gin/pull/1640)
|
||||||
|
- [FIX] Make sure the debug log contains line breaks [#1650](https://github.com/gin-gonic/gin/pull/1650)
|
||||||
|
- [FIX] Panic stack trace being printed during recovery of broken pipe [#1089](https://github.com/gin-gonic/gin/pull/1089) [#1259](https://github.com/gin-gonic/gin/pull/1259)
|
||||||
|
- [NEW] RunFd method to run http.Server through a file descriptor [#1609](https://github.com/gin-gonic/gin/pull/1609)
|
||||||
|
- [NEW] Yaml binding support [#1618](https://github.com/gin-gonic/gin/pull/1618)
|
||||||
|
- [FIX] Pass MaxMultipartMemory when FormFile is called [#1600](https://github.com/gin-gonic/gin/pull/1600)
|
||||||
|
- [FIX] LoadHTML* tests [#1559](https://github.com/gin-gonic/gin/pull/1559)
|
||||||
|
- [FIX] Removed use of sync.pool from HandleContext [#1565](https://github.com/gin-gonic/gin/pull/1565)
|
||||||
|
- [FIX] Format output log to os.Stderr [#1571](https://github.com/gin-gonic/gin/pull/1571)
|
||||||
|
- [FIX] Make logger use a yellow background and a darkgray text for legibility [#1570](https://github.com/gin-gonic/gin/pull/1570)
|
||||||
|
- [FIX] Remove sensitive request information from panic log. [#1370](https://github.com/gin-gonic/gin/pull/1370)
|
||||||
|
- [FIX] log.Println() does not print timestamp [#829](https://github.com/gin-gonic/gin/pull/829) [#1560](https://github.com/gin-gonic/gin/pull/1560)
|
||||||
|
- [NEW] Add PureJSON renderer [#694](https://github.com/gin-gonic/gin/pull/694)
|
||||||
|
- [FIX] Add missing copyright and update if/else [#1497](https://github.com/gin-gonic/gin/pull/1497)
|
||||||
|
- [FIX] Update msgpack usage [#1498](https://github.com/gin-gonic/gin/pull/1498)
|
||||||
|
- [FIX] Use protobuf on render [#1496](https://github.com/gin-gonic/gin/pull/1496)
|
||||||
|
- [FIX] Add support for Protobuf format response [#1479](https://github.com/gin-gonic/gin/pull/1479)
|
||||||
|
- [NEW] Set default time format in form binding [#1487](https://github.com/gin-gonic/gin/pull/1487)
|
||||||
|
- [FIX] Add BindXML and ShouldBindXML [#1485](https://github.com/gin-gonic/gin/pull/1485)
|
||||||
|
- [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491)
|
||||||
|
|
||||||
|
|
||||||
### Gin 1.3.0
|
### Gin 1.3.0
|
||||||
|
|
||||||
|
31
README.md
31
README.md
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Contents
|
## Contents
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
|
|
||||||
To install Gin package, you need to install Go and set your Go workspace first.
|
To install Gin package, you need to install Go and set your Go workspace first.
|
||||||
|
|
||||||
1. Download and install it:
|
1. The first need [Go](https://golang.org/) installed (**version 1.8+ is required**), then you can use the below Go command to install Gin.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go get -u github.com/gin-gonic/gin
|
$ go get -u github.com/gin-gonic/gin
|
||||||
@ -111,7 +110,7 @@ $ govendor fetch github.com/gin-gonic/gin@v1.3
|
|||||||
4. Copy a starting template inside your project
|
4. Copy a starting template inside your project
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
|
$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Run your project
|
5. Run your project
|
||||||
@ -120,10 +119,6 @@ $ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/mai
|
|||||||
$ go run main.go
|
$ go run main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@ -1697,9 +1692,9 @@ func main() {
|
|||||||
// Wait for interrupt signal to gracefully shutdown the server with
|
// Wait for interrupt signal to gracefully shutdown the server with
|
||||||
// a timeout of 5 seconds.
|
// a timeout of 5 seconds.
|
||||||
quit := make(chan os.Signal)
|
quit := make(chan os.Signal)
|
||||||
// kill (no param) default send syscanll.SIGTERM
|
// kill (no param) default send syscall.SIGTERM
|
||||||
// kill -2 is syscall.SIGINT
|
// kill -2 is syscall.SIGINT
|
||||||
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
|
// kill -9 is syscall.SIGKILL but can"t be catch, so don't need add it
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
log.Println("Shutdown Server ...")
|
log.Println("Shutdown Server ...")
|
||||||
@ -1836,24 +1831,6 @@ $ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
|
|||||||
{"d":"world","x":{"FieldX":"hello"}}
|
{"d":"world","x":{"FieldX":"hello"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
**NOTE**: NOT support the follow style struct:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type StructX struct {
|
|
||||||
X struct {} `form:"name_x"` // HERE have form
|
|
||||||
}
|
|
||||||
|
|
||||||
type StructY struct {
|
|
||||||
Y StructX `form:"name_y"` // HERE have form
|
|
||||||
}
|
|
||||||
|
|
||||||
type StructZ struct {
|
|
||||||
Z *StructZ `form:"name_z"` // HERE have form
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In a word, only support nested custom struct which have no `form` now.
|
|
||||||
|
|
||||||
### Try to bind body into different structs
|
### Try to bind body into different structs
|
||||||
|
|
||||||
The normal methods for binding request body consumes `c.Request.Body` and they
|
The normal methods for binding request body consumes `c.Request.Body` and they
|
||||||
|
@ -98,7 +98,9 @@ func Default(method, contentType string) Binding {
|
|||||||
return MsgPack
|
return MsgPack
|
||||||
case MIMEYAML:
|
case MIMEYAML:
|
||||||
return YAML
|
return YAML
|
||||||
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
|
case MIMEMultipartPOSTForm:
|
||||||
|
return FormMultipart
|
||||||
|
default: // case MIMEPOSTForm:
|
||||||
return Form
|
return Form
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -21,6 +24,16 @@ import (
|
|||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type appkey struct {
|
||||||
|
Appkey string `json:"appkey" form:"appkey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryTest struct {
|
||||||
|
Page int `json:"page" form:"page"`
|
||||||
|
Size int `json:"size" form:"size"`
|
||||||
|
appkey
|
||||||
|
}
|
||||||
|
|
||||||
type FooStruct struct {
|
type FooStruct struct {
|
||||||
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
|
Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
|
||||||
}
|
}
|
||||||
@ -30,6 +43,18 @@ type FooBarStruct struct {
|
|||||||
Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"`
|
Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FooBarFileStruct struct {
|
||||||
|
FooBarStruct
|
||||||
|
File *multipart.FileHeader `form:"file" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FooBarFileFailStruct struct {
|
||||||
|
FooBarStruct
|
||||||
|
File *multipart.FileHeader `invalid_name:"file" binding:"required"`
|
||||||
|
// for unexport test
|
||||||
|
data *multipart.FileHeader `form:"data" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type FooDefaultBarStruct struct {
|
type FooDefaultBarStruct struct {
|
||||||
FooStruct
|
FooStruct
|
||||||
Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"`
|
Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"`
|
||||||
@ -57,7 +82,6 @@ type FooStructForTimeTypeFailLocation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForMapType struct {
|
type FooStructForMapType struct {
|
||||||
// Unknown type: not support map
|
|
||||||
MapFoo map[string]interface{} `form:"map_foo"`
|
MapFoo map[string]interface{} `form:"map_foo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,71 +124,6 @@ type FooStructForBoolType struct {
|
|||||||
BoolFoo bool `form:"bool_foo"`
|
BoolFoo bool `form:"bool_foo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FooBarStructForIntType struct {
|
|
||||||
IntFoo int `form:"int_foo"`
|
|
||||||
IntBar int `form:"int_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt8Type struct {
|
|
||||||
Int8Foo int8 `form:"int8_foo"`
|
|
||||||
Int8Bar int8 `form:"int8_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt16Type struct {
|
|
||||||
Int16Foo int16 `form:"int16_foo"`
|
|
||||||
Int16Bar int16 `form:"int16_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt32Type struct {
|
|
||||||
Int32Foo int32 `form:"int32_foo"`
|
|
||||||
Int32Bar int32 `form:"int32_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForInt64Type struct {
|
|
||||||
Int64Foo int64 `form:"int64_foo"`
|
|
||||||
Int64Bar int64 `form:"int64_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUintType struct {
|
|
||||||
UintFoo uint `form:"uint_foo"`
|
|
||||||
UintBar uint `form:"uint_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint8Type struct {
|
|
||||||
Uint8Foo uint8 `form:"uint8_foo"`
|
|
||||||
Uint8Bar uint8 `form:"uint8_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint16Type struct {
|
|
||||||
Uint16Foo uint16 `form:"uint16_foo"`
|
|
||||||
Uint16Bar uint16 `form:"uint16_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint32Type struct {
|
|
||||||
Uint32Foo uint32 `form:"uint32_foo"`
|
|
||||||
Uint32Bar uint32 `form:"uint32_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForUint64Type struct {
|
|
||||||
Uint64Foo uint64 `form:"uint64_foo"`
|
|
||||||
Uint64Bar uint64 `form:"uint64_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForBoolType struct {
|
|
||||||
BoolFoo bool `form:"bool_foo"`
|
|
||||||
BoolBar bool `form:"bool_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForFloat32Type struct {
|
|
||||||
Float32Foo float32 `form:"float32_foo"`
|
|
||||||
Float32Bar float32 `form:"float32_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooBarStructForFloat64Type struct {
|
|
||||||
Float64Foo float64 `form:"float64_foo"`
|
|
||||||
Float64Bar float64 `form:"float64_bar" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooStructForStringPtrType struct {
|
type FooStructForStringPtrType struct {
|
||||||
PtrFoo *string `form:"ptr_foo"`
|
PtrFoo *string `form:"ptr_foo"`
|
||||||
PtrBar *string `form:"ptr_bar" binding:"required"`
|
PtrBar *string `form:"ptr_bar" binding:"required"`
|
||||||
@ -187,8 +146,8 @@ func TestBindingDefault(t *testing.T) {
|
|||||||
assert.Equal(t, Form, Default("POST", MIMEPOSTForm))
|
assert.Equal(t, Form, Default("POST", MIMEPOSTForm))
|
||||||
assert.Equal(t, Form, Default("PUT", MIMEPOSTForm))
|
assert.Equal(t, Form, Default("PUT", MIMEPOSTForm))
|
||||||
|
|
||||||
assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm))
|
assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm))
|
||||||
assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm))
|
assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm))
|
||||||
|
|
||||||
assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF))
|
assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF))
|
||||||
assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF))
|
assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF))
|
||||||
@ -240,6 +199,18 @@ func TestBindingForm2(t *testing.T) {
|
|||||||
"", "")
|
"", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingFormEmbeddedStruct(t *testing.T) {
|
||||||
|
testFormBindingEmbeddedStruct(t, "POST",
|
||||||
|
"/", "/",
|
||||||
|
"page=1&size=2&appkey=test-appkey", "bar2=foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingFormEmbeddedStruct2(t *testing.T) {
|
||||||
|
testFormBindingEmbeddedStruct(t, "GET",
|
||||||
|
"/?page=1&size=2&appkey=test-appkey", "/?bar2=foo",
|
||||||
|
"", "")
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindingFormDefaultValue(t *testing.T) {
|
func TestBindingFormDefaultValue(t *testing.T) {
|
||||||
testFormBindingDefaultValue(t, "POST",
|
testFormBindingDefaultValue(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
@ -303,7 +274,7 @@ func TestBindingFormInvalidName2(t *testing.T) {
|
|||||||
func TestBindingFormForType(t *testing.T) {
|
func TestBindingFormForType(t *testing.T) {
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"map_foo=", "bar2=1", "Map")
|
"map_foo={\"bar\":123}", "map_foo=1", "Map")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
@ -321,110 +292,6 @@ func TestBindingFormForType(t *testing.T) {
|
|||||||
"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
|
"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
|
||||||
"", "", "SliceMap")
|
"", "", "SliceMap")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int_foo=&int_bar=-12", "bar2=-123", "Int")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int_foo=&int_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int8_foo=&int8_bar=-12", "bar2=-123", "Int8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int8_foo=&int8_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int16_foo=&int16_bar=-12", "bar2=-123", "Int16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int16_foo=&int16_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int32_foo=&int32_bar=-12", "bar2=-123", "Int32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int32_foo=&int32_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"int64_foo=&int64_bar=-12", "bar2=-123", "Int64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?int64_foo=&int64_bar=-12", "/?bar2=-123",
|
|
||||||
"", "", "Int64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint_foo=&uint_bar=12", "bar2=123", "Uint")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint_foo=&uint_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint8_foo=&uint8_bar=12", "bar2=123", "Uint8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint8_foo=&uint8_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint8")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint16_foo=&uint16_bar=12", "bar2=123", "Uint16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint16_foo=&uint16_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint16")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint32_foo=&uint32_bar=12", "bar2=123", "Uint32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint32_foo=&uint32_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"uint64_foo=&uint64_bar=12", "bar2=123", "Uint64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?uint64_foo=&uint64_bar=12", "/?bar2=123",
|
|
||||||
"", "", "Uint64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"bool_foo=&bool_bar=true", "bar2=true", "Bool")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?bool_foo=&bool_bar=true", "/?bar2=true",
|
|
||||||
"", "", "Bool")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3",
|
|
||||||
"", "", "Float32")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
|
||||||
"/", "/",
|
|
||||||
"float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "GET",
|
|
||||||
"/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3",
|
|
||||||
"", "", "Float64")
|
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"ptr_bar=test", "bar2=test", "Ptr")
|
"ptr_bar=test", "bar2=test", "Ptr")
|
||||||
@ -508,24 +375,82 @@ func TestBindingYAMLFail(t *testing.T) {
|
|||||||
`foo:\nbar`, `bar: foo`)
|
`foo:\nbar`, `bar: foo`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormPostRequest() *http.Request {
|
func createFormPostRequest(t *testing.T) *http.Request {
|
||||||
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
|
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
|
||||||
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultFormPostRequest() *http.Request {
|
func createDefaultFormPostRequest(t *testing.T) *http.Request {
|
||||||
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
|
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
|
||||||
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormPostRequestFail() *http.Request {
|
func createFormPostRequestForMap(t *testing.T) *http.Request {
|
||||||
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar"))
|
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}"))
|
||||||
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createFormPostRequestForMapFail(t *testing.T) *http.Request {
|
||||||
|
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFormFilesMultipartRequest(t *testing.T) *http.Request {
|
||||||
|
boundary := "--testboundary"
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
mw := multipart.NewWriter(body)
|
||||||
|
defer mw.Close()
|
||||||
|
|
||||||
|
assert.NoError(t, mw.SetBoundary(boundary))
|
||||||
|
assert.NoError(t, mw.WriteField("foo", "bar"))
|
||||||
|
assert.NoError(t, mw.WriteField("bar", "foo"))
|
||||||
|
|
||||||
|
f, err := os.Open("form.go")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer f.Close()
|
||||||
|
fw, err1 := mw.CreateFormFile("file", "form.go")
|
||||||
|
assert.NoError(t, err1)
|
||||||
|
io.Copy(fw, f)
|
||||||
|
|
||||||
|
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFormFilesMultipartRequestFail(t *testing.T) *http.Request {
|
||||||
|
boundary := "--testboundary"
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
mw := multipart.NewWriter(body)
|
||||||
|
defer mw.Close()
|
||||||
|
|
||||||
|
assert.NoError(t, mw.SetBoundary(boundary))
|
||||||
|
assert.NoError(t, mw.WriteField("foo", "bar"))
|
||||||
|
assert.NoError(t, mw.WriteField("bar", "foo"))
|
||||||
|
|
||||||
|
f, err := os.Open("form.go")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer f.Close()
|
||||||
|
fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go")
|
||||||
|
assert.NoError(t, err1)
|
||||||
|
io.Copy(fw, f)
|
||||||
|
|
||||||
|
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
func createFormMultipartRequest(t *testing.T) *http.Request {
|
func createFormMultipartRequest(t *testing.T) *http.Request {
|
||||||
boundary := "--testboundary"
|
boundary := "--testboundary"
|
||||||
body := new(bytes.Buffer)
|
body := new(bytes.Buffer)
|
||||||
@ -535,26 +460,42 @@ func createFormMultipartRequest(t *testing.T) *http.Request {
|
|||||||
assert.NoError(t, mw.SetBoundary(boundary))
|
assert.NoError(t, mw.SetBoundary(boundary))
|
||||||
assert.NoError(t, mw.WriteField("foo", "bar"))
|
assert.NoError(t, mw.WriteField("foo", "bar"))
|
||||||
assert.NoError(t, mw.WriteField("bar", "foo"))
|
assert.NoError(t, mw.WriteField("bar", "foo"))
|
||||||
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
||||||
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormMultipartRequestFail(t *testing.T) *http.Request {
|
func createFormMultipartRequestForMap(t *testing.T) *http.Request {
|
||||||
boundary := "--testboundary"
|
boundary := "--testboundary"
|
||||||
body := new(bytes.Buffer)
|
body := new(bytes.Buffer)
|
||||||
mw := multipart.NewWriter(body)
|
mw := multipart.NewWriter(body)
|
||||||
defer mw.Close()
|
defer mw.Close()
|
||||||
|
|
||||||
assert.NoError(t, mw.SetBoundary(boundary))
|
assert.NoError(t, mw.SetBoundary(boundary))
|
||||||
assert.NoError(t, mw.WriteField("map_foo", "bar"))
|
assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}"))
|
||||||
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
|
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFormMultipartRequestForMapFail(t *testing.T) *http.Request {
|
||||||
|
boundary := "--testboundary"
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
mw := multipart.NewWriter(body)
|
||||||
|
defer mw.Close()
|
||||||
|
|
||||||
|
assert.NoError(t, mw.SetBoundary(boundary))
|
||||||
|
assert.NoError(t, mw.WriteField("map_foo", "3.14"))
|
||||||
|
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
|
||||||
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormPost(t *testing.T) {
|
func TestBindingFormPost(t *testing.T) {
|
||||||
req := createFormPostRequest()
|
req := createFormPostRequest(t)
|
||||||
var obj FooBarStruct
|
var obj FooBarStruct
|
||||||
assert.NoError(t, FormPost.Bind(req, &obj))
|
assert.NoError(t, FormPost.Bind(req, &obj))
|
||||||
|
|
||||||
@ -564,7 +505,7 @@ func TestBindingFormPost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingDefaultValueFormPost(t *testing.T) {
|
func TestBindingDefaultValueFormPost(t *testing.T) {
|
||||||
req := createDefaultFormPostRequest()
|
req := createDefaultFormPostRequest(t)
|
||||||
var obj FooDefaultBarStruct
|
var obj FooDefaultBarStruct
|
||||||
assert.NoError(t, FormPost.Bind(req, &obj))
|
assert.NoError(t, FormPost.Bind(req, &obj))
|
||||||
|
|
||||||
@ -572,10 +513,46 @@ func TestBindingDefaultValueFormPost(t *testing.T) {
|
|||||||
assert.Equal(t, "hello", obj.Bar)
|
assert.Equal(t, "hello", obj.Bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormPostFail(t *testing.T) {
|
func TestBindingFormPostForMap(t *testing.T) {
|
||||||
req := createFormPostRequestFail()
|
req := createFormPostRequestForMap(t)
|
||||||
var obj FooStructForMapType
|
var obj FooStructForMapType
|
||||||
err := FormPost.Bind(req, &obj)
|
err := FormPost.Bind(req, &obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingFormPostForMapFail(t *testing.T) {
|
||||||
|
req := createFormPostRequestForMapFail(t)
|
||||||
|
var obj FooStructForMapType
|
||||||
|
err := FormPost.Bind(req, &obj)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingFormFilesMultipart(t *testing.T) {
|
||||||
|
req := createFormFilesMultipartRequest(t)
|
||||||
|
var obj FooBarFileStruct
|
||||||
|
FormMultipart.Bind(req, &obj)
|
||||||
|
|
||||||
|
// file from os
|
||||||
|
f, _ := os.Open("form.go")
|
||||||
|
defer f.Close()
|
||||||
|
fileActual, _ := ioutil.ReadAll(f)
|
||||||
|
|
||||||
|
// file from multipart
|
||||||
|
mf, _ := obj.File.Open()
|
||||||
|
defer mf.Close()
|
||||||
|
fileExpect, _ := ioutil.ReadAll(mf)
|
||||||
|
|
||||||
|
assert.Equal(t, FormMultipart.Name(), "multipart/form-data")
|
||||||
|
assert.Equal(t, obj.Foo, "bar")
|
||||||
|
assert.Equal(t, obj.Bar, "foo")
|
||||||
|
assert.Equal(t, fileExpect, fileActual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingFormFilesMultipartFail(t *testing.T) {
|
||||||
|
req := createFormFilesMultipartRequestFail(t)
|
||||||
|
var obj FooBarFileFailStruct
|
||||||
|
err := FormMultipart.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,8 +566,18 @@ func TestBindingFormMultipart(t *testing.T) {
|
|||||||
assert.Equal(t, "foo", obj.Bar)
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormMultipartFail(t *testing.T) {
|
func TestBindingFormMultipartForMap(t *testing.T) {
|
||||||
req := createFormMultipartRequestFail(t)
|
req := createFormMultipartRequestForMap(t)
|
||||||
|
var obj FooStructForMapType
|
||||||
|
err := FormMultipart.Bind(req, &obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
|
||||||
|
assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string))
|
||||||
|
assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindingFormMultipartForMapFail(t *testing.T) {
|
||||||
|
req := createFormMultipartRequestForMapFail(t)
|
||||||
var obj FooStructForMapType
|
var obj FooStructForMapType
|
||||||
err := FormMultipart.Bind(req, &obj)
|
err := FormMultipart.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@ -723,6 +710,23 @@ func TestUriInnerBinding(t *testing.T) {
|
|||||||
assert.Equal(t, tag.S.Age, expectedAge)
|
assert.Equal(t, tag.S.Age, expectedAge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) {
|
||||||
|
b := Form
|
||||||
|
assert.Equal(t, "form", b.Name())
|
||||||
|
|
||||||
|
obj := QueryTest{}
|
||||||
|
req := requestWithBody(method, path, body)
|
||||||
|
if method == "POST" {
|
||||||
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
|
}
|
||||||
|
err := b.Bind(req, &obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, obj.Page)
|
||||||
|
assert.Equal(t, 2, obj.Size)
|
||||||
|
assert.Equal(t, "test-appkey", obj.Appkey)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
|
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
|
||||||
b := Form
|
b := Form
|
||||||
assert.Equal(t, "form", b.Name())
|
assert.Equal(t, "form", b.Name())
|
||||||
@ -773,6 +777,17 @@ func TestFormBindingFail(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormBindingMultipartFail(t *testing.T) {
|
||||||
|
obj := FooBarStruct{}
|
||||||
|
req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary")
|
||||||
|
_, err = req.MultipartReader()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = Form.Bind(req, &obj)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFormPostBindingFail(t *testing.T) {
|
func TestFormPostBindingFail(t *testing.T) {
|
||||||
b := FormPost
|
b := FormPost
|
||||||
assert.Equal(t, "form-urlencoded", b.Name())
|
assert.Equal(t, "form-urlencoded", b.Name())
|
||||||
@ -931,149 +946,6 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
|
|||||||
req.Header.Add("Content-Type", MIMEPOSTForm)
|
req.Header.Add("Content-Type", MIMEPOSTForm)
|
||||||
}
|
}
|
||||||
switch typ {
|
switch typ {
|
||||||
case "Int":
|
|
||||||
obj := FooBarStructForIntType{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int(0), obj.IntFoo)
|
|
||||||
assert.Equal(t, int(-12), obj.IntBar)
|
|
||||||
|
|
||||||
obj = FooBarStructForIntType{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int8":
|
|
||||||
obj := FooBarStructForInt8Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int8(0), obj.Int8Foo)
|
|
||||||
assert.Equal(t, int8(-12), obj.Int8Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt8Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int16":
|
|
||||||
obj := FooBarStructForInt16Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int16(0), obj.Int16Foo)
|
|
||||||
assert.Equal(t, int16(-12), obj.Int16Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt16Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int32":
|
|
||||||
obj := FooBarStructForInt32Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int32(0), obj.Int32Foo)
|
|
||||||
assert.Equal(t, int32(-12), obj.Int32Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt32Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Int64":
|
|
||||||
obj := FooBarStructForInt64Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int64(0), obj.Int64Foo)
|
|
||||||
assert.Equal(t, int64(-12), obj.Int64Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForInt64Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint":
|
|
||||||
obj := FooBarStructForUintType{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint(0x0), obj.UintFoo)
|
|
||||||
assert.Equal(t, uint(0xc), obj.UintBar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUintType{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint8":
|
|
||||||
obj := FooBarStructForUint8Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint8(0x0), obj.Uint8Foo)
|
|
||||||
assert.Equal(t, uint8(0xc), obj.Uint8Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint8Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint16":
|
|
||||||
obj := FooBarStructForUint16Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint16(0x0), obj.Uint16Foo)
|
|
||||||
assert.Equal(t, uint16(0xc), obj.Uint16Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint16Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint32":
|
|
||||||
obj := FooBarStructForUint32Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint32(0x0), obj.Uint32Foo)
|
|
||||||
assert.Equal(t, uint32(0xc), obj.Uint32Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint32Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Uint64":
|
|
||||||
obj := FooBarStructForUint64Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint64(0x0), obj.Uint64Foo)
|
|
||||||
assert.Equal(t, uint64(0xc), obj.Uint64Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForUint64Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Float32":
|
|
||||||
obj := FooBarStructForFloat32Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, float32(0.0), obj.Float32Foo)
|
|
||||||
assert.Equal(t, float32(-12.34), obj.Float32Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForFloat32Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Float64":
|
|
||||||
obj := FooBarStructForFloat64Type{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, float64(0.0), obj.Float64Foo)
|
|
||||||
assert.Equal(t, float64(-12.34), obj.Float64Bar)
|
|
||||||
|
|
||||||
obj = FooBarStructForFloat64Type{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Bool":
|
|
||||||
obj := FooBarStructForBoolType{}
|
|
||||||
err := b.Bind(req, &obj)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.False(t, obj.BoolFoo)
|
|
||||||
assert.True(t, obj.BoolBar)
|
|
||||||
|
|
||||||
obj = FooBarStructForBoolType{}
|
|
||||||
req = requestWithBody(method, badPath, badBody)
|
|
||||||
err = JSON.Bind(req, &obj)
|
|
||||||
assert.Error(t, err)
|
|
||||||
case "Slice":
|
case "Slice":
|
||||||
obj := FooStructForSliceType{}
|
obj := FooStructForSliceType{}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@ -1109,7 +981,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
|
|||||||
case "Map":
|
case "Map":
|
||||||
obj := FooStructForMapType{}
|
obj := FooStructForMapType{}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
|
||||||
case "SliceMap":
|
case "SliceMap":
|
||||||
obj := FooStructForSliceMapType{}
|
obj := FooStructForSliceMapType{}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@ -1308,12 +1181,3 @@ func requestWithBody(method, path, body string) (req *http.Request) {
|
|||||||
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
|
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCanSet(t *testing.T) {
|
|
||||||
type CanSetStruct struct {
|
|
||||||
lowerStart string `form:"lower"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var c CanSetStruct
|
|
||||||
assert.Nil(t, mapForm(&c, nil))
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,11 @@
|
|||||||
|
|
||||||
package binding
|
package binding
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
const defaultMemory = 32 * 1024 * 1024
|
const defaultMemory = 32 * 1024 * 1024
|
||||||
|
|
||||||
@ -53,8 +57,33 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
|||||||
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := mapForm(obj, req.MultipartForm.Value); err != nil {
|
if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return validate(obj)
|
return validate(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type multipartRequest http.Request
|
||||||
|
|
||||||
|
var _ setter = (*multipartRequest)(nil)
|
||||||
|
|
||||||
|
var (
|
||||||
|
multipartFileHeaderStructType = reflect.TypeOf(multipart.FileHeader{})
|
||||||
|
)
|
||||||
|
|
||||||
|
// TrySet tries to set a value by the multipart request with the binding a form file
|
||||||
|
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {
|
||||||
|
if value.Type() == multipartFileHeaderStructType {
|
||||||
|
_, file, err := (*http.Request)(r).FormFile(key)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if file != nil {
|
||||||
|
value.Set(reflect.ValueOf(*file))
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setByForm(value, field, r.MultipartForm.Value, key, opt)
|
||||||
|
}
|
||||||
|
@ -6,12 +6,17 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errUnknownType = errors.New("Unknown type")
|
||||||
|
|
||||||
func mapUri(ptr interface{}, m map[string][]string) error {
|
func mapUri(ptr interface{}, m map[string][]string) error {
|
||||||
return mapFormByTag(ptr, m, "uri")
|
return mapFormByTag(ptr, m, "uri")
|
||||||
}
|
}
|
||||||
@ -20,124 +25,193 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
return mapFormByTag(ptr, form, "form")
|
return mapFormByTag(ptr, form, "form")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emptyField = reflect.StructField{}
|
||||||
|
|
||||||
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||||
typ := reflect.TypeOf(ptr).Elem()
|
return mappingByPtr(ptr, formSource(form), tag)
|
||||||
val := reflect.ValueOf(ptr).Elem()
|
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
|
||||||
typeField := typ.Field(i)
|
|
||||||
structField := val.Field(i)
|
|
||||||
if !structField.CanSet() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
structFieldKind := structField.Kind()
|
|
||||||
inputFieldName := typeField.Tag.Get(tag)
|
|
||||||
inputFieldNameList := strings.Split(inputFieldName, ",")
|
|
||||||
inputFieldName = inputFieldNameList[0]
|
|
||||||
var defaultValue string
|
|
||||||
if len(inputFieldNameList) > 1 {
|
|
||||||
defaultList := strings.SplitN(inputFieldNameList[1], "=", 2)
|
|
||||||
if defaultList[0] == "default" {
|
|
||||||
defaultValue = defaultList[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if inputFieldName == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if inputFieldName == "" {
|
|
||||||
inputFieldName = typeField.Name
|
|
||||||
|
|
||||||
// if "form" tag is nil, we inspect if the field is a struct or struct pointer.
|
|
||||||
// this would not make sense for JSON parsing but it does for a form
|
|
||||||
// since data is flatten
|
|
||||||
if structFieldKind == reflect.Ptr {
|
|
||||||
if !structField.Elem().IsValid() {
|
|
||||||
structField.Set(reflect.New(structField.Type().Elem()))
|
|
||||||
}
|
|
||||||
structField = structField.Elem()
|
|
||||||
structFieldKind = structField.Kind()
|
|
||||||
}
|
|
||||||
if structFieldKind == reflect.Struct {
|
|
||||||
err := mapFormByTag(structField.Addr().Interface(), form, tag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inputValue, exists := form[inputFieldName]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
if defaultValue == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
inputValue = make([]string, 1)
|
|
||||||
inputValue[0] = defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
numElems := len(inputValue)
|
|
||||||
if structFieldKind == reflect.Slice && numElems > 0 {
|
|
||||||
sliceOf := structField.Type().Elem().Kind()
|
|
||||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
|
||||||
for i := 0; i < numElems; i++ {
|
|
||||||
if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val.Field(i).Set(slice)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, isTime := structField.Interface().(time.Time); isTime {
|
|
||||||
if err := setTimeField(inputValue[0], typeField, structField); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
// setter tries to set value on a walking by fields of a struct
|
||||||
switch valueKind {
|
type setter interface {
|
||||||
case reflect.Int:
|
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
|
||||||
return setIntField(val, 0, structField)
|
}
|
||||||
case reflect.Int8:
|
|
||||||
return setIntField(val, 8, structField)
|
type formSource map[string][]string
|
||||||
case reflect.Int16:
|
|
||||||
return setIntField(val, 16, structField)
|
var _ setter = formSource(nil)
|
||||||
case reflect.Int32:
|
|
||||||
return setIntField(val, 32, structField)
|
// TrySet tries to set a value by request's form source (like map[string][]string)
|
||||||
case reflect.Int64:
|
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
|
||||||
return setIntField(val, 64, structField)
|
return setByForm(value, field, form, tagValue, opt)
|
||||||
case reflect.Uint:
|
}
|
||||||
return setUintField(val, 0, structField)
|
|
||||||
case reflect.Uint8:
|
func mappingByPtr(ptr interface{}, setter setter, tag string) error {
|
||||||
return setUintField(val, 8, structField)
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
|
||||||
case reflect.Uint16:
|
return err
|
||||||
return setUintField(val, 16, structField)
|
}
|
||||||
case reflect.Uint32:
|
|
||||||
return setUintField(val, 32, structField)
|
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
||||||
case reflect.Uint64:
|
var vKind = value.Kind()
|
||||||
return setUintField(val, 64, structField)
|
|
||||||
case reflect.Bool:
|
if vKind == reflect.Ptr {
|
||||||
return setBoolField(val, structField)
|
var isNew bool
|
||||||
case reflect.Float32:
|
vPtr := value
|
||||||
return setFloatField(val, 32, structField)
|
if value.IsNil() {
|
||||||
case reflect.Float64:
|
isNew = true
|
||||||
return setFloatField(val, 64, structField)
|
vPtr = reflect.New(value.Type().Elem())
|
||||||
case reflect.String:
|
|
||||||
structField.SetString(val)
|
|
||||||
case reflect.Ptr:
|
|
||||||
if !structField.Elem().IsValid() {
|
|
||||||
structField.Set(reflect.New(structField.Type().Elem()))
|
|
||||||
}
|
}
|
||||||
structFieldElem := structField.Elem()
|
isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
|
||||||
return setWithProperType(structFieldElem.Kind(), val, structFieldElem)
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if isNew && isSetted {
|
||||||
|
value.Set(vPtr)
|
||||||
|
}
|
||||||
|
return isSetted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if vKind != reflect.Struct || !field.Anonymous {
|
||||||
|
ok, err := tryToSetValue(value, field, setter, tag)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vKind == reflect.Struct {
|
||||||
|
tValue := value.Type()
|
||||||
|
|
||||||
|
var isSetted bool
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
sf := tValue.Field(i)
|
||||||
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
isSetted = isSetted || ok
|
||||||
|
}
|
||||||
|
return isSetted, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type setOptions struct {
|
||||||
|
isDefaultExists bool
|
||||||
|
defaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
||||||
|
var tagValue string
|
||||||
|
var setOpt setOptions
|
||||||
|
|
||||||
|
tagValue = field.Tag.Get(tag)
|
||||||
|
tagValue, opts := head(tagValue, ",")
|
||||||
|
|
||||||
|
if tagValue == "-" { // just ignoring this field
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if tagValue == "" { // default value is FieldName
|
||||||
|
tagValue = field.Name
|
||||||
|
}
|
||||||
|
if tagValue == "" { // when field is "emptyField" variable
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt string
|
||||||
|
for len(opts) > 0 {
|
||||||
|
opt, opts = head(opts, ",")
|
||||||
|
|
||||||
|
if k, v := head(opt, "="); k == "default" {
|
||||||
|
setOpt.isDefaultExists = true
|
||||||
|
setOpt.defaultValue = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setter.TrySet(value, field, tagValue, setOpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
|
||||||
|
vs, ok := form[tagValue]
|
||||||
|
if !ok && !opt.isDefaultExists {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
if !ok {
|
||||||
|
vs = []string{opt.defaultValue}
|
||||||
|
}
|
||||||
|
return true, setSlice(vs, value, field)
|
||||||
|
case reflect.Array:
|
||||||
|
if !ok {
|
||||||
|
vs = []string{opt.defaultValue}
|
||||||
|
}
|
||||||
|
if len(vs) != value.Len() {
|
||||||
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
||||||
|
}
|
||||||
|
return true, setArray(vs, value, field)
|
||||||
default:
|
default:
|
||||||
return errors.New("Unknown type")
|
var val string
|
||||||
|
if !ok {
|
||||||
|
val = opt.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vs) > 0 {
|
||||||
|
val = vs[0]
|
||||||
|
}
|
||||||
|
return true, setWithProperType(val, value, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
return setIntField(val, 0, value)
|
||||||
|
case reflect.Int8:
|
||||||
|
return setIntField(val, 8, value)
|
||||||
|
case reflect.Int16:
|
||||||
|
return setIntField(val, 16, value)
|
||||||
|
case reflect.Int32:
|
||||||
|
return setIntField(val, 32, value)
|
||||||
|
case reflect.Int64:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case time.Duration:
|
||||||
|
return setTimeDuration(val, value, field)
|
||||||
|
}
|
||||||
|
return setIntField(val, 64, value)
|
||||||
|
case reflect.Uint:
|
||||||
|
return setUintField(val, 0, value)
|
||||||
|
case reflect.Uint8:
|
||||||
|
return setUintField(val, 8, value)
|
||||||
|
case reflect.Uint16:
|
||||||
|
return setUintField(val, 16, value)
|
||||||
|
case reflect.Uint32:
|
||||||
|
return setUintField(val, 32, value)
|
||||||
|
case reflect.Uint64:
|
||||||
|
return setUintField(val, 64, value)
|
||||||
|
case reflect.Bool:
|
||||||
|
return setBoolField(val, value)
|
||||||
|
case reflect.Float32:
|
||||||
|
return setFloatField(val, 32, value)
|
||||||
|
case reflect.Float64:
|
||||||
|
return setFloatField(val, 64, value)
|
||||||
|
case reflect.String:
|
||||||
|
value.SetString(val)
|
||||||
|
case reflect.Struct:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
|
return setTimeField(val, field, value)
|
||||||
|
}
|
||||||
|
return json.Unmarshal([]byte(val), value.Addr().Interface())
|
||||||
|
case reflect.Map:
|
||||||
|
return json.Unmarshal([]byte(val), value.Addr().Interface())
|
||||||
|
default:
|
||||||
|
return errUnknownType
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -218,3 +292,40 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
value.Set(reflect.ValueOf(t))
|
value.Set(reflect.ValueOf(t))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
for i, s := range vals {
|
||||||
|
err := setWithProperType(s, value.Index(i), field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
|
||||||
|
err := setArray(vals, slice, field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.Set(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
d, err := time.ParseDuration(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.Set(reflect.ValueOf(d))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func head(str, sep string) (head string, tail string) {
|
||||||
|
idx := strings.Index(str, sep)
|
||||||
|
if idx < 0 {
|
||||||
|
return str, ""
|
||||||
|
}
|
||||||
|
return str[:idx], str[idx+len(sep):]
|
||||||
|
}
|
||||||
|
61
binding/form_mapping_benchmark_test.go
Normal file
61
binding/form_mapping_benchmark_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var form = map[string][]string{
|
||||||
|
"name": {"mike"},
|
||||||
|
"friends": {"anna", "nicole"},
|
||||||
|
"id_number": {"12345678"},
|
||||||
|
"id_date": {"2018-01-20"},
|
||||||
|
}
|
||||||
|
|
||||||
|
type structFull struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Age int `form:"age,default=25"`
|
||||||
|
Friends []string `form:"friends"`
|
||||||
|
ID *struct {
|
||||||
|
Number string `form:"id_number"`
|
||||||
|
DateOfIssue time.Time `form:"id_date" time_format:"2006-01-02" time_utc:"true"`
|
||||||
|
}
|
||||||
|
Nationality *string `form:"nationality"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMapFormFull(b *testing.B) {
|
||||||
|
var s structFull
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
mapForm(&s, form)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
t := b
|
||||||
|
assert.Equal(t, "mike", s.Name)
|
||||||
|
assert.Equal(t, 25, s.Age)
|
||||||
|
assert.Equal(t, []string{"anna", "nicole"}, s.Friends)
|
||||||
|
assert.Equal(t, "12345678", s.ID.Number)
|
||||||
|
assert.Equal(t, time.Date(2018, 1, 20, 0, 0, 0, 0, time.UTC), s.ID.DateOfIssue)
|
||||||
|
assert.Nil(t, s.Nationality)
|
||||||
|
}
|
||||||
|
|
||||||
|
type structName struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMapFormName(b *testing.B) {
|
||||||
|
var s structName
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
mapForm(&s, form)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
t := b
|
||||||
|
assert.Equal(t, "mike", s.Name)
|
||||||
|
}
|
271
binding/form_mapping_test.go
Normal file
271
binding/form_mapping_test.go
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMappingBaseTypes(t *testing.T) {
|
||||||
|
intPtr := func(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
value interface{}
|
||||||
|
form string
|
||||||
|
expect interface{}
|
||||||
|
}{
|
||||||
|
{"base type", struct{ F int }{}, "9", int(9)},
|
||||||
|
{"base type", struct{ F int8 }{}, "9", int8(9)},
|
||||||
|
{"base type", struct{ F int16 }{}, "9", int16(9)},
|
||||||
|
{"base type", struct{ F int32 }{}, "9", int32(9)},
|
||||||
|
{"base type", struct{ F int64 }{}, "9", int64(9)},
|
||||||
|
{"base type", struct{ F uint }{}, "9", uint(9)},
|
||||||
|
{"base type", struct{ F uint8 }{}, "9", uint8(9)},
|
||||||
|
{"base type", struct{ F uint16 }{}, "9", uint16(9)},
|
||||||
|
{"base type", struct{ F uint32 }{}, "9", uint32(9)},
|
||||||
|
{"base type", struct{ F uint64 }{}, "9", uint64(9)},
|
||||||
|
{"base type", struct{ F bool }{}, "True", true},
|
||||||
|
{"base type", struct{ F float32 }{}, "9.1", float32(9.1)},
|
||||||
|
{"base type", struct{ F float64 }{}, "9.1", float64(9.1)},
|
||||||
|
{"base type", struct{ F string }{}, "test", string("test")},
|
||||||
|
{"base type", struct{ F *int }{}, "9", intPtr(9)},
|
||||||
|
|
||||||
|
// zero values
|
||||||
|
{"zero value", struct{ F int }{}, "", int(0)},
|
||||||
|
{"zero value", struct{ F uint }{}, "", uint(0)},
|
||||||
|
{"zero value", struct{ F bool }{}, "", false},
|
||||||
|
{"zero value", struct{ F float32 }{}, "", float32(0)},
|
||||||
|
} {
|
||||||
|
tp := reflect.TypeOf(tt.value)
|
||||||
|
testName := tt.name + ":" + tp.Field(0).Type.String()
|
||||||
|
|
||||||
|
val := reflect.New(reflect.TypeOf(tt.value))
|
||||||
|
val.Elem().Set(reflect.ValueOf(tt.value))
|
||||||
|
|
||||||
|
field := val.Elem().Type().Field(0)
|
||||||
|
|
||||||
|
_, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form")
|
||||||
|
assert.NoError(t, err, testName)
|
||||||
|
|
||||||
|
actual := val.Elem().Field(0).Interface()
|
||||||
|
assert.Equal(t, tt.expect, actual, testName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingDefault(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Int int `form:",default=9"`
|
||||||
|
Slice []int `form:",default=9"`
|
||||||
|
Array [1]int `form:",default=9"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 9, s.Int)
|
||||||
|
assert.Equal(t, []int{9}, s.Slice)
|
||||||
|
assert.Equal(t, [1]int{9}, s.Array)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingSkipField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
A int
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, s.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingIgnoreField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
A int `form:"A"`
|
||||||
|
B int `form:"-"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 9, s.A)
|
||||||
|
assert.Equal(t, 0, s.B)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingUnexportedField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
A int `form:"a"`
|
||||||
|
b int `form:"b"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 9, s.A)
|
||||||
|
assert.Equal(t, 0, s.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingPrivateField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
f int `form:"field"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"field": {"6"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int(0), s.f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingUnknownFieldType(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
U uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, errUnknownType, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingURI(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
F int `uri:"field"`
|
||||||
|
}
|
||||||
|
err := mapUri(&s, map[string][]string{"field": {"6"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int(6), s.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingForm(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
F int `form:"field"`
|
||||||
|
}
|
||||||
|
err := mapForm(&s, map[string][]string{"field": {"6"}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int(6), s.F)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingTime(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Time time.Time
|
||||||
|
LocalTime time.Time `time_format:"2006-01-02"`
|
||||||
|
ZeroValue time.Time
|
||||||
|
CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"`
|
||||||
|
UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
time.Local, err = time.LoadLocation("Europe/Berlin")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = mapForm(&s, map[string][]string{
|
||||||
|
"Time": {"2019-01-20T16:02:58Z"},
|
||||||
|
"LocalTime": {"2019-01-20"},
|
||||||
|
"ZeroValue": {},
|
||||||
|
"CSTTime": {"2019-01-20"},
|
||||||
|
"UTCTime": {"2019-01-20"},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String())
|
||||||
|
assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String())
|
||||||
|
assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String())
|
||||||
|
assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String())
|
||||||
|
assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String())
|
||||||
|
assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String())
|
||||||
|
assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String())
|
||||||
|
|
||||||
|
// wrong location
|
||||||
|
var wrongLoc struct {
|
||||||
|
Time time.Time `time_location:"wrong"`
|
||||||
|
}
|
||||||
|
err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// wrong time value
|
||||||
|
var wrongTime struct {
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapiingTimeDuration(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
D time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok
|
||||||
|
err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 5*time.Second, s.D)
|
||||||
|
|
||||||
|
// error
|
||||||
|
err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingSlice(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Slice []int `form:"slice,default=9"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// default value
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []int{9}, s.Slice)
|
||||||
|
|
||||||
|
// ok
|
||||||
|
err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []int{3, 4}, s.Slice)
|
||||||
|
|
||||||
|
// error
|
||||||
|
err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingArray(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Array [2]int `form:"array,default=9"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrong default
|
||||||
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// ok
|
||||||
|
err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, [2]int{3, 4}, s.Array)
|
||||||
|
|
||||||
|
// error - not enough vals
|
||||||
|
err = mappingByPtr(&s, formSource{"array": {"3"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// error - wrong value
|
||||||
|
err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingStructField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
J struct {
|
||||||
|
I int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 9, s.J.I)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingMapField(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
M map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, map[string]int{"one": 1}, s.M)
|
||||||
|
}
|
21
context.go
21
context.go
@ -442,11 +442,6 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
|||||||
if values := req.PostForm[key]; len(values) > 0 {
|
if values := req.PostForm[key]; len(values) > 0 {
|
||||||
return values, true
|
return values, true
|
||||||
}
|
}
|
||||||
if req.MultipartForm != nil && req.MultipartForm.File != nil {
|
|
||||||
if values := req.MultipartForm.Value[key]; len(values) > 0 {
|
|
||||||
return values, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []string{}, false
|
return []string{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,13 +460,7 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
|||||||
debugPrint("error on parse multipart form map: %v", err)
|
debugPrint("error on parse multipart form map: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dicts, exist := c.get(req.PostForm, key)
|
return c.get(req.PostForm, key)
|
||||||
|
|
||||||
if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil {
|
|
||||||
dicts, exist = c.get(req.MultipartForm.Value, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dicts, exist
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get is an internal method and returns a map which satisfy conditions.
|
// get is an internal method and returns a map which satisfy conditions.
|
||||||
@ -685,7 +674,7 @@ func (c *Context) ContentType() string {
|
|||||||
// handshake is being initiated by the client.
|
// handshake is being initiated by the client.
|
||||||
func (c *Context) IsWebsocket() bool {
|
func (c *Context) IsWebsocket() bool {
|
||||||
if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &&
|
if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &&
|
||||||
strings.ToLower(c.requestHeader("Upgrade")) == "websocket" {
|
strings.EqualFold(c.requestHeader("Upgrade"), "websocket") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -831,6 +820,12 @@ func (c *Context) AsciiJSON(code int, obj interface{}) {
|
|||||||
c.Render(code, render.AsciiJSON{Data: obj})
|
c.Render(code, render.AsciiJSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PureJSON serializes the given struct as JSON into the response body.
|
||||||
|
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
|
||||||
|
func (c *Context) PureJSON(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.PureJSON{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
// XML serializes the given struct as XML into the response body.
|
// XML serializes the given struct as XML into the response body.
|
||||||
// It also sets the Content-Type as "application/xml".
|
// It also sets the Content-Type as "application/xml".
|
||||||
func (c *Context) XML(code int, obj interface{}) {
|
func (c *Context) XML(code int, obj interface{}) {
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gin-gonic/gin/render"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PureJSON serializes the given struct as JSON into the response body.
|
|
||||||
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
|
|
||||||
func (c *Context) PureJSON(code int, obj interface{}) {
|
|
||||||
c.Render(code, render.PureJSON{Data: obj})
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tests that the response is serialized as JSON
|
|
||||||
// and Content-Type is set to application/json
|
|
||||||
// and special HTML characters are preserved
|
|
||||||
func TestContextRenderPureJSON(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
c, _ := CreateTestContext(w)
|
|
||||||
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
|
||||||
assert.Equal(t, http.StatusCreated, w.Code)
|
|
||||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
|
||||||
}
|
|
@ -622,8 +622,7 @@ func TestContextGetCookie(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContextBodyAllowedForStatus(t *testing.T) {
|
func TestContextBodyAllowedForStatus(t *testing.T) {
|
||||||
// todo(thinkerou): go1.6 not support StatusProcessing
|
assert.False(t, false, bodyAllowedForStatus(http.StatusProcessing))
|
||||||
assert.False(t, false, bodyAllowedForStatus(102))
|
|
||||||
assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
|
assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent))
|
||||||
assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
|
assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified))
|
||||||
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
||||||
@ -661,7 +660,7 @@ func TestContextRenderJSON(t *testing.T) {
|
|||||||
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
c.JSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||||
|
|
||||||
assert.Equal(t, http.StatusCreated, w.Code)
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String())
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,7 +688,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) {
|
|||||||
c.JSONP(http.StatusCreated, H{"foo": "bar"})
|
c.JSONP(http.StatusCreated, H{"foo": "bar"})
|
||||||
|
|
||||||
assert.Equal(t, http.StatusCreated, w.Code)
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -715,7 +714,7 @@ func TestContextRenderAPIJSON(t *testing.T) {
|
|||||||
c.JSON(http.StatusCreated, H{"foo": "bar"})
|
c.JSON(http.StatusCreated, H{"foo": "bar"})
|
||||||
|
|
||||||
assert.Equal(t, http.StatusCreated, w.Code)
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
|
||||||
assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -794,6 +793,18 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) {
|
|||||||
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that the response is serialized as JSON
|
||||||
|
// and Content-Type is set to application/json
|
||||||
|
// and special HTML characters are preserved
|
||||||
|
func TestContextRenderPureJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": "<b>"})
|
||||||
|
assert.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that the response executes the templates
|
// Tests that the response executes the templates
|
||||||
// and responds with Content-Type set to text/html
|
// and responds with Content-Type set to text/html
|
||||||
func TestContextRenderHTML(t *testing.T) {
|
func TestContextRenderHTML(t *testing.T) {
|
||||||
@ -1092,9 +1103,7 @@ func TestContextRenderRedirectAll(t *testing.T) {
|
|||||||
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
assert.Panics(t, func() { c.Redirect(299, "/resource") })
|
||||||
assert.Panics(t, func() { c.Redirect(309, "/resource") })
|
assert.Panics(t, func() { c.Redirect(309, "/resource") })
|
||||||
assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") })
|
assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") })
|
||||||
// todo(thinkerou): go1.6 not support StatusPermanentRedirect(308)
|
assert.NotPanics(t, func() { c.Redirect(http.StatusPermanentRedirect, "/resource") })
|
||||||
// when we upgrade go version we can use http.StatusPermanentRedirect
|
|
||||||
assert.NotPanics(t, func() { c.Redirect(308, "/resource") })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextNegotiationWithJSON(t *testing.T) {
|
func TestContextNegotiationWithJSON(t *testing.T) {
|
||||||
@ -1108,7 +1117,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
|
assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String())
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1272,7 +1281,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
|||||||
_, err := buf.ReadFrom(w.Body)
|
_, err := buf.ReadFrom(w.Body)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
jsonStringBody := buf.String()
|
jsonStringBody := buf.String()
|
||||||
assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody)
|
assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}\n"), jsonStringBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextError(t *testing.T) {
|
func TestContextError(t *testing.T) {
|
||||||
|
11
debug.go
11
debug.go
@ -8,13 +8,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 6
|
const ginSupportMinGoVer = 8
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
@ -54,7 +53,7 @@ func debugPrint(format string, values ...interface{}) {
|
|||||||
if !strings.HasSuffix(format, "\n") {
|
if !strings.HasSuffix(format, "\n") {
|
||||||
format += "\n"
|
format += "\n"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...)
|
fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ func getMinVer(v string) (uint64, error) {
|
|||||||
|
|
||||||
func debugPrintWARNINGDefault() {
|
func debugPrintWARNINGDefault() {
|
||||||
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
||||||
debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
|
debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
@ -98,6 +97,8 @@ at initialization. ie. before any route is registered or the router is listening
|
|||||||
|
|
||||||
func debugPrintError(err error) {
|
func debugPrintError(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debugPrint("[ERROR] %v\n", err)
|
if IsDebugging() {
|
||||||
|
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
|||||||
})
|
})
|
||||||
m, e := getMinVer(runtime.Version())
|
m, e := getMinVer(runtime.Version())
|
||||||
if e == nil && m <= ginSupportMinGoVer {
|
if e == nil && m <= ginSupportMinGoVer {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
}
|
}
|
||||||
@ -111,15 +111,15 @@ func captureOutput(t *testing.T, f func()) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
stdout := os.Stdout
|
defaultWriter := DefaultWriter
|
||||||
stderr := os.Stderr
|
defaultErrorWriter := DefaultErrorWriter
|
||||||
defer func() {
|
defer func() {
|
||||||
os.Stdout = stdout
|
DefaultWriter = defaultWriter
|
||||||
os.Stderr = stderr
|
DefaultErrorWriter = defaultErrorWriter
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
}()
|
}()
|
||||||
os.Stdout = writer
|
DefaultWriter = writer
|
||||||
os.Stderr = writer
|
DefaultErrorWriter = writer
|
||||||
log.SetOutput(writer)
|
log.SetOutput(writer)
|
||||||
out := make(chan string)
|
out := make(chan string)
|
||||||
wg := new(sync.WaitGroup)
|
wg := new(sync.WaitGroup)
|
||||||
|
2
doc.go
2
doc.go
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Package gin implements a HTTP web framework called gin.
|
Package gin implements a HTTP web framework called gin.
|
||||||
|
|
||||||
See https://gin-gonic.github.io/gin/ for more information about gin.
|
See https://gin-gonic.com/ for more information about gin.
|
||||||
*/
|
*/
|
||||||
package gin // import "github.com/gin-gonic/gin"
|
package gin // import "github.com/gin-gonic/gin"
|
||||||
|
@ -53,7 +53,7 @@ func (msg *Error) SetMeta(data interface{}) *Error {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON creates a properly formated JSON
|
// JSON creates a properly formatted JSON
|
||||||
func (msg *Error) JSON() interface{} {
|
func (msg *Error) JSON() interface{} {
|
||||||
json := H{}
|
json := H{}
|
||||||
if msg.Meta != nil {
|
if msg.Meta != nil {
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# Gin Examples
|
# Gin Examples
|
||||||
|
|
||||||
⚠️ **NOTICE:** All gin examples has moved as alone repository to [here](https://github.com/gin-gonic/examples).
|
⚠️ **NOTICE:** All gin examples have been moved as standalone repository to [here](https://github.com/gin-gonic/examples).
|
||||||
|
19
gin.go
19
gin.go
@ -225,7 +225,7 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
|||||||
engine.rebuild405Handlers()
|
engine.rebuild405Handlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
|
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
|
||||||
// included in the handlers chain for every single request. Even 404, 405, static files...
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||||
// For example, this is the right place for a logger or error management middleware.
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
@ -366,12 +366,13 @@ func (engine *Engine) HandleContext(c *Context) {
|
|||||||
|
|
||||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
httpMethod := c.Request.Method
|
httpMethod := c.Request.Method
|
||||||
path := c.Request.URL.Path
|
rPath := c.Request.URL.Path
|
||||||
unescape := false
|
unescape := false
|
||||||
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
||||||
path = c.Request.URL.RawPath
|
rPath = c.Request.URL.RawPath
|
||||||
unescape = engine.UnescapePathValues
|
unescape = engine.UnescapePathValues
|
||||||
}
|
}
|
||||||
|
rPath = cleanPath(rPath)
|
||||||
|
|
||||||
// Find root of the tree for the given HTTP method
|
// Find root of the tree for the given HTTP method
|
||||||
t := engine.trees
|
t := engine.trees
|
||||||
@ -381,7 +382,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
}
|
}
|
||||||
root := t[i].root
|
root := t[i].root
|
||||||
// Find route in tree
|
// Find route in tree
|
||||||
handlers, params, tsr := root.getValue(path, c.Params, unescape)
|
handlers, params, tsr := root.getValue(rPath, c.Params, unescape)
|
||||||
if handlers != nil {
|
if handlers != nil {
|
||||||
c.handlers = handlers
|
c.handlers = handlers
|
||||||
c.Params = params
|
c.Params = params
|
||||||
@ -389,7 +390,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
c.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if httpMethod != "CONNECT" && path != "/" {
|
if httpMethod != "CONNECT" && rPath != "/" {
|
||||||
if tsr && engine.RedirectTrailingSlash {
|
if tsr && engine.RedirectTrailingSlash {
|
||||||
redirectTrailingSlash(c)
|
redirectTrailingSlash(c)
|
||||||
return
|
return
|
||||||
@ -406,7 +407,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
if tree.method == httpMethod {
|
if tree.method == httpMethod {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil {
|
if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil {
|
||||||
c.handlers = engine.allNoMethod
|
c.handlers = engine.allNoMethod
|
||||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
return
|
return
|
||||||
@ -459,15 +460,15 @@ func redirectTrailingSlash(c *Context) {
|
|||||||
|
|
||||||
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||||
req := c.Request
|
req := c.Request
|
||||||
path := req.URL.Path
|
rPath := req.URL.Path
|
||||||
|
|
||||||
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok {
|
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
|
||||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||||
if req.Method != "GET" {
|
if req.Method != "GET" {
|
||||||
code = http.StatusTemporaryRedirect
|
code = http.StatusTemporaryRedirect
|
||||||
}
|
}
|
||||||
req.URL.Path = string(fixedPath)
|
req.URL.Path = string(fixedPath)
|
||||||
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
debugPrint("redirecting request %d: %s --> %s", code, rPath, req.URL.String())
|
||||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
c.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
return true
|
return true
|
||||||
|
20
ginS/gins.go
20
ginS/gins.go
@ -118,30 +118,42 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
|
|||||||
return engine().StaticFS(relativePath, fs)
|
return engine().StaticFS(relativePath, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use attachs a global middleware to the router. ie. the middlewares attached though Use() will be
|
// Use attaches a global middleware to the router. ie. the middlewares attached though Use() will be
|
||||||
// included in the handlers chain for every single request. Even 404, 405, static files...
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||||
// For example, this is the right place for a logger or error management middleware.
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
|
func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
|
||||||
return engine().Use(middlewares...)
|
return engine().Use(middlewares...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run : The router is attached to a http.Server and starts listening and serving HTTP requests.
|
// Routes returns a slice of registered routes.
|
||||||
|
func Routes() gin.RoutesInfo {
|
||||||
|
return engine().Routes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run attaches to a http.Server and starts listening and serving HTTP requests.
|
||||||
// It is a shortcut for http.ListenAndServe(addr, router)
|
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
func Run(addr ...string) (err error) {
|
func Run(addr ...string) (err error) {
|
||||||
return engine().Run(addr...)
|
return engine().Run(addr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests.
|
// RunTLS attaches to a http.Server and starts listening and serving HTTPS requests.
|
||||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
func RunTLS(addr, certFile, keyFile string) (err error) {
|
func RunTLS(addr, certFile, keyFile string) (err error) {
|
||||||
return engine().RunTLS(addr, certFile, keyFile)
|
return engine().RunTLS(addr, certFile, keyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunUnix : The router is attached to a http.Server and starts listening and serving HTTP requests
|
// RunUnix attaches to a http.Server and starts listening and serving HTTP requests
|
||||||
// through the specified unix socket (ie. a file)
|
// through the specified unix socket (ie. a file)
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
func RunUnix(file string) (err error) {
|
func RunUnix(file string) (err error) {
|
||||||
return engine().RunUnix(file)
|
return engine().RunUnix(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||||
|
// through the specified file descriptor.
|
||||||
|
// Note: the method will block the calling goroutine indefinitely unless on error happens.
|
||||||
|
func RunFd(fd int) (err error) {
|
||||||
|
return engine().RunFd(fd)
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -69,6 +70,42 @@ func TestRunTLS(t *testing.T) {
|
|||||||
testRequest(t, "https://localhost:8443/example")
|
testRequest(t, "https://localhost:8443/example")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPusher(t *testing.T) {
|
||||||
|
var html = template.Must(template.New("https").Parse(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Https Test</title>
|
||||||
|
<script src="/assets/app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 style="color:red;">Welcome, Ginner!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
router := New()
|
||||||
|
router.Static("./assets", "./assets")
|
||||||
|
router.SetHTMLTemplate(html)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
router.GET("/pusher", func(c *Context) {
|
||||||
|
if pusher := c.Writer.Pusher(); pusher != nil {
|
||||||
|
pusher.Push("/assets/app.js", nil)
|
||||||
|
}
|
||||||
|
c.String(http.StatusOK, "it worked")
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// have to wait for the goroutine to start and run the server
|
||||||
|
// otherwise the main thread will complete
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
|
||||||
|
assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||||
|
testRequest(t, "https://localhost:8449/pusher")
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunEmptyWithEnv(t *testing.T) {
|
func TestRunEmptyWithEnv(t *testing.T) {
|
||||||
os.Setenv("PORT", "3123")
|
os.Setenv("PORT", "3123")
|
||||||
router := New()
|
router := New()
|
||||||
|
16
go.mod
16
go.mod
@ -1,17 +1,17 @@
|
|||||||
module github.com/gin-gonic/gin
|
module github.com/gin-gonic/gin
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3
|
||||||
github.com/golang/protobuf v1.2.0
|
github.com/golang/protobuf v1.3.1
|
||||||
github.com/json-iterator/go v1.1.5
|
github.com/json-iterator/go v1.1.6
|
||||||
github.com/mattn/go-isatty v0.0.4
|
github.com/mattn/go-isatty v0.0.7
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43
|
github.com/ugorji/go v1.1.4
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 // indirect
|
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2
|
gopkg.in/go-playground/validator.v8 v8.18.2
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
|
38
go.sum
38
go.sum
@ -1,30 +1,34 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 h1:FaI7wNyesdMBSkIRVUuEEYEvmzufs7EqQvRAxfEXGbQ=
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||||
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs=
|
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
|
||||||
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 h1:bzeyCHgoAyjZjAhvTpks+qM7sdlh4cCSitmXeCEO3B4=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
@ -11,6 +11,8 @@ import "encoding/json"
|
|||||||
var (
|
var (
|
||||||
// Marshal is exported by gin/json package.
|
// Marshal is exported by gin/json package.
|
||||||
Marshal = json.Marshal
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
// MarshalIndent is exported by gin/json package.
|
// MarshalIndent is exported by gin/json package.
|
||||||
MarshalIndent = json.MarshalIndent
|
MarshalIndent = json.MarshalIndent
|
||||||
// NewDecoder is exported by gin/json package.
|
// NewDecoder is exported by gin/json package.
|
||||||
|
@ -12,6 +12,8 @@ var (
|
|||||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
// Marshal is exported by gin/json package.
|
// Marshal is exported by gin/json package.
|
||||||
Marshal = json.Marshal
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
// MarshalIndent is exported by gin/json package.
|
// MarshalIndent is exported by gin/json package.
|
||||||
MarshalIndent = json.MarshalIndent
|
MarshalIndent = json.MarshalIndent
|
||||||
// NewDecoder is exported by gin/json package.
|
// NewDecoder is exported by gin/json package.
|
||||||
|
40
logger.go
40
logger.go
@ -14,6 +14,14 @@ import (
|
|||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type consoleColorModeValue int
|
||||||
|
|
||||||
|
const (
|
||||||
|
autoColor consoleColorModeValue = iota
|
||||||
|
disableColor
|
||||||
|
forceColor
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||||
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||||
@ -23,8 +31,7 @@ var (
|
|||||||
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||||
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||||
reset = string([]byte{27, 91, 48, 109})
|
reset = string([]byte{27, 91, 48, 109})
|
||||||
disableColor = false
|
consoleColorMode = autoColor
|
||||||
forceColor = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoggerConfig defines the config for Logger middleware.
|
// LoggerConfig defines the config for Logger middleware.
|
||||||
@ -62,10 +69,12 @@ type LogFormatterParams struct {
|
|||||||
Path string
|
Path string
|
||||||
// ErrorMessage is set if error has occurred in processing the request.
|
// ErrorMessage is set if error has occurred in processing the request.
|
||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
// IsTerm shows whether does gin's output descriptor refers to a terminal.
|
// isTerm shows whether does gin's output descriptor refers to a terminal.
|
||||||
IsTerm bool
|
isTerm bool
|
||||||
// BodySize is the size of the Response Body
|
// BodySize is the size of the Response Body
|
||||||
BodySize int
|
BodySize int
|
||||||
|
// Keys are the keys set on the request's context.
|
||||||
|
Keys map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
|
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
|
||||||
@ -113,15 +122,24 @@ func (p *LogFormatterParams) ResetColor() string {
|
|||||||
return reset
|
return reset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsOutputColor indicates whether can colors be outputted to the log.
|
||||||
|
func (p *LogFormatterParams) IsOutputColor() bool {
|
||||||
|
return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)
|
||||||
|
}
|
||||||
|
|
||||||
// defaultLogFormatter is the default log format function Logger middleware uses.
|
// defaultLogFormatter is the default log format function Logger middleware uses.
|
||||||
var defaultLogFormatter = func(param LogFormatterParams) string {
|
var defaultLogFormatter = func(param LogFormatterParams) string {
|
||||||
var statusColor, methodColor, resetColor string
|
var statusColor, methodColor, resetColor string
|
||||||
if param.IsTerm {
|
if param.IsOutputColor() {
|
||||||
statusColor = param.StatusCodeColor()
|
statusColor = param.StatusCodeColor()
|
||||||
methodColor = param.MethodColor()
|
methodColor = param.MethodColor()
|
||||||
resetColor = param.ResetColor()
|
resetColor = param.ResetColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if param.Latency > time.Minute {
|
||||||
|
// Truncate in a golang < 1.8 safe way
|
||||||
|
param.Latency = param.Latency - param.Latency%time.Second
|
||||||
|
}
|
||||||
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
|
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
|
||||||
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
||||||
statusColor, param.StatusCode, resetColor,
|
statusColor, param.StatusCode, resetColor,
|
||||||
@ -135,12 +153,12 @@ var defaultLogFormatter = func(param LogFormatterParams) string {
|
|||||||
|
|
||||||
// DisableConsoleColor disables color output in the console.
|
// DisableConsoleColor disables color output in the console.
|
||||||
func DisableConsoleColor() {
|
func DisableConsoleColor() {
|
||||||
disableColor = true
|
consoleColorMode = disableColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceConsoleColor force color output in the console.
|
// ForceConsoleColor force color output in the console.
|
||||||
func ForceConsoleColor() {
|
func ForceConsoleColor() {
|
||||||
forceColor = true
|
consoleColorMode = forceColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorLogger returns a handlerfunc for any error type.
|
// ErrorLogger returns a handlerfunc for any error type.
|
||||||
@ -197,9 +215,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
|||||||
|
|
||||||
isTerm := true
|
isTerm := true
|
||||||
|
|
||||||
if w, ok := out.(*os.File); (!ok ||
|
if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
|
||||||
(os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) ||
|
(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
|
||||||
disableColor) && !forceColor {
|
|
||||||
isTerm = false
|
isTerm = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +243,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
|||||||
if _, ok := skip[path]; !ok {
|
if _, ok := skip[path]; !ok {
|
||||||
param := LogFormatterParams{
|
param := LogFormatterParams{
|
||||||
Request: c.Request,
|
Request: c.Request,
|
||||||
IsTerm: isTerm,
|
isTerm: isTerm,
|
||||||
|
Keys: c.Keys,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop timer
|
// Stop timer
|
||||||
|
@ -181,6 +181,7 @@ func TestLoggerWithFormatter(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoggerWithConfigFormatting(t *testing.T) {
|
func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||||
var gotParam LogFormatterParams
|
var gotParam LogFormatterParams
|
||||||
|
var gotKeys map[string]interface{}
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
router := New()
|
router := New()
|
||||||
@ -204,6 +205,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
router.GET("/example", func(c *Context) {
|
router.GET("/example", func(c *Context) {
|
||||||
// set dummy ClientIP
|
// set dummy ClientIP
|
||||||
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
||||||
|
gotKeys = c.Keys
|
||||||
})
|
})
|
||||||
performRequest(router, "GET", "/example?a=100")
|
performRequest(router, "GET", "/example?a=100")
|
||||||
|
|
||||||
@ -223,6 +225,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
assert.Equal(t, "GET", gotParam.Method)
|
assert.Equal(t, "GET", gotParam.Method)
|
||||||
assert.Equal(t, "/example?a=100", gotParam.Path)
|
assert.Equal(t, "/example?a=100", gotParam.Path)
|
||||||
assert.Empty(t, gotParam.ErrorMessage)
|
assert.Empty(t, gotParam.ErrorMessage)
|
||||||
|
assert.Equal(t, gotKeys, gotParam.Keys)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +240,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
IsTerm: false,
|
isTerm: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
termTrueParam := LogFormatterParams{
|
termTrueParam := LogFormatterParams{
|
||||||
@ -248,12 +251,36 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
IsTerm: true,
|
isTerm: true,
|
||||||
|
}
|
||||||
|
termTrueLongDurationParam := LogFormatterParams{
|
||||||
|
TimeStamp: timeStamp,
|
||||||
|
StatusCode: 200,
|
||||||
|
Latency: time.Millisecond * 9876543210,
|
||||||
|
ClientIP: "20.20.20.20",
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/",
|
||||||
|
ErrorMessage: "",
|
||||||
|
isTerm: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
termFalseLongDurationParam := LogFormatterParams{
|
||||||
|
TimeStamp: timeStamp,
|
||||||
|
StatusCode: 200,
|
||||||
|
Latency: time.Millisecond * 9876543210,
|
||||||
|
ClientIP: "20.20.20.20",
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/",
|
||||||
|
ErrorMessage: "",
|
||||||
|
isTerm: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam))
|
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam))
|
||||||
|
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseLongDurationParam))
|
||||||
|
|
||||||
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam))
|
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam))
|
||||||
|
assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueLongDurationParam))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorForMethod(t *testing.T) {
|
func TestColorForMethod(t *testing.T) {
|
||||||
@ -293,6 +320,39 @@ func TestResetColor(t *testing.T) {
|
|||||||
assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())
|
assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsOutputColor(t *testing.T) {
|
||||||
|
// test with isTerm flag true.
|
||||||
|
p := LogFormatterParams{
|
||||||
|
isTerm: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleColorMode = autoColor
|
||||||
|
assert.Equal(t, true, p.IsOutputColor())
|
||||||
|
|
||||||
|
ForceConsoleColor()
|
||||||
|
assert.Equal(t, true, p.IsOutputColor())
|
||||||
|
|
||||||
|
DisableConsoleColor()
|
||||||
|
assert.Equal(t, false, p.IsOutputColor())
|
||||||
|
|
||||||
|
// test with isTerm flag false.
|
||||||
|
p = LogFormatterParams{
|
||||||
|
isTerm: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleColorMode = autoColor
|
||||||
|
assert.Equal(t, false, p.IsOutputColor())
|
||||||
|
|
||||||
|
ForceConsoleColor()
|
||||||
|
assert.Equal(t, true, p.IsOutputColor())
|
||||||
|
|
||||||
|
DisableConsoleColor()
|
||||||
|
assert.Equal(t, false, p.IsOutputColor())
|
||||||
|
|
||||||
|
// reset console color mode.
|
||||||
|
consoleColorMode = autoColor
|
||||||
|
}
|
||||||
|
|
||||||
func TestErrorLogger(t *testing.T) {
|
func TestErrorLogger(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
router.Use(ErrorLogger())
|
router.Use(ErrorLogger())
|
||||||
@ -309,15 +369,15 @@ func TestErrorLogger(t *testing.T) {
|
|||||||
|
|
||||||
w := performRequest(router, "GET", "/error")
|
w := performRequest(router, "GET", "/error")
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String())
|
assert.Equal(t, "{\"error\":\"this is an error\"}\n", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/abort")
|
w = performRequest(router, "GET", "/abort")
|
||||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||||
assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String())
|
assert.Equal(t, "{\"error\":\"no authorized\"}\n", w.Body.String())
|
||||||
|
|
||||||
w = performRequest(router, "GET", "/print")
|
w = performRequest(router, "GET", "/print")
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
assert.Equal(t, "hola!{\"error\":\"this is an error\"}\n", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggerWithWriterSkippingPaths(t *testing.T) {
|
func TestLoggerWithWriterSkippingPaths(t *testing.T) {
|
||||||
@ -355,14 +415,20 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) {
|
|||||||
|
|
||||||
func TestDisableConsoleColor(t *testing.T) {
|
func TestDisableConsoleColor(t *testing.T) {
|
||||||
New()
|
New()
|
||||||
assert.False(t, disableColor)
|
assert.Equal(t, autoColor, consoleColorMode)
|
||||||
DisableConsoleColor()
|
DisableConsoleColor()
|
||||||
assert.True(t, disableColor)
|
assert.Equal(t, disableColor, consoleColorMode)
|
||||||
|
|
||||||
|
// reset console color mode.
|
||||||
|
consoleColorMode = autoColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestForceConsoleColor(t *testing.T) {
|
func TestForceConsoleColor(t *testing.T) {
|
||||||
New()
|
New()
|
||||||
assert.False(t, forceColor)
|
assert.Equal(t, autoColor, consoleColorMode)
|
||||||
ForceConsoleColor()
|
ForceConsoleColor()
|
||||||
assert.True(t, forceColor)
|
assert.Equal(t, forceColor, consoleColorMode)
|
||||||
|
|
||||||
|
// reset console color mode.
|
||||||
|
consoleColorMode = autoColor
|
||||||
}
|
}
|
||||||
|
@ -246,5 +246,5 @@ func TestMiddlewareWrite(t *testing.T) {
|
|||||||
w := performRequest(router, "GET", "/")
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
assert.Equal(t, strings.Replace("hola\n<map><foo>bar</foo></map>{\"foo\":\"bar\"}\n{\"foo\":\"bar\"}\nevent:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1))
|
||||||
}
|
}
|
||||||
|
17
recovery.go
17
recovery.go
@ -52,12 +52,19 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
|||||||
}
|
}
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
stack := stack(3)
|
stack := stack(3)
|
||||||
httprequest, _ := httputil.DumpRequest(c.Request, false)
|
httpRequest, _ := httputil.DumpRequest(c.Request, false)
|
||||||
|
headers := strings.Split(string(httpRequest), "\r\n")
|
||||||
|
for idx, header := range headers {
|
||||||
|
current := strings.Split(header, ":")
|
||||||
|
if current[0] == "Authorization" {
|
||||||
|
headers[idx] = current[0] + ": *"
|
||||||
|
}
|
||||||
|
}
|
||||||
if brokenPipe {
|
if brokenPipe {
|
||||||
logger.Printf("%s\n%s%s", err, string(httprequest), reset)
|
logger.Printf("%s\n%s%s", err, string(httpRequest), reset)
|
||||||
} else if IsDebugging() {
|
} else if IsDebugging() {
|
||||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
||||||
timeFormat(time.Now()), string(httprequest), err, stack, reset)
|
timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset)
|
||||||
} else {
|
} else {
|
||||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
||||||
timeFormat(time.Now()), err, stack, reset)
|
timeFormat(time.Now()), err, stack, reset)
|
||||||
@ -128,8 +135,8 @@ func function(pc uintptr) []byte {
|
|||||||
// *T.ptrmethod
|
// *T.ptrmethod
|
||||||
// Also the package path might contains dot (e.g. code.google.com/...),
|
// Also the package path might contains dot (e.g. code.google.com/...),
|
||||||
// so first eliminate the path prefix
|
// so first eliminate the path prefix
|
||||||
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
||||||
name = name[lastslash+1:]
|
name = name[lastSlash+1:]
|
||||||
}
|
}
|
||||||
if period := bytes.Index(name, dot); period >= 0 {
|
if period := bytes.Index(name, dot); period >= 0 {
|
||||||
name = name[period+1:]
|
name = name[period+1:]
|
||||||
|
@ -2,12 +2,11 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -18,6 +17,37 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestPanicClean(t *testing.T) {
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
router := New()
|
||||||
|
password := "my-super-secret-password"
|
||||||
|
router.Use(RecoveryWithWriter(buffer))
|
||||||
|
router.GET("/recovery", func(c *Context) {
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
panic("Oupps, Houston, we have a problem")
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/recovery",
|
||||||
|
header{
|
||||||
|
Key: "Host",
|
||||||
|
Value: "www.google.com",
|
||||||
|
},
|
||||||
|
header{
|
||||||
|
Key: "Authorization",
|
||||||
|
Value: fmt.Sprintf("Bearer %s", password),
|
||||||
|
},
|
||||||
|
header{
|
||||||
|
Key: "Content-Type",
|
||||||
|
Value: "application/json",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
|
||||||
|
// Check the buffer does not have the secret key
|
||||||
|
assert.NotContains(t, buffer.String(), password)
|
||||||
|
}
|
||||||
|
|
||||||
// TestPanicInHandler assert that panic has been recovered.
|
// TestPanicInHandler assert that panic has been recovered.
|
||||||
func TestPanicInHandler(t *testing.T) {
|
func TestPanicInHandler(t *testing.T) {
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
@ -43,6 +43,11 @@ type AsciiJSON struct {
|
|||||||
// SecureJSONPrefix is a string which represents SecureJSON prefix.
|
// SecureJSONPrefix is a string which represents SecureJSON prefix.
|
||||||
type SecureJSONPrefix string
|
type SecureJSONPrefix string
|
||||||
|
|
||||||
|
// PureJSON contains the given interface object.
|
||||||
|
type PureJSON struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||||
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||||
var jsonAsciiContentType = []string{"application/json"}
|
var jsonAsciiContentType = []string{"application/json"}
|
||||||
@ -63,11 +68,8 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
|
|||||||
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||||
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
jsonBytes, err := json.Marshal(obj)
|
encoder := json.NewEncoder(w)
|
||||||
if err != nil {
|
err := encoder.Encode(&obj)
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(jsonBytes)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,3 +176,16 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
|||||||
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonAsciiContentType)
|
writeContentType(w, jsonAsciiContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||||
|
func (r PureJSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
return encoder.Encode(r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (PureJSON) writes custom ContentType.
|
||||||
|
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PureJSON contains the given interface object.
|
|
||||||
type PureJSON struct {
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
|
||||||
func (r PureJSON) Render(w http.ResponseWriter) error {
|
|
||||||
r.WriteContentType(w)
|
|
||||||
encoder := json.NewEncoder(w)
|
|
||||||
encoder.SetEscapeHTML(false)
|
|
||||||
return encoder.Encode(r.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteContentType (PureJSON) writes custom ContentType.
|
|
||||||
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
|
||||||
writeContentType(w, jsonContentType)
|
|
||||||
}
|
|
@ -18,9 +18,7 @@ type Redirect struct {
|
|||||||
|
|
||||||
// Render (Redirect) redirects the http request to new location and writes redirect response.
|
// Render (Redirect) redirects the http request to new location and writes redirect response.
|
||||||
func (r Redirect) Render(w http.ResponseWriter) error {
|
func (r Redirect) Render(w http.ResponseWriter) error {
|
||||||
// todo(thinkerou): go1.6 not support StatusPermanentRedirect(308)
|
if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
|
||||||
// when we upgrade go version we can use http.StatusPermanentRedirect
|
|
||||||
if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
|
|
||||||
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
||||||
}
|
}
|
||||||
http.Redirect(w, r.Request, r.Location, r.Code)
|
http.Redirect(w, r.Request, r.Location, r.Code)
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRenderPureJSON(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"foo": "bar",
|
|
||||||
"html": "<b>",
|
|
||||||
}
|
|
||||||
err := (PureJSON{data}).Render(w)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
|
||||||
}
|
|
@ -62,7 +62,7 @@ func TestRenderJSON(t *testing.T) {
|
|||||||
err := (JSON{data}).Render(w)
|
err := (JSON{data}).Render(w)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String())
|
||||||
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,6 +215,18 @@ func TestRenderAsciiJSONFail(t *testing.T) {
|
|||||||
assert.Error(t, (AsciiJSON{data}).Render(w))
|
assert.Error(t, (AsciiJSON{data}).Render(w))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderPureJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
"html": "<b>",
|
||||||
|
}
|
||||||
|
err := (PureJSON{data}).Render(w)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
type xmlmap map[string]interface{}
|
type xmlmap map[string]interface{}
|
||||||
|
|
||||||
// Allows type H to be used with xml.Marshal
|
// Allows type H to be used with xml.Marshal
|
||||||
|
@ -16,7 +16,8 @@ const (
|
|||||||
defaultStatus = http.StatusOK
|
defaultStatus = http.StatusOK
|
||||||
)
|
)
|
||||||
|
|
||||||
type responseWriterBase interface {
|
// ResponseWriter ...
|
||||||
|
type ResponseWriter interface {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
http.Hijacker
|
http.Hijacker
|
||||||
http.Flusher
|
http.Flusher
|
||||||
@ -37,6 +38,9 @@ type responseWriterBase interface {
|
|||||||
|
|
||||||
// Forces to write the http header (status code + headers).
|
// Forces to write the http header (status code + headers).
|
||||||
WriteHeaderNow()
|
WriteHeaderNow()
|
||||||
|
|
||||||
|
// get the http.Pusher for server push
|
||||||
|
Pusher() http.Pusher
|
||||||
}
|
}
|
||||||
|
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
@ -113,3 +117,10 @@ func (w *responseWriter) Flush() {
|
|||||||
w.WriteHeaderNow()
|
w.WriteHeaderNow()
|
||||||
w.ResponseWriter.(http.Flusher).Flush()
|
w.ResponseWriter.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Pusher() (pusher http.Pusher) {
|
||||||
|
if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
|
||||||
|
return pusher
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
// +build !go1.8
|
|
||||||
|
|
||||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
// ResponseWriter ...
|
|
||||||
type ResponseWriter interface {
|
|
||||||
responseWriterBase
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
// +build go1.8
|
|
||||||
|
|
||||||
// Copyright 2018 Gin Core Team. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponseWriter ...
|
|
||||||
type ResponseWriter interface {
|
|
||||||
responseWriterBase
|
|
||||||
// get the http.Pusher for server push
|
|
||||||
Pusher() http.Pusher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *responseWriter) Pusher() (pusher http.Pusher) {
|
|
||||||
if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
|
|
||||||
return pusher
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -195,7 +195,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
|||||||
// Check if file exists and/or if we have permission to access it
|
// Check if file exists and/or if we have permission to access it
|
||||||
if _, err := fs.Open(file); err != nil {
|
if _, err := fs.Open(file); err != nil {
|
||||||
c.Writer.WriteHeader(http.StatusNotFound)
|
c.Writer.WriteHeader(http.StatusNotFound)
|
||||||
c.handlers = group.engine.allNoRoute
|
c.handlers = group.engine.noRoute
|
||||||
// Reset index
|
// Reset index
|
||||||
c.index = -1
|
c.index = -1
|
||||||
return
|
return
|
||||||
|
@ -22,7 +22,7 @@ type header struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
|
func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder {
|
||||||
req, _ := http.NewRequest(method, path, nil)
|
req := httptest.NewRequest(method, path, nil)
|
||||||
for _, h := range headers {
|
for _, h := range headers {
|
||||||
req.Header.Add(h.Key, h.Value)
|
req.Header.Add(h.Key, h.Value)
|
||||||
}
|
}
|
||||||
@ -257,6 +257,39 @@ func TestRouteParamsByName(t *testing.T) {
|
|||||||
assert.Equal(t, "/is/super/great", wild)
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes.
|
||||||
|
func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
|
||||||
|
name := ""
|
||||||
|
lastName := ""
|
||||||
|
wild := ""
|
||||||
|
router := New()
|
||||||
|
router.GET("/test/:name/:last_name/*wild", func(c *Context) {
|
||||||
|
name = c.Params.ByName("name")
|
||||||
|
lastName = c.Params.ByName("last_name")
|
||||||
|
var ok bool
|
||||||
|
wild, ok = c.Params.Get("wild")
|
||||||
|
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, name, c.Param("name"))
|
||||||
|
assert.Equal(t, name, c.Param("name"))
|
||||||
|
assert.Equal(t, lastName, c.Param("last_name"))
|
||||||
|
|
||||||
|
assert.Empty(t, c.Param("wtf"))
|
||||||
|
assert.Empty(t, c.Params.ByName("wtf"))
|
||||||
|
|
||||||
|
wtf, ok := c.Params.Get("wtf")
|
||||||
|
assert.Empty(t, wtf)
|
||||||
|
assert.False(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
w := performRequest(router, "GET", "//test//john//smith//is//super//great")
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Equal(t, "john", name)
|
||||||
|
assert.Equal(t, "smith", lastName)
|
||||||
|
assert.Equal(t, "/is/super/great", wild)
|
||||||
|
}
|
||||||
|
|
||||||
// TestHandleStaticFile - ensure the static file handles properly
|
// TestHandleStaticFile - ensure the static file handles properly
|
||||||
func TestRouteStaticFile(t *testing.T) {
|
func TestRouteStaticFile(t *testing.T) {
|
||||||
// SETUP file
|
// SETUP file
|
||||||
@ -388,12 +421,11 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
{"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/
|
||||||
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
{"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/
|
||||||
{"", http.StatusMovedPermanently, "/"}, // TSR +/
|
|
||||||
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
{"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case
|
||||||
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
{"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case
|
||||||
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
{"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/
|
||||||
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
{"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/
|
||||||
{"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath
|
{"/../path", http.StatusOK, ""}, // CleanPath
|
||||||
{"/nope", http.StatusNotFound, ""}, // NotFound
|
{"/nope", http.StatusNotFound, ""}, // NotFound
|
||||||
}
|
}
|
||||||
for _, tr := range testRoutes {
|
for _, tr := range testRoutes {
|
||||||
@ -429,7 +461,6 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
|
|
||||||
func TestRouterStaticFSNotFound(t *testing.T) {
|
func TestRouterStaticFSNotFound(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
|
|
||||||
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||||
router.NoRoute(func(c *Context) {
|
router.NoRoute(func(c *Context) {
|
||||||
c.String(404, "non existent")
|
c.String(404, "non existent")
|
||||||
@ -452,6 +483,27 @@ func TestRouterStaticFSFileNotFound(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reproduction test for the bug of issue #1805
|
||||||
|
func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
|
||||||
|
// Middleware must be called just only once by per request.
|
||||||
|
middlewareCalledNum := 0
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
middlewareCalledNum += 1
|
||||||
|
})
|
||||||
|
|
||||||
|
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||||
|
|
||||||
|
// First access
|
||||||
|
performRequest(router, "GET", "/nonexistent")
|
||||||
|
assert.Equal(t, 1, middlewareCalledNum)
|
||||||
|
|
||||||
|
// Second access
|
||||||
|
performRequest(router, "HEAD", "/nonexistent")
|
||||||
|
assert.Equal(t, 2, middlewareCalledNum)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouteRawPath(t *testing.T) {
|
func TestRouteRawPath(t *testing.T) {
|
||||||
route := New()
|
route := New()
|
||||||
route.UseRawPath = true
|
route.UseRawPath = true
|
||||||
|
4
tree.go
4
tree.go
@ -514,7 +514,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
|||||||
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
|
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
|
||||||
|
|
||||||
// Outer loop for walking the tree
|
// Outer loop for walking the tree
|
||||||
for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) {
|
for len(path) >= len(n.path) && strings.EqualFold(path[:len(n.path)], n.path) {
|
||||||
path = path[len(n.path):]
|
path = path[len(n.path):]
|
||||||
ciPath = append(ciPath, n.path...)
|
ciPath = append(ciPath, n.path...)
|
||||||
|
|
||||||
@ -618,7 +618,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
|||||||
return ciPath, true
|
return ciPath, true
|
||||||
}
|
}
|
||||||
if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
|
if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
|
||||||
strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) &&
|
strings.EqualFold(path, n.path[:len(path)]) &&
|
||||||
n.handlers != nil {
|
n.handlers != nil {
|
||||||
return append(ciPath, n.path...), true
|
return append(ciPath, n.path...), true
|
||||||
}
|
}
|
||||||
|
84
vendor/vendor.json
vendored
84
vendor/vendor.json
vendored
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"comment": "v1.3.0",
|
"comment": "v1.4.0",
|
||||||
"ignore": "test",
|
"ignore": "test",
|
||||||
"package": [
|
"package": [
|
||||||
{
|
{
|
||||||
@ -13,32 +13,44 @@
|
|||||||
{
|
{
|
||||||
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
|
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
|
||||||
"path": "github.com/gin-contrib/sse",
|
"path": "github.com/gin-contrib/sse",
|
||||||
"revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae",
|
"revision": "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae",
|
||||||
"revisionTime": "2017-01-09T09:34:21Z"
|
"revisionTime": "2019-03-01T06:25:29Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "mE9XW26JSpe4meBObM6J/Oeq0eg=",
|
"checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=",
|
||||||
"path": "github.com/golang/protobuf/proto",
|
"path": "github.com/golang/protobuf/proto",
|
||||||
"revision": "aa810b61a9c79d51363740d207bb46cf8e620ed5",
|
"revision": "c823c79ea1570fb5ff454033735a8e68575d1d0f",
|
||||||
"revisionTime": "2018-08-14T21:14:27Z",
|
"revisionTime": "2019-02-05T22:20:52Z",
|
||||||
"version": "v1.2",
|
"version": "v1.3",
|
||||||
"versionExact": "v1.2.0"
|
"versionExact": "v1.3.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=",
|
"checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=",
|
||||||
"path": "github.com/json-iterator/go",
|
"path": "github.com/json-iterator/go",
|
||||||
"revision": "1624edc4454b8682399def8740d46db5e4362ba4",
|
"revision": "0ff49de124c6f76f8494e194af75bde0f1a49a29",
|
||||||
"revisionTime": "2018-08-06T06:07:27Z",
|
"revisionTime": "2019-03-06T14:29:09Z",
|
||||||
"version": "v1.1",
|
"version": "v1.1",
|
||||||
"versionExact": "v1.1.5"
|
"versionExact": "v1.1.6"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=",
|
"checksumSHA1": "Ya+baVBU/RkXXUWD3LGFmGJiiIg=",
|
||||||
"path": "github.com/mattn/go-isatty",
|
"path": "github.com/mattn/go-isatty",
|
||||||
"revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c",
|
"revision": "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7",
|
||||||
"revisionTime": "2017-11-07T05:05:31Z",
|
"revisionTime": "2019-03-12T13:58:54Z",
|
||||||
"version": "v0.0",
|
"version": "v0.0",
|
||||||
"versionExact": "v0.0.4"
|
"versionExact": "v0.0.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=",
|
||||||
|
"path": "github.com/modern-go/concurrent",
|
||||||
|
"revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94",
|
||||||
|
"revisionTime": "2018-03-06T01:26:44Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=",
|
||||||
|
"path": "github.com/modern-go/reflect2",
|
||||||
|
"revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8",
|
||||||
|
"revisionTime": "2018-07-18T01:23:57Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
||||||
@ -46,6 +58,20 @@
|
|||||||
"revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc",
|
"revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc",
|
||||||
"revisionTime": "2018-12-26T10:54:42Z"
|
"revisionTime": "2018-12-26T10:54:42Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "cpNsoLqBprpKh+VZTBOZNVXzBEk=",
|
||||||
|
"path": "github.com/stretchr/objx",
|
||||||
|
"revision": "c61a9dfcced1815e7d40e214d00d1a8669a9f58c",
|
||||||
|
"revisionTime": "2019-02-11T16:23:28Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "DBdcVxnvaINHhWyyGgih/Mel6gE=",
|
||||||
|
"path": "github.com/stretchr/testify",
|
||||||
|
"revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053",
|
||||||
|
"revisionTime": "2018-12-05T02:12:43Z",
|
||||||
|
"version": "v1.3",
|
||||||
|
"versionExact": "v1.3.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
|
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
|
||||||
"path": "github.com/stretchr/testify/assert",
|
"path": "github.com/stretchr/testify/assert",
|
||||||
@ -55,24 +81,24 @@
|
|||||||
"versionExact": "v1.2.2"
|
"versionExact": "v1.2.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=",
|
"checksumSHA1": "csplo594qomjp2IZj82y7mTueOw=",
|
||||||
"path": "github.com/ugorji/go/codec",
|
"path": "github.com/ugorji/go/codec",
|
||||||
"revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab",
|
"revision": "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13",
|
||||||
"revisionTime": "2018-04-07T10:07:33Z",
|
"revisionTime": "2019-04-08T19:08:48Z",
|
||||||
"version": "v1.1",
|
"version": "v1.1",
|
||||||
"versionExact": "v1.1.1"
|
"versionExact": "v1.1.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
|
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
|
||||||
"path": "golang.org/x/net/context",
|
"path": "golang.org/x/net/context",
|
||||||
"revision": "49bb7cea24b1df9410e1712aa6433dae904ff66a",
|
"revision": "f4e77d36d62c17c2336347bb2670ddbd02d092b7",
|
||||||
"revisionTime": "2018-10-11T05:27:23Z"
|
"revisionTime": "2019-05-02T22:26:14Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "SiJNkx+YGtq3Gtr6Ldu6OW83O+U=",
|
"checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=",
|
||||||
"path": "golang.org/x/sys/unix",
|
"path": "golang.org/x/sys/unix",
|
||||||
"revision": "fa43e7bc11baaae89f3f902b2b4d832b68234844",
|
"revision": "a43fa875dd822b81eb6d2ad538bc1f4caba169bd",
|
||||||
"revisionTime": "2018-10-11T14:35:51Z"
|
"revisionTime": "2019-05-02T15:41:39Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
|
"checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
|
||||||
@ -83,12 +109,12 @@
|
|||||||
"versionExact": "v8.18.2"
|
"versionExact": "v8.18.2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=",
|
"checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=",
|
||||||
"path": "gopkg.in/yaml.v2",
|
"path": "gopkg.in/yaml.v2",
|
||||||
"revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183",
|
"revision": "51d6538a90f86fe93ac480b35f37b2be17fef232",
|
||||||
"revisionTime": "2018-03-28T19:50:20Z",
|
"revisionTime": "2018-11-15T11:05:04Z",
|
||||||
"version": "v2.2",
|
"version": "v2.2",
|
||||||
"versionExact": "v2.2.1"
|
"versionExact": "v2.2.2"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rootPath": "github.com/gin-gonic/gin"
|
"rootPath": "github.com/gin-gonic/gin"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user