github.com/kubeshop/testkube@v1.17.23/pkg/server/httpserver.go (about) 1 package server 2 3 import ( 4 "context" 5 "encoding/json" 6 "net" 7 8 "github.com/gofiber/adaptor/v2" 9 "github.com/gofiber/fiber/v2" 10 "github.com/gofiber/fiber/v2/middleware/pprof" 11 "github.com/prometheus/client_golang/prometheus/promhttp" 12 "go.uber.org/zap" 13 14 "github.com/kubeshop/testkube/pkg/log" 15 "github.com/kubeshop/testkube/pkg/problem" 16 ) 17 18 // NewServer returns new HTTP server instance, initializes logger and metrics 19 func NewServer(config Config) HTTPServer { 20 config.Http.DisableStartupMessage = true 21 22 s := HTTPServer{ 23 Mux: fiber.New(config.Http), 24 Log: log.DefaultLogger, 25 Config: config, 26 } 27 28 s.Init() 29 return s 30 } 31 32 // HTTPServer represents basic REST HTTP server abstarction 33 type HTTPServer struct { 34 Mux *fiber.App 35 Log *zap.SugaredLogger 36 Routes fiber.Router 37 Config Config 38 } 39 40 // Init initializes router and setting up basic routes for health and metrics 41 func (s *HTTPServer) Init() { 42 43 // global log for requests 44 s.Mux.Use(func(c *fiber.Ctx) error { 45 s.Log.Debugw("request", "method", string(c.Request().Header.Method()), "path", c.Request().URI().String()) 46 return c.Next() 47 }) 48 49 s.Mux.Use(pprof.New()) 50 51 // server generic endpoints 52 s.Mux.Get("/health", s.HealthEndpoint()) 53 s.Mux.Get("/metrics", adaptor.HTTPHandler(promhttp.Handler())) 54 55 // v1 API 56 v1 := s.Mux.Group("/v1") 57 v1.Static("/api-docs", "./api/v1") 58 59 s.Routes = v1 60 61 } 62 63 // Warn writes RFC-7807 json problem to response 64 func (s *HTTPServer) Warn(c *fiber.Ctx, status int, err error, context ...interface{}) error { 65 c.Status(status) 66 c.Response().Header.Set("Content-Type", "application/problem+json") 67 s.Log.Warnw(err.Error(), "status", status) 68 pr := problem.New(status, s.getProblemMessage(err, context)) 69 return c.JSON(pr) 70 } 71 72 // Error writes RFC-7807 json problem to response 73 func (s *HTTPServer) Error(c *fiber.Ctx, status int, err error, context ...interface{}) error { 74 c.Status(status) 75 c.Response().Header.Set("Content-Type", "application/problem+json") 76 s.Log.Errorw(err.Error(), "status", status) 77 pr := problem.New(status, s.getProblemMessage(err, context)) 78 return c.JSON(pr) 79 } 80 81 // getProblemMessage creates new JSON based problem message and returns it as string 82 func (s *HTTPServer) getProblemMessage(err error, context ...interface{}) string { 83 message := err.Error() 84 if len(context) > 0 { 85 b, err := json.Marshal(context[0]) 86 if err == nil { 87 message += ", context: " + string(b) 88 } 89 } 90 91 return message 92 } 93 94 // Run starts listening for incoming connetions 95 func (s *HTTPServer) Run(ctx context.Context) error { 96 l, err := net.Listen("tcp", s.Config.Addr()) 97 if err != nil { 98 return err 99 } 100 // this function listens for finished context and calls graceful shutdown on the API server 101 go func() { 102 <-ctx.Done() 103 s.Log.Infof("shutting down Testkube API server") 104 _ = s.Mux.Shutdown() 105 }() 106 return s.Mux.Listener(l) 107 }