fix(web): the title-part hang-up is handled like the rest, and an abandoned request is answered 499, not an implicit 200 (fourth review)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 17:47:13 +02:00
co-authored by Claude Sonnet 5.5
parent 1266e18038
commit 9d7afb2bcb
+10 -4
View File
@@ -95,7 +95,7 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
} }
if err != nil { if err != nil {
var tooBig *http.MaxBytesError var tooBig *http.MaxBytesError
if a.abandoned(r) { if a.abandoned(w, r) {
return return
} }
if !errors.As(err, &tooBig) { if !errors.As(err, &tooBig) {
@@ -113,6 +113,9 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
case "title": case "title":
b, err := io.ReadAll(io.LimitReader(part, maxTitleRunes*utf8.UTFMax)) b, err := io.ReadAll(io.LimitReader(part, maxTitleRunes*utf8.UTFMax))
if err != nil { if err != nil {
if a.abandoned(w, r) {
return
}
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "That upload could not be read. Please try again.") a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "That upload could not be read. Please try again.")
return return
} }
@@ -133,14 +136,17 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "Choose an audio file to upload.") a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "Choose an audio file to upload.")
} }
// abandoned reports, and logs at INFO, that the client has already gone: its request // abandoned reports, logs at INFO and answers 499, because the client has already gone: its request
// context is cancelled, so there is nobody to answer and rendering a page would run its // context is cancelled, so there is nobody to answer and rendering a page would run its
// database read on a dead context and log a false ERROR. A hang-up mid-upload is routine. // database read on a dead context and log a false ERROR. A hang-up mid-upload is routine.
func (a *App) abandoned(r *http.Request) bool { func (a *App) abandoned(w http.ResponseWriter, r *http.Request) bool {
if r.Context().Err() == nil { if r.Context().Err() == nil {
return false return false
} }
a.logger().Info("upload abandoned", "reason", "client_gone") a.logger().Info("upload abandoned", "reason", "client_gone")
// 499 (the "client closed request" convention): a client that half-closed after sending
// can still read, and an unwritten status would reach it as a false, empty 200.
w.WriteHeader(499)
return true return true
} }
@@ -148,7 +154,7 @@ func (a *App) abandoned(r *http.Request) bool {
// rejection is the user's to fix (422, or 413 for size) and carries its own // rejection is the user's to fix (422, or 413 for size) and carries its own
// message; anything else is ours. // message; anything else is ours.
func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID string, err error) { func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID string, err error) {
if a.abandoned(r) { if a.abandoned(w, r) {
return return
} }
var rej *uploads.RejectError var rej *uploads.RejectError