mirror of
https://github.com/stefanprodan/podinfo.git
synced 2026-03-02 18:10:20 +00:00
- add swagger definitions for all API routes - self-host the swagger UI on `/swagger/` - serve swagger spec on `/swagger.json`
380 lines
9.9 KiB
Go
380 lines
9.9 KiB
Go
package api
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
)
|
|
|
|
type PrometheusMiddleware struct {
|
|
Histogram *prometheus.HistogramVec
|
|
Counter *prometheus.CounterVec
|
|
}
|
|
|
|
func NewPrometheusMiddleware() *PrometheusMiddleware {
|
|
// used for monitoring and alerting (RED method)
|
|
histogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
|
Subsystem: "http",
|
|
Name: "request_duration_seconds",
|
|
Help: "Seconds spent serving HTTP requests.",
|
|
Buckets: prometheus.DefBuckets,
|
|
}, []string{"method", "path", "status"})
|
|
// used for horizontal pod auto-scaling (Kubernetes HPA v2)
|
|
counter := prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Subsystem: "http",
|
|
Name: "requests_total",
|
|
Help: "The total number of HTTP requests.",
|
|
},
|
|
[]string{"status"},
|
|
)
|
|
|
|
prometheus.MustRegister(histogram)
|
|
prometheus.MustRegister(counter)
|
|
|
|
return &PrometheusMiddleware{
|
|
Histogram: histogram,
|
|
Counter: counter,
|
|
}
|
|
}
|
|
|
|
// Metrics godoc
|
|
// @Summary Prometheus metrics
|
|
// @Description returns HTTP requests duration and Go runtime metrics
|
|
// @Tags Kubernetes
|
|
// @Produce plain
|
|
// @Router /metrics [get]
|
|
// @Success 200 {string} string "OK"
|
|
func (p *PrometheusMiddleware) Handler(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
begin := time.Now()
|
|
interceptor := &interceptor{ResponseWriter: w, statusCode: http.StatusOK}
|
|
path := p.getRouteName(r)
|
|
next.ServeHTTP(interceptor.wrappedResponseWriter(), r)
|
|
var (
|
|
status = strconv.Itoa(interceptor.statusCode)
|
|
took = time.Since(begin)
|
|
)
|
|
p.Histogram.WithLabelValues(r.Method, path, status).Observe(took.Seconds())
|
|
p.Counter.WithLabelValues(status).Inc()
|
|
})
|
|
}
|
|
|
|
// converts gorilla mux routes from '/api/delay/{wait}' to 'api_delay_wait'
|
|
func (p *PrometheusMiddleware) getRouteName(r *http.Request) string {
|
|
if mux.CurrentRoute(r) != nil {
|
|
if name := mux.CurrentRoute(r).GetName(); len(name) > 0 {
|
|
return urlToLabel(name)
|
|
}
|
|
if path, err := mux.CurrentRoute(r).GetPathTemplate(); err == nil {
|
|
if len(path) > 0 {
|
|
return urlToLabel(path)
|
|
}
|
|
}
|
|
}
|
|
return urlToLabel(r.RequestURI)
|
|
}
|
|
|
|
var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9]+`)
|
|
|
|
// converts a URL path to a string compatible with Prometheus label value.
|
|
func urlToLabel(path string) string {
|
|
result := invalidChars.ReplaceAllString(path, "_")
|
|
result = strings.ToLower(strings.Trim(result, "_"))
|
|
if result == "" {
|
|
result = "root"
|
|
}
|
|
return result
|
|
}
|
|
|
|
type interceptor struct {
|
|
http.ResponseWriter
|
|
statusCode int
|
|
recorded bool
|
|
}
|
|
|
|
func (i *interceptor) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
hj, ok := i.ResponseWriter.(http.Hijacker)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("interceptor: can't cast parent ResponseWriter to Hijacker")
|
|
}
|
|
return hj.Hijack()
|
|
}
|
|
|
|
func (i *interceptor) WriteHeader(code int) {
|
|
if !i.recorded {
|
|
i.statusCode = code
|
|
i.recorded = true
|
|
}
|
|
i.ResponseWriter.WriteHeader(code)
|
|
}
|
|
|
|
// Returns a wrapped http.ResponseWriter that implements the same optional interfaces
|
|
// that the underlying ResponseWriter has.
|
|
// Handle every possible combination so that code that checks for the existence of each
|
|
// optional interface functions properly.
|
|
// Based on https://github.com/felixge/httpsnoop/blob/eadd4fad6aac69ae62379194fe0219f3dbc80fd3/wrap_generated_gteq_1.8.go#L66
|
|
func (i *interceptor) wrappedResponseWriter() http.ResponseWriter {
|
|
closeNotifier, isCloseNotifier := i.ResponseWriter.(http.CloseNotifier)
|
|
flush, isFlusher := i.ResponseWriter.(http.Flusher)
|
|
hijack, isHijacker := i.ResponseWriter.(http.Hijacker)
|
|
push, isPusher := i.ResponseWriter.(http.Pusher)
|
|
readFrom, isReaderFrom := i.ResponseWriter.(io.ReaderFrom)
|
|
|
|
switch {
|
|
case !isCloseNotifier && !isFlusher && !isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
}{i}
|
|
|
|
case isCloseNotifier && !isFlusher && !isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
}{i, closeNotifier}
|
|
|
|
case !isCloseNotifier && isFlusher && !isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
}{i, flush}
|
|
|
|
case !isCloseNotifier && !isFlusher && isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Hijacker
|
|
}{i, hijack}
|
|
|
|
case !isCloseNotifier && !isFlusher && !isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Pusher
|
|
}{i, push}
|
|
|
|
case !isCloseNotifier && !isFlusher && !isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
io.ReaderFrom
|
|
}{i, readFrom}
|
|
|
|
case isCloseNotifier && isFlusher && !isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
}{i, closeNotifier, flush}
|
|
|
|
case isCloseNotifier && !isFlusher && isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Hijacker
|
|
}{i, closeNotifier, hijack}
|
|
|
|
case isCloseNotifier && !isFlusher && !isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Pusher
|
|
}{i, closeNotifier, push}
|
|
|
|
case isCloseNotifier && !isFlusher && !isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, readFrom}
|
|
|
|
case !isCloseNotifier && isFlusher && isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
http.Hijacker
|
|
}{i, flush, hijack}
|
|
|
|
case !isCloseNotifier && isFlusher && !isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
http.Pusher
|
|
}{i, flush, push}
|
|
|
|
case !isCloseNotifier && isFlusher && !isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
io.ReaderFrom
|
|
}{i, flush, readFrom}
|
|
|
|
case !isCloseNotifier && !isFlusher && isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Hijacker
|
|
http.Pusher
|
|
}{i, hijack, push}
|
|
|
|
case !isCloseNotifier && !isFlusher && isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
}{i, hijack, readFrom}
|
|
|
|
case !isCloseNotifier && !isFlusher && !isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, push, readFrom}
|
|
|
|
case isCloseNotifier && isFlusher && isHijacker && !isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
http.Hijacker
|
|
}{i, closeNotifier, flush, hijack}
|
|
|
|
case isCloseNotifier && isFlusher && !isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
http.Pusher
|
|
}{i, closeNotifier, flush, push}
|
|
|
|
case isCloseNotifier && isFlusher && !isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, flush, readFrom}
|
|
|
|
case isCloseNotifier && !isFlusher && isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Hijacker
|
|
http.Pusher
|
|
}{i, closeNotifier, hijack, push}
|
|
|
|
case isCloseNotifier && !isFlusher && isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, hijack, readFrom}
|
|
|
|
case isCloseNotifier && !isFlusher && !isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, push, readFrom}
|
|
|
|
case !isCloseNotifier && isFlusher && isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
http.Hijacker
|
|
http.Pusher
|
|
}{i, flush, hijack, push}
|
|
|
|
case !isCloseNotifier && isFlusher && isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
}{i, flush, hijack, readFrom}
|
|
|
|
case !isCloseNotifier && isFlusher && !isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, flush, push, readFrom}
|
|
|
|
case !isCloseNotifier && !isFlusher && isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, hijack, push, readFrom}
|
|
|
|
case isCloseNotifier && isFlusher && isHijacker && isPusher && !isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
http.Hijacker
|
|
http.Pusher
|
|
}{i, closeNotifier, flush, hijack, push}
|
|
|
|
case isCloseNotifier && isFlusher && isHijacker && !isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
http.Hijacker
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, flush, hijack, readFrom}
|
|
|
|
case isCloseNotifier && isFlusher && !isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, flush, push, readFrom}
|
|
|
|
case isCloseNotifier && !isFlusher && isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, hijack, push, readFrom}
|
|
|
|
case !isCloseNotifier && isFlusher && isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.Flusher
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, flush, hijack, push, readFrom}
|
|
|
|
case isCloseNotifier && isFlusher && isHijacker && isPusher && isReaderFrom:
|
|
return struct {
|
|
http.ResponseWriter
|
|
http.CloseNotifier
|
|
http.Flusher
|
|
http.Hijacker
|
|
http.Pusher
|
|
io.ReaderFrom
|
|
}{i, closeNotifier, flush, hijack, push, readFrom}
|
|
|
|
default:
|
|
return struct {
|
|
http.ResponseWriter
|
|
}{i}
|
|
}
|
|
}
|