Render templates via a buffer, not the ResponseWriter (closes #123)
All checks were successful
check / check (push) Successful in 3m48s
All checks were successful
check / check (push) Successful in 3m48s
executeTemplate ran the template straight into the ResponseWriter, so a mid-render failure left the already-emitted prefix written and the response committed: the handler could no longer set a 500 and the client got a truncated page, typically with a 200. It also let handler tests pass against the flushed prefix of a page that aborted below the assertions. Execute into a bytes.Buffer instead, and set the content type and copy the buffer out only once rendering has fully succeeded. On failure nothing has been written, so the 500 still reaches the client. Add a test that renders a template failing partway through and asserts both the 500 and that the body carries no part of the aborted page. Against the previous streaming renderer it fails on both counts (200, body "PARTIAL PAGE CONTENTInternal server error").
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -224,13 +225,20 @@ func (s *Handlers) renderTemplate(
|
||||
s.executeTemplate(w, tmpl, wrapper)
|
||||
}
|
||||
|
||||
// executeTemplate runs the template and handles errors.
|
||||
// executeTemplate renders the template into a buffer and writes to
|
||||
// the response only once rendering has fully succeeded. Executing
|
||||
// straight into the ResponseWriter commits a partial body and a 200
|
||||
// status before a mid-render error can be reported, leaving no way
|
||||
// to serve a 500. These pages are small, so holding one in memory is
|
||||
// the right trade.
|
||||
func (s *Handlers) executeTemplate(
|
||||
w http.ResponseWriter,
|
||||
tmpl *template.Template,
|
||||
data any,
|
||||
) {
|
||||
err := tmpl.Execute(w, data)
|
||||
var buf bytes.Buffer
|
||||
|
||||
err := tmpl.Execute(&buf, data)
|
||||
if err != nil {
|
||||
s.log.Error(
|
||||
"failed to execute template", "error", err,
|
||||
@@ -239,5 +247,16 @@ func (s *Handlers) executeTemplate(
|
||||
w, "Internal server error",
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
_, err = buf.WriteTo(w)
|
||||
if err != nil {
|
||||
s.log.Error(
|
||||
"failed to write rendered page", "error", err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user