mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-20 00:02:16 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
9db5f6b697
5
Makefile
5
Makefile
@ -18,7 +18,10 @@ test:
|
||||
cat tmp.out; \
|
||||
if grep -q "^--- FAIL" tmp.out; then \
|
||||
rm tmp.out; \
|
||||
exit 1;\
|
||||
exit 1; \
|
||||
elif grep -q "build failed" tmp.out; then \
|
||||
rm tmp.out; \
|
||||
exit; \
|
||||
fi; \
|
||||
if [ -f profile.out ]; then \
|
||||
cat profile.out | grep -v "mode:" >> coverage.out; \
|
||||
|
50
README.md
50
README.md
@ -35,6 +35,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
|
||||
- [Using middleware](#using-middleware)
|
||||
- [How to write log file](#how-to-write-log-file)
|
||||
- [Custom Log Format](#custom-log-format)
|
||||
- [Model binding and validation](#model-binding-and-validation)
|
||||
- [Custom Validators](#custom-validators)
|
||||
- [Only Bind Query String](#only-bind-query-string)
|
||||
@ -363,6 +364,10 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
|
||||
|
||||
References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single).
|
||||
|
||||
`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693)
|
||||
|
||||
> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
@ -528,6 +533,43 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Log Format
|
||||
```go
|
||||
func main() {
|
||||
router := gin.New()
|
||||
|
||||
// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
|
||||
// By default gin.DefaultWriter = os.Stdout
|
||||
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
|
||||
// your custom format
|
||||
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
|
||||
param.ClientIP,
|
||||
param.TimeStamp.Format(time.RFC1123),
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.Request.Proto,
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
param.Request.UserAgent(),
|
||||
param.ErrorMessage,
|
||||
)
|
||||
}))
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
router.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
})
|
||||
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
**Sample Output**
|
||||
```
|
||||
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
|
||||
```
|
||||
|
||||
### Model binding and validation
|
||||
|
||||
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
|
||||
@ -578,7 +620,7 @@ func main() {
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
// <root>
|
||||
// <user>user</user>
|
||||
// <password>123</user>
|
||||
// <password>123</password>
|
||||
// </root>)
|
||||
router.POST("/loginXML", func(c *gin.Context) {
|
||||
var xml Login
|
||||
@ -1591,6 +1633,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -1618,7 +1661,10 @@ func main() {
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 5 seconds.
|
||||
quit := make(chan os.Signal)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
// kill (no param) default send syscanll.SIGTERM
|
||||
// kill -2 is syscall.SIGINT
|
||||
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("Shutdown Server ...")
|
||||
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -515,28 +516,28 @@ func createFormPostRequestFail() *http.Request {
|
||||
return req
|
||||
}
|
||||
|
||||
func createFormMultipartRequest() *http.Request {
|
||||
func createFormMultipartRequest(t *testing.T) *http.Request {
|
||||
boundary := "--testboundary"
|
||||
body := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(body)
|
||||
defer mw.Close()
|
||||
|
||||
mw.SetBoundary(boundary)
|
||||
mw.WriteField("foo", "bar")
|
||||
mw.WriteField("bar", "foo")
|
||||
assert.NoError(t, mw.SetBoundary(boundary))
|
||||
assert.NoError(t, mw.WriteField("foo", "bar"))
|
||||
assert.NoError(t, mw.WriteField("bar", "foo"))
|
||||
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
|
||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||
return req
|
||||
}
|
||||
|
||||
func createFormMultipartRequestFail() *http.Request {
|
||||
func createFormMultipartRequestFail(t *testing.T) *http.Request {
|
||||
boundary := "--testboundary"
|
||||
body := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(body)
|
||||
defer mw.Close()
|
||||
|
||||
mw.SetBoundary(boundary)
|
||||
mw.WriteField("map_foo", "bar")
|
||||
assert.NoError(t, mw.SetBoundary(boundary))
|
||||
assert.NoError(t, mw.WriteField("map_foo", "bar"))
|
||||
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
|
||||
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||
return req
|
||||
@ -545,7 +546,7 @@ func createFormMultipartRequestFail() *http.Request {
|
||||
func TestBindingFormPost(t *testing.T) {
|
||||
req := createFormPostRequest()
|
||||
var obj FooBarStruct
|
||||
FormPost.Bind(req, &obj)
|
||||
assert.NoError(t, FormPost.Bind(req, &obj))
|
||||
|
||||
assert.Equal(t, "form-urlencoded", FormPost.Name())
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
@ -555,7 +556,7 @@ func TestBindingFormPost(t *testing.T) {
|
||||
func TestBindingDefaultValueFormPost(t *testing.T) {
|
||||
req := createDefaultFormPostRequest()
|
||||
var obj FooDefaultBarStruct
|
||||
FormPost.Bind(req, &obj)
|
||||
assert.NoError(t, FormPost.Bind(req, &obj))
|
||||
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, "hello", obj.Bar)
|
||||
@ -569,9 +570,9 @@ func TestBindingFormPostFail(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBindingFormMultipart(t *testing.T) {
|
||||
req := createFormMultipartRequest()
|
||||
req := createFormMultipartRequest(t)
|
||||
var obj FooBarStruct
|
||||
FormMultipart.Bind(req, &obj)
|
||||
assert.NoError(t, FormMultipart.Bind(req, &obj))
|
||||
|
||||
assert.Equal(t, "multipart/form-data", FormMultipart.Name())
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
@ -579,7 +580,7 @@ func TestBindingFormMultipart(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBindingFormMultipartFail(t *testing.T) {
|
||||
req := createFormMultipartRequestFail()
|
||||
req := createFormMultipartRequestFail(t)
|
||||
var obj FooStructForMapType
|
||||
err := FormMultipart.Bind(req, &obj)
|
||||
assert.Error(t, err)
|
||||
@ -690,6 +691,28 @@ func TestUriBinding(t *testing.T) {
|
||||
assert.Equal(t, map[string]interface{}(nil), not.Name)
|
||||
}
|
||||
|
||||
func TestUriInnerBinding(t *testing.T) {
|
||||
type Tag struct {
|
||||
Name string `uri:"name"`
|
||||
S struct {
|
||||
Age int `uri:"age"`
|
||||
}
|
||||
}
|
||||
|
||||
expectedName := "mike"
|
||||
expectedAge := 25
|
||||
|
||||
m := map[string][]string{
|
||||
"name": {expectedName},
|
||||
"age": {strconv.Itoa(expectedAge)},
|
||||
}
|
||||
|
||||
var tag Tag
|
||||
assert.NoError(t, Uri.BindUri(m, &tag))
|
||||
assert.Equal(t, tag.Name, expectedName)
|
||||
assert.Equal(t, tag.S.Age, expectedAge)
|
||||
}
|
||||
|
||||
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
|
||||
b := Form
|
||||
assert.Equal(t, "form", b.Name())
|
||||
|
@ -20,7 +20,11 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
req.ParseMultipartForm(defaultMemory)
|
||||
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||
if err != http.ErrNotMultipart {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := mapForm(obj, req.Form); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||
structFieldKind = structField.Kind()
|
||||
}
|
||||
if structFieldKind == reflect.Struct {
|
||||
err := mapForm(structField.Addr().Interface(), form)
|
||||
err := mapFormByTag(structField.Addr().Interface(), form, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
41
context.go
41
context.go
@ -105,8 +105,9 @@ func (c *Context) Handler() HandlerFunc {
|
||||
// See example in GitHub.
|
||||
func (c *Context) Next() {
|
||||
c.index++
|
||||
for s := int8(len(c.handlers)); c.index < s; c.index++ {
|
||||
for c.index < int8(len(c.handlers)) {
|
||||
c.handlers[c.index](c)
|
||||
c.index++
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,7 +416,11 @@ func (c *Context) PostFormArray(key string) []string {
|
||||
// a boolean value whether at least one value exists for the given key.
|
||||
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||
req := c.Request
|
||||
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
||||
if err != http.ErrNotMultipart {
|
||||
debugPrint("error on parse multipart form array: %v", err)
|
||||
}
|
||||
}
|
||||
if values := req.PostForm[key]; len(values) > 0 {
|
||||
return values, true
|
||||
}
|
||||
@ -437,7 +442,11 @@ func (c *Context) PostFormMap(key string) map[string]string {
|
||||
// whether at least one value exists for the given key.
|
||||
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
||||
req := c.Request
|
||||
req.ParseMultipartForm(c.engine.MaxMultipartMemory)
|
||||
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
||||
if err != http.ErrNotMultipart {
|
||||
debugPrint("error on parse multipart form map: %v", err)
|
||||
}
|
||||
}
|
||||
dicts, exist := c.get(req.PostForm, key)
|
||||
|
||||
if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil {
|
||||
@ -493,8 +502,8 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
io.Copy(out, src)
|
||||
return nil
|
||||
_, err = io.Copy(out, src)
|
||||
return err
|
||||
}
|
||||
|
||||
// Bind checks the Content-Type to select a binding engine automatically,
|
||||
@ -530,15 +539,25 @@ func (c *Context) BindYAML(obj interface{}) error {
|
||||
return c.MustBindWith(obj, binding.YAML)
|
||||
}
|
||||
|
||||
// BindUri binds the passed struct pointer using binding.Uri.
|
||||
// It will abort the request with HTTP 400 if any error occurs.
|
||||
func (c *Context) BindUri(obj interface{}) error {
|
||||
if err := c.ShouldBindUri(obj); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustBindWith binds the passed struct pointer using the specified binding engine.
|
||||
// It will abort the request with HTTP 400 if any error occurs.
|
||||
// See the binding package.
|
||||
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
|
||||
if err = c.ShouldBindWith(obj, b); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)
|
||||
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {
|
||||
if err := c.ShouldBindWith(obj, b); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldBind checks the Content-Type to select a binding engine automatically,
|
||||
@ -904,7 +923,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
||||
c.XML(code, data)
|
||||
|
||||
default:
|
||||
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
|
||||
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,8 @@ func TestContextFormFile(t *testing.T) {
|
||||
mw := multipart.NewWriter(buf)
|
||||
w, err := mw.CreateFormFile("file", "test")
|
||||
if assert.NoError(t, err) {
|
||||
w.Write([]byte("test"))
|
||||
_, err = w.Write([]byte("test"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
mw.Close()
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
@ -100,10 +101,11 @@ func TestContextFormFileFailed(t *testing.T) {
|
||||
func TestContextMultipartForm(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
mw.WriteField("foo", "bar")
|
||||
assert.NoError(t, mw.WriteField("foo", "bar"))
|
||||
w, err := mw.CreateFormFile("file", "test")
|
||||
if assert.NoError(t, err) {
|
||||
w.Write([]byte("test"))
|
||||
_, err = w.Write([]byte("test"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
mw.Close()
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
@ -137,7 +139,8 @@ func TestSaveUploadedCreateFailed(t *testing.T) {
|
||||
mw := multipart.NewWriter(buf)
|
||||
w, err := mw.CreateFormFile("file", "test")
|
||||
if assert.NoError(t, err) {
|
||||
w.Write([]byte("test"))
|
||||
_, err = w.Write([]byte("test"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
mw.Close()
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
@ -159,7 +162,7 @@ func TestContextReset(t *testing.T) {
|
||||
c.index = 2
|
||||
c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()}
|
||||
c.Params = Params{Param{}}
|
||||
c.Error(errors.New("test"))
|
||||
c.Error(errors.New("test")) // nolint: errcheck
|
||||
c.Set("foo", "bar")
|
||||
c.reset()
|
||||
|
||||
@ -798,7 +801,7 @@ func TestContextRenderHTML2(t *testing.T) {
|
||||
assert.Len(t, router.trees, 1)
|
||||
|
||||
templ := template.Must(template.New("t").Parse(`Hello {{.name}}`))
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
router.SetHTMLTemplate(templ)
|
||||
SetMode(TestMode)
|
||||
@ -1211,7 +1214,8 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
||||
assert.Equal(t, "application/json; charset=utf-8", contentType)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(w.Body)
|
||||
_, err := buf.ReadFrom(w.Body)
|
||||
assert.NoError(t, err)
|
||||
jsonStringBody := buf.String()
|
||||
assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody)
|
||||
}
|
||||
@ -1220,11 +1224,11 @@ func TestContextError(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
assert.Empty(t, c.Errors)
|
||||
|
||||
c.Error(errors.New("first error"))
|
||||
c.Error(errors.New("first error")) // nolint: errcheck
|
||||
assert.Len(t, c.Errors, 1)
|
||||
assert.Equal(t, "Error #01: first error\n", c.Errors.String())
|
||||
|
||||
c.Error(&Error{
|
||||
c.Error(&Error{ // nolint: errcheck
|
||||
Err: errors.New("second error"),
|
||||
Meta: "some data 2",
|
||||
Type: ErrorTypePublic,
|
||||
@ -1246,13 +1250,13 @@ func TestContextError(t *testing.T) {
|
||||
t.Error("didn't panic")
|
||||
}
|
||||
}()
|
||||
c.Error(nil)
|
||||
c.Error(nil) // nolint: errcheck
|
||||
}
|
||||
|
||||
func TestContextTypedError(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Error(errors.New("externo 0")).SetType(ErrorTypePublic)
|
||||
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
|
||||
c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck
|
||||
c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck
|
||||
|
||||
for _, err := range c.Errors.ByType(ErrorTypePublic) {
|
||||
assert.Equal(t, ErrorTypePublic, err.Type)
|
||||
@ -1267,7 +1271,7 @@ func TestContextAbortWithError(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input")
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||||
assert.Equal(t, abortIndex, c.index)
|
||||
@ -1457,7 +1461,7 @@ func TestContextShouldBindWithXML(t *testing.T) {
|
||||
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<foo>FOO</foo>
|
||||
<bar>BAR</bar>
|
||||
<bar>BAR</bar>
|
||||
</root>`))
|
||||
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
|
||||
|
||||
@ -1475,15 +1479,19 @@ func TestContextShouldBindWithQuery(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused"))
|
||||
c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused"))
|
||||
|
||||
var obj struct {
|
||||
Foo string `form:"foo"`
|
||||
Bar string `form:"bar"`
|
||||
Foo string `form:"foo"`
|
||||
Bar string `form:"bar"`
|
||||
Foo1 string `form:"Foo"`
|
||||
Bar1 string `form:"Bar"`
|
||||
}
|
||||
assert.NoError(t, c.ShouldBindQuery(&obj))
|
||||
assert.Equal(t, "foo", obj.Bar)
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, "foo1", obj.Bar1)
|
||||
assert.Equal(t, "bar1", obj.Foo1)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
@ -1709,7 +1717,8 @@ func TestContextStream(t *testing.T) {
|
||||
stopStream = false
|
||||
}()
|
||||
|
||||
w.Write([]byte("test"))
|
||||
_, err := w.Write([]byte("test"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
return stopStream
|
||||
})
|
||||
@ -1726,10 +1735,23 @@ func TestContextStreamWithClientGone(t *testing.T) {
|
||||
w.closeClient()
|
||||
}()
|
||||
|
||||
writer.Write([]byte("test"))
|
||||
_, err := writer.Write([]byte("test"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
assert.Equal(t, "test", w.Body.String())
|
||||
}
|
||||
|
||||
func TestContextResetInHandler(t *testing.T) {
|
||||
w := CreateTestResponseRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.handlers = []HandlerFunc{
|
||||
func(c *Context) { c.reset() },
|
||||
}
|
||||
assert.NotPanics(t, func() {
|
||||
c.Next()
|
||||
})
|
||||
}
|
||||
|
@ -32,21 +32,21 @@ func TestIsDebugging(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDebugPrint(t *testing.T) {
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
SetMode(ReleaseMode)
|
||||
debugPrint("DEBUG this!")
|
||||
SetMode(TestMode)
|
||||
debugPrint("DEBUG this!")
|
||||
SetMode(DebugMode)
|
||||
debugPrint("these are %d %s\n", 2, "error messages")
|
||||
debugPrint("these are %d %s", 2, "error messages")
|
||||
SetMode(TestMode)
|
||||
})
|
||||
assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re)
|
||||
}
|
||||
|
||||
func TestDebugPrintError(t *testing.T) {
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
debugPrintError(nil)
|
||||
debugPrintError(errors.New("this is an error"))
|
||||
@ -56,7 +56,7 @@ func TestDebugPrintError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDebugPrintRoutes(t *testing.T) {
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest})
|
||||
SetMode(TestMode)
|
||||
@ -65,7 +65,7 @@ func TestDebugPrintRoutes(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDebugPrintLoadTemplate(t *testing.T) {
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl"))
|
||||
debugPrintLoadTemplate(templ)
|
||||
@ -75,7 +75,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
debugPrintWARNINGSetHTMLTemplate()
|
||||
SetMode(TestMode)
|
||||
@ -84,7 +84,7 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
debugPrintWARNINGDefault()
|
||||
SetMode(TestMode)
|
||||
@ -98,7 +98,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDebugPrintWARNINGNew(t *testing.T) {
|
||||
re := captureOutput(func() {
|
||||
re := captureOutput(t, func() {
|
||||
SetMode(DebugMode)
|
||||
debugPrintWARNINGNew()
|
||||
SetMode(TestMode)
|
||||
@ -106,7 +106,7 @@ func TestDebugPrintWARNINGNew(t *testing.T) {
|
||||
assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re)
|
||||
}
|
||||
|
||||
func captureOutput(f func()) string {
|
||||
func captureOutput(t *testing.T, f func()) string {
|
||||
reader, writer, err := os.Pipe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -127,7 +127,8 @@ func captureOutput(f func()) string {
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
wg.Done()
|
||||
io.Copy(&buf, reader)
|
||||
_, err := io.Copy(&buf, reader)
|
||||
assert.NoError(t, err)
|
||||
out <- buf.String()
|
||||
}()
|
||||
wg.Wait()
|
||||
|
@ -24,7 +24,9 @@ func TestBindWith(t *testing.T) {
|
||||
Foo string `form:"foo"`
|
||||
Bar string `form:"bar"`
|
||||
}
|
||||
assert.NoError(t, c.BindWith(&obj, binding.Form))
|
||||
captureOutput(t, func() {
|
||||
assert.NoError(t, c.BindWith(&obj, binding.Form))
|
||||
})
|
||||
assert.Equal(t, "foo", obj.Bar)
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
|
@ -34,7 +34,7 @@ func TestError(t *testing.T) {
|
||||
jsonBytes, _ := json.Marshal(err)
|
||||
assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes))
|
||||
|
||||
err.SetMeta(H{
|
||||
err.SetMeta(H{ // nolint: errcheck
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
})
|
||||
@ -44,7 +44,7 @@ func TestError(t *testing.T) {
|
||||
"data": "some data",
|
||||
}, err.JSON())
|
||||
|
||||
err.SetMeta(H{
|
||||
err.SetMeta(H{ // nolint: errcheck
|
||||
"error": "custom error",
|
||||
"status": "200",
|
||||
"data": "some data",
|
||||
@ -59,7 +59,7 @@ func TestError(t *testing.T) {
|
||||
status string
|
||||
data string
|
||||
}
|
||||
err.SetMeta(customError{status: "200", data: "other data"})
|
||||
err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck
|
||||
assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON())
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -35,7 +36,10 @@ func main() {
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 5 seconds.
|
||||
quit := make(chan os.Signal)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
// kill (no param) default send syscanll.SIGTERM
|
||||
// kill -2 is syscall.SIGINT
|
||||
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("Shutdown Server ...")
|
||||
|
||||
|
30
examples/new_relic/README.md
Normal file
30
examples/new_relic/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
The [New Relic Go Agent](https://github.com/newrelic/go-agent) provides a nice middleware for the stdlib handler signature.
|
||||
The following is an adaptation of that middleware for Gin.
|
||||
|
||||
```golang
|
||||
const (
|
||||
// NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context
|
||||
NewRelicTxnKey = "NewRelicTxnKey"
|
||||
)
|
||||
|
||||
// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler
|
||||
func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request)
|
||||
defer txn.End()
|
||||
ctx.Set(NewRelicTxnKey, txn)
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
```
|
||||
and in `main.go` or equivalent...
|
||||
```golang
|
||||
router := gin.Default()
|
||||
cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY"))
|
||||
app, err := newrelic.NewApplication(cfg)
|
||||
if err != nil {
|
||||
log.Printf("failed to make new_relic app: %v", err)
|
||||
} else {
|
||||
router.Use(adapters.NewRelicMonitoring(app))
|
||||
}
|
||||
```
|
42
examples/new_relic/main.go
Normal file
42
examples/new_relic/main.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/newrelic/go-agent"
|
||||
)
|
||||
|
||||
const (
|
||||
// NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context
|
||||
NewRelicTxnKey = "NewRelicTxnKey"
|
||||
)
|
||||
|
||||
// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler
|
||||
func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request)
|
||||
defer txn.End()
|
||||
ctx.Set(NewRelicTxnKey, txn)
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
|
||||
cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY"))
|
||||
app, err := newrelic.NewApplication(cfg)
|
||||
if err != nil {
|
||||
log.Printf("failed to make new_relic app: %v", err)
|
||||
} else {
|
||||
router.Use(NewRelicMonitoring(app))
|
||||
}
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "Hello World!\n")
|
||||
})
|
||||
router.Run()
|
||||
}
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -25,7 +26,8 @@ func main() {
|
||||
files := form.File["files"]
|
||||
|
||||
for _, file := range files {
|
||||
if err := c.SaveUploadedFile(file, file.Filename); err != nil {
|
||||
filename := filepath.Base(file.Filename)
|
||||
if err := c.SaveUploadedFile(file, filename); err != nil {
|
||||
c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -23,7 +24,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.SaveUploadedFile(file, file.Filename); err != nil {
|
||||
filename := filepath.Base(file.Filename)
|
||||
if err := c.SaveUploadedFile(file, filename); err != nil {
|
||||
c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
8
gin.go
8
gin.go
@ -355,8 +355,11 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// This can be done by setting c.Request.URL.Path to your new target.
|
||||
// Disclaimer: You can loop yourself to death with this, use wisely.
|
||||
func (engine *Engine) HandleContext(c *Context) {
|
||||
oldIndexValue := c.index
|
||||
c.reset()
|
||||
engine.handleHTTPRequest(c)
|
||||
|
||||
c.index = oldIndexValue
|
||||
}
|
||||
|
||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
@ -422,7 +425,10 @@ func serveError(c *Context, code int, defaultMessage []byte) {
|
||||
}
|
||||
if c.writermem.Status() == code {
|
||||
c.writermem.Header()["Content-Type"] = mimePlain
|
||||
c.Writer.Write(defaultMessage)
|
||||
_, err := c.Writer.Write(defaultMessage)
|
||||
if err != nil {
|
||||
debugPrint("cannot write message to writer during serve error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.writermem.WriteHeaderNow()
|
||||
|
@ -87,7 +87,7 @@ func TestRunEmptyWithEnv(t *testing.T) {
|
||||
func TestRunTooMuchParams(t *testing.T) {
|
||||
router := New()
|
||||
assert.Panics(t, func() {
|
||||
router.Run("2", "2")
|
||||
assert.NoError(t, router.Run("2", "2"))
|
||||
})
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ func TestBadUnixSocket(t *testing.T) {
|
||||
func TestFileDescriptor(t *testing.T) {
|
||||
router := New()
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", ":8000")
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
assert.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
assert.NoError(t, err)
|
||||
@ -152,7 +152,7 @@ func TestFileDescriptor(t *testing.T) {
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
c, err := net.Dial("tcp", "localhost:8000")
|
||||
c, err := net.Dial("tcp", listener.Addr().String())
|
||||
assert.NoError(t, err)
|
||||
|
||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
@ -188,15 +188,12 @@ func TestConcurrentHandleContext(t *testing.T) {
|
||||
})
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
iterations := 200
|
||||
wg.Add(iterations)
|
||||
for i := 0; i < iterations; i++ {
|
||||
go func() {
|
||||
testRequest(t, ts.URL+"/")
|
||||
testGetRequestHandler(t, router, "/")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
@ -217,3 +214,14 @@ func TestConcurrentHandleContext(t *testing.T) {
|
||||
|
||||
// testRequest(t, "http://localhost:8033/example")
|
||||
// }
|
||||
|
||||
func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
|
||||
assert.Equal(t, 200, w.Code, "should get a 200")
|
||||
}
|
||||
|
85
gin_test.go
85
gin_test.go
@ -12,6 +12,8 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -25,18 +27,23 @@ func formatAsDate(t time.Time) string {
|
||||
|
||||
func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server {
|
||||
SetMode(mode)
|
||||
router := New()
|
||||
router.Delims("{[{", "}]}")
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
})
|
||||
loadMethod(router)
|
||||
router.GET("/test", func(c *Context) {
|
||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
||||
})
|
||||
router.GET("/raw", func(c *Context) {
|
||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||
defer SetMode(TestMode)
|
||||
|
||||
var router *Engine
|
||||
captureOutput(t, func() {
|
||||
router = New()
|
||||
router.Delims("{[{", "}]}")
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"formatAsDate": formatAsDate,
|
||||
})
|
||||
loadMethod(router)
|
||||
router.GET("/test", func(c *Context) {
|
||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
||||
})
|
||||
router.GET("/raw", func(c *Context) {
|
||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -471,6 +478,60 @@ func TestListOfRoutes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestEngineHandleContext(t *testing.T) {
|
||||
r := New()
|
||||
r.GET("/", func(c *Context) {
|
||||
c.Request.URL.Path = "/v2"
|
||||
r.HandleContext(c)
|
||||
})
|
||||
v2 := r.Group("/v2")
|
||||
{
|
||||
v2.GET("/", func(c *Context) {})
|
||||
}
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
w := performRequest(r, "GET", "/")
|
||||
assert.Equal(t, 301, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEngineHandleContextManyReEntries(t *testing.T) {
|
||||
expectValue := 10000
|
||||
|
||||
var handlerCounter, middlewareCounter int64
|
||||
|
||||
r := New()
|
||||
r.Use(func(c *Context) {
|
||||
atomic.AddInt64(&middlewareCounter, 1)
|
||||
})
|
||||
r.GET("/:count", func(c *Context) {
|
||||
countStr := c.Param("count")
|
||||
count, err := strconv.Atoi(countStr)
|
||||
assert.NoError(t, err)
|
||||
|
||||
n, err := c.Writer.Write([]byte("."))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, n)
|
||||
|
||||
switch {
|
||||
case count > 0:
|
||||
c.Request.URL.Path = "/" + strconv.Itoa(count-1)
|
||||
r.HandleContext(c)
|
||||
}
|
||||
}, func(c *Context) {
|
||||
atomic.AddInt64(&handlerCounter, 1)
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.Equal(t, expectValue, w.Body.Len())
|
||||
})
|
||||
|
||||
assert.Equal(t, int64(expectValue), handlerCounter)
|
||||
assert.Equal(t, int64(expectValue), middlewareCounter)
|
||||
}
|
||||
|
||||
func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {
|
||||
for _, gotRoute := range gotRoutes {
|
||||
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {
|
||||
|
@ -287,11 +287,11 @@ var githubAPI = []route{
|
||||
|
||||
func TestShouldBindUri(t *testing.T) {
|
||||
DefaultWriter = os.Stdout
|
||||
router := Default()
|
||||
router := New()
|
||||
|
||||
type Person struct {
|
||||
Name string `uri:"name"`
|
||||
Id string `uri:"id"`
|
||||
Name string `uri:"name" binding:"required"`
|
||||
Id string `uri:"id" binding:"required"`
|
||||
}
|
||||
router.Handle("GET", "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
@ -304,6 +304,46 @@ func TestShouldBindUri(t *testing.T) {
|
||||
path, _ := exampleFromPath("/rest/:name/:id")
|
||||
w := performRequest(router, "GET", path)
|
||||
assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestBindUri(t *testing.T) {
|
||||
DefaultWriter = os.Stdout
|
||||
router := New()
|
||||
|
||||
type Person struct {
|
||||
Name string `uri:"name" binding:"required"`
|
||||
Id string `uri:"id" binding:"required"`
|
||||
}
|
||||
router.Handle("GET", "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
assert.NoError(t, c.BindUri(&person))
|
||||
assert.True(t, "" != person.Name)
|
||||
assert.True(t, "" != person.Id)
|
||||
c.String(http.StatusOK, "BindUri test OK")
|
||||
})
|
||||
|
||||
path, _ := exampleFromPath("/rest/:name/:id")
|
||||
w := performRequest(router, "GET", path)
|
||||
assert.Equal(t, "BindUri test OK", w.Body.String())
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestBindUriError(t *testing.T) {
|
||||
DefaultWriter = os.Stdout
|
||||
router := New()
|
||||
|
||||
type Member struct {
|
||||
Number string `uri:"num" binding:"required,uuid"`
|
||||
}
|
||||
router.Handle("GET", "/new/rest/:num", func(c *Context) {
|
||||
var m Member
|
||||
assert.Error(t, c.BindUri(&m))
|
||||
})
|
||||
|
||||
path1, _ := exampleFromPath("/new/rest/:num")
|
||||
w1 := performRequest(router, "GET", path1)
|
||||
assert.Equal(t, http.StatusBadRequest, w1.Code)
|
||||
}
|
||||
|
||||
func githubConfigRouter(router *Engine) {
|
||||
@ -321,7 +361,7 @@ func githubConfigRouter(router *Engine) {
|
||||
|
||||
func TestGithubAPI(t *testing.T) {
|
||||
DefaultWriter = os.Stdout
|
||||
router := Default()
|
||||
router := New()
|
||||
githubConfigRouter(router)
|
||||
|
||||
for _, route := range githubAPI {
|
||||
@ -396,7 +436,7 @@ func BenchmarkParallelGithub(b *testing.B) {
|
||||
|
||||
func BenchmarkParallelGithubDefault(b *testing.B) {
|
||||
DefaultWriter = os.Stdout
|
||||
router := Default()
|
||||
router := New()
|
||||
githubConfigRouter(router)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil)
|
||||
|
40
go.mod
40
go.mod
@ -1,30 +1,32 @@
|
||||
module github.com/gin-gonic/gin
|
||||
|
||||
require (
|
||||
github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7
|
||||
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b
|
||||
github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482
|
||||
github.com/golang/protobuf v1.2.0
|
||||
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15
|
||||
github.com/json-iterator/go v1.1.5
|
||||
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227
|
||||
github.com/mattn/go-isatty v0.0.4
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/thinkerou/favicon v0.1.0
|
||||
github.com/ugorji/go v1.1.1
|
||||
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e
|
||||
golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba // indirect
|
||||
google.golang.org/grpc v1.15.0
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2
|
||||
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
|
||||
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
||||
exclude (
|
||||
github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66
|
||||
github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768
|
||||
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15
|
||||
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227
|
||||
github.com/newrelic/go-agent v2.5.0+incompatible
|
||||
github.com/thinkerou/favicon v0.1.0
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
|
||||
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1
|
||||
google.golang.org/grpc v1.18.0
|
||||
)
|
||||
|
64
go.sum
64
go.sum
@ -1,72 +1,54 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 h1:tRsilif6pbtt+PX6uRoyGd+qR+4ZPucFZLHlc3Ak6z8=
|
||||
github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296/go.mod h1:/dBk8ICkslPCmyRdn4azP+QvBxL6Eg3EYxUGI9xMMFw=
|
||||
github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160 h1:HJpuhXOHC4EkXDARsLjmXAV9FhlY6qFDnKI/MJM6eoE=
|
||||
github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY=
|
||||
github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8Jc6zMvyRz3PCQrTTCXnVRvEzyBcM890=
|
||||
github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY=
|
||||
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/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482 h1:iOz5sIQUvuOlpiC7Q6+MmJQpWnlneYX98QIGf+2m50Y=
|
||||
github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc=
|
||||
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k=
|
||||
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo=
|
||||
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
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/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
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/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8ABMs=
|
||||
github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s=
|
||||
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=
|
||||
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU=
|
||||
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd h1:cgsAvzdqkDKdI02tIvDjO225vDPHMDCgfKqx5KEVI7U=
|
||||
golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4=
|
||||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q=
|
||||
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc=
|
||||
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e h1:3GIlrlVLfkoipSReOMNAgApI0ajnalyLa/EZHHca/XI=
|
||||
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw=
|
||||
google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
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/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/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/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
125
logger.go
125
logger.go
@ -26,6 +26,65 @@ var (
|
||||
disableColor = false
|
||||
)
|
||||
|
||||
// LoggerConfig defines the config for Logger middleware.
|
||||
type LoggerConfig struct {
|
||||
// Optional. Default value is gin.defaultLogFormatter
|
||||
Formatter LogFormatter
|
||||
|
||||
// Output is a writer where logs are written.
|
||||
// Optional. Default value is gin.DefaultWriter.
|
||||
Output io.Writer
|
||||
|
||||
// SkipPaths is a url path array which logs are not written.
|
||||
// Optional.
|
||||
SkipPaths []string
|
||||
}
|
||||
|
||||
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
|
||||
type LogFormatter func(params LogFormatterParams) string
|
||||
|
||||
// LogFormatterParams is the structure any formatter will be handed when time to log comes
|
||||
type LogFormatterParams struct {
|
||||
Request *http.Request
|
||||
|
||||
// TimeStamp shows the time after the server returns a response.
|
||||
TimeStamp time.Time
|
||||
// StatusCode is HTTP response code.
|
||||
StatusCode int
|
||||
// Latency is how much time the server cost to process a certain request.
|
||||
Latency time.Duration
|
||||
// ClientIP equals Context's ClientIP method.
|
||||
ClientIP string
|
||||
// Method is the HTTP method given to the request.
|
||||
Method string
|
||||
// Path is a path the client requests.
|
||||
Path string
|
||||
// ErrorMessage is set if error has occurred in processing the request.
|
||||
ErrorMessage string
|
||||
// IsTerm shows whether does gin's output descriptor refers to a terminal.
|
||||
IsTerm bool
|
||||
}
|
||||
|
||||
// defaultLogFormatter is the default log format function Logger middleware uses.
|
||||
var defaultLogFormatter = func(param LogFormatterParams) string {
|
||||
var statusColor, methodColor, resetColor string
|
||||
if param.IsTerm {
|
||||
statusColor = colorForStatus(param.StatusCode)
|
||||
methodColor = colorForMethod(param.Method)
|
||||
resetColor = reset
|
||||
}
|
||||
|
||||
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"),
|
||||
statusColor, param.StatusCode, resetColor,
|
||||
param.Latency,
|
||||
param.ClientIP,
|
||||
methodColor, param.Method, resetColor,
|
||||
param.Path,
|
||||
param.ErrorMessage,
|
||||
)
|
||||
}
|
||||
|
||||
// DisableConsoleColor disables color output in the console.
|
||||
func DisableConsoleColor() {
|
||||
disableColor = true
|
||||
@ -50,12 +109,39 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
|
||||
// By default gin.DefaultWriter = os.Stdout.
|
||||
func Logger() HandlerFunc {
|
||||
return LoggerWithWriter(DefaultWriter)
|
||||
return LoggerWithConfig(LoggerConfig{})
|
||||
}
|
||||
|
||||
// LoggerWithFormatter instance a Logger middleware with the specified log format function.
|
||||
func LoggerWithFormatter(f LogFormatter) HandlerFunc {
|
||||
return LoggerWithConfig(LoggerConfig{
|
||||
Formatter: f,
|
||||
})
|
||||
}
|
||||
|
||||
// LoggerWithWriter instance a Logger middleware with the specified writer buffer.
|
||||
// Example: os.Stdout, a file opened in write mode, a socket...
|
||||
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
||||
return LoggerWithConfig(LoggerConfig{
|
||||
Output: out,
|
||||
SkipPaths: notlogged,
|
||||
})
|
||||
}
|
||||
|
||||
// LoggerWithConfig instance a Logger middleware with config.
|
||||
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
||||
formatter := conf.Formatter
|
||||
if formatter == nil {
|
||||
formatter = defaultLogFormatter
|
||||
}
|
||||
|
||||
out := conf.Output
|
||||
if out == nil {
|
||||
out = DefaultWriter
|
||||
}
|
||||
|
||||
notlogged := conf.SkipPaths
|
||||
|
||||
isTerm := true
|
||||
|
||||
if w, ok := out.(*os.File); !ok ||
|
||||
@ -85,34 +171,27 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
||||
|
||||
// Log only when path is not being skipped
|
||||
if _, ok := skip[path]; !ok {
|
||||
// Stop timer
|
||||
end := time.Now()
|
||||
latency := end.Sub(start)
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
statusCode := c.Writer.Status()
|
||||
var statusColor, methodColor, resetColor string
|
||||
if isTerm {
|
||||
statusColor = colorForStatus(statusCode)
|
||||
methodColor = colorForMethod(method)
|
||||
resetColor = reset
|
||||
param := LogFormatterParams{
|
||||
Request: c.Request,
|
||||
IsTerm: isTerm,
|
||||
}
|
||||
comment := c.Errors.ByType(ErrorTypePrivate).String()
|
||||
|
||||
// Stop timer
|
||||
param.TimeStamp = time.Now()
|
||||
param.Latency = param.TimeStamp.Sub(start)
|
||||
|
||||
param.ClientIP = c.ClientIP()
|
||||
param.Method = c.Request.Method
|
||||
param.StatusCode = c.Writer.Status()
|
||||
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
|
||||
|
||||
if raw != "" {
|
||||
path = path + "?" + raw
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
|
||||
end.Format("2006/01/02 - 15:04:05"),
|
||||
statusColor, statusCode, resetColor,
|
||||
latency,
|
||||
clientIP,
|
||||
methodColor, method, resetColor,
|
||||
path,
|
||||
comment,
|
||||
)
|
||||
param.Path = path
|
||||
|
||||
fmt.Fprint(out, formatter(param))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
200
logger_test.go
200
logger_test.go
@ -7,8 +7,10 @@ package gin
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -79,7 +81,179 @@ func TestLogger(t *testing.T) {
|
||||
assert.Contains(t, buffer.String(), "404")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), "/notfound")
|
||||
}
|
||||
|
||||
func TestLoggerWithConfig(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
router := New()
|
||||
router.Use(LoggerWithConfig(LoggerConfig{Output: buffer}))
|
||||
router.GET("/example", func(c *Context) {})
|
||||
router.POST("/example", func(c *Context) {})
|
||||
router.PUT("/example", func(c *Context) {})
|
||||
router.DELETE("/example", func(c *Context) {})
|
||||
router.PATCH("/example", func(c *Context) {})
|
||||
router.HEAD("/example", func(c *Context) {})
|
||||
router.OPTIONS("/example", func(c *Context) {})
|
||||
|
||||
performRequest(router, "GET", "/example?a=100")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
|
||||
// I wrote these first (extending the above) but then realized they are more
|
||||
// like integration tests because they test the whole logging process rather
|
||||
// than individual functions. Im not sure where these should go.
|
||||
buffer.Reset()
|
||||
performRequest(router, "POST", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "POST")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PUT", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PUT")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "DELETE", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "DELETE")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "PATCH", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "PATCH")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "HEAD", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "HEAD")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "OPTIONS", "/example")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "OPTIONS")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "GET", "/notfound")
|
||||
assert.Contains(t, buffer.String(), "404")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), "/notfound")
|
||||
}
|
||||
|
||||
func TestLoggerWithFormatter(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
d := DefaultWriter
|
||||
DefaultWriter = buffer
|
||||
defer func() {
|
||||
DefaultWriter = d
|
||||
}()
|
||||
|
||||
router := New()
|
||||
router.Use(LoggerWithFormatter(func(param LogFormatterParams) string {
|
||||
return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s",
|
||||
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
param.ClientIP,
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.ErrorMessage,
|
||||
)
|
||||
}))
|
||||
router.GET("/example", func(c *Context) {})
|
||||
performRequest(router, "GET", "/example?a=100")
|
||||
|
||||
// output test
|
||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
}
|
||||
|
||||
func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||
var gotParam LogFormatterParams
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
router := New()
|
||||
router.Use(LoggerWithConfig(LoggerConfig{
|
||||
Output: buffer,
|
||||
Formatter: func(param LogFormatterParams) string {
|
||||
// for assert test
|
||||
gotParam = param
|
||||
|
||||
return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s",
|
||||
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
param.ClientIP,
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.ErrorMessage,
|
||||
)
|
||||
},
|
||||
}))
|
||||
router.GET("/example", func(c *Context) {
|
||||
// set dummy ClientIP
|
||||
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
||||
})
|
||||
performRequest(router, "GET", "/example?a=100")
|
||||
|
||||
// output test
|
||||
assert.Contains(t, buffer.String(), "[FORMATTER TEST]")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
assert.Contains(t, buffer.String(), "GET")
|
||||
assert.Contains(t, buffer.String(), "/example")
|
||||
assert.Contains(t, buffer.String(), "a=100")
|
||||
|
||||
// LogFormatterParams test
|
||||
assert.NotNil(t, gotParam.Request)
|
||||
assert.NotEmpty(t, gotParam.TimeStamp)
|
||||
assert.Equal(t, 200, gotParam.StatusCode)
|
||||
assert.NotEmpty(t, gotParam.Latency)
|
||||
assert.Equal(t, "20.20.20.20", gotParam.ClientIP)
|
||||
assert.Equal(t, "GET", gotParam.Method)
|
||||
assert.Equal(t, "/example?a=100", gotParam.Path)
|
||||
assert.Empty(t, gotParam.ErrorMessage)
|
||||
|
||||
}
|
||||
|
||||
func TestDefaultLogFormatter(t *testing.T) {
|
||||
timeStamp := time.Unix(1544173902, 0).UTC()
|
||||
|
||||
termFalseParam := LogFormatterParams{
|
||||
TimeStamp: timeStamp,
|
||||
StatusCode: 200,
|
||||
Latency: time.Second * 5,
|
||||
ClientIP: "20.20.20.20",
|
||||
Method: "GET",
|
||||
Path: "/",
|
||||
ErrorMessage: "",
|
||||
IsTerm: false,
|
||||
}
|
||||
|
||||
termTrueParam := LogFormatterParams{
|
||||
TimeStamp: timeStamp,
|
||||
StatusCode: 200,
|
||||
Latency: time.Second * 5,
|
||||
ClientIP: "20.20.20.20",
|
||||
Method: "GET",
|
||||
Path: "/",
|
||||
ErrorMessage: "",
|
||||
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 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam))
|
||||
}
|
||||
|
||||
func TestColorForMethod(t *testing.T) {
|
||||
@ -104,13 +278,13 @@ func TestErrorLogger(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(ErrorLogger())
|
||||
router.GET("/error", func(c *Context) {
|
||||
c.Error(errors.New("this is an error"))
|
||||
c.Error(errors.New("this is an error")) // nolint: errcheck
|
||||
})
|
||||
router.GET("/abort", func(c *Context) {
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized"))
|
||||
c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck
|
||||
})
|
||||
router.GET("/print", func(c *Context) {
|
||||
c.Error(errors.New("this is an error"))
|
||||
c.Error(errors.New("this is an error")) // nolint: errcheck
|
||||
c.String(http.StatusInternalServerError, "hola!")
|
||||
})
|
||||
|
||||
@ -127,7 +301,7 @@ func TestErrorLogger(t *testing.T) {
|
||||
assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String())
|
||||
}
|
||||
|
||||
func TestSkippingPaths(t *testing.T) {
|
||||
func TestLoggerWithWriterSkippingPaths(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
router := New()
|
||||
router.Use(LoggerWithWriter(buffer, "/skipped"))
|
||||
@ -142,6 +316,24 @@ func TestSkippingPaths(t *testing.T) {
|
||||
assert.Contains(t, buffer.String(), "")
|
||||
}
|
||||
|
||||
func TestLoggerWithConfigSkippingPaths(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
router := New()
|
||||
router.Use(LoggerWithConfig(LoggerConfig{
|
||||
Output: buffer,
|
||||
SkipPaths: []string{"/skipped"},
|
||||
}))
|
||||
router.GET("/logged", func(c *Context) {})
|
||||
router.GET("/skipped", func(c *Context) {})
|
||||
|
||||
performRequest(router, "GET", "/logged")
|
||||
assert.Contains(t, buffer.String(), "200")
|
||||
|
||||
buffer.Reset()
|
||||
performRequest(router, "GET", "/skipped")
|
||||
assert.Contains(t, buffer.String(), "")
|
||||
}
|
||||
|
||||
func TestDisableConsoleColor(t *testing.T) {
|
||||
New()
|
||||
assert.False(t, disableColor)
|
||||
|
@ -208,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
||||
router := New()
|
||||
router.Use(func(context *Context) {
|
||||
signature += "A"
|
||||
context.AbortWithError(http.StatusInternalServerError, errors.New("foo"))
|
||||
context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck
|
||||
})
|
||||
router.Use(func(context *Context) {
|
||||
signature += "B"
|
||||
|
@ -66,7 +66,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||
|
||||
// If the connection is dead, we can't write a status to it.
|
||||
if brokenPipe {
|
||||
c.Error(err.(error))
|
||||
c.Error(err.(error)) // nolint: errcheck
|
||||
c.Abort()
|
||||
} else {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
|
@ -43,6 +43,7 @@ func TestPanicInHandler(t *testing.T) {
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||
|
||||
SetMode(TestMode)
|
||||
}
|
||||
|
||||
// TestPanicWithAbort assert that panic has been recovered even if context.Abort was used.
|
||||
|
@ -67,8 +67,8 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Write(jsonBytes)
|
||||
return nil
|
||||
_, err = w.Write(jsonBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
||||
@ -78,8 +78,8 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Write(jsonBytes)
|
||||
return nil
|
||||
_, err = w.Write(jsonBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteContentType (IndentedJSON) writes JSON ContentType.
|
||||
@ -96,10 +96,13 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||
}
|
||||
// if the jsonBytes is array values
|
||||
if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) {
|
||||
w.Write([]byte(r.Prefix))
|
||||
_, err = w.Write([]byte(r.Prefix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Write(jsonBytes)
|
||||
return nil
|
||||
_, err = w.Write(jsonBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteContentType (SecureJSON) writes JSON ContentType.
|
||||
@ -116,15 +119,27 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||
}
|
||||
|
||||
if r.Callback == "" {
|
||||
w.Write(ret)
|
||||
return nil
|
||||
_, err = w.Write(ret)
|
||||
return err
|
||||
}
|
||||
|
||||
callback := template.JSEscapeString(r.Callback)
|
||||
w.Write([]byte(callback))
|
||||
w.Write([]byte("("))
|
||||
w.Write(ret)
|
||||
w.Write([]byte(")"))
|
||||
_, err = w.Write([]byte(callback))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write([]byte("("))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(ret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write([]byte(")"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -151,8 +166,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||
buffer.WriteString(cvt)
|
||||
}
|
||||
|
||||
w.Write(buffer.Bytes())
|
||||
return nil
|
||||
_, err = w.Write(buffer.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
||||
|
@ -26,8 +26,8 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Write(bytes)
|
||||
return nil
|
||||
_, err = w.Write(bytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
|
||||
|
@ -71,7 +71,7 @@ func TestRenderJSONPanics(t *testing.T) {
|
||||
data := make(chan int)
|
||||
|
||||
// json: unsupported type: chan int
|
||||
assert.Panics(t, func() { (JSON{data}).Render(w) })
|
||||
assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) })
|
||||
}
|
||||
|
||||
func TestRenderIndentedJSON(t *testing.T) {
|
||||
@ -335,7 +335,7 @@ func TestRenderRedirect(t *testing.T) {
|
||||
}
|
||||
|
||||
w = httptest.NewRecorder()
|
||||
assert.Panics(t, func() { data2.Render(w) })
|
||||
assert.Panics(t, func() { assert.NoError(t, data2.Render(w)) })
|
||||
|
||||
// only improve coverage
|
||||
data2.WriteContentType(w)
|
||||
|
@ -20,8 +20,7 @@ var plainContentType = []string{"text/plain; charset=utf-8"}
|
||||
|
||||
// Render (String) writes data with custom ContentType.
|
||||
func (r String) Render(w http.ResponseWriter) error {
|
||||
WriteString(w, r.Format, r.Data)
|
||||
return nil
|
||||
return WriteString(w, r.Format, r.Data)
|
||||
}
|
||||
|
||||
// WriteContentType (String) writes Plain ContentType.
|
||||
@ -30,11 +29,12 @@ func (r String) WriteContentType(w http.ResponseWriter) {
|
||||
}
|
||||
|
||||
// WriteString writes data according to its format and write custom ContentType.
|
||||
func WriteString(w http.ResponseWriter, format string, data []interface{}) {
|
||||
func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) {
|
||||
writeContentType(w, plainContentType)
|
||||
if len(data) > 0 {
|
||||
fmt.Fprintf(w, format, data...)
|
||||
_, err = fmt.Fprintf(w, format, data...)
|
||||
return
|
||||
}
|
||||
io.WriteString(w, format)
|
||||
_, err = io.WriteString(w, format)
|
||||
return
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ func (r YAML) Render(w http.ResponseWriter) error {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Write(bytes)
|
||||
return nil
|
||||
_, err = w.Write(bytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteContentType (YAML) writes YAML ContentType for response.
|
||||
|
@ -103,7 +103,8 @@ func TestResponseWriterHijack(t *testing.T) {
|
||||
w := ResponseWriter(writer)
|
||||
|
||||
assert.Panics(t, func() {
|
||||
w.Hijack()
|
||||
_, _, err := w.Hijack()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
assert.True(t, w.Written())
|
||||
|
||||
|
@ -47,7 +47,7 @@ type RouterGroup struct {
|
||||
|
||||
var _ IRouter = &RouterGroup{}
|
||||
|
||||
// Use adds middleware to the group, see example code in github.
|
||||
// Use adds middleware to the group, see example code in GitHub.
|
||||
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
|
||||
group.Handlers = append(group.Handlers, middleware...)
|
||||
return group.returnObj()
|
||||
@ -78,7 +78,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl
|
||||
|
||||
// Handle registers a new request handle and middleware with the given path and method.
|
||||
// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
|
||||
// See the example code in github.
|
||||
// See the example code in GitHub.
|
||||
//
|
||||
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
||||
// functions can be used.
|
||||
|
@ -251,7 +251,8 @@ func TestRouteStaticFile(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
f.WriteString("Gin Web Framework")
|
||||
_, err = f.WriteString("Gin Web Framework")
|
||||
assert.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
dir, filename := filepath.Split(f.Name())
|
||||
@ -426,6 +427,16 @@ func TestRouterStaticFSNotFound(t *testing.T) {
|
||||
assert.Equal(t, "non existent", w.Body.String())
|
||||
}
|
||||
|
||||
func TestRouterStaticFSFileNotFound(t *testing.T) {
|
||||
router := New()
|
||||
|
||||
router.StaticFS("/", http.FileSystem(http.Dir(".")))
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
performRequest(router, "GET", "/nonexistent")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteRawPath(t *testing.T) {
|
||||
route := New()
|
||||
route.UseRawPath = true
|
||||
|
25
tools.go
25
tools.go
@ -1,25 +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 tools
|
||||
|
||||
// This file exists to cause `go mod` and `go get` to believe these tools
|
||||
// are dependencies, even though they are not runtime dependencies of any
|
||||
// gin package. This means they will appear in `go.mod` file, but will not
|
||||
// be a part of the build.
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
_ "github.com/campoy/embedmd"
|
||||
_ "github.com/client9/misspell/cmd/misspell"
|
||||
_ "github.com/dustin/go-broadcast"
|
||||
_ "github.com/gin-gonic/autotls"
|
||||
_ "github.com/jessevdk/go-assets"
|
||||
_ "github.com/manucorporat/stats"
|
||||
_ "github.com/thinkerou/favicon"
|
||||
_ "golang.org/x/crypto/acme/autocert"
|
||||
_ "golang.org/x/lint/golint"
|
||||
_ "google.golang.org/grpc"
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user