package main import ( "context" wizard_vue "github.com/egor3f/rssalchemy/frontend/wizard-vue" "github.com/egor3f/rssalchemy/internal/adapters/natsadapter" httpApi "github.com/egor3f/rssalchemy/internal/api/http" "github.com/egor3f/rssalchemy/internal/config" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/labstack/gommon/log" "github.com/nats-io/nats.go" "golang.org/x/time/rate" "net" "net/http" "os" "os/signal" "slices" "time" ) func main() { cfg, err := config.Read() if err != nil { log.Panicf("reading config failed: %v", err) } log.SetHeader(`${time_rfc3339_nano} ${level}`) if cfg.Debug { log.SetLevel(log.DEBUG) } baseCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() natsc, err := nats.Connect(cfg.NatsUrl) if err != nil { log.Panicf("nats connect failed: %v", err) } defer func() { if err := natsc.Drain(); err != nil { log.Errorf("nats drain failed: %v", err) } }() na, err := natsadapter.New(natsc, "RENDER_TASKS") if err != nil { log.Panicf("create nats adapter: %v", err) } e := echo.New() e.Use(middleware.Logger()) e.Use(middleware.Recover()) setIPExtractor(e, cfg) e.StaticFS("/", echo.MustSubFS(wizard_vue.EmbedFS, wizard_vue.FSPrefix)) apiHandler := httpApi.New( na, na, rate.Every(time.Duration(float64(time.Second)*cfg.TaskRateLimitEvery)), cfg.TaskRateLimitBurst, ) apiHandler.SetupRoutes(e.Group("/api/v1")) go func() { if err := e.Start(cfg.WebserverAddress); err != nil && err != http.ErrServerClosed { e.Logger.Errorf("http server error, shutting down: %v", err) } }() <-baseCtx.Done() log.Infof("stopping webserver gracefully") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := e.Shutdown(ctx); err != nil { e.Logger.Errorf("failed to shutdown server: %v", err) } } func setIPExtractor(e *echo.Echo, cfg config.Config) { if len(cfg.RealIpHeader) > 0 { // Real ip header e.IPExtractor = func(req *http.Request) string { if len(req.Header.Get(cfg.RealIpHeader)) > 0 { return req.Header.Get(cfg.RealIpHeader) } // fallback ra, _, _ := net.SplitHostPort(req.RemoteAddr) return ra } } else { // X-Forwarded-For with trusted ip ranges var trustOptions []echo.TrustOption for _, ipRange := range slices.Concat(IpRanges, cfg.TrustedIpRanges) { _, network, err := net.ParseCIDR(ipRange) if err != nil { log.Panicf("Invalid ip range: %s", ipRange) } trustOptions = append(trustOptions, echo.TrustIPRange(network)) } e.IPExtractor = echo.ExtractIPFromXFFHeader(trustOptions...) } }