diff --git a/main.go b/main.go index 5edcf0689..c4b2d64b4 100644 --- a/main.go +++ b/main.go @@ -81,6 +81,7 @@ type prometheus struct { ruleManager *rules.Manager targetManager *retrieval.TargetManager notificationHandler *notification.NotificationHandler + statusHandler *web.PrometheusStatusHandler storage local.Storage remoteStorageQueues []*remote.StorageQueueManager @@ -189,25 +190,26 @@ func NewPrometheus() *prometheus { QueryEngine: queryEngine, } - webService := &web.WebService{ + webService := web.NewWebService(&web.WebServiceOptions{ + PathPrefix: *pathPrefix, StatusHandler: prometheusStatus, MetricsHandler: metricsService, ConsolesHandler: consolesHandler, AlertsHandler: alertsHandler, GraphsHandler: graphsHandler, - } + }) p := &prometheus{ queryEngine: queryEngine, ruleManager: ruleManager, targetManager: targetManager, notificationHandler: notificationHandler, + statusHandler: prometheusStatus, storage: memStorage, remoteStorageQueues: remoteStorageQueues, webService: webService, } - webService.QuitChan = make(chan struct{}) if !p.reloadConfig() { os.Exit(1) @@ -227,7 +229,7 @@ func (p *prometheus) reloadConfig() bool { } success := true - success = success && p.webService.StatusHandler.ApplyConfig(conf) + success = success && p.statusHandler.ApplyConfig(conf) success = success && p.targetManager.ApplyConfig(conf) success = success && p.ruleManager.ApplyConfig(conf) @@ -268,7 +270,7 @@ func (p *prometheus) Serve() { defer p.queryEngine.Stop() - go p.webService.ServeForever(*pathPrefix) + go p.webService.Run() // Wait for reload or termination signals. hup := make(chan os.Signal) diff --git a/web/api/api.go b/web/api/api.go index 1d28de3f8..9fc12bd4b 100644 --- a/web/api/api.go +++ b/web/api/api.go @@ -33,19 +33,19 @@ type MetricsService struct { } // RegisterHandler registers the handler for the various endpoints below /api. -func (msrv *MetricsService) RegisterHandler(pathPrefix string) { +func (msrv *MetricsService) RegisterHandler(mux *http.ServeMux, pathPrefix string) { handler := func(h func(http.ResponseWriter, *http.Request)) http.Handler { return httputil.CompressionHandler{ Handler: http.HandlerFunc(h), } } - http.Handle(pathPrefix+"/api/query", prometheus.InstrumentHandler( + mux.Handle(pathPrefix+"/api/query", prometheus.InstrumentHandler( pathPrefix+"/api/query", handler(msrv.Query), )) - http.Handle(pathPrefix+"/api/query_range", prometheus.InstrumentHandler( + mux.Handle(pathPrefix+"/api/query_range", prometheus.InstrumentHandler( pathPrefix+"/api/query_range", handler(msrv.QueryRange), )) - http.Handle(pathPrefix+"/api/metrics", prometheus.InstrumentHandler( + mux.Handle(pathPrefix+"/api/metrics", prometheus.InstrumentHandler( pathPrefix+"/api/metrics", handler(msrv.Metrics), )) } diff --git a/web/api/api_test.go b/web/api/api_test.go index a663926cf..3dffe420b 100644 --- a/web/api/api_test.go +++ b/web/api/api_test.go @@ -97,7 +97,7 @@ func TestQuery(t *testing.T) { Storage: storage, QueryEngine: promql.NewEngine(storage), } - api.RegisterHandler("") + api.RegisterHandler(http.DefaultServeMux, "") server := httptest.NewServer(http.DefaultServeMux) defer server.Close() diff --git a/web/web.go b/web/web.go index 0a789d882..ea75f37ef 100644 --- a/web/web.go +++ b/web/web.go @@ -49,77 +49,88 @@ var ( // WebService handles the HTTP endpoints with the exception of /api. type WebService struct { + QuitChan chan struct{} + mux *http.ServeMux +} + +type WebServiceOptions struct { + PathPrefix string StatusHandler *PrometheusStatusHandler MetricsHandler *api.MetricsService AlertsHandler *AlertsHandler ConsolesHandler *ConsolesHandler GraphsHandler *GraphsHandler - - QuitChan chan struct{} } -// ServeForever serves the HTTP endpoints and only returns upon errors. -func (ws WebService) ServeForever(pathPrefix string) { +// NewWebService returns a new WebService. +func NewWebService(o *WebServiceOptions) *WebService { + mux := http.NewServeMux() - http.Handle("/favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "", 404) - })) + ws := &WebService{ + mux: mux, + QuitChan: make(chan struct{}), + } - http.HandleFunc("/", prometheus.InstrumentHandlerFunc(pathPrefix, func(rw http.ResponseWriter, req *http.Request) { + mux.HandleFunc("/", prometheus.InstrumentHandlerFunc(o.PathPrefix, func(rw http.ResponseWriter, req *http.Request) { // The "/" pattern matches everything, so we need to check // that we're at the root here. - if req.URL.Path == pathPrefix+"/" { - ws.StatusHandler.ServeHTTP(rw, req) - } else if req.URL.Path == pathPrefix { - http.Redirect(rw, req, pathPrefix+"/", http.StatusFound) - } else if !strings.HasPrefix(req.URL.Path, pathPrefix+"/") { + if req.URL.Path == o.PathPrefix+"/" { + o.StatusHandler.ServeHTTP(rw, req) + } else if req.URL.Path == o.PathPrefix { + http.Redirect(rw, req, o.PathPrefix+"/", http.StatusFound) + } else if !strings.HasPrefix(req.URL.Path, o.PathPrefix+"/") { // We're running under a prefix but the user requested something // outside of it. Let's see if this page exists under the prefix. - http.Redirect(rw, req, pathPrefix+req.URL.Path, http.StatusFound) + http.Redirect(rw, req, o.PathPrefix+req.URL.Path, http.StatusFound) } else { http.NotFound(rw, req) } })) - http.Handle(pathPrefix+"/alerts", prometheus.InstrumentHandler( - pathPrefix+"/alerts", ws.AlertsHandler, + mux.Handle(o.PathPrefix+"/alerts", prometheus.InstrumentHandler( + o.PathPrefix+"/alerts", o.AlertsHandler, )) - http.Handle(pathPrefix+"/consoles/", prometheus.InstrumentHandler( - pathPrefix+"/consoles/", http.StripPrefix(pathPrefix+"/consoles/", ws.ConsolesHandler), + mux.Handle(o.PathPrefix+"/consoles/", prometheus.InstrumentHandler( + o.PathPrefix+"/consoles/", http.StripPrefix(o.PathPrefix+"/consoles/", o.ConsolesHandler), )) - http.Handle(pathPrefix+"/graph", prometheus.InstrumentHandler( - pathPrefix+"/graph", ws.GraphsHandler, + mux.Handle(o.PathPrefix+"/graph", prometheus.InstrumentHandler( + o.PathPrefix+"/graph", o.GraphsHandler, )) - http.Handle(pathPrefix+"/heap", prometheus.InstrumentHandler( - pathPrefix+"/heap", http.HandlerFunc(dumpHeap), + mux.Handle(o.PathPrefix+"/heap", prometheus.InstrumentHandler( + o.PathPrefix+"/heap", http.HandlerFunc(dumpHeap), )) - ws.MetricsHandler.RegisterHandler(pathPrefix) - http.Handle(pathPrefix+*metricsPath, prometheus.Handler()) + o.MetricsHandler.RegisterHandler(mux, o.PathPrefix) + mux.Handle(o.PathPrefix+*metricsPath, prometheus.Handler()) if *useLocalAssets { - http.Handle(pathPrefix+"/static/", prometheus.InstrumentHandler( - pathPrefix+"/static/", http.StripPrefix(pathPrefix+"/static/", http.FileServer(http.Dir("web/static"))), + mux.Handle(o.PathPrefix+"/static/", prometheus.InstrumentHandler( + o.PathPrefix+"/static/", http.StripPrefix(o.PathPrefix+"/static/", http.FileServer(http.Dir("web/static"))), )) } else { - http.Handle(pathPrefix+"/static/", prometheus.InstrumentHandler( - pathPrefix+"/static/", http.StripPrefix(pathPrefix+"/static/", new(blob.Handler)), + mux.Handle(o.PathPrefix+"/static/", prometheus.InstrumentHandler( + o.PathPrefix+"/static/", http.StripPrefix(o.PathPrefix+"/static/", new(blob.Handler)), )) } if *userAssetsPath != "" { - http.Handle(pathPrefix+"/user/", prometheus.InstrumentHandler( - pathPrefix+"/user/", http.StripPrefix(pathPrefix+"/user/", http.FileServer(http.Dir(*userAssetsPath))), + mux.Handle(o.PathPrefix+"/user/", prometheus.InstrumentHandler( + o.PathPrefix+"/user/", http.StripPrefix(o.PathPrefix+"/user/", http.FileServer(http.Dir(*userAssetsPath))), )) } if *enableQuit { - http.Handle(pathPrefix+"/-/quit", http.HandlerFunc(ws.quitHandler)) + mux.Handle(o.PathPrefix+"/-/quit", http.HandlerFunc(ws.quitHandler)) } + return ws +} + +// Run serves the HTTP endpoints. +func (ws *WebService) Run() { log.Infof("Listening on %s", *listenAddress) // If we cannot bind to a port, retry after 30 seconds. for { - err := http.ListenAndServe(*listenAddress, nil) + err := http.ListenAndServe(*listenAddress, ws.mux) if err != nil { log.Errorf("Could not listen on %s: %s", *listenAddress, err) } @@ -127,7 +138,7 @@ func (ws WebService) ServeForever(pathPrefix string) { } } -func (ws WebService) quitHandler(w http.ResponseWriter, r *http.Request) { +func (ws *WebService) quitHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { w.Header().Add("Allow", "POST") w.WriteHeader(http.StatusMethodNotAllowed)