Skip to content

Commit

Permalink
Post query (#1)
Browse files Browse the repository at this point in the history
* add POST method for /query

Signed-off-by: Nikhil Bhatia <nbhatia@microsoft.com>
  • Loading branch information
rite2nikhil authored and tsandall committed Oct 23, 2018
1 parent c4aeb2e commit 93542db
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 23 deletions.
47 changes: 46 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
71 changes: 49 additions & 22 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, ""},
}},
Expand Down Expand Up @@ -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",
Expand All @@ -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)
for _, get := range requests {
f := newFixture(t)
recorder := newMockConn()
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},
}
if test.wait != nil {
<-test.wait

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()
recorder.Close()

if result := recorder.buf.String(); result != exp {
t.Fatalf("Expected stream to equal %s, got %s", exp, result)
if result := recorder.buf.String(); result != exp {
t.Fatalf("Expected stream to equal %s, got %s", exp, result)
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions server/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 93542db

Please sign in to comment.