server.go 4.8 KB

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