package main import ( "embed" "fmt" "io" "log" "net/http" "os" "time" "website/internal/config" "github.com/ansrivas/fiberprometheus/v2" "github.com/getsentry/sentry-go" "github.com/gofiber/contrib/fibersentry" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cache" "github.com/gofiber/fiber/v2/middleware/compress" "github.com/gofiber/fiber/v2/middleware/etag" "github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/fiber/v2/middleware/healthcheck" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/skip" "github.com/shengyanli1982/law" ) // TODO purge CSS // TODO HTTP2 https://github.com/dgrr/http2 type Host struct { Fiber *fiber.App } //go:embed all:public/* var fs embed.FS func main() { err := sentry.Init(sentry.ClientOptions{ Dsn: os.Getenv("SENTRY_DSN"), Release: os.Getenv("FLY_MACHINE_VERSION"), Environment: os.Getenv("ENV"), }) if err != nil { log.Panic("could not set up sentry") } defer sentry.Flush(2 * time.Second) metricServer := fiber.New(fiber.Config{ GETOnly: true, StrictRouting: true, DisableDefaultDate: true, DisableHeaderNormalizing: true, DisableStartupMessage: true, }) prometheus := fiberprometheus.New("homestead") prometheus.RegisterAt(metricServer, "/metrics") hosts := map[string]*Host{} config, err := config.GetConfig() if err != nil { log.Panic("config error", err) } internal := fiber.New(fiber.Config{ GETOnly: true, StrictRouting: true, }) internal.Use(healthcheck.New(healthcheck.Config{})) hosts["fly-internal"] = &Host{internal} website := fiber.New(fiber.Config{ EnableTrustedProxyCheck: true, TrustedProxies: []string{"172.16.0.0/16"}, ProxyHeader: "Fly-Client-IP", GETOnly: true, ReadTimeout: 5 * time.Minute, WriteTimeout: 5 * time.Minute, ServerHeader: "Fiber", StrictRouting: true, UnescapePath: true, }) website.Use(prometheus.Middleware) website.Use(fibersentry.New(fibersentry.Config{})) website.Use(compress.New()) website.Use(cache.New(cache.Config{ CacheControl: true, Expiration: 24 * time.Hour, StoreResponseHeaders: true, })) // must be after compress to be encoding-independent website.Use(etag.New(etag.Config{ Weak: true, })) website.Use(recover.New(recover.Config{})) files := http.FS(fs) notFoundHandler := func(c *fiber.Ctx) error { c.Status(fiber.StatusNotFound).Type("html", "utf-8") content, err := fs.ReadFile("public/404.html") if err != nil { content = []byte("404 Not Found") c.Type("txt") } return c.Send(content) } website.Get("/404.html", notFoundHandler) website.Use("/", filesystem.New(filesystem.Config{ Root: files, PathPrefix: "public", ContentTypeCharset: "utf-8", MaxAge: int((24 * time.Hour).Seconds()), })) website.Use(notFoundHandler) hosts[config.BaseURL.Host] = &Host{website} toplevel := fiber.New(fiber.Config{ DisableStartupMessage: config.Production, }) toplevel.Get("/health", func(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) }) var logWriter io.Writer if config.Production { logWriter = law.NewWriteAsyncer(os.Stdout, nil) } else { logWriter = os.Stdout } toplevel.Use(skip.New(logger.New(logger.Config{ Output: logWriter, Format: "${protocol} ${method} ${status} ${host} ${url} ${respHeader:Location}\n", }), func(c *fiber.Ctx) bool { return c.Hostname() == "fly-internal" })) toplevel.Use(func(c *fiber.Ctx) error { host := hosts[c.Hostname()] if host == nil { if config.RedirectOtherHostnames { return c.Redirect(config.BaseURL.JoinPath(c.OriginalURL()).String()) } else { hosts[config.BaseURL.Host].Fiber.Handler()(c.Context()) return nil } } else { host.Fiber.Handler()(c.Context()) return nil } }) go func() { err := metricServer.Listen(":9091") log.Printf("failed to start metrics server: %v", err) }() log.Fatal(toplevel.Listen(fmt.Sprintf("%s:%d", "", config.Port))) }