Skip to content

Commit

Permalink
Add a POST handler for render-diff (#338)
Browse files Browse the repository at this point in the history
* initial changes for using POST

Signed-off-by: Yash Sharma <yashrsharma44@gmail.com>

* added a post handler for render-diff

Signed-off-by: Yash Sharma <yashrsharma44@gmail.com>

* added a test

Signed-off-by: Yash Sharma <yashrsharma44@gmail.com>

* fixed a nil ptr bug

Signed-off-by: Yash Sharma <yashrsharma44@gmail.com>

* make sure a helper method checks for an error, does not behave like a handler

Signed-off-by: Yash Sharma <yashrsharma44@gmail.com>

* change the json methods to camel case and refactor common logic

Signed-off-by: Yash Sharma <yashrsharma44@gmail.com>
  • Loading branch information
yashrsharma44 authored Aug 23, 2021
1 parent 673ae2b commit f3186f4
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 18 deletions.
11 changes: 7 additions & 4 deletions pkg/server/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,12 @@ func (ctrl *Controller) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
}
}

func (ctrl *Controller) expectJSON(w http.ResponseWriter, format string) (ok bool) {
func (ctrl *Controller) expectJSON(format string) error {
switch format {
case "json", "":
return true
return nil
default:
ctrl.writeInvalidParameterError(w, errUnknownFormat)
return false
return errUnknownFormat
}
}

Expand All @@ -358,6 +357,10 @@ func (ctrl *Controller) writeResponseJSON(w http.ResponseWriter, res interface{}
}
}

func (ctrl *Controller) writeInvalidMethodError(w http.ResponseWriter, err error) {
ctrl.writeError(w, http.StatusMethodNotAllowed, err, "method not supported")
}

func (ctrl *Controller) writeInvalidParameterError(w http.ResponseWriter, err error) {
ctrl.writeError(w, http.StatusBadRequest, err, "invalid parameter")
}
Expand Down
136 changes: 122 additions & 14 deletions pkg/server/render.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package server

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"runtime/debug"
"strconv"
"sync"
Expand All @@ -24,12 +24,18 @@ var (
errLabelIsRequired = errors.New("label parameter is required")
errNoData = errors.New("no data")
errTimeParamsAreRequired = errors.New("leftFrom,leftUntil,rightFrom,rightUntil are required")
errMethodNotAllowed = errors.New("Method not allowed")
)

type renderParams struct {
format string
maxNodes int
gi *storage.GetInput

leftStartTime time.Time
leftEndTime time.Time
rghtStartTime time.Time
rghtEndTime time.Time
}

func (ctrl *Controller) renderHandler(w http.ResponseWriter, r *http.Request) {
Expand All @@ -38,7 +44,9 @@ func (ctrl *Controller) renderHandler(w http.ResponseWriter, r *http.Request) {
ctrl.writeInvalidParameterError(w, err)
return
}
if ok := ctrl.expectJSON(w, p.format); !ok {

if err := ctrl.expectJSON(p.format); err != nil {
ctrl.writeInvalidParameterError(w, errUnknownFormat)
return
}

Expand All @@ -59,17 +67,41 @@ func (ctrl *Controller) renderHandler(w http.ResponseWriter, r *http.Request) {
}

func (ctrl *Controller) renderDiffHandler(w http.ResponseWriter, r *http.Request) {
var p renderParams
if err := ctrl.renderParametersFromRequest(r, &p); err != nil {
ctrl.writeInvalidParameterError(w, err)
return
}
if ok := ctrl.expectJSON(w, p.format); !ok {

var (
p renderParams
rP RenderDiffParams

leftStartParam string
leftEndParam string
rghtStartParam string
rghtEndParam string
)

switch r.Method {
case http.MethodGet:
if err := ctrl.renderParametersFromRequest(r, &p); err != nil {
ctrl.writeInvalidParameterError(w, err)
return
}
leftStartParam, leftEndParam = "leftFrom", "leftUntil"
rghtStartParam, rghtEndParam = "rightFrom", "rightUntil"

case http.MethodPost:
if err := ctrl.renderParametersFromRequestBody(r, &p, &rP); err != nil {
ctrl.writeInvalidParameterError(w, err)
return
}
leftStartParam, leftEndParam = rP.Left.From, rP.Left.Until
rghtStartParam, rghtEndParam = rP.Right.From, rP.Right.Until

default:
ctrl.writeInvalidMethodError(w, errMethodNotAllowed)
return
}

leftStartTime, leftEndTime, leftOK := parseRenderRangeParams(r.URL.Query(), "leftFrom", "leftUntil")
rghtStartTime, rghtEndTime, rghtOK := parseRenderRangeParams(r.URL.Query(), "rightFrom", "rightUntil")
leftStartTime, leftEndTime, leftOK := parseRenderRangeParams(r, leftStartParam, leftEndParam)
rghtStartTime, rghtEndTime, rghtOK := parseRenderRangeParams(r, rghtStartParam, rghtEndParam)
if !leftOK || !rghtOK {
ctrl.writeInvalidParameterError(w, errTimeParamsAreRequired)
return
Expand Down Expand Up @@ -124,6 +156,52 @@ func (ctrl *Controller) renderParametersFromRequest(r *http.Request, p *renderPa
p.gi.StartTime = attime.Parse(v.Get("from"))
p.gi.EndTime = attime.Parse(v.Get("until"))
p.format = v.Get("format")

if err := ctrl.expectJSON(p.format); err != nil {
return err
}

return nil
}

func (ctrl *Controller) renderParametersFromRequestBody(r *http.Request, p *renderParams, rP *RenderDiffParams) error {

decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(rP); err != nil {
return err
}

p.gi = new(storage.GetInput)
switch {
case rP.Name == nil && rP.Query == nil:
return fmt.Errorf("'query' or 'name' parameter is required")
case rP.Name != nil:
sk, err := segment.ParseKey(*rP.Name)
if err != nil {
return fmt.Errorf("name: parsing storage key: %w", err)
}
p.gi.Key = sk
case rP.Query != nil:
qry, err := flameql.ParseQuery(*rP.Query)
if err != nil {
return fmt.Errorf("query: %w", err)
}
p.gi.Query = qry
}

p.maxNodes = ctrl.config.MaxNodesRender
if rP.MaxNodes != nil && *rP.MaxNodes > 0 {
p.maxNodes = *rP.MaxNodes
}

p.gi.StartTime = attime.Parse(rP.From)
p.gi.EndTime = attime.Parse(rP.Until)
p.format = rP.Format

if err := ctrl.expectJSON(p.format); err != nil {
return err
}

return nil
}

Expand All @@ -145,10 +223,20 @@ func renderResponse(fs *tree.Flamebearer, out *storage.GetOutput) map[string]int
return res
}

func parseRenderRangeParams(v url.Values, from, until string) (startTime, endTime time.Time, ok bool) {
fromStr, untilStr := v.Get(from), v.Get(until)
startTime, endTime = attime.Parse(fromStr), attime.Parse(untilStr)
return startTime, endTime, fromStr != "" || untilStr != ""
func parseRenderRangeParams(r *http.Request, from, until string) (startTime, endTime time.Time, ok bool) {

switch r.Method {
case http.MethodGet:
fromStr, untilStr := r.URL.Query().Get(from), r.URL.Query().Get(until)
startTime, endTime = attime.Parse(fromStr), attime.Parse(untilStr)
return startTime, endTime, fromStr != "" || untilStr != ""
case http.MethodPost:
startTime, endTime = attime.Parse(from), attime.Parse(until)
return startTime, endTime, from != "" || until != ""
}

return time.Now(), time.Now(), false

}

func (ctrl *Controller) loadTreeConcurrently(
Expand Down Expand Up @@ -198,3 +286,23 @@ func (ctrl *Controller) loadTree(gi *storage.GetInput, startTime, endTime time.T
}
return out, nil
}

// Request Body Interface
type RenderDiffParams struct {
Name *string `json:"name,omitempty"`
Query *string `json:"query,omitempty"`

From string `json:"from"`
Until string `json:"until"`

Format string `json:"format"`
MaxNodes *int `json:"maxNodes,omitempty"`

Left RenderTreeParams `json:"leftParams"`
Right RenderTreeParams `json:"rightParams"`
}

type RenderTreeParams struct {
From string `json:"from"`
Until string `json:"until"`
}
54 changes: 54 additions & 0 deletions pkg/server/render_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package server

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
Expand All @@ -17,6 +19,48 @@ import (
)

var _ = Describe("server", func() {

query := "pyroscope.server.alloc_objects{}"
formedBody := &RenderDiffParams{
Query: &query,
From: "now-1h",
Until: "now",
Left: RenderTreeParams{
From: "now-1h",
Until: "now",
},

Right: RenderTreeParams{
From: "now-30m",
Until: "now",
},
Format: "json",
}

formedBodyJSON, err := json.Marshal(formedBody)
if err != nil {
panic(err)
}

malFormedBody := &RenderDiffParams{

Until: "now",
Left: RenderTreeParams{
From: "now-1h",
Until: "now",
},

Right: RenderTreeParams{
From: "",
Until: "now",
},
}

malFormedBodyJSON, err := json.Marshal(malFormedBody)
if err != nil {
panic(err)
}

testing.WithConfig(func(cfg **config.Config) {
Context("/render", func() {
It("supports name and query parameters", func() {
Expand Down Expand Up @@ -44,6 +88,16 @@ var _ = Describe("server", func() {
resp, err = http.Get(fmt.Sprintf("%s/render", httpServer.URL))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))

// POST Method
resp, err = http.Post(fmt.Sprintf("%s/render-diff", httpServer.URL), "application/json", bytes.NewBuffer(formedBodyJSON))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusOK))

resp, err = http.Post(fmt.Sprintf("%s/render-diff", httpServer.URL), "application/json", bytes.NewBuffer(malFormedBodyJSON))
Expect(err).ToNot(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))

})
})
})
Expand Down

0 comments on commit f3186f4

Please sign in to comment.