Post query (#1)

* add POST method for /query

Signed-off-by: Nikhil Bhatia <nbhatia@microsoft.com>
This commit is contained in:
NIkhil Bhatia
2018-10-23 10:27:26 -07:00
committed by Torin Sandall
parent c4aeb2e07a
commit 93542dbbec
3 changed files with 102 additions and 25 deletions
+46 -1
View File
@@ -385,6 +385,7 @@ func (s *Server) initRouter() {
s.registerHandler(router, 1, "/policies/{path:.+}", http.MethodGet, promhttp.InstrumentHandlerDuration(v1PoliciesDur, http.HandlerFunc(s.v1PoliciesGet)))
s.registerHandler(router, 1, "/policies/{path:.+}", http.MethodPut, promhttp.InstrumentHandlerDuration(v1PoliciesDur, http.HandlerFunc(s.v1PoliciesPut)))
s.registerHandler(router, 1, "/query", http.MethodGet, promhttp.InstrumentHandlerDuration(v1QueryDur, http.HandlerFunc(s.v1QueryGet)))
s.registerHandler(router, 1, "/query", http.MethodPost, promhttp.InstrumentHandlerDuration(v1QueryDur, http.HandlerFunc(s.v1QueryPost)))
s.registerHandler(router, 1, "/compile", http.MethodPost, promhttp.InstrumentHandlerDuration(v1CompileDur, http.HandlerFunc(s.v1CompilePost)))
router.HandleFunc("/", promhttp.InstrumentHandlerDuration(indexDur, http.HandlerFunc(s.unversionedPost))).Methods(http.MethodPost)
router.HandleFunc("/", promhttp.InstrumentHandlerDuration(indexDur, http.HandlerFunc(s.indexGet))).Methods(http.MethodGet)
@@ -410,7 +411,7 @@ func (s *Server) initRouter() {
router.HandleFunc("/v1/query/{path:.*}", promhttp.InstrumentHandlerDuration(catchAllDur, http.HandlerFunc(writer.HTTPStatus(405)))).Methods(http.MethodHead,
http.MethodConnect, http.MethodDelete, http.MethodOptions, http.MethodTrace, http.MethodPost, http.MethodPut, http.MethodPatch)
router.HandleFunc("/v1/query", promhttp.InstrumentHandlerDuration(catchAllDur, http.HandlerFunc(writer.HTTPStatus(405)))).Methods(http.MethodHead,
http.MethodConnect, http.MethodDelete, http.MethodOptions, http.MethodTrace, http.MethodPost, http.MethodPut, http.MethodPatch)
http.MethodConnect, http.MethodDelete, http.MethodOptions, http.MethodTrace, http.MethodPut, http.MethodPatch)
s.Handler = router
}
@@ -1379,6 +1380,50 @@ func (s *Server) v1QueryGet(w http.ResponseWriter, r *http.Request) {
writer.JSON(w, 200, results, pretty)
}
func (s *Server) v1QueryPost(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var request types.QueryRequestV1
err := util.NewJSONDecoder(r.Body).Decode(&request)
if err != nil {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "error(s) occurred while decoding request: %v", err.Error()))
return
}
qStr := request.Query
unsafeBuiltins, err := validateQuery(qStr)
if err != nil {
writer.ErrorAuto(w, err)
return
} else if len(unsafeBuiltins) > 0 {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "unsafe built-in function calls in query: %v", strings.Join(unsafeBuiltins, ",")))
return
}
watch := getWatch(r.URL.Query()[types.ParamWatchV1])
if watch {
s.watchQuery(qStr, w, r, false)
return
}
pretty := getBoolParam(r.URL, types.ParamPrettyV1, true)
explainMode := getExplain(r.URL.Query()["explain"], types.ExplainOffV1)
includeMetrics := getBoolParam(r.URL, types.ParamMetricsV1, true)
includeInstrumentation := getBoolParam(r.URL, types.ParamInstrumentV1, true)
results, err := s.execQuery(ctx, r, qStr, nil, explainMode, includeMetrics, includeInstrumentation, pretty)
if err != nil {
switch err := err.(type) {
case ast.Errors:
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgCompileQueryError).WithASTErrors(err))
default:
writer.ErrorAuto(w, err)
}
return
}
writer.JSON(w, 200, results, pretty)
}
func (s *Server) watchQuery(query string, w http.ResponseWriter, r *http.Request, data bool) {
pretty := getBoolParam(r.URL, types.ParamPrettyV1, true)
explainMode := getExplain(r.URL.Query()["explain"], types.ExplainOffV1)
+51 -24
View File
@@ -140,7 +140,6 @@ func Test405StatusCodev1(t *testing.T) {
{http.MethodDelete, "/query", "", 405, ""},
{http.MethodOptions, "/query", "", 405, ""},
{http.MethodTrace, "/query", "", 405, ""},
{http.MethodPost, "/query", "", 405, ""},
{http.MethodPut, "/query", "", 405, ""},
{http.MethodPatch, "/query", "", 405, ""},
}},
@@ -1527,9 +1526,30 @@ func TestPoliciesPathSlashes(t *testing.T) {
}
}
func TestQueryWatchBasic(t *testing.T) {
func TestQueryPostBasic(t *testing.T) {
f := newFixture(t)
f.server, _ = New().
WithAddresses([]string{":8182"}).
WithStore(f.server.store).
WithManager(f.server.manager).
WithDiagnosticsBuffer(NewBoundedBuffer(8)).
Init(context.Background())
setup := []tr{
{http.MethodPost, "/query", `{"query": "a=data.k.x with data.k as {\"x\" : 7}"}`, 200, `{"result":[{"a":7}]}`},
}
for _, tr := range setup {
req := newReqV1(tr.method, tr.path, tr.body)
req.RemoteAddr = "testaddr"
if err := f.executeRequest(req, tr.code, tr.resp); err != nil {
t.Fatal(err)
}
}
}
func TestQueryWatchBasic(t *testing.T) {
// Test basic watch results.
exp := strings.Join([]string{
"HTTP/1.1 200 OK\nContent-Type: application/json\nTransfer-Encoding: chunked\n\n10",
@@ -1546,32 +1566,39 @@ func TestQueryWatchBasic(t *testing.T) {
`,
``,
}, "\r\n")
recorder := newMockConn()
get := newReqV1(http.MethodGet, `/query?q=a=data.x&watch`, "")
go f.server.Handler.ServeHTTP(recorder, get)
<-recorder.hijacked
<-recorder.write
tests := []trw{
{tr{http.MethodPut, "/data/x", `{"a":1,"b":2}`, 204, ""}, recorder.write},
{tr{http.MethodPut, "/data/x", `"foo"`, 204, ""}, recorder.write},
{tr{http.MethodPut, "/data/x", `7`, 204, ""}, recorder.write},
requests := []*http.Request{
newReqV1(http.MethodGet, `/query?q=a=data.x&watch`, ""),
newReqV1(http.MethodPost, `/query?&watch`, `{"query": "a=data.x"}`),
}
for _, test := range tests {
tr := test.tr
if err := f.v1(tr.method, tr.path, tr.body, tr.code, tr.resp); err != nil {
t.Fatal(err)
}
if test.wait != nil {
<-test.wait
}
}
recorder.Close()
for _, get := range requests {
f := newFixture(t)
recorder := newMockConn()
go f.server.Handler.ServeHTTP(recorder, get)
<-recorder.hijacked
<-recorder.write
if result := recorder.buf.String(); result != exp {
t.Fatalf("Expected stream to equal %s, got %s", exp, result)
tests := []trw{
{tr{http.MethodPut, "/data/x", `{"a":1,"b":2}`, 204, ""}, recorder.write},
{tr{http.MethodPut, "/data/x", `"foo"`, 204, ""}, recorder.write},
{tr{http.MethodPut, "/data/x", `7`, 204, ""}, recorder.write},
}
for _, test := range tests {
tr := test.tr
if err := f.v1(tr.method, tr.path, tr.body, tr.code, tr.resp); err != nil {
t.Fatal(err)
}
if test.wait != nil {
<-test.wait
}
}
recorder.Close()
if result := recorder.buf.String(); result != exp {
t.Fatalf("Expected stream to equal %s, got %s", exp, result)
}
}
}
+5
View File
@@ -376,6 +376,11 @@ type PartialEvaluationResultV1 struct {
Support []*ast.Module `json:"support,omitempty"`
}
// QueryRequestV1 models the request message for Query API operations.
type QueryRequestV1 struct {
Query string `json:"query"`
}
const (
// ParamQueryV1 defines the name of the HTTP URL parameter that specifies
// values for the request query.