From e88e5f680b54c6f13f86a59c6c6ee539fee5ff2d Mon Sep 17 00:00:00 2001 From: Fabian Reinartz Date: Wed, 3 Jun 2015 08:38:50 +0200 Subject: [PATCH] web: simplify prefix handling using util/route package. --- util/route/route.go | 3 +- web/api/api.go | 25 +++++++------- web/api/api_test.go | 6 ++-- web/blob/blob.go | 6 +++- web/consoles.go | 11 +++++-- web/web.go | 80 +++++++++++++++++---------------------------- 6 files changed, 59 insertions(+), 72 deletions(-) diff --git a/util/route/route.go b/util/route/route.go index af53a05c3..96063ea25 100644 --- a/util/route/route.go +++ b/util/route/route.go @@ -60,8 +60,7 @@ func New() *Router { return &Router{rtr: httprouter.New()} } -// WithPrefix returns a router that prefixes all registered routes -// with preifx. +// WithPrefix returns a router that prefixes all registered routes with prefix. func (r *Router) WithPrefix(prefix string) *Router { return &Router{rtr: r.rtr, prefix: r.prefix + prefix} } diff --git a/web/api/api.go b/web/api/api.go index 9fc12bd4b..dab2691bf 100644 --- a/web/api/api.go +++ b/web/api/api.go @@ -23,6 +23,7 @@ import ( "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage/local" "github.com/prometheus/prometheus/util/httputil" + "github.com/prometheus/prometheus/util/route" ) // MetricsService manages the /api HTTP endpoint. @@ -33,19 +34,15 @@ type MetricsService struct { } // RegisterHandler registers the handler for the various endpoints below /api. -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), - } +func (msrv *MetricsService) RegisterHandler(router *route.Router) { + router.Get("/query", handle("query", msrv.Query)) + router.Get("/query_range", handle("query_range", msrv.QueryRange)) + router.Get("/metrics", handle("metrics", msrv.Metrics)) +} + +func handle(name string, f http.HandlerFunc) http.HandlerFunc { + h := httputil.CompressionHandler{ + Handler: f, } - mux.Handle(pathPrefix+"/api/query", prometheus.InstrumentHandler( - pathPrefix+"/api/query", handler(msrv.Query), - )) - mux.Handle(pathPrefix+"/api/query_range", prometheus.InstrumentHandler( - pathPrefix+"/api/query_range", handler(msrv.QueryRange), - )) - mux.Handle(pathPrefix+"/api/metrics", prometheus.InstrumentHandler( - pathPrefix+"/api/metrics", handler(msrv.Metrics), - )) + return prometheus.InstrumentHandler(name, h) } diff --git a/web/api/api_test.go b/web/api/api_test.go index 3dffe420b..b1971b1aa 100644 --- a/web/api/api_test.go +++ b/web/api/api_test.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/storage/local" + "github.com/prometheus/prometheus/util/route" ) // This is a bit annoying. On one hand, we have to choose a current timestamp @@ -97,9 +98,10 @@ func TestQuery(t *testing.T) { Storage: storage, QueryEngine: promql.NewEngine(storage), } - api.RegisterHandler(http.DefaultServeMux, "") + rtr := route.New() + api.RegisterHandler(rtr.WithPrefix("/api")) - server := httptest.NewServer(http.DefaultServeMux) + server := httptest.NewServer(rtr) defer server.Close() for i, s := range scenarios { diff --git a/web/blob/blob.go b/web/blob/blob.go index 52b1ad595..1d03ba88e 100644 --- a/web/blob/blob.go +++ b/web/blob/blob.go @@ -9,6 +9,8 @@ import ( "strings" "github.com/prometheus/log" + + "github.com/prometheus/prometheus/util/route" ) // Sub-directories for templates and static content. @@ -46,7 +48,9 @@ func GetFile(bucket string, name string) ([]byte, error) { type Handler struct{} func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - name := r.URL.Path + ctx := route.Context(r) + + name := strings.Trim(route.Param(ctx, "filepath"), "/") if name == "" { name = "index.html" } diff --git a/web/consoles.go b/web/consoles.go index 27e70e05f..6fa0300bb 100644 --- a/web/consoles.go +++ b/web/consoles.go @@ -22,8 +22,10 @@ import ( "path/filepath" clientmodel "github.com/prometheus/client_golang/model" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/template" + "github.com/prometheus/prometheus/util/route" ) var ( @@ -38,7 +40,10 @@ type ConsolesHandler struct { } func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - file, err := http.Dir(*consoleTemplatesPath).Open(r.URL.Path) + ctx := route.Context(r) + name := route.Param(ctx, "filepath") + + file, err := http.Dir(*consoleTemplatesPath).Open(name) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return @@ -67,10 +72,10 @@ func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { }{ RawParams: rawParams, Params: params, - Path: r.URL.Path, + Path: name, } - tmpl := template.NewTemplateExpander(string(text), "__console_"+r.URL.Path, data, clientmodel.Now(), h.QueryEngine, h.PathPrefix) + tmpl := template.NewTemplateExpander(string(text), "__console_"+name, data, clientmodel.Now(), h.QueryEngine, h.PathPrefix) filenames, err := filepath.Glob(*consoleLibrariesPath + "/*.lib") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/web/web.go b/web/web.go index ea75f37ef..63e015062 100644 --- a/web/web.go +++ b/web/web.go @@ -28,6 +28,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/log" + "github.com/prometheus/prometheus/util/route" clientmodel "github.com/prometheus/client_golang/model" @@ -50,7 +51,7 @@ var ( // WebService handles the HTTP endpoints with the exception of /api. type WebService struct { QuitChan chan struct{} - mux *http.ServeMux + router *route.Router } type WebServiceOptions struct { @@ -64,61 +65,46 @@ type WebServiceOptions struct { // NewWebService returns a new WebService. func NewWebService(o *WebServiceOptions) *WebService { - mux := http.NewServeMux() + router := route.New() ws := &WebService{ - mux: mux, + router: router, QuitChan: make(chan struct{}), } - 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 == 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, o.PathPrefix+req.URL.Path, http.StatusFound) - } else { - http.NotFound(rw, req) - } - })) - mux.Handle(o.PathPrefix+"/alerts", prometheus.InstrumentHandler( - o.PathPrefix+"/alerts", o.AlertsHandler, - )) - mux.Handle(o.PathPrefix+"/consoles/", prometheus.InstrumentHandler( - o.PathPrefix+"/consoles/", http.StripPrefix(o.PathPrefix+"/consoles/", o.ConsolesHandler), - )) - mux.Handle(o.PathPrefix+"/graph", prometheus.InstrumentHandler( - o.PathPrefix+"/graph", o.GraphsHandler, - )) - mux.Handle(o.PathPrefix+"/heap", prometheus.InstrumentHandler( - o.PathPrefix+"/heap", http.HandlerFunc(dumpHeap), - )) - - o.MetricsHandler.RegisterHandler(mux, o.PathPrefix) - mux.Handle(o.PathPrefix+*metricsPath, prometheus.Handler()) + if o.PathPrefix != "" { + // If the prefix is missing for the root path, append it. + router.Get("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, o.PathPrefix, 301) + }) + router = router.WithPrefix(o.PathPrefix) + } + + instr := prometheus.InstrumentHandler + + router.Get("/", instr("status", o.StatusHandler)) + router.Get("/alerts", instr("alerts", o.AlertsHandler)) + router.Get("/graph", instr("graph", o.GraphsHandler)) + router.Get("/heap", instr("heap", http.HandlerFunc(dumpHeap))) + + router.Get(*metricsPath, prometheus.Handler().ServeHTTP) + + o.MetricsHandler.RegisterHandler(router.WithPrefix("/api")) + + router.Get("/consoles/*filepath", instr("consoles", o.ConsolesHandler)) + if *useLocalAssets { - mux.Handle(o.PathPrefix+"/static/", prometheus.InstrumentHandler( - o.PathPrefix+"/static/", http.StripPrefix(o.PathPrefix+"/static/", http.FileServer(http.Dir("web/static"))), - )) + router.Get("/static/*filepath", instr("static", route.FileServe("web/static"))) } else { - mux.Handle(o.PathPrefix+"/static/", prometheus.InstrumentHandler( - o.PathPrefix+"/static/", http.StripPrefix(o.PathPrefix+"/static/", new(blob.Handler)), - )) + router.Get("/static/*filepath", instr("static", blob.Handler{})) } if *userAssetsPath != "" { - mux.Handle(o.PathPrefix+"/user/", prometheus.InstrumentHandler( - o.PathPrefix+"/user/", http.StripPrefix(o.PathPrefix+"/user/", http.FileServer(http.Dir(*userAssetsPath))), - )) + router.Get("/user/*filepath", instr("user", route.FileServe(*userAssetsPath))) } if *enableQuit { - mux.Handle(o.PathPrefix+"/-/quit", http.HandlerFunc(ws.quitHandler)) + router.Post("/-/quit", ws.quitHandler) } return ws @@ -130,7 +116,7 @@ func (ws *WebService) Run() { // If we cannot bind to a port, retry after 30 seconds. for { - err := http.ListenAndServe(*listenAddress, ws.mux) + err := http.ListenAndServe(*listenAddress, ws.router) if err != nil { log.Errorf("Could not listen on %s: %s", *listenAddress, err) } @@ -139,12 +125,6 @@ func (ws *WebService) Run() { } func (ws *WebService) quitHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - w.Header().Add("Allow", "POST") - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - fmt.Fprintf(w, "Requesting termination... Goodbye!") close(ws.QuitChan)