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
|
package surf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"git.golem15.com/golem15/summercms/pact"
|
"git.golem15.com/golem15/summercms/pact"
|
||||||
"git.golem15.com/golem15/summercms/party"
|
"git.golem15.com/golem15/summercms/party"
|
||||||
"git.golem15.com/golem15/summercms/towel"
|
"git.golem15.com/golem15/summercms/towel"
|
||||||
|
"git.golem15.com/golem15/summercms/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use names a middleware list for Group, matching PHP ->middleware().
|
// 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 {
|
func recoverJSON(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
buffered := newBufferedResponse()
|
||||||
defer func() {
|
defer func() {
|
||||||
if rec := recover(); rec != nil {
|
if rec := recover(); rec != nil {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
wire.WriteOpaque500(w)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
return
|
||||||
_, _ = w.Write([]byte(`{"error":true,"message":"Internal server error"}`))
|
|
||||||
}
|
}
|
||||||
|
buffered.commit(w)
|
||||||
}()
|
}()
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(buffered, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func recoverBare(next http.Handler) http.Handler {
|
func recoverBare(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
buffered := newBufferedResponse()
|
||||||
defer func() {
|
defer func() {
|
||||||
if rec := recover(); rec != nil {
|
if rec := recover(); rec != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
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 {
|
func locale(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
next.ServeHTTP(w, r.WithContext(towel.WithLocale(r.Context(), r.Header.Get("Accept-Language"))))
|
next.ServeHTTP(w, r.WithContext(towel.WithLocale(r.Context(), r.Header.Get("Accept-Language"))))
|
||||||
|
|||||||
Reference in New Issue
Block a user