diff --git a/server/rpc/handlers/module.go b/server/rpc/handlers/module.go new file mode 100644 index 00000000..21d92a09 --- /dev/null +++ b/server/rpc/handlers/module.go @@ -0,0 +1,66 @@ +package handlers + +import ( + "net/http" + + "connectrpc.com/connect" + "go.uber.org/fx" + + "github.com/crlssn/getstronger/server/gen/proto/api/v1/apiv1connect" + handlers "github.com/crlssn/getstronger/server/rpc/handlers/v1" +) + +func Module() fx.Option { + return fx.Options( + fx.Provide( + BuildHandlers, + handlers.NewAuthHandler, + handlers.NewFeedHandler, + handlers.NewUserHandler, + handlers.NewRoutineHandler, + handlers.NewWorkoutHandler, + handlers.NewExerciseHandler, + handlers.NewNotificationHandler, + ), + ) +} + +type BuildHandlersParams struct { + fx.In + + Auth apiv1connect.AuthServiceHandler + Feed apiv1connect.FeedServiceHandler + User apiv1connect.UserServiceHandler + Routine apiv1connect.RoutineServiceHandler + Workout apiv1connect.WorkoutServiceHandler + Exercise apiv1connect.ExerciseServiceHandler + Notification apiv1connect.NotificationServiceHandler +} + +type HandlerFunc func(opts ...connect.HandlerOption) (string, http.Handler) + +func BuildHandlers(p BuildHandlersParams) []HandlerFunc { + return []HandlerFunc{ + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewAuthServiceHandler(p.Auth, opts...) + }, + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewFeedServiceHandler(p.Feed, opts...) + }, + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewUserServiceHandler(p.User, opts...) + }, + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewRoutineServiceHandler(p.Routine, opts...) + }, + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewWorkoutServiceHandler(p.Workout, opts...) + }, + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewExerciseServiceHandler(p.Exercise, opts...) + }, + func(opts ...connect.HandlerOption) (string, http.Handler) { + return apiv1connect.NewNotificationServiceHandler(p.Notification, opts...) + }, + } +} diff --git a/server/rpc/server/module.go b/server/rpc/server/module.go index fcded58f..14c24d0c 100644 --- a/server/rpc/server/module.go +++ b/server/rpc/server/module.go @@ -1,134 +1,27 @@ package server import ( - "context" - "crypto/tls" - "errors" - "fmt" - "log" - "net/http" - "time" - - "connectrpc.com/connect" "go.uber.org/fx" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" - "github.com/crlssn/getstronger/server/config" - "github.com/crlssn/getstronger/server/gen/proto/api/v1/apiv1connect" - handlers "github.com/crlssn/getstronger/server/rpc/handlers/v1" + "github.com/crlssn/getstronger/server/rpc/handlers" "github.com/crlssn/getstronger/server/rpc/interceptors" "github.com/crlssn/getstronger/server/rpc/middlewares" - "github.com/crlssn/getstronger/server/stream" ) func Module() fx.Option { return fx.Module("rpc", fx.Options( - interceptors.Module(), fx.Provide( - registerHandlers, - handlers.NewAuthHandler, - handlers.NewFeedHandler, - handlers.NewUserHandler, - handlers.NewRoutineHandler, - handlers.NewWorkoutHandler, - handlers.NewExerciseHandler, - handlers.NewNotificationHandler, + NewServer, + NewMultiplexer, middlewares.New, ), - fx.Invoke( - startServer, - ), + handlers.Module(), + interceptors.Module(), + fx.Invoke(func(lc fx.Lifecycle, s *Server) { + lc.Append(fx.Hook{ + OnStart: s.ListenAndServe, + OnStop: s.server.Shutdown, + }) + }), )) } - -type Handlers struct { - fx.In - - Auth apiv1connect.AuthServiceHandler - Feed apiv1connect.FeedServiceHandler - User apiv1connect.UserServiceHandler - Routine apiv1connect.RoutineServiceHandler - Workout apiv1connect.WorkoutServiceHandler - Exercise apiv1connect.ExerciseServiceHandler - Notification apiv1connect.NotificationServiceHandler -} - -func registerHandlers(p Handlers, o []connect.HandlerOption, m *middlewares.Middleware) *http.ServeMux { - handlers := []func(opts ...connect.HandlerOption) (string, http.Handler){ - func(opts ...connect.HandlerOption) (string, http.Handler) { - return apiv1connect.NewAuthServiceHandler(p.Auth, opts...) - }, - func(opts ...connect.HandlerOption) (string, http.Handler) { - return apiv1connect.NewFeedServiceHandler(p.Feed, opts...) - }, - func(opts ...connect.HandlerOption) (string, http.Handler) { - return apiv1connect.NewUserServiceHandler(p.User, opts...) - }, - func(opts ...connect.HandlerOption) (string, http.Handler) { - return apiv1connect.NewRoutineServiceHandler(p.Routine, opts...) - }, - func(opts ...connect.HandlerOption) (string, http.Handler) { - return apiv1connect.NewWorkoutServiceHandler(p.Workout, opts...) - }, - func(opts ...connect.HandlerOption) (string, http.Handler) { - return apiv1connect.NewExerciseServiceHandler(p.Exercise, opts...) - }, - func(opts ...connect.HandlerOption) (string, http.Handler) { - return apiv1connect.NewNotificationServiceHandler(p.Notification, opts...) - }, - } - - mux := http.NewServeMux() - for _, h := range handlers { - path, handler := h(o...) - mux.Handle(path, m.Register(handler)) - } - - return mux -} - -const ( - readTimeout = 10 * time.Second - idleTimeout = 120 * time.Second -) - -func startServer(l fx.Lifecycle, c *config.Config, m *http.ServeMux, conn *stream.Conn) { - s := &http.Server{ - Addr: fmt.Sprintf(":%s", c.Server.Port), - Handler: h2c.NewHandler(m, &http2.Server{}), - ReadTimeout: readTimeout, - WriteTimeout: 0, - IdleTimeout: idleTimeout, - TLSConfig: &tls.Config{ - MinVersion: tls.VersionTLS12, - }, - } - - s.RegisterOnShutdown(conn.Cancel) - - l.Append(fx.Hook{ - OnStart: func(_ context.Context) error { - go func() { - if err := listenAndServe(s, c.Server.CertPath, c.Server.KeyPath); err != nil { - if errors.Is(err, http.ErrServerClosed) { - return - } - log.Fatalf("listen and serve: %v", err) - } - }() - return nil - }, - OnStop: func(ctx context.Context) error { - return s.Shutdown(ctx) - }, - }) -} - -func listenAndServe(s *http.Server, certPath, keyPath string) error { - if certPath == "" && keyPath == "" { - return s.ListenAndServe() //nolint:wrapcheck - } - - return s.ListenAndServeTLS(certPath, keyPath) //nolint:wrapcheck -} diff --git a/server/rpc/server/server.go b/server/rpc/server/server.go new file mode 100644 index 00000000..c7d721d3 --- /dev/null +++ b/server/rpc/server/server.go @@ -0,0 +1,90 @@ +package server + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/http" + "time" + + "connectrpc.com/connect" + "go.uber.org/fx" + "go.uber.org/zap" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + + "github.com/crlssn/getstronger/server/config" + "github.com/crlssn/getstronger/server/rpc/handlers" + "github.com/crlssn/getstronger/server/rpc/middlewares" + "github.com/crlssn/getstronger/server/stream" +) + +type Server struct { + log *zap.Logger + conn *stream.Conn + server *http.Server + keyPath string + certPath string +} + +type Params struct { + fx.In + + Log *zap.Logger + Mux *http.ServeMux + Conn *stream.Conn + Config *config.Config +} + +func NewServer(p Params) *Server { + return &Server{ + conn: p.Conn, + keyPath: p.Config.Server.KeyPath, + certPath: p.Config.Server.CertPath, + server: &http.Server{ + Addr: fmt.Sprintf(":%s", p.Config.Server.Port), + Handler: h2c.NewHandler(p.Mux, &http2.Server{}), + ReadTimeout: 10 * time.Second, //nolint:mnd + WriteTimeout: 0, + IdleTimeout: 120 * time.Second, //nolint:mnd + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + }, + }, + } +} + +func (s *Server) ListenAndServe(_ context.Context) error { + go func() { + if err := s.listenAndServe(); err != nil { + if errors.Is(err, http.ErrServerClosed) { + return + } + + s.log.Fatal("server: listen and serve", zap.Error(err)) + } + }() + + return nil +} + +func (s *Server) listenAndServe() error { + s.server.RegisterOnShutdown(s.conn.Cancel) + + if s.certPath == "" && s.keyPath == "" { + return s.server.ListenAndServe() //nolint:wrapcheck + } + + return s.server.ListenAndServeTLS(s.certPath, s.keyPath) //nolint:wrapcheck +} + +func NewMultiplexer(f []handlers.HandlerFunc, o []connect.HandlerOption, m *middlewares.Middleware) *http.ServeMux { + mux := http.NewServeMux() + for _, h := range f { + path, handler := h(o...) + mux.Handle(path, m.Register(handler)) + } + + return mux +}