package server import ( "context" "crypto/tls" "crypto/x509" "fmt" "html/template" "io/fs" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" ) type Server struct { http.Server } func NewServer(bindAddr, caCert string, views, assets fs.FS) *Server { router := gin.Default() if views != nil { templates := template.New("") if err := fs.WalkDir(views, ".", func(path string, info fs.DirEntry, e error) error { if e != nil { return fmt.Errorf("failed accessing path: %w", e) } if info.IsDir() { return nil } file, err := fs.ReadFile(views, path) if err != nil { return fmt.Errorf("failed reading file: %w", err) } if _, err := templates.Parse(string(file)); err != nil { return fmt.Errorf("failed parsing template: %w", err) } return nil }); err != nil { log.Fatalf("failed creating views resources: %v", err) } router.SetHTMLTemplate(templates) } if assets != nil { router.StaticFS("/assets", http.FS(assets)) } var tlsConfig *tls.Config if len(caCert) > 0 { pool := x509.NewCertPool() data, err := os.ReadFile(caCert) if err != nil { log.Fatalf("failed opening trusted SSL Certificate Authorities file: %v", err) } if !pool.AppendCertsFromPEM(data) { log.Fatal("failed parsing CA certificate") } tlsConfig = &tls.Config{ RootCAs: pool, ClientAuth: tls.RequireAndVerifyClientCert, MinVersion: tls.VersionTLS12, } } return &Server{ Server: http.Server{ Addr: bindAddr, Handler: router, TLSConfig: tlsConfig, }, } } func (srv *Server) Start(clientCert, clientKey string, gracefulShutdownTimeout time.Duration) { go func() { var fn string var err error if len(clientCert) > 0 && len(clientKey) > 0 { fn = "ListenAndServeTLS" err = srv.ListenAndServeTLS(clientCert, clientKey) } else { fn = "ListenAndServe" err = srv.ListenAndServe() } if err != http.ErrServerClosed { log.Fatalf("failed listenning and serving (%v): %v", fn, err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("server forced to shutdown: %v", err) } log.Println("server exiting...") }