package main import ( "context" "embed" "encoding/json" "errors" "flag" "io/fs" "log" "net/http" "os" "os/signal" "syscall" "time" "git.gensokyo.uk/yonah/monstersirenfetch" ) const shutdownTimeout = 5 * time.Second var ( flagVerbose bool flagAddr string ) func init() { flag.BoolVar(&flagVerbose, "v", false, "Increase log verbosity") flag.StringVar(&flagAddr, "a", ":3000", `TCP address for the server to listen on, in the form "host:port".`) } var ( //go:embed all:static staticFS embed.FS ) func main() { log.SetFlags(0) log.SetPrefix("msrserve: ") flag.Parse() mux := http.NewServeMux() mux.HandleFunc("/", http.FileServerFS(mustSub(staticFS, "static")).ServeHTTP) mux.HandleFunc("/{$}", serveAppShell) mux.HandleFunc("/about/{$}", serveAppShell) mux.HandleFunc("/music/", serveAppShell) mux.HandleFunc("/info/{$}", serveAppShell) mux.HandleFunc("/contact/{$}", serveAppShell) mux.HandleFunc("GET /api/", handleAPINew(mustReadJSON[*monstersirenfetch.Metadata](flagMetadataPath))) mux.HandleFunc("/data/", handleDataNew(flagFetchDirPath)) s := http.Server{Addr: flagAddr, Handler: mux} sig := make(chan os.Signal, 2) go func() { defer signal.Stop(sig) v := <-sig ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() log.Print(v) if err := s.Shutdown(ctx); err != nil { log.Fatal(err) } }() signal.Notify(sig, os.Interrupt, syscall.SIGTERM) if err := s.ListenAndServe(); err != nil { if !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) } } } func serveAppShell(w http.ResponseWriter, r *http.Request) { http.ServeFileFS(w, r, staticFS, "static/appShell.html") } func writeResp(writer http.ResponseWriter, data []byte) bool { if _, err := writer.Write(data); err != nil { verboseln(err) return false } return true } func mustMarshalJSON(v any) []byte { if data, err := json.Marshal(v); err != nil { log.Fatal(err) return nil } else { return data } } func mustReadJSON[T any](pathname string) T { var v T if r, err := os.OpenFile(pathname, os.O_RDONLY, 0); err != nil { log.Fatal(err) } else if err = json.NewDecoder(r).Decode(&v); err != nil { log.Fatal(err) } else if err = r.Close(); err != nil { log.Fatal(err) } return v } func mustSub(fsys fs.FS, dir string) fs.FS { if sub, err := fs.Sub(fsys, dir); err != nil { log.Fatal(err) return nil } else { return sub } } func verboseln(v ...any) { if flagVerbose { log.Println(v...) } }