mirror of https://github.com/gin-gonic/gin.git
404 not found performance improvements
benchmark old ns/op new ns/op delta Benchmark404 737 249 -66.21% Benchmark404Many 2330 454 -80.52% benchmark old allocs new allocs delta Benchmark404 3 0 -100.00% Benchmark404Many 10 0 -100.00% benchmark old bytes new bytes delta Benchmark404 115 68 -40.87% Benchmark404Many 235 57 -75.74%
This commit is contained in:
parent
deb137cdd2
commit
835f66fdc9
|
@ -283,7 +283,7 @@ func (c *Context) Header(key, value string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Render(code int, r render.Render) {
|
func (c *Context) Render(code int, r render.Render) {
|
||||||
c.Writer.WriteHeader(code)
|
c.writermem.WriteHeader(code)
|
||||||
if err := r.Write(c.Writer); err != nil {
|
if err := r.Write(c.Writer); err != nil {
|
||||||
debugPrintError(err)
|
debugPrintError(err)
|
||||||
c.AbortWithError(500, err).SetType(ErrorTypeRender)
|
c.AbortWithError(500, err).SetType(ErrorTypeRender)
|
||||||
|
|
|
@ -111,7 +111,7 @@ func (a errorMsgs) Last() *Error {
|
||||||
// ``
|
// ``
|
||||||
func (a errorMsgs) Errors() []string {
|
func (a errorMsgs) Errors() []string {
|
||||||
if len(a) == 0 {
|
if len(a) == 0 {
|
||||||
return []string{}
|
return nil
|
||||||
}
|
}
|
||||||
errorStrings := make([]string, len(a))
|
errorStrings := make([]string, len(a))
|
||||||
for i, err := range a {
|
for i, err := range a {
|
||||||
|
|
18
gin.go
18
gin.go
|
@ -11,7 +11,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/binding"
|
|
||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,8 +72,8 @@ func New() *Engine {
|
||||||
BasePath: "/",
|
BasePath: "/",
|
||||||
},
|
},
|
||||||
RedirectTrailingSlash: true,
|
RedirectTrailingSlash: true,
|
||||||
RedirectFixedPath: true,
|
RedirectFixedPath: false,
|
||||||
HandleMethodNotAllowed: true,
|
HandleMethodNotAllowed: false,
|
||||||
trees: make(methodTrees, 0, 6),
|
trees: make(methodTrees, 0, 6),
|
||||||
}
|
}
|
||||||
engine.RouterGroup.engine = engine
|
engine.RouterGroup.engine = engine
|
||||||
|
@ -285,7 +284,7 @@ func (engine *Engine) serveAutoRedirect(c *Context, root *node, tsr bool) bool {
|
||||||
// Try to fix the request path
|
// Try to fix the request path
|
||||||
if engine.RedirectFixedPath {
|
if engine.RedirectFixedPath {
|
||||||
fixedPath, found := root.findCaseInsensitivePath(
|
fixedPath, found := root.findCaseInsensitivePath(
|
||||||
CleanPath(path),
|
cleanPath(path),
|
||||||
engine.RedirectTrailingSlash,
|
engine.RedirectTrailingSlash,
|
||||||
)
|
)
|
||||||
if found {
|
if found {
|
||||||
|
@ -299,14 +298,17 @@ func (engine *Engine) serveAutoRedirect(c *Context, root *node, tsr bool) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mimePlain = []string{MIMEPlain}
|
||||||
|
|
||||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||||
c.writermem.status = code
|
c.writermem.status = code
|
||||||
c.Next()
|
c.Next()
|
||||||
if !c.Writer.Written() {
|
if !c.writermem.Written() {
|
||||||
if c.Writer.Status() == code {
|
if c.writermem.Status() == code {
|
||||||
c.Data(-1, binding.MIMEPlain, defaultMessage)
|
c.writermem.Header()["Content-Type"] = mimePlain
|
||||||
|
c.Writer.Write(defaultMessage)
|
||||||
} else {
|
} else {
|
||||||
c.Writer.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,6 @@ func TestCreateEngine(t *testing.T) {
|
||||||
assert.Equal(t, "/", router.BasePath)
|
assert.Equal(t, "/", router.BasePath)
|
||||||
assert.Equal(t, router.engine, router)
|
assert.Equal(t, router.engine, router)
|
||||||
assert.Empty(t, router.Handlers)
|
assert.Empty(t, router.Handlers)
|
||||||
assert.True(t, router.RedirectTrailingSlash)
|
|
||||||
assert.True(t, router.RedirectFixedPath)
|
|
||||||
assert.True(t, router.HandleMethodNotAllowed)
|
|
||||||
|
|
||||||
assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) })
|
assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) })
|
||||||
assert.Panics(t, func() { router.addRoute("GET", "a", HandlersChain{func(_ *Context) {}}) })
|
assert.Panics(t, func() { router.addRoute("GET", "a", HandlersChain{func(_ *Context) {}}) })
|
||||||
|
|
|
@ -77,9 +77,10 @@ func TestMiddlewareNoRoute(t *testing.T) {
|
||||||
assert.Equal(t, signature, "ACEGHFDB")
|
assert.Equal(t, signature, "ACEGHFDB")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMiddlewareNoMethod(t *testing.T) {
|
func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
||||||
signature := ""
|
signature := ""
|
||||||
router := New()
|
router := New()
|
||||||
|
router.HandleMethodNotAllowed = true
|
||||||
router.Use(func(c *Context) {
|
router.Use(func(c *Context) {
|
||||||
signature += "A"
|
signature += "A"
|
||||||
c.Next()
|
c.Next()
|
||||||
|
@ -113,6 +114,43 @@ func TestMiddlewareNoMethod(t *testing.T) {
|
||||||
assert.Equal(t, signature, "ACEGHFDB")
|
assert.Equal(t, signature, "ACEGHFDB")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
||||||
|
signature := ""
|
||||||
|
router := New()
|
||||||
|
router.HandleMethodNotAllowed = false
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "A"
|
||||||
|
c.Next()
|
||||||
|
signature += "B"
|
||||||
|
})
|
||||||
|
router.Use(func(c *Context) {
|
||||||
|
signature += "C"
|
||||||
|
c.Next()
|
||||||
|
signature += "D"
|
||||||
|
})
|
||||||
|
router.NoMethod(func(c *Context) {
|
||||||
|
signature += "E"
|
||||||
|
c.Next()
|
||||||
|
signature += "F"
|
||||||
|
}, func(c *Context) {
|
||||||
|
signature += "G"
|
||||||
|
c.Next()
|
||||||
|
signature += "H"
|
||||||
|
})
|
||||||
|
router.NoRoute(func(c *Context) {
|
||||||
|
signature += " X "
|
||||||
|
})
|
||||||
|
router.POST("/", func(c *Context) {
|
||||||
|
signature += " XX "
|
||||||
|
})
|
||||||
|
// RUN
|
||||||
|
w := performRequest(router, "GET", "/")
|
||||||
|
|
||||||
|
// TEST
|
||||||
|
assert.Equal(t, w.Code, 404)
|
||||||
|
assert.Equal(t, signature, "AC X DB")
|
||||||
|
}
|
||||||
|
|
||||||
func TestMiddlewareAbort(t *testing.T) {
|
func TestMiddlewareAbort(t *testing.T) {
|
||||||
signature := ""
|
signature := ""
|
||||||
router := New()
|
router := New()
|
||||||
|
|
2
path.go
2
path.go
|
@ -18,7 +18,7 @@ package gin
|
||||||
// that is, replace "/.." by "/" at the beginning of a path.
|
// that is, replace "/.." by "/" at the beginning of a path.
|
||||||
//
|
//
|
||||||
// If the result of this process is an empty string, "/" is returned
|
// If the result of this process is an empty string, "/" is returned
|
||||||
func CleanPath(p string) string {
|
func cleanPath(p string) string {
|
||||||
// Turn empty string into "/"
|
// Turn empty string into "/"
|
||||||
if p == "" {
|
if p == "" {
|
||||||
return "/"
|
return "/"
|
||||||
|
|
|
@ -67,8 +67,8 @@ var cleanTests = []struct {
|
||||||
|
|
||||||
func TestPathClean(t *testing.T) {
|
func TestPathClean(t *testing.T) {
|
||||||
for _, test := range cleanTests {
|
for _, test := range cleanTests {
|
||||||
assert.Equal(t, CleanPath(test.path), test.result)
|
assert.Equal(t, cleanPath(test.path), test.result)
|
||||||
assert.Equal(t, CleanPath(test.result), test.result)
|
assert.Equal(t, cleanPath(test.result), test.result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ func TestPathCleanMallocs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range cleanTests {
|
for _, test := range cleanTests {
|
||||||
allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) })
|
allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) })
|
||||||
assert.Equal(t, allocs, 0)
|
assert.Equal(t, allocs, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ type Data struct {
|
||||||
|
|
||||||
func (r Data) Write(w http.ResponseWriter) error {
|
func (r Data) Write(w http.ResponseWriter) error {
|
||||||
if len(r.ContentType) > 0 {
|
if len(r.ContentType) > 0 {
|
||||||
w.Header().Set("Content-Type", r.ContentType)
|
w.Header()["Content-Type"] = []string{r.ContentType}
|
||||||
}
|
}
|
||||||
w.Write(r.Data)
|
w.Write(r.Data)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,6 +6,7 @@ package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ func (r String) Write(w http.ResponseWriter) error {
|
||||||
if len(r.Data) > 0 {
|
if len(r.Data) > 0 {
|
||||||
fmt.Fprintf(w, r.Format, r.Data...)
|
fmt.Fprintf(w, r.Format, r.Data...)
|
||||||
} else {
|
} else {
|
||||||
w.Write([]byte(r.Format))
|
io.WriteString(w, r.Format)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
@ -24,6 +25,7 @@ type (
|
||||||
|
|
||||||
Status() int
|
Status() int
|
||||||
Size() int
|
Size() int
|
||||||
|
WriteString(string) (int, error)
|
||||||
Written() bool
|
Written() bool
|
||||||
WriteHeaderNow()
|
WriteHeaderNow()
|
||||||
}
|
}
|
||||||
|
@ -35,6 +37,8 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ ResponseWriter = &responseWriter{}
|
||||||
|
|
||||||
func (w *responseWriter) reset(writer http.ResponseWriter) {
|
func (w *responseWriter) reset(writer http.ResponseWriter) {
|
||||||
w.ResponseWriter = writer
|
w.ResponseWriter = writer
|
||||||
w.size = noWritten
|
w.size = noWritten
|
||||||
|
@ -64,6 +68,13 @@ func (w *responseWriter) Write(data []byte) (n int, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) WriteString(s string) (n int, err error) {
|
||||||
|
w.WriteHeaderNow()
|
||||||
|
n, err = io.WriteString(w.ResponseWriter, s)
|
||||||
|
w.size += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Status() int {
|
func (w *responseWriter) Status() int {
|
||||||
return w.status
|
return w.status
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ func testRouteNotOK(method string, t *testing.T) {
|
||||||
func testRouteNotOK2(method string, t *testing.T) {
|
func testRouteNotOK2(method string, t *testing.T) {
|
||||||
passed := false
|
passed := false
|
||||||
router := New()
|
router := New()
|
||||||
|
router.HandleMethodNotAllowed = true
|
||||||
var methodRoute string
|
var methodRoute string
|
||||||
if method == "POST" {
|
if method == "POST" {
|
||||||
methodRoute = "GET"
|
methodRoute = "GET"
|
||||||
|
@ -224,9 +225,9 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
|
||||||
assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework")
|
assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteNotAllowed(t *testing.T) {
|
func TestRouteNotAllowedEnabled(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
|
router.HandleMethodNotAllowed = true
|
||||||
router.POST("/path", func(c *Context) {})
|
router.POST("/path", func(c *Context) {})
|
||||||
w := performRequest(router, "GET", "/path")
|
w := performRequest(router, "GET", "/path")
|
||||||
assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
|
assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
|
||||||
|
@ -239,8 +240,24 @@ func TestRouteNotAllowed(t *testing.T) {
|
||||||
assert.Equal(t, w.Code, http.StatusTeapot)
|
assert.Equal(t, w.Code, http.StatusTeapot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteNotAllowedDisabled(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.HandleMethodNotAllowed = false
|
||||||
|
router.POST("/path", func(c *Context) {})
|
||||||
|
w := performRequest(router, "GET", "/path")
|
||||||
|
assert.Equal(t, w.Code, 404)
|
||||||
|
|
||||||
|
router.NoMethod(func(c *Context) {
|
||||||
|
c.String(http.StatusTeapot, "responseText")
|
||||||
|
})
|
||||||
|
w = performRequest(router, "GET", "/path")
|
||||||
|
assert.Equal(t, w.Body.String(), "404 page not found")
|
||||||
|
assert.Equal(t, w.Code, 404)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouterNotFound(t *testing.T) {
|
func TestRouterNotFound(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
|
router.RedirectFixedPath = true
|
||||||
router.GET("/path", func(c *Context) {})
|
router.GET("/path", func(c *Context) {})
|
||||||
router.GET("/dir/", func(c *Context) {})
|
router.GET("/dir/", func(c *Context) {})
|
||||||
router.GET("/", func(c *Context) {})
|
router.GET("/", func(c *Context) {})
|
||||||
|
|
Loading…
Reference in New Issue