Files
podinfo/pkg/api/server.go
2018-08-20 17:03:07 +03:00

171 lines
5.0 KiB
Go

package api
import (
"context"
"fmt"
"net/http"
_ "net/http/pprof"
"strings"
"sync/atomic"
"time"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/stefanprodan/k8s-podinfo/pkg/fscache"
"go.uber.org/zap"
)
var (
healthy int32
ready int32
watcher *fscache.Watcher
)
type Config struct {
HttpClientTimeout time.Duration
HttpServerTimeout time.Duration
HttpServerShutdownTimeout time.Duration
BackendURL string
UIMessage string
UIColor string
UIPath string
DataPath string
ConfigPath string
Port string
Hostname string
}
type Server struct {
router *mux.Router
logger *zap.Logger
config *Config
}
func NewServer(config *Config, logger *zap.Logger) (*Server, error) {
srv := &Server{
router: mux.NewRouter(),
logger: logger,
config: config,
}
return srv, nil
}
func (s *Server) registerHandlers() {
s.router.Handle("/metrics", promhttp.Handler())
s.router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)
s.router.HandleFunc("/", s.indexHandler).HeadersRegexp("User-Agent", "^Mozilla.*").Methods("GET")
s.router.HandleFunc("/", s.infoHandler).Methods("GET")
s.router.HandleFunc("/version", s.versionHandler).Methods("GET")
s.router.HandleFunc("/echo", s.echoHandler).Methods("POST")
s.router.HandleFunc("/headers", s.echoHeadersHandler).Methods("GET")
s.router.HandleFunc("/delay/{wait:[0-9]+}", s.delayHandler).Methods("GET")
s.router.HandleFunc("/healthz", s.healthzHandler).Methods("GET")
s.router.HandleFunc("/readyz", s.readyzHandler).Methods("GET")
s.router.HandleFunc("/readyz/enable", s.enableReadyHandler).Methods("POST")
s.router.HandleFunc("/readyz/disable", s.disableReadyHandler).Methods("POST")
s.router.HandleFunc("/panic", s.panicHandler).Methods("GET")
s.router.HandleFunc("/status/{code:[0-9]+}", s.statusHandler).Methods("GET", "POST", "PUT")
s.router.HandleFunc("/store", s.storeWriteHandler).Methods("POST")
s.router.HandleFunc("/store/{hash}", s.storeReadHandler).Methods("GET")
s.router.HandleFunc("/configs", s.configReadHandler).Methods("GET")
s.router.HandleFunc("/api/info", s.infoHandler).Methods("GET")
s.router.HandleFunc("/api/echo", s.echoHandler).Methods("POST")
}
func (s *Server) registerMiddlewares() {
prom := NewPrometheusMiddleware()
s.router.Use(prom.Handler)
zapLog := NewLoggingMiddleware(s.logger)
s.router.Use(zapLog.Handler)
s.router.Use(versionMiddleware)
}
func (s *Server) ListenAndServe(stopCh <-chan struct{}) {
s.registerHandlers()
s.registerMiddlewares()
srv := &http.Server{
Addr: ":" + s.config.Port,
WriteTimeout: s.config.HttpServerTimeout,
ReadTimeout: s.config.HttpServerTimeout,
IdleTimeout: 2 * s.config.HttpServerTimeout,
Handler: s.router,
}
//s.printRoutes()
// load configs in memory and start watching for changes in the config dir
if len(s.config.ConfigPath) > 0 {
var err error
watcher, err = fscache.NewWatch(s.config.ConfigPath)
if err != nil {
s.logger.Error("config watch error", zap.Error(err), zap.String("path", s.config.ConfigPath))
} else {
watcher.Watch()
}
}
// run server in background
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
s.logger.Fatal("HTTP server crashed", zap.Error(err))
}
}()
// signal Kubernetes the server is ready to receive traffic
atomic.StoreInt32(&healthy, 1)
atomic.StoreInt32(&ready, 1)
// wait for SIGTERM or SIGINT
<-stopCh
ctx, cancel := context.WithTimeout(context.Background(), s.config.HttpServerShutdownTimeout)
defer cancel()
// all calls to /healthz and /readyz will fail from now on
atomic.StoreInt32(&healthy, 0)
atomic.StoreInt32(&ready, 0)
s.logger.Info("Shutting down HTTP server", zap.Duration("timeout", s.config.HttpServerShutdownTimeout))
// wait for Kubernetes readiness probe
// to remove this instance from the load balancer
// the readiness check interval must lower than the timeout
//time.Sleep(s.config.HttpServerShutdownTimeout)
// attempt graceful shutdown
if err := srv.Shutdown(ctx); err != nil {
s.logger.Warn("HTTP server graceful shutdown failed", zap.Error(err))
} else {
s.logger.Info("HTTP server stopped")
}
}
func (s *Server) printRoutes() {
s.router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
pathTemplate, err := route.GetPathTemplate()
if err == nil {
fmt.Println("ROUTE:", pathTemplate)
}
pathRegexp, err := route.GetPathRegexp()
if err == nil {
fmt.Println("Path regexp:", pathRegexp)
}
queriesTemplates, err := route.GetQueriesTemplates()
if err == nil {
fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
}
queriesRegexps, err := route.GetQueriesRegexp()
if err == nil {
fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
}
methods, err := route.GetMethods()
if err == nil {
fmt.Println("Methods:", strings.Join(methods, ","))
}
fmt.Println()
return nil
})
}