gomux

Dynamically instantiating HandleFunc


I've been trying to wrap my head around implementing HandleFunc() in a dynamic way.

This yaml file I want to turn into a local http web server in Go.

# Example .mock.yaml config
Endpoints:
  - Resource: /city/1
    Method: GET
    Response: '{ Id": 1, "Name": "Albuquerque", "Population": 559.374, "State": "New Mexico" }'
    StatusCode: 200

  - Resource: /city
    Method: POST
    Response: '{ "Name": "Albuquerque", "Population": 559.374, "State": "New Mexico" }'
    statusCode: 200

  - Resource: /city/1
    Method: PUT
    Response: '{ "Population": 601.255 }'
    StatusCode: 204

  - Resource: /city/1
    Method: DELETE
    StatusCode: 204

Now I managed to implement something as following:

package utils

import (
    "io"
    "net/http"

    "github.com/gorilla/mux"

    "github.com/bschaatsbergen/mock/model"
)

func StartServer(conf model.Config, port string) {
    r := mux.NewRouter()

    for _, endpoint := range conf.Endpoints {
        r.HandleFunc(endpoint.Resource, func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(endpoint.StatusCOde)

            io.WriteString(w, endpoint.Response)
        }).Methods(endpoint.Method)
    }

    address := ":" + port

    http.ListenAndServe(address, r)
}

^ https://play.golang.org/p/YoTTUKnQL_5

But this doesn't cut it as it overwrites an earlier created route ('/city/1' GET, DELETE and POST are conflicting).

If anyone could give me a hand on how to dynamically translate the yaml config into a local web server, it would be appreciated!


Solution

  • I quote from @mkopriva, thanks!

    I'm fairly certain your problem is the same as the one here: stackoverflow.com/questions/69595865/…. i.e. All of your anon handler closures capture one and the same variable, the iteration variable, which, at the end of the loop, will hold the last element of the Endpoints slice. Correct? Can the question be closed as duplicate?

    Working piece of code:

    func StartServer(conf model.Config, port string) {
        r := mux.NewRouter()
    
        for _, endpoint := range conf.Endpoints {
            route := endpoint
            r.HandleFunc(route.Resource, func(w http.ResponseWriter, r *http.Request) {
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(route.Statuscode)
                io.WriteString(w, route.Response)
            }).Methods(route.Method)
        }
    
        address := ":" + port
    
        log.Fatal(http.ListenAndServe(address, r))
    }