Merge branch 'master' into remove-go1.6

This commit is contained in:
田欧 2019-03-18 18:44:55 +08:00 committed by GitHub
commit adbe29e902
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 206 additions and 31 deletions

View File

@ -1,5 +1,4 @@
language: go language: go
sudo: false
matrix: matrix:
fast_finish: true fast_finish: true

View File

@ -116,7 +116,7 @@ All documentation is available on the Gin website.
- [English](https://gin-gonic.com/docs/) - [English](https://gin-gonic.com/docs/)
- [简体中文](https://gin-gonic.com/zh-cn/docs/) - [简体中文](https://gin-gonic.com/zh-cn/docs/)
- [繁體中文](https://gin-gonic.com/zh-tw/docs/) - [繁體中文](https://gin-gonic.com/zh-tw/docs/)
- [にほんご](https://gin-gonic.com/ja/docs/) - [日本語](https://gin-gonic.com/ja/docs/)
## Examples ## Examples

View File

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

View File

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

View File

@ -56,5 +56,10 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
if err := mapForm(obj, req.MultipartForm.Value); err != nil { if err := mapForm(obj, req.MultipartForm.Value); err != nil {
return err return err
} }
if err := mapFiles(obj, req); err != nil {
return err
}
return validate(obj) return validate(obj)
} }

View File

@ -7,6 +7,7 @@ package binding
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/http"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -15,6 +16,34 @@ import (
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
) )
func mapFiles(ptr interface{}, req *http.Request) error {
typ := reflect.TypeOf(ptr).Elem()
val := reflect.ValueOf(ptr).Elem()
for i := 0; i < typ.NumField(); i++ {
typeField := typ.Field(i)
structField := val.Field(i)
t := fmt.Sprintf("%s", typeField.Type)
if string(t) != "*multipart.FileHeader" {
continue
}
inputFieldName := typeField.Tag.Get("form")
if inputFieldName == "" {
inputFieldName = typeField.Name
}
_, fileHeader, err := req.FormFile(inputFieldName)
if err != nil {
return err
}
structField.Set(reflect.ValueOf(fileHeader))
}
return nil
}
var errUnknownType = errors.New("Unknown type") var errUnknownType = errors.New("Unknown type")
func mapUri(ptr interface{}, m map[string][]string) error { func mapUri(ptr interface{}, m map[string][]string) error {

View File

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

View File

@ -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,10 +122,15 @@ 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()
@ -137,12 +149,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 +211,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 +239,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,
} }

View File

@ -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,7 +251,7 @@ func TestDefaultLogFormatter(t *testing.T) {
Method: "GET", Method: "GET",
Path: "/", Path: "/",
ErrorMessage: "", ErrorMessage: "",
IsTerm: true, isTerm: true,
} }
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))
@ -296,6 +296,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 +391,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
} }