package server import ( "context" "crypto/tls" "log/slog" "net/http" "os" "os/signal" "time" "github.com/go-co-op/gocron/v2" ) // Server represents an HTTP server. type Server struct { srv *http.Server logger *slog.Logger cron gocron.Scheduler cancel context.CancelFunc disableTls bool tlsCertFilename string tlsKeyFilename string encryptionKey string } // New creates a new server with the given logger, address and options. func New(logger *slog.Logger, cron gocron.Scheduler, cancel context.CancelFunc, addr string, opts ...Option) *Server { // Set some options for TLS tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, PreferServerCipherSuites: true, InsecureSkipVerify: true, CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_CBC_SHA, }, } srv := &http.Server{ Addr: addr, WriteTimeout: 120 * time.Second, ReadTimeout: 30 * time.Second, TLSConfig: tlsConfig, TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } // Set the initial server values server := &Server{ srv: srv, logger: logger, cron: cron, cancel: cancel, } // Apply any options for _, opt := range opts { opt(server) } return server } // Option represents a server option. type Option func(*Server) // WithWriteTimeout sets the write timeout. func WithWriteTimeout(timeout time.Duration) Option { return func(s *Server) { s.srv.WriteTimeout = timeout } } // WithReadTimeout sets the read timeout. func WithReadTimeout(timeout time.Duration) Option { return func(s *Server) { s.srv.ReadTimeout = timeout } } // WithRouter sets the handler. func WithRouter(handler http.Handler) Option { return func(s *Server) { s.srv.Handler = handler } } // SetKey sets the encryption key we use when generating secrets func SetKey(key string) Option { return func(s *Server) { s.encryptionKey = key } } // SetTls sets the disable tls value func SetTls(disableTls bool) Option { return func(s *Server) { s.disableTls = disableTls } } // SetCertificate sets the path to the certificate used for TLS, in PEM format func SetCertificate(tlsCertFilename string) Option { return func(s *Server) { //fmt.Printf("Setting tlsCertFilename to '%s'\n", tlsCertFilename) s.tlsCertFilename = tlsCertFilename } } // SetPrivateKey sets the path to the private key used for TLS, in PEM format func SetPrivateKey(tlsKeyFilename string) Option { return func(s *Server) { s.tlsKeyFilename = tlsKeyFilename } } // StartAndWait starts the server and waits for a signal to shut down. func (s *Server) StartAndWait() { s.Start() s.GracefulShutdown() } // Start starts the server. func (s *Server) Start() { go func() { if s.disableTls { s.logger.Info("starting server", "port", s.srv.Addr) if err := s.srv.ListenAndServe(); err != nil { s.logger.Error("failed to start server", "error", err) os.Exit(1) } } else { s.logger.Info("starting TLS server", "port", s.srv.Addr, "cert", s.tlsCertFilename, "key", s.tlsKeyFilename) if err := s.srv.ListenAndServeTLS(s.tlsCertFilename, s.tlsKeyFilename); err != nil && err != http.ErrServerClosed { s.logger.Error("failed to start server", "error", err) os.Exit(1) } } }() } // GracefulShutdown shuts down the server gracefully. func (s *Server) GracefulShutdown() { c := make(chan os.Signal, 1) // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. signal.Notify(c, os.Interrupt) // Block until we receive our signal. <-c // Create a deadline to wait for. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Doesn't block if no connections, but will otherwise wait // until the timeout deadline. _ = s.srv.Shutdown(ctx) s.logger.Info("runing cron shutdown") err := s.cron.Shutdown() if err != nil { s.logger.Error("error shutting cron", "error", err) } s.logger.Info("runing cancel") s.cancel() // Optionally, you could run srv.Shutdown in a goroutine and block on // <-ctx.Done() if your application should wait for other services // to finalize based on context cancellation. s.logger.Info("shutting down") //os.Exit(0) }