fix(06-09): buffer route responses before recovery
- Discard partial route output when house or raw handlers panic - Commit private headers status and body only after successful return
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package surf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"git.golem15.com/golem15/summercms/pact"
|
||||
"git.golem15.com/golem15/summercms/party"
|
||||
"git.golem15.com/golem15/summercms/towel"
|
||||
"git.golem15.com/golem15/summercms/wire"
|
||||
)
|
||||
|
||||
// Use names a middleware list for Group, matching PHP ->middleware().
|
||||
@@ -519,28 +521,73 @@ func corsOrigins(app *backpack.App) []string {
|
||||
|
||||
func recoverJSON(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buffered := newBufferedResponse()
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(`{"error":true,"message":"Internal server error"}`))
|
||||
wire.WriteOpaque500(w)
|
||||
return
|
||||
}
|
||||
buffered.commit(w)
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
next.ServeHTTP(buffered, r)
|
||||
})
|
||||
}
|
||||
|
||||
func recoverBare(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buffered := newBufferedResponse()
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
buffered.commit(w)
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
next.ServeHTTP(buffered, r)
|
||||
})
|
||||
}
|
||||
|
||||
type bufferedResponse struct {
|
||||
header http.Header
|
||||
status int
|
||||
body bytes.Buffer
|
||||
}
|
||||
|
||||
func newBufferedResponse() *bufferedResponse {
|
||||
return &bufferedResponse{header: make(http.Header)}
|
||||
}
|
||||
|
||||
func (w *bufferedResponse) Header() http.Header { return w.header }
|
||||
|
||||
func (w *bufferedResponse) WriteHeader(status int) {
|
||||
if w.status == 0 {
|
||||
w.status = status
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bufferedResponse) Write(p []byte) (int, error) {
|
||||
if w.status == 0 {
|
||||
w.status = http.StatusOK
|
||||
}
|
||||
return w.body.Write(p)
|
||||
}
|
||||
|
||||
// Flush deliberately does not expose or commit the destination writer. Route
|
||||
// output becomes visible only after the handler returns successfully.
|
||||
func (*bufferedResponse) Flush() {}
|
||||
|
||||
func (w *bufferedResponse) commit(dst http.ResponseWriter) {
|
||||
for key, values := range w.header {
|
||||
dst.Header()[key] = append([]string(nil), values...)
|
||||
}
|
||||
status := w.status
|
||||
if status == 0 {
|
||||
status = http.StatusOK
|
||||
}
|
||||
dst.WriteHeader(status)
|
||||
_, _ = dst.Write(w.body.Bytes())
|
||||
}
|
||||
|
||||
func locale(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r.WithContext(towel.WithLocale(r.Context(), r.Header.Get("Accept-Language"))))
|
||||
|
||||
Reference in New Issue
Block a user