Answer 499 to a client that goes away while waiting for an image (closes #339)
check / check (push) Canceled after 0s
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
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -19,6 +20,12 @@ import (
|
||||
"sneak.berlin/go/pixa/internal/magic"
|
||||
)
|
||||
|
||||
// statusClientClosedRequest is the status nginx calls "client closed
|
||||
// request". The image routes answer it when the client goes away before its
|
||||
// image is ready, unless its request is the one making the image. Nobody reads
|
||||
// it; it is for logs and metrics.
|
||||
const statusClientClosedRequest = 499
|
||||
|
||||
// HandleImage handles the main image proxy route:
|
||||
// /v1/image/<host>/<path>/<width>x<height>.<format>, or with no format
|
||||
// /v1/image/<host>/<path>/<width>x<height>
|
||||
@@ -345,7 +352,8 @@ func notModified(w http.ResponseWriter, r *http.Request, etag string) bool {
|
||||
// serveImage serves the image that req asks for. Both image routes call it
|
||||
// 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, and answers an error
|
||||
// If-None-Match with 304 and HEAD with the headers only. It answers 499 when
|
||||
// the image service stopped because the client went away, and any other error
|
||||
// of the image service with respondImageError.
|
||||
func (s *Handlers) serveImage(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
@@ -362,6 +370,21 @@ func (s *Handlers) serveImage(
|
||||
startTime := time.Now()
|
||||
|
||||
resp, err := s.imgSvc.Get(r.Context(), req)
|
||||
|
||||
// The client went away before its image was ready, which is not pixa's
|
||||
// failure. A cancellation while the request's context is live is pixa's,
|
||||
// and respondImageError answers it 500.
|
||||
if errors.Is(err, context.Canceled) && r.Context().Err() != nil {
|
||||
s.log.Info("client went away before the image was ready",
|
||||
"request_id", middleware.GetReqID(r.Context()),
|
||||
"host", req.Source.Host,
|
||||
"path", req.Source.Path,
|
||||
)
|
||||
w.WriteHeader(statusClientClosedRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.respondImageError(w, req, err)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user