package main import ( "context" "errors" "fmt" "net/http" "os" "os/signal" "sync" "syscall" "time" "github.com/alecthomas/kong" "github.com/apex/log" "github.com/apex/log/handlers/json" "gitlab.com/unboundsoftware/s3uploader/server" "gitlab.com/unboundsoftware/s3uploader/storage" ) var CLI struct { Port int `name:"port" env:"PORT" help:"Port which the service listens to" default:"80"` Bucket string `name:"bucket" env:"BUCKET" help:"The AWS S3 bucket where the uploaded objects should be stored" required:"true"` ReturnURL string `name:"return-url" env:"RETURN_URL" help:"Base-url to be prepended to all returned locations" required:"true"` } func main() { _ = kong.Parse(&CLI) log.SetHandler(json.New(os.Stdout)) logger := log.WithField("service", "s3uploader") if err := start(logger); err != nil { logger.WithError(err).Error("process error") } } func start(logger log.Interface) error { rootCtx, rootCancel := context.WithCancel(context.Background()) defer rootCancel() s3, err := storage.New(CLI.Bucket) if err != nil { return fmt.Errorf("storage failed: %w", err) } srv := server.New(s3, CLI.ReturnURL, logger) httpSrvAddr := fmt.Sprintf(":%d", CLI.Port) httpSrv := &http.Server{Addr: httpSrvAddr, Handler: srv} wg := sync.WaitGroup{} sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) wg.Add(1) go func() { defer wg.Done() sig := <-sigint if sig != nil { // In case our shutdown logic is broken/incomplete we reset signal // handlers so next signal goes to go itself. Go is more aggressive when // shutting down goroutines signal.Reset(os.Interrupt, syscall.SIGTERM) logger.Info("Got shutdown signal..") rootCancel() } }() wg.Add(1) go func() { defer wg.Done() <-rootCtx.Done() close(sigint) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() err := httpSrv.Shutdown(ctx) logger.WithError(err).Info("Shutdown of HTTP server complete") }() wg.Add(1) go func() { defer wg.Done() logger.Info(fmt.Sprintf("Serving HTTP API on %s", httpSrvAddr)) err := httpSrv.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { logger.WithError(err).Error("HTTP server failed") rootCancel() } }() wg.Wait() return nil }