package errorpage import ( "context" "fmt" "os" "path" "sync" . "github.com/yusing/go-proxy/internal/api/v1/utils" "github.com/yusing/go-proxy/internal/common" U "github.com/yusing/go-proxy/internal/utils" F "github.com/yusing/go-proxy/internal/utils/functional" W "github.com/yusing/go-proxy/internal/watcher" "github.com/yusing/go-proxy/internal/watcher/events" ) const errPagesBasePath = common.ErrorPagesBasePath var ( dirWatcher W.Watcher fileContentMap = F.NewMapOf[string, []byte]() ) var setup = sync.OnceFunc(func() { dirWatcher = W.NewDirectoryWatcher(context.Background(), errPagesBasePath) loadContent() go watchDir() }) func GetStaticFile(filename string) ([]byte, bool) { return fileContentMap.Load(filename) } // try .html -> 404.html -> not ok. func GetErrorPageByStatus(statusCode int) (content []byte, ok bool) { content, ok = fileContentMap.Load(fmt.Sprintf("%d.html", statusCode)) if !ok && statusCode != 404 { return fileContentMap.Load("404.html") } return } func loadContent() { files, err := U.ListFiles(errPagesBasePath, 0) if err != nil { Logger.Error(err) return } for _, file := range files { if fileContentMap.Has(file) { continue } content, err := os.ReadFile(file) if err != nil { Logger.Errorf("failed to read error page resource %s: %s", file, err) continue } file = path.Base(file) Logger.Infof("error page resource %s loaded", file) fileContentMap.Store(file, content) } } func watchDir() { eventCh, errCh := dirWatcher.Events(context.Background()) for { select { case event, ok := <-eventCh: if !ok { return } filename := event.ActorName switch event.Action { case events.ActionFileWritten: fileContentMap.Delete(filename) loadContent() case events.ActionFileDeleted: fileContentMap.Delete(filename) Logger.Infof("error page resource %s deleted", filename) case events.ActionFileRenamed: Logger.Infof("error page resource %s deleted", filename) fileContentMap.Delete(filename) loadContent() } case err := <-errCh: Logger.Errorf("error watching error page directory: %s", err) } } }