Yonah 8edf742aaa
cmd/msrserve: initial drop-in replacement server
This is still quite ugly and only meant to get things going for now, as
a proof of concept. It is usable though, and quite fast.

Signed-off-by: Yonah <contrib@gensokyo.uk>
2025-09-19 01:18:28 +09:00

123 lines
2.4 KiB
Go

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...)
}
}