Dynamically Register Handlers

I’m curious if someone could point me to a pattern for dynamically registering handlers that would be enabled from a list.

I have all my handlers written and I just build a map like

type handlers map[string]http.HandlerFunc

h := make(handlers)
h["a"] = handlerA
h["b"] = handlerB

then I’m looping a list of routes from a config file

for routeName, route := range s.config.Routes {
	mux.Handle(route.Path, http.HandlerFunc(h[routeName]))
}

Is there a cleaner/better/elegant/idiomatic solution? Any ideas/opinions greatly appreciated.

So I am curious, what kind of problem you are dealing that needs dynamic route definition?

Thanks @kync for taking the time. There may in fact just be another way to solve the problem I’m trying to solve.

We are building a HTTP server for receiving requests from webhooks configured on various different vendor platforms. The goal is to have all the vendors specific handlers built to process and transform the requests to relay them to another service. We would like to be able to enable these on a per handler basis in a configuration file so that there are not endpoints registered that are not being used.

I’m thinking of way logstash inputs, filters and outputs are configured and enabled. In logstash you would have a configuration file that looks like

input {
  syslog {
    port => 12345
    syslog_field => "syslog"
  }
}

And only the syslog input would be enabled but you could also run it with

input {
  file {
    path => "/tmp/access_log"
    start_position => "beginning"
  }
  syslog {
    port => 12345
    syslog_field => "syslog"
  }
}

And you would have syslog and file reader enabled.

I could pull the inputs from the config file then loop through them to register the handlers if the config exists.

Are you using templates?

there is nothing wrong with your solution until you need to distinguish vendors via custom rules. How I would do it:

type StripeHandler struct {
	Active bool
}

func (s StripeHandler) Matches(r *http.Request) bool {
	// you can check a key in the query string, hostname whatever you need to identify
	if r.URL.Path == "/stripe" {
		return true
	}
	return false
}
func (s StripeHandler) IsActive() bool {
	return s.Active
}
func (s StripeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Println("stripe handled")
}

type PaypalHandler struct {
	Active bool
}

func (p PaypalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Println("paypal handled")
}
func (p PaypalHandler) IsActive() bool {
	return p.Active
}
func (p PaypalHandler) Matches(r *http.Request) bool {
	if r.URL.Path == "/paypal" {
		return true
	}
	return false
}

type VendorHandler interface {
	IsActive() bool
	Matches(r *http.Request) bool
	ServeHTTP(w http.ResponseWriter, r *http.Request)
}

type WebhookHandler struct {
	handlers []VendorHandler
}

func (wh *WebhookHandler) Register(h VendorHandler) {
	wh.handlers = append(wh.handlers, h)

}
func (wh *WebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	for _, handler := range wh.handlers {
		if handler.IsActive() && handler.Matches(r) {
			handler.ServeHTTP(w, r)
			break
		}
	}
}

func main() {
	configs := map[string]bool{"stripe": true, "paypal": false}
	wh := WebhookHandler{}
	for key, active := range configs {
		if !active {
			continue
		}
		switch key {
		case "stripe":
			{
				wh.Register(StripeHandler{Active: true})
			}
		case "paypal":
			{
				wh.Register(PaypalHandler{Active: true})
			}
		}
	}
	uri, _ := url.Parse("https://play.golang.org/stripe")
	wh.ServeHTTP(nil, &http.Request{URL: uri})
	uri, _ = url.Parse("https://play.golang.org/paypal")
	wh.ServeHTTP(nil, &http.Request{URL: uri})
	fmt.Println()
}

I would go with above solution, not because it is better than yours, I like to avoid to configure services via a configuration file as much as possible.

https://play.golang.org/p/i099WLUK8_6

1 Like