package todo import ( "bytes" "embed" "html/template" "net/http" "strconv" "amadan.net/rastrillo/rastrillo" ) //go:embed templates static var appFS embed.FS // assets fingerprints static/: {{asset "static/index.css"}} returns a // content-hashed URL served immutable-cacheable, and the bare name // (/static/index.css) is served too, never long-cached. var assets = rastrillo.NewAssets(appFS) var templates = template.Must(template.New(""). Funcs(template.FuncMap{ "asset": assets.Path, "plural": func(n int) string { if n == 1 { return "item" } return "items" }, }). ParseFS(appFS, "templates/*.html")) // render executes a template into a buffer before anything touches the // wire, so a template error becomes a clean 500 and the response // carries an exact Content-Length. func render(w http.ResponseWriter, name string, data any) { renderStatus(w, http.StatusOK, name, data) } func renderStatus(w http.ResponseWriter, status int, name string, data any) { var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, name, data); err != nil { http.Error(w, "something went wrong", http.StatusInternalServerError) return } h := w.Header() h.Set("Content-Type", "text/html; charset=utf-8") h.Set("Content-Length", strconv.Itoa(buf.Len())) w.WriteHeader(status) buf.WriteTo(w) } // ErrorPage is the rastrillo.ErrorPageFunc Options.ErrorPage points at, // so a panic gets the same page a handled failure does: TodoMVC's frame // with the status and, for a 500, the ref the log line carries. func ErrorPage(w http.ResponseWriter, r *http.Request, status int, ref string) { title := http.StatusText(status) if status == http.StatusInternalServerError { title = "Something went wrong on our side" } renderStatus(w, status, "errors", map[string]any{"Title": title, "Ref": ref}) } func errorPage(w http.ResponseWriter, status int, title string) { renderStatus(w, status, "errors", map[string]any{"Title": title}) }