Merge branch 'master' into master

This commit is contained in:
田欧 2019-02-20 19:41:40 +08:00 committed by GitHub
commit d258fed771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 162 additions and 21 deletions

View File

@ -1672,6 +1672,7 @@ import (
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
@ -1699,7 +1700,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 ...")
@ -1708,6 +1712,11 @@ func main() {
if err := srv.Shutdown(ctx); err != nil {
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")
}
```

View File

@ -39,7 +39,7 @@ func TestDebugPrint(t *testing.T) {
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)

View File

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

View File

@ -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 ...")
@ -44,5 +48,10 @@ func main() {
if err := srv.Shutdown(ctx); err != nil {
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")
}

View 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))
}
```

View 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
View File

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

View File

@ -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),
})
})
})
@ -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) {
for _, gotRoute := range gotRoutes {
if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method {

View File

@ -287,7 +287,7 @@ var githubAPI = []route{
func TestShouldBindUri(t *testing.T) {
DefaultWriter = os.Stdout
router := Default()
router := New()
type Person struct {
Name string `uri:"name" binding:"required"`
@ -309,7 +309,7 @@ func TestShouldBindUri(t *testing.T) {
func TestBindUri(t *testing.T) {
DefaultWriter = os.Stdout
router := Default()
router := New()
type Person struct {
Name string `uri:"name" binding:"required"`
@ -331,7 +331,7 @@ func TestBindUri(t *testing.T) {
func TestBindUriError(t *testing.T) {
DefaultWriter = os.Stdout
router := Default()
router := New()
type Member struct {
Number string `uri:"num" binding:"required,uuid"`
@ -361,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 {
@ -436,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)

1
go.mod
View File

@ -24,6 +24,7 @@ exclude (
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

View File

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