Files
any2anexoj/cmd/any2anexoj-web/main.go
T
2026-09-28 21:46:54 +01:00

86 lines
1.8 KiB
Go

package main
import (
"context"
"embed"
"errors"
"io/fs"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/nmoniz/any2anexoj/internal/web"
)
//go:embed all:static
var staticFS embed.FS
func main() {
if err := run(); err != nil {
slog.Error("any2anexoj-web: fatal error", slog.Any("err", err))
os.Exit(1)
}
}
// run wires up the server, installs signal-driven cancellation, and blocks on
// the HTTP server until it stops. It is split out from main so tests can drive
// the same code paths if needed.
func run() error {
addr := os.Getenv("PORT")
if addr == "" {
addr = ":8080"
}
// A bare "8080" value is friendlier than the net.Listen-style ":8080".
if addr[0] != ':' {
addr = ":" + addr
}
// Sub-mount the embedded FS so the static subtree is rooted at "" inside
// the web package (matches /static/...).
staticSub, err := fs.Sub(staticFS, "static")
if err != nil {
return err
}
state := web.NewState()
srv, err := web.NewServer(state, staticSub)
if err != nil {
return err
}
httpServer := &http.Server{
Addr: addr,
Handler: srv.Handler(),
ReadHeaderTimeout: 10 * time.Second,
}
ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt, syscall.SIGTERM)
defer cancel()
errCh := make(chan error, 1)
go func() {
slog.Info("any2anexoj-web: listening", slog.String("addr", addr))
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- err
return
}
errCh <- nil
}()
select {
case <-ctx.Done():
slog.Info("any2anexoj-web: shutting down")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
if err := httpServer.Shutdown(shutdownCtx); err != nil {
return err
}
return nil
case err := <-errCh:
return err
}
}