mirror of
https://github.com/glauth/glauth.git
synced 2026-10-26 14:09:33 +00:00
596d28efbd
Thanks!
183 lines
4.2 KiB
Go
183 lines
4.2 KiB
Go
package frontend
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"github.com/rs/zerolog"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/glauth/glauth/v2/pkg/assets"
|
|
)
|
|
|
|
// RunAPI provides a basic REST API
|
|
func RunAPI(opts ...Option) {
|
|
options := newOptions(opts...)
|
|
log := options.Logger
|
|
cfg := options.Config
|
|
|
|
register(http.DefaultServeMux, log, cfg.TLS, cfg.Listen)
|
|
if cfg.TLS {
|
|
log.Info().Str("address", cfg.Listen).Msg("Starting HTTPS server")
|
|
err := http.ListenAndServeTLS(cfg.Listen, cfg.Cert, cfg.Key, nil)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("error starting HTTPS server")
|
|
return
|
|
}
|
|
} else {
|
|
log.Info().Str("address", cfg.Listen).Msg("Starting HTTP server")
|
|
err := http.ListenAndServe(cfg.Listen, nil)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("error starting HTTP server")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func register(mux *http.ServeMux, log zerolog.Logger, tls bool, listen string) {
|
|
fs := http.FileServer(http.FS(assets.Content))
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
log.Info().Str("path", r.URL.Path).Msg("Web")
|
|
if r.URL.Path != "/" {
|
|
log.Info().Msg("Web 404")
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
fs.ServeHTTP(w, r)
|
|
})
|
|
mux.Handle("/assets/", http.StripPrefix("/assets/", fs))
|
|
var url string
|
|
if tls {
|
|
url = fmt.Sprintf("https://%s/debug/vars", listen)
|
|
} else {
|
|
url = fmt.Sprintf("http://%s/debug/vars", listen)
|
|
}
|
|
c := &collector{
|
|
url: url,
|
|
names: map[string]string{
|
|
"proxy": "glauth_proxy",
|
|
"proxy_frontend": "glauth_proxy_frontend",
|
|
"proxy_backend": "glauth_proxy_backend",
|
|
},
|
|
helps: map[string]string{
|
|
"proxy": "General Metrics",
|
|
"proxy_frontend": "Frontend Metrics",
|
|
"proxy_backend": "Backend Metrics",
|
|
},
|
|
labelName: map[string]string{
|
|
"proxy": "metric",
|
|
"proxy_frontend": "metric",
|
|
"proxy_backend": "metric",
|
|
},
|
|
}
|
|
prometheus.MustRegister(c)
|
|
mux.Handle("/metrics", promhttp.Handler())
|
|
}
|
|
|
|
// The code below liberally borrows from https://github.com/albertito/prometheus-expvar-exporter
|
|
// and is adapted (just a little) for our purpose.
|
|
|
|
type collector struct {
|
|
url string
|
|
names map[string]string
|
|
helps map[string]string
|
|
labelName map[string]string
|
|
}
|
|
|
|
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
|
return
|
|
}
|
|
|
|
func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
|
resp, err := http.Get(c.url)
|
|
if err != nil {
|
|
log.Printf("Error scraping %q: %v", c.url, err)
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("Error reading body of %q: %v", c.url, err)
|
|
return
|
|
}
|
|
|
|
// Replace "\xNN" with "?" because the default parser doesn't handle them
|
|
// well.
|
|
re := regexp.MustCompile(`\\x..`)
|
|
body = re.ReplaceAllFunc(body, func(s []byte) []byte {
|
|
return []byte("?")
|
|
})
|
|
|
|
var vs map[string]interface{}
|
|
err = json.Unmarshal(body, &vs)
|
|
if err != nil {
|
|
log.Printf("Error unmarshalling json from %q: %v", c.url, err)
|
|
return
|
|
}
|
|
|
|
for k, v := range vs {
|
|
name := strings.ReplaceAll(k, "/", "_")
|
|
if n, ok := c.names[k]; ok {
|
|
name = n
|
|
}
|
|
|
|
help := fmt.Sprintf("expvar %q", k)
|
|
if h, ok := c.helps[k]; ok {
|
|
help = h
|
|
}
|
|
|
|
lnames := []string{}
|
|
if ln, ok := c.labelName[k]; ok {
|
|
lnames = append(lnames, ln)
|
|
}
|
|
|
|
desc := prometheus.NewDesc(name, help, lnames, nil)
|
|
|
|
switch v := v.(type) {
|
|
case float64:
|
|
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue, v)
|
|
case bool:
|
|
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
|
|
valToFloat(v))
|
|
case map[string]interface{}:
|
|
// We only support explicitly written label names.
|
|
if len(lnames) != 1 {
|
|
continue
|
|
}
|
|
for lk, lv := range v {
|
|
ch <- prometheus.MustNewConstMetric(desc, prometheus.UntypedValue,
|
|
valToFloat(lv), lk)
|
|
}
|
|
case string:
|
|
// Not supported by Prometheus.
|
|
continue
|
|
case []interface{}:
|
|
// Not supported by Prometheus.
|
|
continue
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func valToFloat(v interface{}) float64 {
|
|
switch v := v.(type) {
|
|
case float64:
|
|
return v
|
|
case bool:
|
|
if v {
|
|
return 1.0
|
|
}
|
|
return 0.0
|
|
case string:
|
|
return 0.0
|
|
}
|
|
log.Printf("unexpected value type: %#v", v)
|
|
return 0.0
|
|
}
|