depot/web/fup/fuphttp/fuphttp.go
Luke Granger-Brown e21db7a061 fup: add a template function for getting paths to static assets
We add the hash of the file to the static assets, so they can be cached
indefinitely.  This also, however, means that we need some way of referring to
them.
2021-03-21 03:03:15 +00:00

101 lines
2.5 KiB
Go

// SPDX-FileCopyrightText: 2021 Luke Granger-Brown <depot@lukegb.com>
//
// SPDX-License-Identifier: Apache-2.0
package fuphttp
import (
"context"
"fmt"
"io/fs"
"net/http"
"github.com/google/safehtml"
"github.com/google/safehtml/template"
"github.com/google/safehtml/template/uncheckedconversions"
shuncheckedconversions "github.com/google/safehtml/uncheckedconversions"
"github.com/gorilla/mux"
"hg.lukegb.com/lukegb/depot/web/fup/hashfs"
)
type Config struct {
Templates fs.FS
Static fs.FS
StaticRoot safehtml.TrustedResourceURL
}
type Application struct {
indexTmpl *template.Template
notFoundTmpl *template.Template
}
func (a *Application) Handler() http.Handler {
r := mux.NewRouter()
r.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusNotFound)
a.notFoundTmpl.Execute(rw, nil)
})
return r
}
func parseTemplate(t *template.Template, fsys fs.FS, name string) (*template.Template, error) {
bs, err := fs.ReadFile(fsys, name)
if err != nil {
return nil, fmt.Errorf("reading template %q: %w", name, err)
}
return t.ParseFromTrustedTemplate(
uncheckedconversions.TrustedTemplateFromStringKnownToSatisfyTypeContract(string(bs)),
)
}
func loadTemplate(fsys fs.FS, name string, funcs template.FuncMap) (*template.Template, error) {
t := template.New(name).Funcs(funcs)
var err error
if t, err = parseTemplate(t, fsys, "base.html"); err != nil {
return nil, fmt.Errorf("loading base template: %w", err)
}
if t, err = parseTemplate(t, fsys, fmt.Sprintf("%s.html", name)); err != nil {
return nil, fmt.Errorf("loading leaf template: %w", err)
}
return t, nil
}
func New(ctx context.Context, cfg *Config) (*Application, error) {
a := new(Application)
tmpls := []struct {
t **template.Template
name string
}{
{&a.indexTmpl, "index"},
{&a.notFoundTmpl, "404"},
}
funcMap := template.FuncMap{
"static": func(s string) safehtml.TrustedResourceURL {
staticPath := s
if fs, ok := cfg.Static.(*hashfs.FS); ok {
sp, ok := fs.LookupHashedName(staticPath)
if ok {
staticPath = sp
} else {
log.Printf("warning: couldn't find static file %v", staticPath)
}
}
return shuncheckedconversions.TrustedResourceURLFromStringKnownToSatisfyTypeContract(cfg.StaticRoot.String() + staticPath)
},
}
for _, tmpl := range tmpls {
t, err := loadTemplate(cfg.Templates, tmpl.name, funcMap)
if err != nil {
return nil, fmt.Errorf("loading template %q: %w", tmpl.name, err)
}
*tmpl.t = t
}
return a, nil
}