From 544841315381725d213141493b4ee385255f6cd2 Mon Sep 17 00:00:00 2001 From: early Date: Mon, 2 Sep 2024 23:26:04 -0600 Subject: [PATCH] static support --- app/app.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 + go.sum | 2 + 3 files changed, 134 insertions(+) create mode 100644 go.sum diff --git a/app/app.go b/app/app.go index 8ab8a78..2f10dd0 100644 --- a/app/app.go +++ b/app/app.go @@ -4,21 +4,35 @@ package app import ( "context" "errors" + "fmt" + "io" "log/slog" "net" "net/http" "os" + "path" + "path/filepath" "strings" "sync" "sync/atomic" + + "git.earlybird.gay/today-engine/include" + "git.earlybird.gay/today-engine/page" ) +func init() { + include.SetNotEligible("git.earlybird.gay/today-app/app") +} + type App struct { http.Server http.ServeMux Logger *slog.Logger + static map[string]http.Handler + explicitPages []*page.Page + running atomic.Bool wg sync.WaitGroup shutdown, stop chan struct{} @@ -64,8 +78,124 @@ func (app *App) Subprocess(process func(done <-chan struct{}) error) { }() } +// Static serves the files in rootDir at rootPath. +// +// - .html files have their extensions stripped, and are served as their +// filenames, following a directory structure. If there is an index.html +// in a directory, it is served under the directory name, with a trailing +// slash, so /foo/bar/index.html is served at /foo/bar/. +// - Other non-excluded files are served with their extensions intact. +// Currently, .go files and anything in a hidden directory are excluded. +// MIME types are chosen by Go's standard HTTP library behavior until that +// becomes a pain. +// - If using today-engine, page.New and page.Static will *override* the +// default behavior of Static. *This is only possible when using +// today-engine.* +// - If static is called twice for the same rootPath, the second call will +// override the first. +func (app *App) Static(rootPath, rootDir string) error { + if app.static == nil { + app.static = make(map[string]http.Handler) + } + var f func(fpath string) error + f = func(fpath string) error { + stat, err := os.Stat(fpath) + if err != nil { + return err + } + if stat.IsDir() { + // Recurse to subdirectories + entries, err := os.ReadDir(fpath) + if err != nil { + return err + } + for _, entry := range entries { + err = errors.Join(err, f(filepath.Join(fpath, entry.Name()))) + } + return err + } else { + // Serve file + fileName := filepath.Base(fpath) + fileExt := filepath.Ext(fileName) + servePath := filepath.Join(rootPath, strings.TrimPrefix(fpath, rootDir)) + var handler http.Handler + switch fileExt { + case ".go": + // explicit ignore .go + handler = nil + case ".html": + if fileName == "index.html" { + servePath = path.Clean(path.Dir(servePath) + "/{$}") + } else { + servePath = strings.TrimSuffix(servePath, ".html") + } + handler = page.Static(fpath) + default: + handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + f, err := os.Open(fpath) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, "failed to open file") + return + } + w.WriteHeader(http.StatusOK) + io.Copy(w, f) + }) + } + + // If not ignoring the file, handle it. + if handler != nil { + serveExpr := "GET " + servePath + app.static[serveExpr] = handler + } + return nil + } + } + return f(rootDir) +} + +func (app *App) Handle(expr string, handler http.Handler) { + // Check page handlers against static + if todayPage, ok := handler.(*page.Page); ok { + app.explicitPages = append(app.explicitPages, todayPage) + } + app.ServeMux.Handle(expr, handler) +} + +func (app *App) registerStatic() { +registerStaticHandlers: + for expr, staticHandler := range app.static { + // If the staticHandler is a *page.Page, + if staticPage, ok := staticHandler.(*page.Page); ok { + // and the source file can be identified, + var staticPath string + if fopener, ok := staticPage.File().(include.FileOpener); !ok { + continue + } else { + staticPath = fopener.FileName() + } + // Make sure it isn't overridden by an explicitly handled *page.Page. + for _, expPage := range app.explicitPages { + var expPath string + if fopener, ok := expPage.File().(include.FileOpener); !ok { + continue + } else { + expPath = fopener.FileName() + } + + // If paths are equal, continue to the next static handler. + if staticPath == expPath { + continue registerStaticHandlers + } + } + } + app.Handle(expr, staticHandler) + } +} + func (app *App) ListenAndServe() error { app.setDefaults() + app.registerStatic() app.Logger.Info("application starting", "host", app.Addr) var listener net.Listener diff --git a/go.mod b/go.mod index a2595c8..cc559d2 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.earlybird.gay/today-app go 1.22.4 + +require git.earlybird.gay/today-engine v0.0.0-20240902192420-b42b76d1d4c5 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1d27a66 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.earlybird.gay/today-engine v0.0.0-20240902192420-b42b76d1d4c5 h1:I+u8fVR315zuANZKWjDCgeHtJ2aSyS3xJHts/hDhFLc= +git.earlybird.gay/today-engine v0.0.0-20240902192420-b42b76d1d4c5/go.mod h1:9w8xpAPxs1QvT//ph/jgAuRIoWyqdi2QEifwsKWOKns= -- 2.39.5