MdTp
MdTp

Reputation: 377

http.Server {} - multiple handlers?

I have a structural problem: I can't figure out how I can do with the way I built it up now (I can find solutions where I can see it work with other models).

I'm using standard net/http, and I initiate my server with the following code:

gv := GlobalVars{
    jobs:             make(chan QueueElement),
    appConfig:        appConfig,
}

go worker(&gv)

server := http.Server{
    Handler: &gv,
    Addr:    ":" + appConfig.Port,
}

log.Fatal(server.ListenAndServe())

Then I have one handler that checks for all the routes in a case:

func (gv *GlobalVars) ServeHTTP(w http.ResponseWriter, r *http.Request) {

}

The application is starting up some APIs that pass jobs to a queue.

My issue is, I need to start up 3 of these (different queues, different config) but same globalvar structure.

But this one only has one Handler that I can set - how would I add multiple handlers (I don't want multiple servers, has to run on the same port) that can understand its addressing different globalvars variables?

server := http.Server{
    Handler: &gv,
    Addr:    ":" + appConfig.Port,
}

Upvotes: 7

Views: 11538

Answers (1)

jtepe
jtepe

Reputation: 3350

Have a look at the net.http.ServeMux type:

ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

A ServeMux is itself an http.Handler and multiplexes to different subhandlers by request route. From what I understand of your question, you want to have different handlers on the same server, and each handler addresses a different queue with a different config.

Using a ServeMux one can achieve that easily:

gv1 := GlobalVars{
    jobs:             make(chan QueueElement),
    appConfig:        appConfig1,
}
gv2 := GlobalVars{
    jobs:             make(chan QueueElement),
    appConfig:        appConfig2,
}
gv3 := GlobalVars{
    jobs:             make(chan QueueElement),
    appConfig:        appConfig3,
}

sm := http.NewServeMux()
// Let gv{1,2,3} handle routes{1,2,3} respectively
sm.Handle("/route1", &gv1)
sm.Handle("/route2", &gv2)
sm.Handle("/route3", &gv3)

// Register the ServeMux as the sole Handler. It will delegate to the subhandlers.
server := http.Server{
    Handler: sm,
    Addr:    ":" + globalAppConfig.Port,
}

Note, you don't have to construct an http.Server by yourself. If you just need one server, you can use http package level functions http.ListenAndServe and http.Handle which take care of creating a Server and default ServeMux for you.

// same GlobalVars as above
// ...

// Instead of creating a ServeMux we can use the global DefaultServeMux
http.Handle("/route1", &gv1)
http.Handle("/route2", &gv2)
http.Handle("/route3", &gv3)

// Calling the package level ListenAndServe uses the single global server.
// Passing nil as the Handler uses the DefaultServeMux as Handler on which we registered the Handlers above.
log.Fatal(http.ListenAndServe(":" + globalAppConfig.Port, nil)

UPDATE

A small example of the standard ServeMux with two Handlers serving 3 routes

// Keeping this type simple for the example
type GlobalVars struct {
    appConfig string
}

// This method makes every GlobalVars a net.http.Handler
func (gv *GlobalVars) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "%s here. Receiving request for %s\n", gv.appConfig, req.URL.Path)
}

func main() {
    gv1 := &GlobalVars{
        appConfig: "gv1",
    }
    gv2 := &GlobalVars{
        appConfig: "gv2",
    }

    // Handle requires a route and a Handler, our gvs are Handlers.
    // gv1 handles two routes, while gv2 handles only one.
    http.Handle("/route1", gv1)
    http.Handle("/route2", gv1)
    http.Handle("/route3", gv2)

    log.Fatal(http.ListenAndServe(":8000", nil))
}

If I call all three routes after one another, I will receive the following responses:

$ curl localhost:8000/route1
gv1 here. Receiving request for /route1
$ curl localhost:8000/route2
gv1 here. Receiving request for /route2
$ curl localhost:8000/route3
gv2 here. Receiving request for /route3

This shows how to use stateful variables as Handlers (e.g. variables of type GlobalVars).

NOTE: The handler method ServeHTTP has a pointer receiver for GlobalVars. This means the method could potentially change the GlobalVars variable. HTTP Handlers are executed concurrently (imagine multiple requests to the same handler in a very short time period, you'll want to stay as responsive as possible). This example only reads the appConfig value, so it's fine. Once a write to the variable/field comes into the mix, however, you'll need proper synchronization.

Upvotes: 11

Related Questions