瀏覽代碼

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%
Manu Mtz-Almeida 10 年之前
父節點
當前提交
835f66fdc9
共有 11 個文件被更改,包括 88 次插入22 次删除
  1. 1 1
      context.go
  2. 1 1
      errors.go
  3. 10 8
      gin.go
  4. 0 3
      gin_test.go
  5. 39 1
      middleware_test.go
  6. 1 1
      path.go
  7. 3 3
      path_test.go
  8. 1 1
      render/data.go
  9. 2 1
      render/text.go
  10. 11 0
      response_writer.go
  11. 19 2
      routes_test.go

+ 1 - 1
context.go

@@ -283,7 +283,7 @@ func (c *Context) Header(key, value string) {
 }
 
 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 {
 		debugPrintError(err)
 		c.AbortWithError(500, err).SetType(ErrorTypeRender)

+ 1 - 1
errors.go

@@ -111,7 +111,7 @@ func (a errorMsgs) Last() *Error {
 // ``
 func (a errorMsgs) Errors() []string {
 	if len(a) == 0 {
-		return []string{}
+		return nil
 	}
 	errorStrings := make([]string, len(a))
 	for i, err := range a {

+ 10 - 8
gin.go

@@ -11,7 +11,6 @@ import (
 	"os"
 	"sync"
 
-	"github.com/gin-gonic/gin/binding"
 	"github.com/gin-gonic/gin/render"
 )
 
@@ -73,8 +72,8 @@ func New() *Engine {
 			BasePath: "/",
 		},
 		RedirectTrailingSlash:  true,
-		RedirectFixedPath:      true,
-		HandleMethodNotAllowed: true,
+		RedirectFixedPath:      false,
+		HandleMethodNotAllowed: false,
 		trees: make(methodTrees, 0, 6),
 	}
 	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
 	if engine.RedirectFixedPath {
 		fixedPath, found := root.findCaseInsensitivePath(
-			CleanPath(path),
+			cleanPath(path),
 			engine.RedirectTrailingSlash,
 		)
 		if found {
@@ -299,14 +298,17 @@ func (engine *Engine) serveAutoRedirect(c *Context, root *node, tsr bool) bool {
 	return false
 }
 
+var mimePlain = []string{MIMEPlain}
+
 func serveError(c *Context, code int, defaultMessage []byte) {
 	c.writermem.status = code
 	c.Next()
-	if !c.Writer.Written() {
-		if c.Writer.Status() == code {
-			c.Data(-1, binding.MIMEPlain, defaultMessage)
+	if !c.writermem.Written() {
+		if c.writermem.Status() == code {
+			c.writermem.Header()["Content-Type"] = mimePlain
+			c.Writer.Write(defaultMessage)
 		} else {
-			c.Writer.WriteHeaderNow()
+			c.writermem.WriteHeaderNow()
 		}
 	}
 }

+ 0 - 3
gin_test.go

@@ -25,9 +25,6 @@ func TestCreateEngine(t *testing.T) {
 	assert.Equal(t, "/", router.BasePath)
 	assert.Equal(t, router.engine, router)
 	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("GET", "a", HandlersChain{func(_ *Context) {}}) })

+ 39 - 1
middleware_test.go

@@ -77,9 +77,10 @@ func TestMiddlewareNoRoute(t *testing.T) {
 	assert.Equal(t, signature, "ACEGHFDB")
 }
 
-func TestMiddlewareNoMethod(t *testing.T) {
+func TestMiddlewareNoMethodEnabled(t *testing.T) {
 	signature := ""
 	router := New()
+	router.HandleMethodNotAllowed = true
 	router.Use(func(c *Context) {
 		signature += "A"
 		c.Next()
@@ -113,6 +114,43 @@ func TestMiddlewareNoMethod(t *testing.T) {
 	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) {
 	signature := ""
 	router := New()

+ 1 - 1
path.go

@@ -18,7 +18,7 @@ package gin
 //	   that is, replace "/.." by "/" at the beginning of a path.
 //
 // 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 "/"
 	if p == "" {
 		return "/"

+ 3 - 3
path_test.go

@@ -67,8 +67,8 @@ var cleanTests = []struct {
 
 func TestPathClean(t *testing.T) {
 	for _, test := range cleanTests {
-		assert.Equal(t, CleanPath(test.path), test.result)
-		assert.Equal(t, CleanPath(test.result), test.result)
+		assert.Equal(t, cleanPath(test.path), test.result)
+		assert.Equal(t, cleanPath(test.result), test.result)
 	}
 }
 
@@ -82,7 +82,7 @@ func TestPathCleanMallocs(t *testing.T) {
 	}
 
 	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)
 	}
 }

+ 1 - 1
render/data.go

@@ -13,7 +13,7 @@ type Data struct {
 
 func (r Data) Write(w http.ResponseWriter) error {
 	if len(r.ContentType) > 0 {
-		w.Header().Set("Content-Type", r.ContentType)
+		w.Header()["Content-Type"] = []string{r.ContentType}
 	}
 	w.Write(r.Data)
 	return nil

+ 2 - 1
render/text.go

@@ -6,6 +6,7 @@ package render
 
 import (
 	"fmt"
+	"io"
 	"net/http"
 )
 
@@ -24,7 +25,7 @@ func (r String) Write(w http.ResponseWriter) error {
 	if len(r.Data) > 0 {
 		fmt.Fprintf(w, r.Format, r.Data...)
 	} else {
-		w.Write([]byte(r.Format))
+		io.WriteString(w, r.Format)
 	}
 	return nil
 }

+ 11 - 0
response_writer.go

@@ -6,6 +6,7 @@ package gin
 
 import (
 	"bufio"
+	"io"
 	"net"
 	"net/http"
 )
@@ -24,6 +25,7 @@ type (
 
 		Status() int
 		Size() int
+		WriteString(string) (int, error)
 		Written() bool
 		WriteHeaderNow()
 	}
@@ -35,6 +37,8 @@ type (
 	}
 )
 
+var _ ResponseWriter = &responseWriter{}
+
 func (w *responseWriter) reset(writer http.ResponseWriter) {
 	w.ResponseWriter = writer
 	w.size = noWritten
@@ -64,6 +68,13 @@ func (w *responseWriter) Write(data []byte) (n int, err error) {
 	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 {
 	return w.status
 }

+ 19 - 2
routes_test.go

@@ -61,6 +61,7 @@ func testRouteNotOK(method string, t *testing.T) {
 func testRouteNotOK2(method string, t *testing.T) {
 	passed := false
 	router := New()
+	router.HandleMethodNotAllowed = true
 	var methodRoute string
 	if method == "POST" {
 		methodRoute = "GET"
@@ -224,9 +225,9 @@ func TestRouterMiddlewareAndStatic(t *testing.T) {
 	assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework")
 }
 
-func TestRouteNotAllowed(t *testing.T) {
+func TestRouteNotAllowedEnabled(t *testing.T) {
 	router := New()
-
+	router.HandleMethodNotAllowed = true
 	router.POST("/path", func(c *Context) {})
 	w := performRequest(router, "GET", "/path")
 	assert.Equal(t, w.Code, http.StatusMethodNotAllowed)
@@ -239,8 +240,24 @@ func TestRouteNotAllowed(t *testing.T) {
 	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) {
 	router := New()
+	router.RedirectFixedPath = true
 	router.GET("/path", func(c *Context) {})
 	router.GET("/dir/", func(c *Context) {})
 	router.GET("/", func(c *Context) {})