server.go 5.0 KB

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