From a4daf0098cbdb8d67db6a1cef73c7e7956edef05 Mon Sep 17 00:00:00 2001 From: Thomas Jackson Date: Tue, 30 Apr 2019 00:13:48 -0700 Subject: [PATCH] Implement POST with get fallback for Query/QueryRange (#557) * Implement POST with get fallback for Query/QueryRange Signed-off-by: Thomas Jackson --- api/client.go | 22 +++++++++++ api/client_test.go | 72 +++++++++++++++++++++++++++++++++++ api/prometheus/v1/api.go | 16 +------- api/prometheus/v1/api_test.go | 10 ++--- go.mod | 1 + go.sum | 13 +++++++ 6 files changed, 115 insertions(+), 19 deletions(-) diff --git a/api/client.go b/api/client.go index f7ca60b..db78ce2 100644 --- a/api/client.go +++ b/api/client.go @@ -58,6 +58,28 @@ type Client interface { Do(context.Context, *http.Request) (*http.Response, []byte, error) } +// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request. +func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, error) { + req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode())) + if err != nil { + return nil, nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, body, err := c.Do(ctx, req) + if resp != nil && resp.StatusCode == http.StatusMethodNotAllowed { + u.RawQuery = args.Encode() + req, err = http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return nil, nil, err + } + + } else { + return resp, body, err + } + return c.Do(ctx, req) +} + // NewClient returns a new Client. // // It is safe to use the returned Client from multiple goroutines. diff --git a/api/client_test.go b/api/client_test.go index 47094fc..7877e6a 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -14,9 +14,14 @@ package api import ( + "context" + "encoding/json" "net/http" + "net/http/httptest" "net/url" "testing" + + "github.com/prometheus/tsdb/testutil" ) func TestConfig(t *testing.T) { @@ -111,3 +116,70 @@ func TestClientURL(t *testing.T) { } } } + +func TestDoGetFallback(t *testing.T) { + v := url.Values{"a": []string{"1", "2"}} + + type testResponse struct { + Values string + Method string + } + + // Start a local HTTP server. + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + req.ParseForm() + r := &testResponse{ + Values: req.Form.Encode(), + Method: req.Method, + } + + body, _ := json.Marshal(r) + + if req.Method == http.MethodPost { + if req.URL.Path == "/blockPost" { + http.Error(w, string(body), http.StatusMethodNotAllowed) + return + } + } + + w.Write(body) + })) + // Close the server when test finishes. + defer server.Close() + + u, err := url.Parse(server.URL) + testutil.Ok(t, err) + client := &httpClient{client: *(server.Client())} + + // Do a post, and ensure that the post succeeds. + _, b, err := DoGetFallback(client, context.TODO(), u, v) + if err != nil { + t.Fatalf("Error doing local request: %v", err) + } + resp := &testResponse{} + if err := json.Unmarshal(b, resp); err != nil { + testutil.Ok(t, err) + } + if resp.Method != http.MethodPost { + t.Fatalf("Mismatch method") + } + if resp.Values != v.Encode() { + t.Fatalf("Mismatch in values") + } + + // Do a fallbcak to a get. + u.Path = "/blockPost" + _, b, err = DoGetFallback(client, context.TODO(), u, v) + if err != nil { + t.Fatalf("Error doing local request: %v", err) + } + if err := json.Unmarshal(b, resp); err != nil { + testutil.Ok(t, err) + } + if resp.Method != http.MethodGet { + t.Fatalf("Mismatch method") + } + if resp.Values != v.Encode() { + t.Fatalf("Mismatch in values") + } +} diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index c1cf47e..187527f 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -540,12 +540,7 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model. u.RawQuery = q.Encode() - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { - return nil, err - } - - _, body, err := h.client.Do(ctx, req) + _, body, err := api.DoGetFallback(h.client, ctx, u, q) if err != nil { return nil, err } @@ -571,14 +566,7 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model. q.Set("end", end) q.Set("step", step) - u.RawQuery = q.Encode() - - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { - return nil, err - } - - _, body, err := h.client.Do(ctx, req) + _, body, err := api.DoGetFallback(h.client, ctx, u, q) if err != nil { return nil, err } diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index b3204a9..f195f2b 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -179,7 +179,7 @@ func TestAPIs(t *testing.T) { }, }, - reqMethod: "GET", + reqMethod: "POST", reqPath: "/api/v1/query", reqParam: url.Values{ "query": []string{"2"}, @@ -194,7 +194,7 @@ func TestAPIs(t *testing.T) { do: doQuery("2", testTime), inErr: fmt.Errorf("some error"), - reqMethod: "GET", + reqMethod: "POST", reqPath: "/api/v1/query", reqParam: url.Values{ "query": []string{"2"}, @@ -212,7 +212,7 @@ func TestAPIs(t *testing.T) { Detail: "some body", }, - reqMethod: "GET", + reqMethod: "POST", reqPath: "/api/v1/query", reqParam: url.Values{ "query": []string{"2"}, @@ -230,7 +230,7 @@ func TestAPIs(t *testing.T) { Detail: "some body", }, - reqMethod: "GET", + reqMethod: "POST", reqPath: "/api/v1/query", reqParam: url.Values{ "query": []string{"2"}, @@ -247,7 +247,7 @@ func TestAPIs(t *testing.T) { }), inErr: fmt.Errorf("some error"), - reqMethod: "GET", + reqMethod: "POST", reqPath: "/api/v1/query_range", reqParam: url.Values{ "query": []string{"2"}, diff --git a/go.mod b/go.mod index 37f7f40..2168f76 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 github.com/prometheus/common v0.3.0 github.com/prometheus/procfs v0.0.0-20190412120340-e22ddced7142 + github.com/prometheus/tsdb v0.7.1 ) diff --git a/go.sum b/go.sum index 235ab95..d998271 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,18 @@ +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -15,10 +21,12 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -26,12 +34,16 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.3.0 h1:taZ4h8Tkxv2kNyoSctBvfXEHmBmxrwmIidZTIaHons4= github.com/prometheus/common v0.3.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190412120340-e22ddced7142 h1:JO6VBMEDSBX/LT4GKwSdvuFigZNwVD4lkPyUE4BDCKE= github.com/prometheus/procfs v0.0.0-20190412120340-e22ddced7142/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -41,6 +53,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=