Handle case where default codec cannot encode the response.

Signed-off-by: Charles Korn <charles.korn@grafana.com>
This commit is contained in:
Charles Korn 2023-02-24 14:44:50 +11:00
parent 374c3f4dec
commit 5d62640e9b
No known key found for this signature in database
2 changed files with 53 additions and 12 deletions

View file

@ -69,14 +69,15 @@ const (
type errorType string type errorType string
const ( const (
errorNone errorType = "" errorNone errorType = ""
errorTimeout errorType = "timeout" errorTimeout errorType = "timeout"
errorCanceled errorType = "canceled" errorCanceled errorType = "canceled"
errorExec errorType = "execution" errorExec errorType = "execution"
errorBadData errorType = "bad_data" errorBadData errorType = "bad_data"
errorInternal errorType = "internal" errorInternal errorType = "internal"
errorUnavailable errorType = "unavailable" errorUnavailable errorType = "unavailable"
errorNotFound errorType = "not_found" errorNotFound errorType = "not_found"
errorNotAcceptable errorType = "not_acceptable"
) )
var LocalhostRepresentations = []string{"127.0.0.1", "localhost", "::1"} var LocalhostRepresentations = []string{"127.0.0.1", "localhost", "::1"}
@ -1582,7 +1583,12 @@ func (api *API) respond(w http.ResponseWriter, req *http.Request, data interface
Warnings: warningStrings, Warnings: warningStrings,
} }
codec := api.negotiateCodec(req, resp) codec, err := api.negotiateCodec(req, resp)
if err != nil {
api.respondError(w, &apiError{errorNotAcceptable, err}, nil)
return
}
b, err := codec.Encode(resp) b, err := codec.Encode(resp)
if err != nil { if err != nil {
level.Error(api.logger).Log("msg", "error marshaling response", "err", err) level.Error(api.logger).Log("msg", "error marshaling response", "err", err)
@ -1597,16 +1603,21 @@ func (api *API) respond(w http.ResponseWriter, req *http.Request, data interface
} }
} }
func (api *API) negotiateCodec(req *http.Request, resp *Response) Codec { func (api *API) negotiateCodec(req *http.Request, resp *Response) (Codec, error) {
for _, clause := range goautoneg.ParseAccept(req.Header.Get("Accept")) { for _, clause := range goautoneg.ParseAccept(req.Header.Get("Accept")) {
for _, codec := range api.codecs { for _, codec := range api.codecs {
if codec.ContentType().Satisfies(clause) && codec.CanEncode(resp) { if codec.ContentType().Satisfies(clause) && codec.CanEncode(resp) {
return codec return codec, nil
} }
} }
} }
return api.codecs[0] defaultCodec := api.codecs[0]
if !defaultCodec.CanEncode(resp) {
return nil, fmt.Errorf("cannot encode response as %s", defaultCodec.ContentType())
}
return defaultCodec, nil
} }
func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) { func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) {
@ -1637,6 +1648,8 @@ func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data inter
code = http.StatusInternalServerError code = http.StatusInternalServerError
case errorNotFound: case errorNotFound:
code = http.StatusNotFound code = http.StatusNotFound
case errorNotAcceptable:
code = http.StatusNotAcceptable
default: default:
code = http.StatusInternalServerError code = http.StatusInternalServerError
} }

View file

@ -2856,6 +2856,34 @@ func TestRespondSuccess(t *testing.T) {
} }
} }
func TestRespondSuccess_DefaultCodecCannotEncodeResponse(t *testing.T) {
api := API{
logger: log.NewNopLogger(),
}
api.ClearCodecs()
api.InstallCodec(&testCodec{contentType: MIMEType{"application", "default-format"}, canEncode: false})
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
api.respond(w, r, "test", nil)
}))
defer s.Close()
req, err := http.NewRequest(http.MethodGet, s.URL, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
body, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
require.NoError(t, err)
require.Equal(t, http.StatusNotAcceptable, resp.StatusCode)
require.Equal(t, "application/json", resp.Header.Get("Content-Type"))
require.Equal(t, `{"status":"error","errorType":"not_acceptable","error":"cannot encode response as application/default-format"}`, string(body))
}
func TestRespondError(t *testing.T) { func TestRespondError(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
api := API{} api := API{}