Repository: gin-gonic/gin
Pull request #3377 (2022-10-28 16:22:19)

docs(readme): The krakend is rename to lura

codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/3377?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
> Merging [#3377](https://codecov.io/gh/gin-gonic/gin/pull/3377?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (06b11ec) into [master](https://codecov.io/gh/gin-gonic/gin/commit/51aea73ba0f125f6cacc3b4b695efdf21d9c634f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) (51aea73) will **decrease** coverage by `0.09%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##           master    #3377      +/-   ##
==========================================
- Coverage   98.27%   98.17%   -0.10%     
==========================================
  Files          42       42              
  Lines        3127     3127              
==========================================
- Hits         3073     3070       -3     
- Misses         40       42       +2     
- Partials       14       15       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
|  | `98.17% <ø> (-0.10%)` | :arrow_down: |
| go-1.16 | `∅ <ø> (?)` | |
| go-1.17 | `?` | |
| go-1.18 | `98.17% <ø> (ø)` | |
| go-1.19 | `?` | |
| macos-latest | `∅ <ø> (∅)` | |
| ubuntu-latest | `98.17% <ø> (?)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/3377?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [context.go](https://codecov.io/gh/gin-gonic/gin/pull/3377/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#diff-Y29udGV4dC5nbw==) | `97.45% <0.00%> (-0.51%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1bc51a8..a682c8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -113,7 +113,7 @@
 * chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378))
 * Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387))
 * update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321))
-* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
+* remove an unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
 * Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389))
 * Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322))
 * binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450))
diff --git a/README.md b/README.md
index 960c66d..8a6b262 100644
--- a/README.md
+++ b/README.md
@@ -293,7 +293,7 @@ func main() {
   router := gin.Default()
 
   // Query string parameters are parsed using the existing underlying request object.
-  // The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
+  // The request responds to an url matching:  /welcome?firstname=Jane&lastname=Doe
   router.GET("/welcome", func(c *gin.Context) {
     firstname := c.DefaultQuery("firstname", "Guest")
     lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
@@ -693,7 +693,7 @@ Also, Gin provides two sets of methods for binding:
 
 When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
 
-You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned.
+You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned.
 
 ```go
 // Binding from JSON
@@ -2096,7 +2096,7 @@ func validate(obj interface{}) error {
 }
 
 // Now we can do this!!!
-// FormA is a external type that we can't modify it's tag
+// FormA is an external type that we can't modify it's tag
 type FormA struct {
   FieldA string `url:"field_a"`
 }
@@ -2364,7 +2364,7 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
 * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
 * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.
 * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.
-* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
+* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares.
 * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
 * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
 * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
diff --git a/gin.go b/gin.go
index a2e2e67..35159d0 100644
--- a/gin.go
+++ b/gin.go
@@ -203,7 +203,7 @@ func New() *Engine {
 	}
 	engine.RouterGroup.engine = engine
 	engine.pool.New = func() any {
-		return engine.allocateContext()
+		return engine.allocateContext(engine.maxParams)
 	}
 	return engine
 }
@@ -225,8 +225,8 @@ func (engine *Engine) Handler() http.Handler {
 	return h2c.NewHandler(engine, h2s)
 }
 
-func (engine *Engine) allocateContext() *Context {
-	v := make(Params, 0, engine.maxParams)
+func (engine *Engine) allocateContext(maxParams uint16) *Context {
+	v := make(Params, 0, maxParams)
 	skippedNodes := make([]skippedNode, 0, engine.maxSections)
 	return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
 }
diff --git a/tree.go b/tree.go
index 956bf4d..3f34b8e 100644
--- a/tree.go
+++ b/tree.go
@@ -107,7 +107,8 @@ func countSections(path string) uint16 {
 type nodeType uint8
 
 const (
-	root nodeType = iota + 1
+	static nodeType = iota
+	root
 	param
 	catchAll
 )
@@ -173,6 +174,7 @@ walk:
 			child := node{
 				path:      n.path[i:],
 				wildChild: n.wildChild,
+				nType:     static,
 				indices:   n.indices,
 				children:  n.children,
 				handlers:  n.handlers,
@@ -604,6 +606,11 @@ walk: // Outer loop for walking the tree
 				return
 			}
 
+			if path == "/" && n.nType == static {
+				value.tsr = true
+				return
+			}
+
 			// No handle found. Check if a handle for this path + a
 			// trailing slash exists for trailing slash recommendation
 			for i, c := range []byte(n.indices) {


Test patch:
diff --git a/context_test.go b/context_test.go
index cc55cb0..85e0a61 100644
--- a/context_test.go
+++ b/context_test.go
@@ -146,7 +146,7 @@ func TestSaveUploadedCreateFailed(t *testing.T) {
 
 func TestContextReset(t *testing.T) {
 	router := New()
-	c := router.allocateContext()
+	c := router.allocateContext(0)
 	assert.Equal(t, c.engine, router)
 
 	c.index = 2
@@ -2354,3 +2354,17 @@ func TestContextAddParam(t *testing.T) {
 	assert.Equal(t, ok, true)
 	assert.Equal(t, value, v)
 }
+
+func TestCreateTestContextWithRouteParams(t *testing.T) {
+	w := httptest.NewRecorder()
+	engine := New()
+	engine.GET("/:action/:name", func(ctx *Context) {
+		ctx.String(http.StatusOK, "%s %s", ctx.Param("action"), ctx.Param("name"))
+	})
+	c := CreateTestContextOnly(w, engine)
+	c.Request, _ = http.NewRequest(http.MethodGet, "/hello/gin", nil)
+	engine.HandleContext(c)
+
+	assert.Equal(t, http.StatusOK, w.Code)
+	assert.Equal(t, "hello gin", w.Body.String())
+}
diff --git a/test_helpers.go b/test_helpers.go
index b3be93b..7508c5c 100644
--- a/test_helpers.go
+++ b/test_helpers.go
@@ -9,7 +9,15 @@ import "net/http"
 // CreateTestContext returns a fresh engine and context for testing purposes
 func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
 	r = New()
-	c = r.allocateContext()
+	c = r.allocateContext(0)
+	c.reset()
+	c.writermem.reset(w)
+	return
+}
+
+// CreateTestContextOnly returns a fresh context base on the engine for testing purposes
+func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
+	c = r.allocateContext(r.maxParams)
 	c.reset()
 	c.writermem.reset(w)
 	return
diff --git a/tree_test.go b/tree_test.go
index 085b580..2005738 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -684,6 +684,26 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
 	}
 }
 
+func TestRedirectTrailingSlash(t *testing.T) {
+	var data = []struct {
+		path string
+	}{
+		{"/hello/:name"},
+		{"/hello/:name/123"},
+		{"/hello/:name/234"},
+	}
+
+	node := &node{}
+	for _, item := range data {
+		node.addRoute(item.path, fakeHandler("test"))
+	}
+
+	value := node.getValue("/hello/abx/", nil, getSkippedNodes(), false)
+	if value.tsr != true {
+		t.Fatalf("want true, is false")
+	}
+}
+
 func TestTreeFindCaseInsensitivePath(t *testing.T) {
 	tree := &node{}