httpgoservemux

Declaring and assigning handlers to ServeMux in the loop does not work


I have the following piece of code, and it doesn't work as expected. Specifically, all the requests to any endpoints are being handled as requests to either /banana/auth or /banana/description endpoints.

type Route struct {
    AuthRoute        string
    DescriptionRoute string
}

var routes = [2]Route{
    {
        AuthRoute:        "/apple/auth",
        DescriptionRoute: "/apple/description",
    },
    {
        AuthRoute:        "/banana/auth",
        DescriptionRoute: "/banana/description",
    },
}

// ...
sm := http.NewServeMux()

for i, authServerConfig := range authServerConfigs {
    authHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authServerConfig.Auth(w, r)
    }
    sm.Handle(routes[i].AuthRoute, authHandler)

    descriptionHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authServerConfig.ServeDescription(w, r)
    }
    sm.Handle(routes[i].DescriptionRoute, descriptionHandler)
}

server := &http.Server{
    // ...
    Handler: sm,
    // ...
}
server.ListenAndServe()

When I went ahead and replaced the for-loop with these statements, it worked exactly as I wanted:

    authHandlerApple := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authServerConfig.Auth(w, r)
    }
    sm.Handle(routes[0].AuthRoute, authHandlerApple)

    descriptionHandlerApple := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authServerConfig.ServeDescription(w, r)
    }
    sm.Handle(routes[0].DescriptionRoute, descriptionHandlerApple)

    authHandlerBanana := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authServerConfig.Auth(w, r)
    }
    sm.Handle(routes[1].AuthRoute, authHandlerBanana)

    descriptionHandlerBanana := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authServerConfig.ServeDescription(w, r)
    }
    sm.Handle(routes[1].DescriptionRoute, descriptionHandlerBanana)

The question is, what was I originally doing wrong and how can I avoid writing a clunky code as in the second example?


Solution

  • Per FAQ - What happens with closures running as goroutines, each closure shares that single variable authServerConfig in the for loop. To fix it, just add authServerConfig := authServerConfig in the loop

    for i, authServerConfig := range authServerConfigs {
         authServerConfig := authServerConfig
    
        authHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authServerConfig.Auth(w, r)
        })
        sm.Handle(routes[i].AuthRoute, authHandler)
    
        descriptionHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            authServerConfig.ServeDescription(w, r)
        })
        sm.Handle(routes[i].DescriptionRoute, descriptionHandler)
    }