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:
		| @ -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) {}) | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user