diff --git a/httptransport/error.go b/httptransport/error.go index f24a1bb110..dc2e1cfa1e 100644 --- a/httptransport/error.go +++ b/httptransport/error.go @@ -1,89 +1,40 @@ package httptransport import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net/http" - "github.com/quay/zlog" + "github.com/quay/clair/v4/httptransport/internal/details" ) -// StatusClientClosedRequest is a nonstandard HTTP status code used when the -// client has gone away. -// -// This convention is cribbed from Nginx. -const statusClientClosedRequest = 499 - -// ApiError writes an untyped (that is, "application/json") error with the -// provided HTTP status code and message. +// ApiError writes an error with the provided HTTP status code and message. // // ApiError does not return, but instead causes the goroutine to exit. +// +// Deprecated: This is implemented via [details.Error], which provides a +// richer API. func apiError(ctx context.Context, w http.ResponseWriter, code int, f string, v ...interface{}) { - const errheader = `Clair-Error` - disconnect := false - select { - case <-ctx.Done(): - disconnect = true - default: - } - if ev := zlog.Debug(ctx); ev.Enabled() { - ev. - Bool("disconnect", disconnect). - Int("code", code). - Str("error", fmt.Sprintf(f, v...)). - Msg("http error response") - } else { - ev.Send() - } - if disconnect { - // Exit immediately if there's no client to read the response, anyway. - w.WriteHeader(statusClientClosedRequest) - panic(http.ErrAbortHandler) + err := genericError{ + status: code, + err: fmt.Errorf(f, v...), } + details.Error(ctx, w, &err) +} + +type genericError struct { + status int + err error +} - h := w.Header() - h.Del("link") - h.Set("content-type", "application/json") - h.Set("x-content-type-options", "nosniff") - h.Set("trailer", errheader) - w.WriteHeader(code) +func (e *genericError) Error() string { + return e.err.Error() +} - var buf bytes.Buffer - buf.WriteString(`{"code":"`) - switch code { - case http.StatusBadRequest: - buf.WriteString("bad-request") - case http.StatusMethodNotAllowed: - buf.WriteString("method-not-allowed") - case http.StatusNotFound: - buf.WriteString("not-found") - case http.StatusTooManyRequests: - buf.WriteString("too-many-requests") - default: - buf.WriteString("internal-error") - } - buf.WriteByte('"') - if f != "" { - buf.WriteString(`,"message":`) - b, _ := json.Marshal(fmt.Sprintf(f, v...)) // OK use of encoding/json. - buf.Write(b) - } - buf.WriteByte('}') +func (e *genericError) Unwrap() error { + return e.err +} - if _, err := buf.WriteTo(w); err != nil { - h.Set(errheader, err.Error()) - } - switch err := http.NewResponseController(w).Flush(); { - case errors.Is(err, nil): - case errors.Is(err, http.ErrNotSupported): - // Skip - default: - zlog.Warn(ctx). - Err(err). - Msg("unable to flush http response") - } - panic(http.ErrAbortHandler) +func (e *genericError) ErrorStatus() int { + return e.status } diff --git a/httptransport/error_test.go b/httptransport/error_test.go index 3442baff88..233ed79a57 100644 --- a/httptransport/error_test.go +++ b/httptransport/error_test.go @@ -52,7 +52,7 @@ func TestClientDisconnect(t *testing.T) { } <-handlerDone - if got, want := status, statusClientClosedRequest; got != want { + if got, want := status, 499; got != want { t.Errorf("bad status code recorded: got: %d, want: %d", got, want) } }