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  }