Skip to content

Commit

Permalink
Ability to serve exporters on route prefix with built in support for …
Browse files Browse the repository at this point in the history
…serving the pprof endpoints accessible on the landing page

Signed-off-by: kwilt <kwilt@pm.me>
  • Loading branch information
kwilt committed Feb 6, 2025
1 parent 90c1ece commit adafd4e
Showing 1 changed file with 124 additions and 19 deletions.
143 changes: 124 additions & 19 deletions web/landing_page.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,40 @@ package web
import (
"bytes"
_ "embed"
"errors"
"fmt"
"net"
"net/http"
"net/http/pprof"
"net/url"
"os"
"path"
"strings"
"text/template"
)

// Config represents the configuration of the web listener.
type LandingConfig struct {
RoutePrefix string // The route prefix for the exporter.
HeaderColor string // Used for the landing page header.
CSS string // CSS style tag for the landing page.
Name string // The name of the exporter, generally suffixed by _exporter.
Description string // A short description about the exporter.
Form LandingForm // A POST form.
Links []LandingLinks // Links displayed on the landing page.
ExtraHTML string // Additional HTML to be embedded.
ExtraCSS string // Additional CSS to be embedded.
Version string // The version displayed.
RoutePrefix string // The route prefix for the exporter.
ExternalURL string // The external URL for the exporter.
ListenAddresses []string // The listen address for the exporter.
UseSystemdSocket bool // Use systemd socket.
HeaderColor string // Used for the landing page header.
CSS string // CSS style tag for the landing page.
Name string // The name of the exporter, generally suffixed by _exporter.
Description string // A short description about the exporter.
Form LandingForm // A POST form.
Links []LandingLinks // Links displayed on the landing page.
ExtraHTML string // Additional HTML to be embedded.
ExtraCSS string // Additional CSS to be embedded.
Version string // The version displayed.
Logger Logger // Logging interface
}

type Logger interface {
Error(msg string, keysAndValues ...interface{})
Info(msg string, keysAndValues ...interface{})
Debug(msg string, keysAndValues ...interface{})
}

// LandingForm provides a configuration struct for creating a POST form on the landing page.
Expand Down Expand Up @@ -65,6 +82,7 @@ type LandingLinks struct {
type LandingPageHandler struct {
landingPage []byte
routePrefix string
pprofMux *http.ServeMux
}

var (
Expand All @@ -74,16 +92,97 @@ var (
landingPagecssContent string
)

func NewLandingPage(c LandingConfig) (*LandingPageHandler, error) {
func NewLandingPage(c LandingConfig) (*LandingPageHandler, string, error) {
var buf bytes.Buffer

// Setup URL and Prefix logic
if c.ExternalURL == "" && c.UseSystemdSocket {
return nil, "", fmt.Errorf("cannot automatically infer external URL with systemd socket listener")
}

if c.ExternalURL == "" && len(c.ListenAddresses) > 1 {
c.Logger.Info("Inferring external URL from first provided listen address")
}

if len(c.ListenAddresses) == 0 {
return nil, "", fmt.Errorf("no listen addresses provided")
}

// Compute external URL
var parsedExternalURL *url.URL
var err error
if c.ExternalURL == "" {
hostname, err := os.Hostname()
if err != nil {
return nil, "", err
}
_, port, err := net.SplitHostPort(c.ListenAddresses[0])
if err != nil {
return nil, "", err
}
c.ExternalURL = fmt.Sprintf("http://%s:%s/", hostname, port)
}

if strings.HasPrefix(c.ExternalURL, "\"") || strings.HasPrefix(c.ExternalURL, "'") ||
strings.HasSuffix(c.ExternalURL, "\"") || strings.HasSuffix(c.ExternalURL, "'") {
return nil, "", errors.New("URL must not begin or end with quotes")
}

parsedExternalURL, err = url.Parse(c.ExternalURL)
if err != nil {
return nil, "", fmt.Errorf("failed to determine external URL: %w", err)
}

// Ensure Path component of ExternalURL is formatted without trailing slashes,
// contains a leading slash, and is not empty
pathPrefix := strings.TrimRight(parsedExternalURL.Path, "/")
if pathPrefix != "" && !strings.HasPrefix(pathPrefix, "/") {

Check warning

Code scanning / CodeQL

Bad redirect check Medium

This is a check that
this value
, which flows into a
redirect
, has a leading slash, but not that it does not have '/' or '' in its second position.
pathPrefix = "/" + pathPrefix
}
parsedExternalURL.Path = pathPrefix

if c.RoutePrefix == "" {
c.RoutePrefix = parsedExternalURL.Path
c.Logger.Info("RoutePrefix is empty, defaulting to ExternalURL path", "url", parsedExternalURL.Path)
} else {
c.Logger.Info("RoutePrefix is set", "RoutePrefix", c.RoutePrefix)
}

c.RoutePrefix = "/" + strings.Trim(c.RoutePrefix, "/")
if c.RoutePrefix != "/" {
c.RoutePrefix += "/"
}

if c.RoutePrefix == "" {
c.RoutePrefix = "/"
} else if !strings.HasSuffix(c.RoutePrefix, "/") {
c.RoutePrefix += "/"
}

// Validate RoutePrefix
if !strings.HasPrefix(c.RoutePrefix, "/") {
return nil, "", fmt.Errorf("route prefix must start with '/'")
}

// Redirect over externalURL for root path only if routePrefix is different from "/"
if c.RoutePrefix != "/" {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
http.Redirect(w, r, parsedExternalURL.String(), http.StatusFound)
})
}

length := 0
for _, input := range c.Form.Inputs {
inputLength := len(input.Label)
if inputLength > length {
length = inputLength
}
}

c.Form.Width = (float64(length) + 1) / 2
if c.CSS == "" {
if c.HeaderColor == "" {
Expand All @@ -92,15 +191,11 @@ func NewLandingPage(c LandingConfig) (*LandingPageHandler, error) {
}
cssTemplate := template.Must(template.New("landing css").Parse(landingPagecssContent))
if err := cssTemplate.Execute(&buf, c); err != nil {
return nil, err
return nil, "", err
}
c.CSS = buf.String()
}
if c.RoutePrefix == "" {
c.RoutePrefix = "/"
} else if !strings.HasSuffix(c.RoutePrefix, "/") {
c.RoutePrefix += "/"
}

// Strip leading '/' from Links if present
for i, link := range c.Links {
c.Links[i].Address = strings.TrimPrefix(link.Address, "/")
Expand All @@ -109,16 +204,26 @@ func NewLandingPage(c LandingConfig) (*LandingPageHandler, error) {

buf.Reset()
if err := t.Execute(&buf, c); err != nil {
return nil, err
return nil, "", err
}

// Create a new ServeMux for pprof endpoints in the LandingPage
pprofMux := http.NewServeMux()
pprofMux.HandleFunc(path.Join(c.RoutePrefix, "debug/pprof/profile"), pprof.Profile)
pprofMux.HandleFunc(path.Join(c.RoutePrefix, "debug/pprof/heap"), pprof.Handler("heap").ServeHTTP)

return &LandingPageHandler{
landingPage: buf.Bytes(),
routePrefix: c.RoutePrefix,
}, nil
pprofMux: pprofMux,
}, c.RoutePrefix, nil
}

func (h *LandingPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, path.Join(h.routePrefix, "debug/pprof/")) {
h.pprofMux.ServeHTTP(w, r)
return
}
if r.URL.Path != h.routePrefix {
http.NotFound(w, r)
return
Expand Down

0 comments on commit adafd4e

Please sign in to comment.