Repository: labstack/echo
Pull request #1898 (2021-06-22 16:12:53)

Adds RequestIDHandler function to RequestID middleware
Adds RequestIDHandler function in RequestID middleware to use the value.
For example, suppose we want to use `RequestID` value in logger after extract.

```go
e.Use(RequestIDWithConfig(RequestIDConfig{
	RequestIDHandler: func(ctx echo.Context, requestID string) {
		logger := logging.DefaultLogger().With("x-request-id", requestID)
		ctx.SetRequest(ctx.Request().WithContext(logging.WithLogger(logger)))
	},
}))
```


Patch:
diff --git a/middleware/request_id.go b/middleware/request_id.go
index 21f801f..b0baeeb 100644
--- a/middleware/request_id.go
+++ b/middleware/request_id.go
@@ -14,6 +14,9 @@ type (
 		// Generator defines a function to generate an ID.
 		// Optional. Default value random.String(32).
 		Generator func() string
+
+		// RequestIDHandler defines a function which is executed for a request id.
+		RequestIDHandler func(echo.Context, string)
 	}
 )
 
@@ -53,6 +56,9 @@ func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
 				rid = config.Generator()
 			}
 			res.Header().Set(echo.HeaderXRequestID, rid)
+			if config.RequestIDHandler != nil {
+				config.RequestIDHandler(c, rid)
+			}
 
 			return next(c)
 		}


Test patch:
diff --git a/echo_test.go b/echo_test.go
index e5bd371..dc55349 100644
--- a/echo_test.go
+++ b/echo_test.go
@@ -472,6 +472,37 @@ func TestEchoRoutes(t *testing.T) {
 	}
 }
 
+func TestEchoRoutesHandleHostsProperly(t *testing.T) {
+	e := New()
+	h := e.Host("route.com")
+	routes := []*Route{
+		{http.MethodGet, "/users/:user/events", ""},
+		{http.MethodGet, "/users/:user/events/public", ""},
+		{http.MethodPost, "/repos/:owner/:repo/git/refs", ""},
+		{http.MethodPost, "/repos/:owner/:repo/git/tags", ""},
+	}
+	for _, r := range routes {
+		h.Add(r.Method, r.Path, func(c Context) error {
+			return c.String(http.StatusOK, "OK")
+		})
+	}
+
+	if assert.Equal(t, len(routes), len(e.Routes())) {
+		for _, r := range e.Routes() {
+			found := false
+			for _, rr := range routes {
+				if r.Method == rr.Method && r.Path == rr.Path {
+					found = true
+					break
+				}
+			}
+			if !found {
+				t.Errorf("Route %s %s not found", r.Method, r.Path)
+			}
+		}
+	}
+}
+
 func TestEchoServeHTTPPathEncoding(t *testing.T) {
 	e := New()
 	e.GET("/with/slash", func(c Context) error {
@@ -514,6 +545,109 @@ func TestEchoServeHTTPPathEncoding(t *testing.T) {
 	}
 }
 
+func TestEchoHost(t *testing.T) {
+	assert := assert.New(t)
+
+	okHandler := func(c Context) error { return c.String(http.StatusOK, http.StatusText(http.StatusOK)) }
+	teapotHandler := func(c Context) error { return c.String(http.StatusTeapot, http.StatusText(http.StatusTeapot)) }
+	acceptHandler := func(c Context) error { return c.String(http.StatusAccepted, http.StatusText(http.StatusAccepted)) }
+	teapotMiddleware := MiddlewareFunc(func(next HandlerFunc) HandlerFunc { return teapotHandler })
+
+	e := New()
+	e.GET("/", acceptHandler)
+	e.GET("/foo", acceptHandler)
+
+	ok := e.Host("ok.com")
+	ok.GET("/", okHandler)
+	ok.GET("/foo", okHandler)
+
+	teapot := e.Host("teapot.com")
+	teapot.GET("/", teapotHandler)
+	teapot.GET("/foo", teapotHandler)
+
+	middle := e.Host("middleware.com", teapotMiddleware)
+	middle.GET("/", okHandler)
+	middle.GET("/foo", okHandler)
+
+	var testCases = []struct {
+		name         string
+		whenHost     string
+		whenPath     string
+		expectBody   string
+		expectStatus int
+	}{
+		{
+			name:         "No Host Root",
+			whenHost:     "",
+			whenPath:     "/",
+			expectBody:   http.StatusText(http.StatusAccepted),
+			expectStatus: http.StatusAccepted,
+		},
+		{
+			name:         "No Host Foo",
+			whenHost:     "",
+			whenPath:     "/foo",
+			expectBody:   http.StatusText(http.StatusAccepted),
+			expectStatus: http.StatusAccepted,
+		},
+		{
+			name:         "OK Host Root",
+			whenHost:     "ok.com",
+			whenPath:     "/",
+			expectBody:   http.StatusText(http.StatusOK),
+			expectStatus: http.StatusOK,
+		},
+		{
+			name:         "OK Host Foo",
+			whenHost:     "ok.com",
+			whenPath:     "/foo",
+			expectBody:   http.StatusText(http.StatusOK),
+			expectStatus: http.StatusOK,
+		},
+		{
+			name:         "Teapot Host Root",
+			whenHost:     "teapot.com",
+			whenPath:     "/",
+			expectBody:   http.StatusText(http.StatusTeapot),
+			expectStatus: http.StatusTeapot,
+		},
+		{
+			name:         "Teapot Host Foo",
+			whenHost:     "teapot.com",
+			whenPath:     "/foo",
+			expectBody:   http.StatusText(http.StatusTeapot),
+			expectStatus: http.StatusTeapot,
+		},
+		{
+			name:         "Middleware Host",
+			whenHost:     "middleware.com",
+			whenPath:     "/",
+			expectBody:   http.StatusText(http.StatusTeapot),
+			expectStatus: http.StatusTeapot,
+		},
+		{
+			name:         "Middleware Host Foo",
+			whenHost:     "middleware.com",
+			whenPath:     "/foo",
+			expectBody:   http.StatusText(http.StatusTeapot),
+			expectStatus: http.StatusTeapot,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			req := httptest.NewRequest(http.MethodGet, tc.whenPath, nil)
+			req.Host = tc.whenHost
+			rec := httptest.NewRecorder()
+
+			e.ServeHTTP(rec, req)
+
+			assert.Equal(tc.expectStatus, rec.Code)
+			assert.Equal(tc.expectBody, rec.Body.String())
+		})
+	}
+}
+
 func TestEchoGroup(t *testing.T) {
 	e := New()
 	buf := new(bytes.Buffer)
@@ -1166,6 +1300,22 @@ func TestEchoReverse(t *testing.T) {
 	assert.Equal("/params/one/bar/two/three", e.Reverse("/params/:foo/bar/:qux/*", "one", "two", "three"))
 }
 
+func TestEchoReverseHandleHostProperly(t *testing.T) {
+	assert := assert.New(t)
+
+	dummyHandler := func(Context) error { return nil }
+
+	e := New()
+	h := e.Host("the_host")
+	h.GET("/static", dummyHandler).Name = "/static"
+	h.GET("/static/*", dummyHandler).Name = "/static/*"
+
+	assert.Equal("/static", e.Reverse("/static"))
+	assert.Equal("/static", e.Reverse("/static", "missing param"))
+	assert.Equal("/static/*", e.Reverse("/static/*"))
+	assert.Equal("/static/foo.txt", e.Reverse("/static/*", "foo.txt"))
+}
+
 func TestEcho_ListenerAddr(t *testing.T) {
 	e := New()
 
diff --git a/middleware/request_id_test.go b/middleware/request_id_test.go
index 86eec8c..944b3b4 100644
--- a/middleware/request_id_test.go
+++ b/middleware/request_id_test.go
@@ -23,13 +23,20 @@ func TestRequestID(t *testing.T) {
 	h(c)
 	assert.Len(t, rec.Header().Get(echo.HeaderXRequestID), 32)
 
-	// Custom generator
+	// Custom generator and handler
+	customID := "customGenerator"
+	calledHandler := false
 	rid = RequestIDWithConfig(RequestIDConfig{
-		Generator: func() string { return "customGenerator" },
+		Generator: func() string { return customID },
+		RequestIDHandler: func(_ echo.Context, id string) {
+			calledHandler = true
+			assert.Equal(t, customID, id)
+		},
 	})
 	h = rid(handler)
 	h(c)
 	assert.Equal(t, rec.Header().Get(echo.HeaderXRequestID), "customGenerator")
+	assert.True(t, calledHandler)
 }
 
 func TestRequestID_IDNotAltered(t *testing.T) {