Repository: goadesign/goa
Pull request #3097 (2022-07-16 22:32:43)

Add ability to define mux middlewares
These HTTP middlewares get executed downstream the mux which
makes it possible to use any data it adds to the context.
The default Goa muxer implementation leverages `httptreemux`
which adds the route and matched params to the context.


Patch:
diff --git a/http/mux.go b/http/mux.go
index 6d4e4cfd..29d06b85 100644
--- a/http/mux.go
+++ b/http/mux.go
@@ -50,6 +50,15 @@ type (
 		Vars(*http.Request) map[string]string
 	}
 
+	// MiddlewareMuxer makes it possible to mount middlewares downstream of the
+	// Muxer.
+	MiddlewareMuxer interface {
+		Muxer
+		// Use appends a middleware to the list of middlewares to be applied
+		// to the Muxer.
+		Use(func(http.Handler) http.Handler)
+	}
+
 	// mux is the default Muxer implementation. It leverages the
 	// httptreemux router and simply substitutes the syntax used to define
 	// wildcards from ":wildcard" and "*wildcard" to "{wildcard}" and
@@ -60,7 +69,7 @@ type (
 )
 
 // NewMuxer returns a Muxer implementation based on the httptreemux router.
-func NewMuxer() Muxer {
+func NewMuxer() MiddlewareMuxer {
 	r := httptreemux.NewContextMux()
 	r.EscapeAddedRoutes = true
 	r.NotFoundHandler = func(w http.ResponseWriter, req *http.Request) {
@@ -82,6 +91,12 @@ func (m *mux) Vars(r *http.Request) map[string]string {
 	return httptreemux.ContextParams(r.Context())
 }
 
+// Use appends a middleware to the list of middlewares to be applied
+// downstream the Muxer.
+func (m *mux) Use(f func(http.Handler) http.Handler) {
+	m.ContextMux.UseHandler(f)
+}
+
 var wildSeg = regexp.MustCompile(`/{([a-zA-Z0-9_]+)}`)
 var wildPath = regexp.MustCompile(`/{\*([a-zA-Z0-9_]+)}`)
 


Test patch:
diff --git a/http/mux_test.go b/http/mux_test.go
index 64b8b9ea..c4616a66 100644
--- a/http/mux_test.go
+++ b/http/mux_test.go
@@ -1,6 +1,11 @@
 package http
 
-import "testing"
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
 
 func TestMuxRegexp(t *testing.T) {
 	cases := []struct{ Name, Pattern, Expected string }{
@@ -24,3 +29,42 @@ func TestMuxRegexp(t *testing.T) {
 		}
 	}
 }
+
+func TestMiddlewares(t *testing.T) {
+	m1 := func(h http.Handler) http.Handler {
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte("m1"))
+			h.ServeHTTP(w, r)
+		})
+	}
+	m2 := func(h http.Handler) http.Handler {
+		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte("m2"))
+			h.ServeHTTP(w, r)
+		})
+	}
+	cases := []struct {
+		Name        string
+		Middlewares []func(http.Handler) http.Handler
+		BodyPrefix  string
+	}{
+		{"empty", nil, ""},
+		{"one", []func(http.Handler) http.Handler{m1}, "m1"},
+		{"two", []func(http.Handler) http.Handler{m1, m2}, "m1m2"},
+	}
+	for _, c := range cases {
+		m := NewMuxer()
+		for _, mw := range c.Middlewares {
+			m.Use(mw)
+		}
+		m.Handle("GET", "/", func(w http.ResponseWriter, r *http.Request) {
+			w.Write([]byte("hello"))
+		})
+		r, _ := http.NewRequest("GET", "/", nil)
+		w := httptest.NewRecorder()
+		m.ServeHTTP(w, r)
+		if w.Body.String() != fmt.Sprintf("%shello", c.BodyPrefix) {
+			t.Errorf("%s: got %s, expected %s", c.Name, w.Body.String(), fmt.Sprintf("%shello", c.BodyPrefix))
+		}
+	}
+}