package main import ( "context" "errors" "flag" "fmt" "log" "net/http" "os" "os/signal" "path/filepath" "strconv" "syscall" "time" ) var errorLog *log.Logger = log.New(os.Stderr, "", log.LstdFlags) // const ( // DEFAULT_RULES_FILE string = "/var/lib/gocustomurls/rules.json" // DEFAULT_LOG_FILE string = "/var/log/gocustomurls/app.log" // ) // flagsSet returns a set of all the flags what were actually set on the // command line. func flagsSet(flags *flag.FlagSet) map[string]bool { s := make(map[string]bool) flags.Visit(func(f *flag.Flag) { s[f.Name] = true }) return s } // generateDefaults - generate the default values for the rules.json and log files func generateDefaults(rulesfp string, logfp string) (string, string, error) { var newlogfp, newrulesfp string var err error newlogfp = logfp newrulesfp = rulesfp if len(newrulesfp) == 0 { dir, err := os.UserConfigDir() if err != nil { return newrulesfp, newlogfp, err } newrulesfp = filepath.Join(dir, "gocustomcurls", "rules.json") } if len(newlogfp) == 0 { dir, err := os.UserHomeDir() if err != nil { return newrulesfp, newlogfp, err } newlogfp = filepath.Join(dir, ".gocustomurls", "logs", "app.log") } return newrulesfp, newlogfp, err } // isValidPort returns true if the port is valid // following the RFC https://datatracker.ietf.org/doc/html/rfc6056#section-2.1 func isValidPort(port int) bool { return port > 0 && port < 65535 } func main() { programName := os.Args[0] // errorLog = log.New(os.Stderr, "", log.LstdFlags) flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) flags.Usage = func() { out := flags.Output() fmt.Fprintf(out, "Usage: %v [flags]\n\n", programName) fmt.Fprint(out, " This utility serves vanity urls for the go get/install command.\n") fmt.Fprint(out, " By default, the server listens on localhost:7070.\n") flags.PrintDefaults() } portFlag := flags.String("port", "7070", "Optional. Default port is 7070. Port to listen to") rulesFileFlag := flags.String("config", "", "Optional. Contains go-import mapping") logFileFlag := flags.String("logfile", "", "Optional. Default log file") flags.Parse(os.Args[1:]) if len(flags.Args()) > 1 { errorLog.Println("Error: too many command-line arguments") flags.Usage() os.Exit(1) } allSetFlags := flagsSet(flags) var port string if allSetFlags["port"] { port = *portFlag } else { port = "7070" } p, err := strconv.Atoi(port) if err != nil { errorLog.Println(err) os.Exit(1) } if !isValidPort(p) { errorLog.Println(fmt.Errorf("provided port (%d) is not valid", p)) os.Exit(1) } var rulesFile string if allSetFlags["config"] { rulesFile = *rulesFileFlag } var logFile string if allSetFlags["logFile"] { logFile = *logFileFlag } rFile, lFile, err := generateDefaults(logFile, rulesFile) if err != nil { errorLog.Println(err) os.Exit(1) } // load rules mapping c := &Config{} err = c.LoadMappingFile(rFile) if err != nil { errorLog.Println(err) os.Exit(1) } l, err := newFileLogger(lFile) if err != nil { errorLog.Println(err) os.Exit(1) } app := &Application{ Config: c, Log: l, } srv := app.Setup(port) // For graceful shutdowns go func() { app.Log.logger.Printf("%s Starting\n", getCurrentDate()) err := srv.ListenAndServe() if !errors.Is(err, http.ErrServerClosed) { errorLog.Printf("HTTP Server error: %+v\n", err) os.Exit(1) } app.Log.logger.Printf("%s Stopped serving new connections.\n", getCurrentDate()) }() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) <-sigChan shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownRelease() if err := srv.Shutdown(shutdownCtx); err != nil { errorLog.Printf("HTTP shutdown error: %+v\n", err) os.Exit(1) } app.Log.logger.Printf("%s Graceful shutdown complete.\n", getCurrentDate()) }