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:
Manu Mtz-Almeida 2015-05-30 14:45:13 +02:00
parent deb137cdd2
commit 835f66fdc9
11 changed files with 88 additions and 22 deletions

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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