Register another handler¶
The handler we defined above has a single handler. Let's add another.
-
Build a new handler in the same file.
// HelloHandler is an HTTP handler that // prints a greeting to the user. type HelloHandler struct { log *zap.Logger } // NewHelloHandler builds a new HelloHandler. func NewHelloHandler(log *zap.Logger) *HelloHandler { return &HelloHandler{log: log} } -
Implement the
Routeinterface for this handler.func (*HelloHandler) Pattern() string { return "/hello" } func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { h.log.Error("Failed to read request", zap.Error(err)) http.Error(w, "Internal server error", http.StatusInternalServerError) return } if _, err := fmt.Fprintf(w, "Hello, %s\n", body); err != nil { h.log.Error("Failed to write response", zap.Error(err)) http.Error(w, "Internal server error", http.StatusInternalServerError) return } }The handler reads its request body, and writes a welcome message back to the caller.
-
Provide this to the application as a
Routenext toNewEchoHandler.fx.Annotate( NewEchoHandler, fx.As(new(Route)), ), fx.Annotate( NewHelloHandler, fx.As(new(Route)), ), -
Run the application--the service will fail to start.
[Fx] PROVIDE *http.Server <= main.NewHTTPServer() [Fx] PROVIDE *http.ServeMux <= main.NewServeMux() [Fx] PROVIDE main.Route <= fx.Annotate(main.NewEchoHandler(), fx.As([[main.Route]]) [Fx] Error after options were applied: fx.Provide(fx.Annotate(main.NewHelloHandler(), fx.As([[main.Route]])) from: [...] [Fx] ERROR Failed to start: the following errors occurred: - fx.Provide(fx.Annotate(main.NewHelloHandler(), fx.As([[main.Route]])) from: [...] Failed: cannot provide function "main".NewHelloHandler ([..]/main.go:53): cannot provide main.Route from [0].Field0: already provided by "main".NewEchoHandler ([..]/main.go:80)That's a lot of output, but inside the error message, we see:
cannot provide main.Route from [0].Field0: already provided by "main".NewEchoHandler ([..]/main.go:80)This fails because Fx does not allow two instances of the same type to be present in the container without annotating them.
NewServeMuxdoes not know whichRouteto use. Let's fix this. -
Annotate
NewEchoHandlerandNewHelloHandlerinmain()with names for both handlers.fx.Annotate( NewEchoHandler, fx.As(new(Route)), fx.ResultTags(`name:"echo"`), ), fx.Annotate( NewHelloHandler, fx.As(new(Route)), fx.ResultTags(`name:"hello"`), ), -
Add another Route parameter to
NewServeMux.// NewServeMux builds a ServeMux that will route requests // to the given routes. func NewServeMux(route1, route2 Route) *http.ServeMux { mux := http.NewServeMux() mux.Handle(route1.Pattern(), route1) mux.Handle(route2.Pattern(), route2) return mux } -
Annotate
NewServeMuxinmain()to pick these two names values.fx.Provide( NewHTTPServer, fx.Annotate( NewServeMux, fx.ParamTags(`name:"echo"`, `name:"hello"`), ), -
Run the program.
{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewServeMux(), fx.ParamTags([\"name:\\\"echo\\\"\" \"name:\\\"hello\\\"\"])","type":"*http.ServeMux"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewEchoHandler(), fx.ResultTags([\"name:\\\"echo\\\"\"]), fx.As([[main.Route]])","type":"main.Route[name = \"echo\"]"} {"level":"info","msg":"provided","constructor":"fx.Annotate(main.NewHelloHandler(), fx.ResultTags([\"name:\\\"hello\\\"\"]), fx.As([[main.Route]])","type":"main.Route[name = \"hello\"]"} {"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":"56.334µ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 added a constructor that produces a value
with the same type as an existing type.
We annotated constructors with fx.ResultTags to produce named values,
and the consumer with fx.ParamTags to consume these named values.