nidus-sync/htmlpage/fileserver.go
Eli Ribble 9774452821 Switch main report page to better example
This is still pulling from our generic district mock, but it's still
better than what we had. This also includes adding static content
hosting for the bootstrap content on the public domain.
2026-01-07 20:47:55 +00:00

115 lines
2.7 KiB
Go

package htmlpage
import (
"embed"
"fmt"
"io/fs"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/go-chi/chi/v5"
)
// FileServer conveniently sets up a http.FileServer handler to serve
// static files from a http.FileSystem.
func FileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embed.FS, embeddedPath string) {
if strings.ContainsAny(path, "{}*") {
panic("FileServer does not permit any URL parameters.")
}
if path != "/" && path[len(path)-1] != '/' {
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
path += "/"
}
path += "*"
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
rctx := chi.RouteContext(r.Context())
pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
// Determine the actual file path
requestedPath := strings.TrimPrefix(r.URL.Path, pathPrefix)
// Try to open from local filesystem first for development
localFile, localErr := root.Open(requestedPath)
var fileToServe http.File
if localErr == nil {
// File found in local filesystem
fileToServe = localFile
} else {
// If not found locall, try embedded filesystem
embeddedFilePath := filepath.Join(embeddedPath, requestedPath)
embeddedFile, err := embeddedFS.Open(embeddedFilePath)
if err != nil {
http.NotFound(w, r)
return
}
// Wrap the embedded file to implement http.File interface
fileToServe = &embeddedFileWrapper{embeddedFile}
}
// Create a custom ResponseWriter that allows us to modify headers
crw := &customResponseWriter{ResponseWriter: w}
// Serve the file
http.ServeContent(crw, r, requestedPath, time.Time{}, fileToServe)
// Close the file
fileToServe.Close()
})
}
type embeddedFileWrapper struct {
file fs.File
}
func (e *embeddedFileWrapper) Close() error {
return e.file.Close()
}
func (e *embeddedFileWrapper) Read(p []byte) (n int, err error) {
return e.file.Read(p)
}
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
func (e *embeddedFileWrapper) Seek(offset int64, whence int) (int64, error) {
if seeker, ok := e.file.(Seeker); ok {
return seeker.Seek(offset, whence)
}
return 0, fmt.Errorf("Seek not supported")
}
func (e *embeddedFileWrapper) Readdir(count int) ([]os.FileInfo, error) {
// This is a bit tricky with embedded files
if dirFile, ok := e.file.(fs.ReadDirFile); ok {
entries, err := dirFile.ReadDir(count)
if err != nil {
return nil, err
}
fileInfos := make([]os.FileInfo, len(entries))
for i, entry := range entries {
fileInfos[i], err = entry.Info()
if err != nil {
return nil, err
}
}
return fileInfos, nil
}
return nil, fmt.Errorf("Readdir not supported")
}
func (e *embeddedFileWrapper) Stat() (os.FileInfo, error) {
return e.file.Stat()
}