]> git.earlybird.gay Git - today/commitdiff
static support
authorearly <me@earlybird.gay>
Tue, 3 Sep 2024 05:26:04 +0000 (23:26 -0600)
committerearly <me@earlybird.gay>
Tue, 3 Sep 2024 05:26:04 +0000 (23:26 -0600)
app/app.go
go.mod
go.sum [new file with mode: 0644]

index 8ab8a78203f1e067493e118f95f2294ab3fdca47..2f10dd095613f9234b6df05c32df1c5b6d394130 100644 (file)
@@ -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 a2595c8188b441d65350def20b71d6c95656f9e0..cc559d29d9f2d1910f556eebdbf4949210f0cad7 100644 (file)
--- 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 (file)
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=