API client: Enable fallback on status code 501, too

When discussing #801, I remembered #794. While dealing with the
latter, I read the HTTP RFC, stumbling upon the following:

    When a request method is received
    that is unrecognized or not implemented by an origin server, the
    origin server SHOULD respond with the 501 (Not Implemented) status
    code.  When a request method is received that is known by an origin
    server but not allowed for the target resource, the origin server
    SHOULD respond with the 405 (Method Not Allowed) status code.

Concluding from that, it is possible that a server desiring a fallback
to GET will send a status code of 501. It is even preferred if that
server does not offer any resource to be used with the POST method.

Therefore, I think we should fallback to GET on a 501, too.

Signed-off-by: beorn7 <beorn@grafana.com>
This commit is contained in:
beorn7 2020-09-10 13:14:07 +02:00
parent 65c5578b2d
commit 64b4a9cf9d
2 changed files with 29 additions and 5 deletions

View File

@ -1004,7 +1004,8 @@ func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Respon
} }
// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request. // DoGetFallback will attempt to do the request as-is, and on a 405 or 501 it
// will fallback to a GET request.
func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) { func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Warnings, error) {
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode())) req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode()))
if err != nil { if err != nil {
@ -1013,7 +1014,7 @@ func (h *apiClientImpl) DoGetFallback(ctx context.Context, u *url.URL, args url.
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, body, warnings, err := h.Do(ctx, req) resp, body, warnings, err := h.Do(ctx, req)
if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed { if resp != nil && (resp.StatusCode == http.StatusMethodNotAllowed || resp.StatusCode == http.StatusNotImplemented) {
u.RawQuery = args.Encode() u.RawQuery = args.Encode()
req, err = http.NewRequest(http.MethodGet, u.String(), nil) req, err = http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil { if err != nil {

View File

@ -1447,12 +1447,19 @@ func TestDoGetFallback(t *testing.T) {
body, _ := json.Marshal(apiResp) body, _ := json.Marshal(apiResp)
if req.Method == http.MethodPost { if req.Method == http.MethodPost {
if req.URL.Path == "/blockPost" { if req.URL.Path == "/blockPost405" {
http.Error(w, string(body), http.StatusMethodNotAllowed) http.Error(w, string(body), http.StatusMethodNotAllowed)
return return
} }
} }
if req.Method == http.MethodPost {
if req.URL.Path == "/blockPost501" {
http.Error(w, string(body), http.StatusNotImplemented)
return
}
}
w.Write(body) w.Write(body)
})) }))
// Close the server when test finishes. // Close the server when test finishes.
@ -1483,8 +1490,24 @@ func TestDoGetFallback(t *testing.T) {
t.Fatalf("Mismatch in values") t.Fatalf("Mismatch in values")
} }
// Do a fallbcak to a get. // Do a fallback to a get on 405.
u.Path = "/blockPost" u.Path = "/blockPost405"
_, b, _, err = api.DoGetFallback(context.TODO(), u, v)
if err != nil {
t.Fatalf("Error doing local request: %v", err)
}
if err := json.Unmarshal(b, resp); err != nil {
t.Fatal(err)
}
if resp.Method != http.MethodGet {
t.Fatalf("Mismatch method")
}
if resp.Values != v.Encode() {
t.Fatalf("Mismatch in values")
}
// Do a fallback to a get on 501.
u.Path = "/blockPost501"
_, b, _, err = api.DoGetFallback(context.TODO(), u, v) _, b, _, err = api.DoGetFallback(context.TODO(), u, v)
if err != nil { if err != nil {
t.Fatalf("Error doing local request: %v", err) t.Fatalf("Error doing local request: %v", err)