2024-04-25 19:48:45 +00:00
|
|
|
package main
|
|
|
|
|
2024-05-06 07:17:11 +00:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
2024-05-28 23:30:59 +00:00
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
2024-05-06 07:17:11 +00:00
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var errorLog *log.Logger = log.New(os.Stderr, "", log.LstdFlags)
|
|
|
|
|
2024-05-28 23:30:59 +00:00
|
|
|
// const (
|
|
|
|
// DEFAULT_RULES_FILE string = "/var/lib/gocustomurls/rules.json"
|
|
|
|
// DEFAULT_LOG_FILE string = "/var/log/gocustomurls/app.log"
|
|
|
|
// )
|
2024-05-06 07:17:11 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2024-05-28 23:30:59 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2024-04-25 19:48:45 +00:00
|
|
|
func main() {
|
2024-05-06 07:17:11 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2024-05-28 23:30:59 +00:00
|
|
|
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")
|
2024-05-06 07:17:11 +00:00
|
|
|
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
|
2024-05-28 23:30:59 +00:00
|
|
|
} 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)
|
2024-05-06 07:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var rulesFile string
|
2024-05-28 23:30:59 +00:00
|
|
|
|
2024-05-06 07:17:11 +00:00
|
|
|
if allSetFlags["config"] {
|
|
|
|
rulesFile = *rulesFileFlag
|
|
|
|
}
|
|
|
|
|
|
|
|
var logFile string
|
|
|
|
if allSetFlags["logFile"] {
|
|
|
|
logFile = *logFileFlag
|
|
|
|
}
|
|
|
|
|
2024-05-28 23:30:59 +00:00
|
|
|
rFile, lFile, err := generateDefaults(logFile, rulesFile)
|
|
|
|
if err != nil {
|
|
|
|
errorLog.Println(err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2024-05-06 07:17:11 +00:00
|
|
|
// load rules mapping
|
|
|
|
c := &Config{}
|
2024-05-28 23:30:59 +00:00
|
|
|
err = c.LoadMappingFile(rFile)
|
2024-05-06 07:17:11 +00:00
|
|
|
if err != nil {
|
|
|
|
errorLog.Println(err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2024-05-28 23:30:59 +00:00
|
|
|
l, err := newFileLogger(lFile)
|
2024-05-06 07:17:11 +00:00
|
|
|
if err != nil {
|
|
|
|
errorLog.Println(err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
app := &Application{
|
|
|
|
Config: c,
|
|
|
|
Log: l,
|
|
|
|
}
|
|
|
|
srv := app.Setup(port)
|
|
|
|
|
|
|
|
// For graceful shutdowns
|
|
|
|
go func() {
|
2024-05-28 23:30:59 +00:00
|
|
|
app.Log.logger.Printf("%s Starting\n", getCurrentDate())
|
2024-05-06 07:17:11 +00:00
|
|
|
err := srv.ListenAndServe()
|
|
|
|
if !errors.Is(err, http.ErrServerClosed) {
|
|
|
|
errorLog.Printf("HTTP Server error: %+v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2024-05-28 23:30:59 +00:00
|
|
|
app.Log.logger.Printf("%s Stopped serving new connections.\n", getCurrentDate())
|
2024-05-06 07:17:11 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
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()
|
2024-04-25 19:48:45 +00:00
|
|
|
|
2024-05-06 07:17:11 +00:00
|
|
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
|
|
errorLog.Printf("HTTP shutdown error: %+v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2024-05-28 23:30:59 +00:00
|
|
|
app.Log.logger.Printf("%s Graceful shutdown complete.\n", getCurrentDate())
|
2024-04-25 19:48:45 +00:00
|
|
|
}
|