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
NewServeMuxto operate on a list ofRouteobjects.func NewServeMux(routes []Route) *http.ServeMux { mux := http.NewServeMux() for _, route := range routes { mux.Handle(route.Pattern(), route) } return mux } -
Annotate the
NewServeMuxentry inmainto 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
AsRouteto 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
NewEchoHandlerandNewHelloHandlerconstructors inmain()withAsRouteso 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.