server.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package server
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "math"
  7. "net"
  8. "net/http"
  9. "time"
  10. "github.com/google/uuid"
  11. grpcrecovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
  12. "github.com/labstack/echo/v4"
  13. "github.com/labstack/echo/v4/middleware"
  14. "github.com/pkg/errors"
  15. "github.com/soheilhy/cmux"
  16. "google.golang.org/grpc"
  17. storepb "github.com/usememos/memos/proto/gen/store"
  18. "github.com/usememos/memos/server/profile"
  19. apiv1 "github.com/usememos/memos/server/router/api/v1"
  20. "github.com/usememos/memos/server/router/frontend"
  21. "github.com/usememos/memos/server/router/rss"
  22. "github.com/usememos/memos/server/runner/memoproperty"
  23. "github.com/usememos/memos/server/runner/s3presign"
  24. "github.com/usememos/memos/server/runner/version"
  25. "github.com/usememos/memos/store"
  26. )
  27. type Server struct {
  28. Secret string
  29. Profile *profile.Profile
  30. Store *store.Store
  31. echoServer *echo.Echo
  32. grpcServer *grpc.Server
  33. }
  34. func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store) (*Server, error) {
  35. s := &Server{
  36. Store: store,
  37. Profile: profile,
  38. }
  39. echoServer := echo.New()
  40. echoServer.Debug = true
  41. echoServer.HideBanner = true
  42. echoServer.HidePort = true
  43. echoServer.Use(middleware.Recover())
  44. s.echoServer = echoServer
  45. workspaceBasicSetting, err := s.getOrUpsertWorkspaceBasicSetting(ctx)
  46. if err != nil {
  47. return nil, errors.Wrap(err, "failed to get workspace basic setting")
  48. }
  49. secret := "usememos"
  50. if profile.Mode == "prod" {
  51. secret = workspaceBasicSetting.SecretKey
  52. }
  53. s.Secret = secret
  54. // Register healthz endpoint.
  55. echoServer.GET("/healthz", func(c echo.Context) error {
  56. return c.String(http.StatusOK, "Service ready.")
  57. })
  58. // Serve frontend resources.
  59. frontend.NewFrontendService(profile, store).Serve(ctx, echoServer)
  60. rootGroup := echoServer.Group("")
  61. // Create and register RSS routes.
  62. rss.NewRSSService(s.Profile, s.Store).RegisterRoutes(rootGroup)
  63. grpcServer := grpc.NewServer(
  64. // Override the maximum receiving message size to math.MaxInt32 for uploading large resources.
  65. grpc.MaxRecvMsgSize(math.MaxInt32),
  66. grpc.ChainUnaryInterceptor(
  67. apiv1.NewLoggerInterceptor().LoggerInterceptor,
  68. grpcrecovery.UnaryServerInterceptor(),
  69. apiv1.NewGRPCAuthInterceptor(store, secret).AuthenticationInterceptor,
  70. ))
  71. s.grpcServer = grpcServer
  72. apiV1Service := apiv1.NewAPIV1Service(s.Secret, profile, store, grpcServer)
  73. // Register gRPC gateway as api v1.
  74. if err := apiV1Service.RegisterGateway(ctx, echoServer); err != nil {
  75. return nil, errors.Wrap(err, "failed to register gRPC gateway")
  76. }
  77. return s, nil
  78. }
  79. func (s *Server) Start(ctx context.Context) error {
  80. address := fmt.Sprintf("%s:%d", s.Profile.Addr, s.Profile.Port)
  81. listener, err := net.Listen("tcp", address)
  82. if err != nil {
  83. return errors.Wrap(err, "failed to listen")
  84. }
  85. muxServer := cmux.New(listener)
  86. go func() {
  87. grpcListener := muxServer.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
  88. if err := s.grpcServer.Serve(grpcListener); err != nil {
  89. slog.Error("failed to serve gRPC", "error", err)
  90. }
  91. }()
  92. go func() {
  93. httpListener := muxServer.Match(cmux.HTTP1Fast(http.MethodPatch))
  94. s.echoServer.Listener = httpListener
  95. if err := s.echoServer.Start(address); err != nil {
  96. slog.Error("failed to start echo server", "error", err)
  97. }
  98. }()
  99. go func() {
  100. if err := muxServer.Serve(); err != nil {
  101. slog.Error("mux server listen error", "error", err)
  102. }
  103. }()
  104. s.StartBackgroundRunners(ctx)
  105. return nil
  106. }
  107. func (s *Server) Shutdown(ctx context.Context) {
  108. ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
  109. defer cancel()
  110. // Shutdown echo server.
  111. if err := s.echoServer.Shutdown(ctx); err != nil {
  112. slog.Error("failed to shutdown server", slog.String("error", err.Error()))
  113. }
  114. // Close database connection.
  115. if err := s.Store.Close(); err != nil {
  116. slog.Error("failed to close database", slog.String("error", err.Error()))
  117. }
  118. slog.Info("memos stopped properly")
  119. }
  120. func (s *Server) StartBackgroundRunners(ctx context.Context) {
  121. s3presignRunner := s3presign.NewRunner(s.Store)
  122. s3presignRunner.RunOnce(ctx)
  123. versionRunner := version.NewRunner(s.Store, s.Profile)
  124. versionRunner.RunOnce(ctx)
  125. memopropertyRunner := memoproperty.NewRunner(s.Store)
  126. memopropertyRunner.RunOnce(ctx)
  127. go s3presignRunner.Run(ctx)
  128. go versionRunner.Run(ctx)
  129. go memopropertyRunner.Run(ctx)
  130. }
  131. func (s *Server) getOrUpsertWorkspaceBasicSetting(ctx context.Context) (*storepb.WorkspaceBasicSetting, error) {
  132. workspaceBasicSetting, err := s.Store.GetWorkspaceBasicSetting(ctx)
  133. if err != nil {
  134. return nil, errors.Wrap(err, "failed to get workspace basic setting")
  135. }
  136. modified := false
  137. if workspaceBasicSetting.SecretKey == "" {
  138. workspaceBasicSetting.SecretKey = uuid.NewString()
  139. modified = true
  140. }
  141. if modified {
  142. workspaceSetting, err := s.Store.UpsertWorkspaceSetting(ctx, &storepb.WorkspaceSetting{
  143. Key: storepb.WorkspaceSettingKey_BASIC,
  144. Value: &storepb.WorkspaceSetting_BasicSetting{BasicSetting: workspaceBasicSetting},
  145. })
  146. if err != nil {
  147. return nil, errors.Wrap(err, "failed to upsert workspace setting")
  148. }
  149. workspaceBasicSetting = workspaceSetting.GetBasicSetting()
  150. }
  151. return workspaceBasicSetting, nil
  152. }