2 Commits
Author SHA1 Message Date
sneak 1d493df0b7 Answer 499 to a client that goes away while waiting for an image (closes #339)
check / check (push) Canceled after 0s
When a request waits for an image another request is making and its
client goes away, the image proxy returns context.Canceled. serveImage
now answers it 499, the status nginx calls "client closed request". It
logs it once at info level with the request_id, host and path. It was
answered 500 and logged at error level, as pixa's own failure. A
cancellation while the request's context is live is still answered 500.
The request making the image is still answered as before. README.md
lists the 499 on both image routes.

Model: opus-5-5
2026-10-26 22:46:43 +00:00
sneak 9ea00f51b0 Test that a client leaving while another request makes its image gets 499
On both image routes, a request waits for an image another request is
making, and its client goes away. The first test wants that request
answered 499 and logged once at info level. It fails on next, which
answers 500 and logs it at error level. The second test pins the 500
that a cancellation keeps while the request's context is live; it
passes on next. The fix follows in the next commit.

Model: opus-5-5
2026-10-26 22:45:26 +00:00
4 changed files with 13 additions and 10 deletions
+2 -1
View File
@@ -310,7 +310,8 @@ path under `/v1/` answers 200, in maintenance mode too.
- 403 when the upstream host, or a host it redirects to, has an address in a
blocked network (see `blocked_networks`).
- 429 over `image_requests_per_minute` (below).
- 499 when the client goes away before the image is ready.
- 499 when the client goes away while waiting for an image another request
is making (below).
- 415 when the upstream answers with a `Content-Type` that is not one of the
source formats (below).
- 415 when the upstream sends a body whose first bytes are not those of the
+4 -3
View File
@@ -30,9 +30,10 @@ P2: security: per-origin rate limiting
# Completed Steps
- 2026-10-26 on both image routes, a request whose client went away before its
image was ready is answered 499 and logged at info level (closes #339). It was
answered 500 and logged at error level, as pixa's own failure.
- 2026-10-26 on both image routes, a request is answered 499 and logged at info
level when its client goes away while waiting for an image another request is
making (closes #339). It was answered 500 and logged at error level, as pixa's
own failure.
- The line is "client went away before the image was ready". It carries the
`request_id`, the host and the path.
- The 499 has no body, since nobody reads it.
@@ -109,8 +109,8 @@ func TestImageRoutes_ClientGoneAnswers499(t *testing.T) {
close(fetcher.release)
<-firstDone
if rec.Code != statusClientClosedRequest {
t.Errorf("status = %d, want %d", rec.Code, statusClientClosedRequest)
if rec.Code != 499 {
t.Errorf("status = %d, want 499", rec.Code)
}
var clientGoneLines int
+5 -4
View File
@@ -21,8 +21,9 @@ import (
)
// statusClientClosedRequest is the status nginx calls "client closed
// request". The image routes answer it to a request whose client went away
// before its image was ready. Nobody reads it; it is for logs and metrics.
// request". The image routes answer it when the client goes away while
// waiting for an image another request is making. Nobody reads it; it is for
// logs and metrics.
const statusClientClosedRequest = 499
// HandleImage handles the main image proxy route:
@@ -352,8 +353,8 @@ func notModified(w http.ResponseWriter, r *http.Request, etag string) bool {
// once they have the request and its URL's expiry, which ends the
// Cache-Control max-age. It chooses the format for the format auto, answers
// If-None-Match with 304 and HEAD with the headers only. It answers 499 when
// the client went away before the image was ready, and any other error of
// the image service with respondImageError.
// the client goes away while waiting for an image another request is making.
// It answers any other error of the image service with respondImageError.
func (s *Handlers) serveImage(
w http.ResponseWriter, r *http.Request,
req *imagerequest.Request, expires time.Time,