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
const (
errorNone errorType = ""
errorTimeout errorType = "timeout"
errorCanceled errorType = "canceled"
errorExec errorType = "execution"
errorBadData errorType = "bad_data"
errorInternal errorType = "internal"
errorUnavailable errorType = "unavailable"
errorNotFound errorType = "not_found"
errorNone errorType = ""
errorTimeout errorType = "timeout"
errorCanceled errorType = "canceled"
errorExec errorType = "execution"
errorBadData errorType = "bad_data"
errorInternal errorType = "internal"
errorUnavailable errorType = "unavailable"
errorNotFound errorType = "not_found"
errorNotAcceptable errorType = "not_acceptable"
)
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,
}
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)
if err != nil {
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 _, codec := range api.codecs {
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{}) {
@ -1637,6 +1648,8 @@ func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data inter
code = http.StatusInternalServerError
case errorNotFound:
code = http.StatusNotFound
case errorNotAcceptable:
code = http.StatusNotAcceptable
default:
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) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
api := API{}