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
d258fed771
11
README.md
11
README.md
@ -1672,6 +1672,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -1699,7 +1700,10 @@ func main() {
|
|||||||
// Wait for interrupt signal to gracefully shutdown the server with
|
// Wait for interrupt signal to gracefully shutdown the server with
|
||||||
// a timeout of 5 seconds.
|
// a timeout of 5 seconds.
|
||||||
quit := make(chan os.Signal)
|
quit := make(chan os.Signal)
|
||||||
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
|
<-quit
|
||||||
log.Println("Shutdown Server ...")
|
log.Println("Shutdown Server ...")
|
||||||
|
|
||||||
@ -1708,6 +1712,11 @@ func main() {
|
|||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
log.Fatal("Server Shutdown:", err)
|
log.Fatal("Server Shutdown:", err)
|
||||||
}
|
}
|
||||||
|
// catching ctx.Done(). timeout of 5 seconds.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Println("timeout of 5 seconds.")
|
||||||
|
}
|
||||||
log.Println("Server exiting")
|
log.Println("Server exiting")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -39,7 +39,7 @@ func TestDebugPrint(t *testing.T) {
|
|||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
debugPrint("DEBUG this!")
|
debugPrint("DEBUG this!")
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
debugPrint("these are %d %s\n", 2, "error messages")
|
debugPrint("these are %d %s", 2, "error messages")
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
})
|
})
|
||||||
assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re)
|
assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re)
|
||||||
|
@ -24,7 +24,9 @@ func TestBindWith(t *testing.T) {
|
|||||||
Foo string `form:"foo"`
|
Foo string `form:"foo"`
|
||||||
Bar string `form:"bar"`
|
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, "foo", obj.Bar)
|
||||||
assert.Equal(t, "bar", obj.Foo)
|
assert.Equal(t, "bar", obj.Foo)
|
||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -35,7 +36,10 @@ func main() {
|
|||||||
// Wait for interrupt signal to gracefully shutdown the server with
|
// Wait for interrupt signal to gracefully shutdown the server with
|
||||||
// a timeout of 5 seconds.
|
// a timeout of 5 seconds.
|
||||||
quit := make(chan os.Signal)
|
quit := make(chan os.Signal)
|
||||||
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
|
<-quit
|
||||||
log.Println("Shutdown Server ...")
|
log.Println("Shutdown Server ...")
|
||||||
|
|
||||||
@ -44,5 +48,10 @@ func main() {
|
|||||||
if err := srv.Shutdown(ctx); err != nil {
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
log.Fatal("Server Shutdown:", err)
|
log.Fatal("Server Shutdown:", err)
|
||||||
}
|
}
|
||||||
|
// catching ctx.Done(). timeout of 5 seconds.
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Println("timeout of 5 seconds.")
|
||||||
|
}
|
||||||
log.Println("Server exiting")
|
log.Println("Server exiting")
|
||||||
}
|
}
|
||||||
|
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
gin.go
3
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.
|
// 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.
|
// Disclaimer: You can loop yourself to death with this, use wisely.
|
||||||
func (engine *Engine) HandleContext(c *Context) {
|
func (engine *Engine) HandleContext(c *Context) {
|
||||||
|
oldIndexValue := c.index
|
||||||
c.reset()
|
c.reset()
|
||||||
engine.handleHTTPRequest(c)
|
engine.handleHTTPRequest(c)
|
||||||
|
|
||||||
|
c.index = oldIndexValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
|
68
gin_test.go
68
gin_test.go
@ -12,6 +12,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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 {
|
func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server {
|
||||||
SetMode(mode)
|
SetMode(mode)
|
||||||
router := New()
|
defer SetMode(TestMode)
|
||||||
router.Delims("{[{", "}]}")
|
|
||||||
router.SetFuncMap(template.FuncMap{
|
var router *Engine
|
||||||
"formatAsDate": formatAsDate,
|
captureOutput(t, func() {
|
||||||
})
|
router = New()
|
||||||
loadMethod(router)
|
router.Delims("{[{", "}]}")
|
||||||
router.GET("/test", func(c *Context) {
|
router.SetFuncMap(template.FuncMap{
|
||||||
c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"})
|
"formatAsDate": formatAsDate,
|
||||||
})
|
})
|
||||||
router.GET("/raw", func(c *Context) {
|
loadMethod(router)
|
||||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
router.GET("/test", func(c *Context) {
|
||||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
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),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -488,6 +495,43 @@ func TestEngineHandleContext(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) {
|
||||||
for _, gotRoute := range gotRoutes {
|
for _, gotRoute := range gotRoutes {
|
||||||
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {
|
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {
|
||||||
|
@ -287,7 +287,7 @@ var githubAPI = []route{
|
|||||||
|
|
||||||
func TestShouldBindUri(t *testing.T) {
|
func TestShouldBindUri(t *testing.T) {
|
||||||
DefaultWriter = os.Stdout
|
DefaultWriter = os.Stdout
|
||||||
router := Default()
|
router := New()
|
||||||
|
|
||||||
type Person struct {
|
type Person struct {
|
||||||
Name string `uri:"name" binding:"required"`
|
Name string `uri:"name" binding:"required"`
|
||||||
@ -309,7 +309,7 @@ func TestShouldBindUri(t *testing.T) {
|
|||||||
|
|
||||||
func TestBindUri(t *testing.T) {
|
func TestBindUri(t *testing.T) {
|
||||||
DefaultWriter = os.Stdout
|
DefaultWriter = os.Stdout
|
||||||
router := Default()
|
router := New()
|
||||||
|
|
||||||
type Person struct {
|
type Person struct {
|
||||||
Name string `uri:"name" binding:"required"`
|
Name string `uri:"name" binding:"required"`
|
||||||
@ -331,7 +331,7 @@ func TestBindUri(t *testing.T) {
|
|||||||
|
|
||||||
func TestBindUriError(t *testing.T) {
|
func TestBindUriError(t *testing.T) {
|
||||||
DefaultWriter = os.Stdout
|
DefaultWriter = os.Stdout
|
||||||
router := Default()
|
router := New()
|
||||||
|
|
||||||
type Member struct {
|
type Member struct {
|
||||||
Number string `uri:"num" binding:"required,uuid"`
|
Number string `uri:"num" binding:"required,uuid"`
|
||||||
@ -361,7 +361,7 @@ func githubConfigRouter(router *Engine) {
|
|||||||
|
|
||||||
func TestGithubAPI(t *testing.T) {
|
func TestGithubAPI(t *testing.T) {
|
||||||
DefaultWriter = os.Stdout
|
DefaultWriter = os.Stdout
|
||||||
router := Default()
|
router := New()
|
||||||
githubConfigRouter(router)
|
githubConfigRouter(router)
|
||||||
|
|
||||||
for _, route := range githubAPI {
|
for _, route := range githubAPI {
|
||||||
@ -436,7 +436,7 @@ func BenchmarkParallelGithub(b *testing.B) {
|
|||||||
|
|
||||||
func BenchmarkParallelGithubDefault(b *testing.B) {
|
func BenchmarkParallelGithubDefault(b *testing.B) {
|
||||||
DefaultWriter = os.Stdout
|
DefaultWriter = os.Stdout
|
||||||
router := Default()
|
router := New()
|
||||||
githubConfigRouter(router)
|
githubConfigRouter(router)
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil)
|
req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil)
|
||||||
|
1
go.mod
1
go.mod
@ -24,6 +24,7 @@ exclude (
|
|||||||
github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768
|
github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768
|
||||||
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15
|
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15
|
||||||
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227
|
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
|
github.com/thinkerou/favicon v0.1.0
|
||||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
|
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
|
||||||
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1
|
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1
|
||||||
|
@ -43,6 +43,7 @@ func TestPanicInHandler(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
assert.Contains(t, buffer.String(), "GET /recovery")
|
assert.Contains(t, buffer.String(), "GET /recovery")
|
||||||
|
|
||||||
|
SetMode(TestMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPanicWithAbort assert that panic has been recovered even if context.Abort was used.
|
// TestPanicWithAbort assert that panic has been recovered even if context.Abort was used.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user