From 9d7afb2bcbf293316e1d6ed9171410d331e715c7 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 2 Oct 2026 17:47:13 +0200 Subject: [PATCH] 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 --- internal/web/uploads.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/internal/web/uploads.go b/internal/web/uploads.go index fa48d5b..1558523 100644 --- a/internal/web/uploads.go +++ b/internal/web/uploads.go @@ -95,7 +95,7 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) { } if err != nil { var tooBig *http.MaxBytesError - if a.abandoned(r) { + if a.abandoned(w, r) { return } if !errors.As(err, &tooBig) { @@ -113,6 +113,9 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) { case "title": b, err := io.ReadAll(io.LimitReader(part, maxTitleRunes*utf8.UTFMax)) 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.") 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.") } -// 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 // 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 { return false } 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 } @@ -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 // message; anything else is ours. func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID string, err error) { - if a.abandoned(r) { + if a.abandoned(w, r) { return } var rej *uploads.RejectError