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 }