mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-22 01:12:16 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
647f984c96
@ -1,5 +1,4 @@
|
|||||||
language: go
|
language: go
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,9 +8,11 @@ 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"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -31,6 +33,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"`
|
||||||
@ -187,8 +201,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))
|
||||||
@ -536,6 +550,54 @@ func createFormPostRequestForMapFail(t *testing.T) *http.Request {
|
|||||||
return req
|
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)
|
||||||
@ -613,6 +675,34 @@ func TestBindingFormPostForMapFail(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
func TestBindingFormMultipart(t *testing.T) {
|
func TestBindingFormMultipart(t *testing.T) {
|
||||||
req := createFormMultipartRequest(t)
|
req := createFormMultipartRequest(t)
|
||||||
var obj FooBarStruct
|
var obj FooBarStruct
|
||||||
@ -1430,3 +1520,31 @@ func TestBindingTimeDuration(t *testing.T) {
|
|||||||
err = Form.Bind(req, &s)
|
err = Form.Bind(req, &s)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindingArray(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Nums [2]int `form:"nums,default=4"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
|
req := formPostRequest("", "")
|
||||||
|
err := Form.Bind(req, &s)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, [2]int{0, 0}, s.Nums)
|
||||||
|
|
||||||
|
// ok
|
||||||
|
req = formPostRequest("", "nums=3&nums=8")
|
||||||
|
err = Form.Bind(req, &s)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, [2]int{3, 8}, s.Nums)
|
||||||
|
|
||||||
|
// not enough vals
|
||||||
|
req = formPostRequest("", "nums=3")
|
||||||
|
err = Form.Bind(req, &s)
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// error
|
||||||
|
req = formPostRequest("", "nums=3&nums=wrong")
|
||||||
|
err = Form.Bind(req, &s)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
@ -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,6 +6,7 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -27,11 +28,29 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
var emptyField = reflect.StructField{}
|
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 {
|
||||||
_, err := mapping(reflect.ValueOf(ptr), emptyField, form, tag)
|
return mappingByPtr(ptr, formSource(form), tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setter tries to set value on a walking by fields of a struct
|
||||||
|
type setter interface {
|
||||||
|
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type formSource map[string][]string
|
||||||
|
|
||||||
|
var _ setter = formSource(nil)
|
||||||
|
|
||||||
|
// TrySet tries to set a value by request's form source (like map[string][]string)
|
||||||
|
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
|
||||||
|
return setByForm(value, field, form, tagValue, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mappingByPtr(ptr interface{}, setter setter, tag string) error {
|
||||||
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapping(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) {
|
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
||||||
var vKind = value.Kind()
|
var vKind = value.Kind()
|
||||||
|
|
||||||
if vKind == reflect.Ptr {
|
if vKind == reflect.Ptr {
|
||||||
@ -41,7 +60,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s
|
|||||||
isNew = true
|
isNew = true
|
||||||
vPtr = reflect.New(value.Type().Elem())
|
vPtr = reflect.New(value.Type().Elem())
|
||||||
}
|
}
|
||||||
isSetted, err := mapping(vPtr.Elem(), field, form, tag)
|
isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -51,7 +70,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s
|
|||||||
return isSetted, nil
|
return isSetted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := tryToSetValue(value, field, form, tag)
|
ok, err := tryToSetValue(value, field, setter, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -67,7 +86,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s
|
|||||||
if !value.Field(i).CanSet() {
|
if !value.Field(i).CanSet() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ok, err := mapping(value.Field(i), tValue.Field(i), form, tag)
|
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -78,9 +97,14 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryToSetValue(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) {
|
type setOptions struct {
|
||||||
var tagValue, defaultValue string
|
isDefaultExists bool
|
||||||
var 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 = field.Tag.Get(tag)
|
||||||
tagValue, opts := head(tagValue, ",")
|
tagValue, opts := head(tagValue, ",")
|
||||||
@ -102,26 +126,38 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri
|
|||||||
k, v := head(opt, "=")
|
k, v := head(opt, "=")
|
||||||
switch k {
|
switch k {
|
||||||
case "default":
|
case "default":
|
||||||
isDefaultExists = true
|
setOpt.isDefaultExists = true
|
||||||
defaultValue = v
|
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]
|
vs, ok := form[tagValue]
|
||||||
if !ok && !isDefaultExists {
|
if !ok && !opt.isDefaultExists {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
if !ok {
|
if !ok {
|
||||||
vs = []string{defaultValue}
|
vs = []string{opt.defaultValue}
|
||||||
}
|
}
|
||||||
return true, setSlice(vs, value, field)
|
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:
|
||||||
var val string
|
var val string
|
||||||
if !ok {
|
if !ok {
|
||||||
val = defaultValue
|
val = opt.defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(vs) > 0 {
|
if len(vs) > 0 {
|
||||||
@ -256,14 +292,22 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
|
func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
|
||||||
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
|
|
||||||
for i, s := range vals {
|
for i, s := range vals {
|
||||||
err := setWithProperType(s, slice.Index(i), field)
|
err := setWithProperType(s, value.Index(i), field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
value.Set(slice)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
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"
|
||||||
|
@ -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).
|
||||||
|
18
ginS/gins.go
18
ginS/gins.go
@ -125,23 +125,35 @@ 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: thie method will block the calling goroutine indefinitely unless on error happens.
|
||||||
|
func RunFd(fd int) (err error) {
|
||||||
|
return engine().RunFd(fd)
|
||||||
|
}
|
||||||
|
53
logger.go
53
logger.go
@ -14,17 +14,24 @@ 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})
|
||||||
yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109})
|
yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109})
|
||||||
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||||
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||||
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,8 +69,8 @@ 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 are the keys set on the request's context.
|
||||||
@ -115,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,
|
||||||
@ -137,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.
|
||||||
@ -199,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +243,7 @@ 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,
|
Keys: c.Keys,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ func TestDefaultLogFormatter(t *testing.T) {
|
|||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: "/",
|
Path: "/",
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
IsTerm: false,
|
isTerm: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
termTrueParam := LogFormatterParams{
|
termTrueParam := LogFormatterParams{
|
||||||
@ -251,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) {
|
||||||
@ -296,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())
|
||||||
@ -358,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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -429,7 +429,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 +451,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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user