Register many handlers¶
We added two handlers in the previous section,
but we reference them both explicitly by name when we build NewServeMux
.
This will quickly become inconvenient if we add more handlers.
It's preferable if NewServeMux
doesn't know how many handlers or their names,
and instead just accepts a list of handlers to register.
Let's do that.
-
Modify
NewServeMux
to operate on a list ofRoute
objects.func NewServeMux(routes []Route) *http.ServeMux { mux := http.NewServeMux() for _, route := range routes { mux.Handle(route.Pattern(), route) } return mux }
-
Annotate the
NewServeMux
entry inmain
to say that it accepts a slice that contains the contents of the "routes" group.fx.Provide( NewHTTPServer, fx.Annotate( NewServeMux, fx.ParamTags(`group:"routes"`), ),
-
Define a new function
AsRoute
to build functions that feed into this group.// AsRoute annotates the given constructor to state that // it provides a route to the "routes" group. func AsRoute(f any) any { return fx.Annotate( f, fx.As(new(Route)), fx.ResultTags(`group:"routes"`), ) }
-
Wrap the
NewEchoHandler
andNewHelloHandler
constructors inmain()
withAsRoute
so that they feed their routes into this group.fx.Provide( AsRoute(NewEchoHandler), AsRoute(NewHelloHandler), zap.NewExample, ),
-
Finally, run the application.
{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewServeMux(), fx.ParamTags([\"group:\\\"routes\\\"\"])","type":"*http.ServeMux"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.ResultTags([\"group:\\\"routes\\\"\"]), fx.As([[main.Route]])","type":"main.Route[group = \"routes\"]"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewHelloHandler(), fx.ResultTags([\"group:\\\"routes\\\"\"]), fx.As([[main.Route]])","type":"main.Route[group = \"routes\"]"} {"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"} {"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"} {"level":"info","msg":"invoking","function":"main.main.func2()"} {"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"} {"level":"info","msg":"Starting HTTP server","addr":":8080"} {"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"5µs"} {"level":"info","msg":"started"}
-
Send requests to it.
$ curl -X POST -d 'hello' http://localhost:8080/echo hello $ curl -X POST -d 'gopher' http://localhost:8080/hello Hello, gopher
What did we just do?
We annotated NewServeMux
to consume a value group as a slice,
and we annotated our existing handler constructors to feed into this value
group.
Any other constructor in the application can also feed values
into this value group as long as the result conforms to the Route
interface.
They will all be collected together and passed into our ServeMux
constructor.
Related Resources
- Value groups further explains what value groups are, and how to use them.