Skip to content

Commit f3186f4

Browse files
Add a POST handler for render-diff (#338)
* 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>
1 parent 673ae2b commit f3186f4

File tree

3 files changed

+183
-18
lines changed

3 files changed

+183
-18
lines changed

pkg/server/controller.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,12 @@ func (ctrl *Controller) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
341341
}
342342
}
343343

344-
func (ctrl *Controller) expectJSON(w http.ResponseWriter, format string) (ok bool) {
344+
func (ctrl *Controller) expectJSON(format string) error {
345345
switch format {
346346
case "json", "":
347-
return true
347+
return nil
348348
default:
349-
ctrl.writeInvalidParameterError(w, errUnknownFormat)
350-
return false
349+
return errUnknownFormat
351350
}
352351
}
353352

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

360+
func (ctrl *Controller) writeInvalidMethodError(w http.ResponseWriter, err error) {
361+
ctrl.writeError(w, http.StatusMethodNotAllowed, err, "method not supported")
362+
}
363+
361364
func (ctrl *Controller) writeInvalidParameterError(w http.ResponseWriter, err error) {
362365
ctrl.writeError(w, http.StatusBadRequest, err, "invalid parameter")
363366
}

pkg/server/render.go

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package server
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67
"net/http"
7-
"net/url"
88
"runtime/debug"
99
"strconv"
1010
"sync"
@@ -24,12 +24,18 @@ var (
2424
errLabelIsRequired = errors.New("label parameter is required")
2525
errNoData = errors.New("no data")
2626
errTimeParamsAreRequired = errors.New("leftFrom,leftUntil,rightFrom,rightUntil are required")
27+
errMethodNotAllowed = errors.New("Method not allowed")
2728
)
2829

2930
type renderParams struct {
3031
format string
3132
maxNodes int
3233
gi *storage.GetInput
34+
35+
leftStartTime time.Time
36+
leftEndTime time.Time
37+
rghtStartTime time.Time
38+
rghtEndTime time.Time
3339
}
3440

3541
func (ctrl *Controller) renderHandler(w http.ResponseWriter, r *http.Request) {
@@ -38,7 +44,9 @@ func (ctrl *Controller) renderHandler(w http.ResponseWriter, r *http.Request) {
3844
ctrl.writeInvalidParameterError(w, err)
3945
return
4046
}
41-
if ok := ctrl.expectJSON(w, p.format); !ok {
47+
48+
if err := ctrl.expectJSON(p.format); err != nil {
49+
ctrl.writeInvalidParameterError(w, errUnknownFormat)
4250
return
4351
}
4452

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

6169
func (ctrl *Controller) renderDiffHandler(w http.ResponseWriter, r *http.Request) {
62-
var p renderParams
63-
if err := ctrl.renderParametersFromRequest(r, &p); err != nil {
64-
ctrl.writeInvalidParameterError(w, err)
65-
return
66-
}
67-
if ok := ctrl.expectJSON(w, p.format); !ok {
70+
71+
var (
72+
p renderParams
73+
rP RenderDiffParams
74+
75+
leftStartParam string
76+
leftEndParam string
77+
rghtStartParam string
78+
rghtEndParam string
79+
)
80+
81+
switch r.Method {
82+
case http.MethodGet:
83+
if err := ctrl.renderParametersFromRequest(r, &p); err != nil {
84+
ctrl.writeInvalidParameterError(w, err)
85+
return
86+
}
87+
leftStartParam, leftEndParam = "leftFrom", "leftUntil"
88+
rghtStartParam, rghtEndParam = "rightFrom", "rightUntil"
89+
90+
case http.MethodPost:
91+
if err := ctrl.renderParametersFromRequestBody(r, &p, &rP); err != nil {
92+
ctrl.writeInvalidParameterError(w, err)
93+
return
94+
}
95+
leftStartParam, leftEndParam = rP.Left.From, rP.Left.Until
96+
rghtStartParam, rghtEndParam = rP.Right.From, rP.Right.Until
97+
98+
default:
99+
ctrl.writeInvalidMethodError(w, errMethodNotAllowed)
68100
return
69101
}
70102

71-
leftStartTime, leftEndTime, leftOK := parseRenderRangeParams(r.URL.Query(), "leftFrom", "leftUntil")
72-
rghtStartTime, rghtEndTime, rghtOK := parseRenderRangeParams(r.URL.Query(), "rightFrom", "rightUntil")
103+
leftStartTime, leftEndTime, leftOK := parseRenderRangeParams(r, leftStartParam, leftEndParam)
104+
rghtStartTime, rghtEndTime, rghtOK := parseRenderRangeParams(r, rghtStartParam, rghtEndParam)
73105
if !leftOK || !rghtOK {
74106
ctrl.writeInvalidParameterError(w, errTimeParamsAreRequired)
75107
return
@@ -124,6 +156,52 @@ func (ctrl *Controller) renderParametersFromRequest(r *http.Request, p *renderPa
124156
p.gi.StartTime = attime.Parse(v.Get("from"))
125157
p.gi.EndTime = attime.Parse(v.Get("until"))
126158
p.format = v.Get("format")
159+
160+
if err := ctrl.expectJSON(p.format); err != nil {
161+
return err
162+
}
163+
164+
return nil
165+
}
166+
167+
func (ctrl *Controller) renderParametersFromRequestBody(r *http.Request, p *renderParams, rP *RenderDiffParams) error {
168+
169+
decoder := json.NewDecoder(r.Body)
170+
if err := decoder.Decode(rP); err != nil {
171+
return err
172+
}
173+
174+
p.gi = new(storage.GetInput)
175+
switch {
176+
case rP.Name == nil && rP.Query == nil:
177+
return fmt.Errorf("'query' or 'name' parameter is required")
178+
case rP.Name != nil:
179+
sk, err := segment.ParseKey(*rP.Name)
180+
if err != nil {
181+
return fmt.Errorf("name: parsing storage key: %w", err)
182+
}
183+
p.gi.Key = sk
184+
case rP.Query != nil:
185+
qry, err := flameql.ParseQuery(*rP.Query)
186+
if err != nil {
187+
return fmt.Errorf("query: %w", err)
188+
}
189+
p.gi.Query = qry
190+
}
191+
192+
p.maxNodes = ctrl.config.MaxNodesRender
193+
if rP.MaxNodes != nil && *rP.MaxNodes > 0 {
194+
p.maxNodes = *rP.MaxNodes
195+
}
196+
197+
p.gi.StartTime = attime.Parse(rP.From)
198+
p.gi.EndTime = attime.Parse(rP.Until)
199+
p.format = rP.Format
200+
201+
if err := ctrl.expectJSON(p.format); err != nil {
202+
return err
203+
}
204+
127205
return nil
128206
}
129207

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

148-
func parseRenderRangeParams(v url.Values, from, until string) (startTime, endTime time.Time, ok bool) {
149-
fromStr, untilStr := v.Get(from), v.Get(until)
150-
startTime, endTime = attime.Parse(fromStr), attime.Parse(untilStr)
151-
return startTime, endTime, fromStr != "" || untilStr != ""
226+
func parseRenderRangeParams(r *http.Request, from, until string) (startTime, endTime time.Time, ok bool) {
227+
228+
switch r.Method {
229+
case http.MethodGet:
230+
fromStr, untilStr := r.URL.Query().Get(from), r.URL.Query().Get(until)
231+
startTime, endTime = attime.Parse(fromStr), attime.Parse(untilStr)
232+
return startTime, endTime, fromStr != "" || untilStr != ""
233+
case http.MethodPost:
234+
startTime, endTime = attime.Parse(from), attime.Parse(until)
235+
return startTime, endTime, from != "" || until != ""
236+
}
237+
238+
return time.Now(), time.Now(), false
239+
152240
}
153241

154242
func (ctrl *Controller) loadTreeConcurrently(
@@ -198,3 +286,23 @@ func (ctrl *Controller) loadTree(gi *storage.GetInput, startTime, endTime time.T
198286
}
199287
return out, nil
200288
}
289+
290+
// Request Body Interface
291+
type RenderDiffParams struct {
292+
Name *string `json:"name,omitempty"`
293+
Query *string `json:"query,omitempty"`
294+
295+
From string `json:"from"`
296+
Until string `json:"until"`
297+
298+
Format string `json:"format"`
299+
MaxNodes *int `json:"maxNodes,omitempty"`
300+
301+
Left RenderTreeParams `json:"leftParams"`
302+
Right RenderTreeParams `json:"rightParams"`
303+
}
304+
305+
type RenderTreeParams struct {
306+
From string `json:"from"`
307+
Until string `json:"until"`
308+
}

pkg/server/render_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package server
22

33
import (
4+
"bytes"
5+
"encoding/json"
46
"fmt"
57
"net/http"
68
"net/http/httptest"
@@ -17,6 +19,48 @@ import (
1719
)
1820

1921
var _ = Describe("server", func() {
22+
23+
query := "pyroscope.server.alloc_objects{}"
24+
formedBody := &RenderDiffParams{
25+
Query: &query,
26+
From: "now-1h",
27+
Until: "now",
28+
Left: RenderTreeParams{
29+
From: "now-1h",
30+
Until: "now",
31+
},
32+
33+
Right: RenderTreeParams{
34+
From: "now-30m",
35+
Until: "now",
36+
},
37+
Format: "json",
38+
}
39+
40+
formedBodyJSON, err := json.Marshal(formedBody)
41+
if err != nil {
42+
panic(err)
43+
}
44+
45+
malFormedBody := &RenderDiffParams{
46+
47+
Until: "now",
48+
Left: RenderTreeParams{
49+
From: "now-1h",
50+
Until: "now",
51+
},
52+
53+
Right: RenderTreeParams{
54+
From: "",
55+
Until: "now",
56+
},
57+
}
58+
59+
malFormedBodyJSON, err := json.Marshal(malFormedBody)
60+
if err != nil {
61+
panic(err)
62+
}
63+
2064
testing.WithConfig(func(cfg **config.Config) {
2165
Context("/render", func() {
2266
It("supports name and query parameters", func() {
@@ -44,6 +88,16 @@ var _ = Describe("server", func() {
4488
resp, err = http.Get(fmt.Sprintf("%s/render", httpServer.URL))
4589
Expect(err).ToNot(HaveOccurred())
4690
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))
91+
92+
// POST Method
93+
resp, err = http.Post(fmt.Sprintf("%s/render-diff", httpServer.URL), "application/json", bytes.NewBuffer(formedBodyJSON))
94+
Expect(err).ToNot(HaveOccurred())
95+
Expect(resp.StatusCode).To(Equal(http.StatusOK))
96+
97+
resp, err = http.Post(fmt.Sprintf("%s/render-diff", httpServer.URL), "application/json", bytes.NewBuffer(malFormedBodyJSON))
98+
Expect(err).ToNot(HaveOccurred())
99+
Expect(resp.StatusCode).To(Equal(http.StatusBadRequest))
100+
47101
})
48102
})
49103
})

0 commit comments

Comments
 (0)