github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/cmd/state-svc/internal/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/99designs/gqlgen/graphql"
    11  	"github.com/99designs/gqlgen/graphql/handler"
    12  	"github.com/99designs/gqlgen/graphql/handler/extension"
    13  	"github.com/99designs/gqlgen/graphql/handler/lru"
    14  	"github.com/99designs/gqlgen/graphql/handler/transport"
    15  	"github.com/ActiveState/cli/internal/analytics/client/sync"
    16  	"github.com/ActiveState/cli/pkg/platform/authentication"
    17  	"github.com/labstack/echo/v4"
    18  	"github.com/labstack/echo/v4/middleware"
    19  	"github.com/vektah/gqlparser/v2/gqlerror"
    20  
    21  	"github.com/ActiveState/cli/cmd/state-svc/internal/resolver"
    22  	genserver "github.com/ActiveState/cli/cmd/state-svc/internal/server/generated"
    23  	"github.com/ActiveState/cli/internal/analytics/constants"
    24  	"github.com/ActiveState/cli/internal/config"
    25  	"github.com/ActiveState/cli/internal/errs"
    26  	"github.com/ActiveState/cli/internal/logging"
    27  )
    28  
    29  type Server struct {
    30  	resolver    *resolver.Resolver
    31  	graphServer *handler.Server
    32  	listener    net.Listener
    33  	httpServer  *echo.Echo
    34  	port        int
    35  	analytics   *sync.Client
    36  }
    37  
    38  func New(cfg *config.Instance, an *sync.Client, auth *authentication.Auth) (*Server, error) {
    39  	listener, err := net.Listen("tcp", "127.0.0.1:0")
    40  	if err != nil {
    41  		return nil, errs.Wrap(err, "Failed to listen")
    42  	}
    43  
    44  	resolver, err := resolver.New(cfg, an, auth)
    45  	if err != nil {
    46  		return nil, errs.Wrap(err, "Failed to initialize new resolver")
    47  	}
    48  
    49  	s := &Server{resolver: resolver, analytics: an}
    50  
    51  	s.graphServer = newGraphServer(s.resolver)
    52  	s.listener = listener
    53  	s.httpServer = newHTTPServer(listener)
    54  
    55  	s.setupRouting()
    56  
    57  	_, portEncoded, err := net.SplitHostPort(s.listener.Addr().String())
    58  	if err != nil {
    59  		return nil, errs.Wrap(err, "Could not parse port from address: %v", s.listener.Addr().String())
    60  	}
    61  	s.port, err = strconv.Atoi(portEncoded)
    62  	if err != nil {
    63  		return nil, errs.Wrap(err, "Could not convert port: %v", portEncoded)
    64  	}
    65  
    66  	return s, nil
    67  }
    68  
    69  func (s *Server) Port() int {
    70  	return s.port
    71  }
    72  
    73  func (s *Server) Resolver() *resolver.Resolver {
    74  	return s.resolver
    75  }
    76  
    77  func (s *Server) Start() error {
    78  	s.analytics.Event(constants.CatStateSvc, "start")
    79  	err := s.httpServer.Start(s.listener.Addr().String())
    80  	if err != nil {
    81  		s.analytics.Event(constants.CatStateSvc, "start-failure")
    82  	}
    83  	return err
    84  }
    85  
    86  func (s *Server) Shutdown() error {
    87  	s.analytics.Event(constants.CatStateSvc, "shutdown")
    88  	logging.Debug("shutting down server")
    89  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    90  	defer cancel()
    91  	if err := s.httpServer.Shutdown(ctx); err != nil {
    92  		return errs.Wrap(err, "Could not close http server")
    93  	}
    94  	if err := s.resolver.Close(); err != nil {
    95  		return errs.Wrap(err, "Could not close resolver")
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  func newGraphServer(r *resolver.Resolver) *handler.Server {
   102  	graphServer := handler.NewDefaultServer(genserver.NewExecutableSchema(genserver.Config{Resolvers: r}))
   103  	graphServer.SetErrorPresenter(func(ctx context.Context, err error) *gqlerror.Error {
   104  		errMsg := errs.JoinMessage(err)
   105  		logging.Warning("graphql server returning error: %s", errMsg)
   106  		var gqlErr *gqlerror.Error
   107  		if !errors.As(err, &gqlErr) {
   108  			gqlErr = gqlerror.WrapPath(graphql.GetPath(ctx), err)
   109  		}
   110  		gqlErr.Message = errMsg
   111  		return gqlErr
   112  	})
   113  	graphServer.AddTransport(&transport.Websocket{})
   114  	graphServer.SetQueryCache(lru.New(1000))
   115  	graphServer.Use(extension.Introspection{})
   116  	graphServer.Use(extension.AutomaticPersistedQuery{
   117  		Cache: lru.New(100),
   118  	})
   119  	return graphServer
   120  }
   121  
   122  func newHTTPServer(listener net.Listener) *echo.Echo {
   123  	httpServer := echo.New()
   124  	httpServer.Listener = listener
   125  	httpServer.Use(middleware.Logger())
   126  	httpServer.Use(middleware.Recover())
   127  	return httpServer
   128  }