Skip to content

Commit 4e20d40

Browse files
committed
change: return 400 when the response can't be modified
With this change, the proxy returns a 400 status code instead of 502 when the incoming request contains multiple label values while the proxy is configured for regex matching (which allows only one label value). Signed-off-by: Simon Pasquier <[email protected]>
1 parent 97e4565 commit 4e20d40

File tree

3 files changed

+37
-11
lines changed

3 files changed

+37
-11
lines changed

injectproxy/routes.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"errors"
2020
"fmt"
2121
"io"
22+
"log"
2223
"net/http"
2324
"net/http/httputil"
2425
"net/url"
@@ -48,6 +49,8 @@ type routes struct {
4849
modifiers map[string]func(*http.Response) error
4950
errorOnReplace bool
5051
regexMatch bool
52+
53+
logger *log.Logger
5154
}
5255

5356
type options struct {
@@ -297,6 +300,7 @@ func NewRoutes(upstream *url.URL, label string, extractLabeler ExtractLabeler, o
297300
el: extractLabeler,
298301
errorOnReplace: opt.errorOnReplace,
299302
regexMatch: opt.regexMatch,
303+
logger: log.Default(),
300304
}
301305
mux := newStrictMux(newInstrumentedMux(http.NewServeMux(), opt.registerer))
302306

@@ -378,10 +382,10 @@ func NewRoutes(upstream *url.URL, label string, extractLabeler ExtractLabeler, o
378382
"/api/v1/rules": modifyAPIResponse(r.filterRules),
379383
"/api/v1/alerts": modifyAPIResponse(r.filterAlerts),
380384
}
381-
//FIXME: when ModifyResponse returns an error, the default ErrorHandler is
382-
//called which returns 502 Bad Gateway. It'd be more appropriate to treat
383-
//the error and return 400 in case of bad input for instance.
384385
proxy.ModifyResponse = r.ModifyResponse
386+
proxy.ErrorHandler = r.errorHandler
387+
proxy.ErrorLog = log.Default()
388+
385389
return r, nil
386390
}
387391

@@ -395,9 +399,19 @@ func (r *routes) ModifyResponse(resp *http.Response) error {
395399
// Return the server's response unmodified.
396400
return nil
397401
}
402+
398403
return m(resp)
399404
}
400405

406+
func (r *routes) errorHandler(rw http.ResponseWriter, _ *http.Request, err error) {
407+
r.logger.Printf("http: proxy error: %v", err)
408+
if errors.Is(err, errModifyResponseFailed) {
409+
rw.WriteHeader(http.StatusBadRequest)
410+
}
411+
412+
rw.WriteHeader(http.StatusBadGateway)
413+
}
414+
401415
func enforceMethods(h http.HandlerFunc, methods ...string) http.HandlerFunc {
402416
return func(w http.ResponseWriter, req *http.Request) {
403417
for _, m := range methods {

injectproxy/rules.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"bytes"
1818
"compress/gzip"
1919
"encoding/json"
20+
"errors"
2021
"fmt"
2122
"io"
2223
"net/http"
@@ -166,6 +167,10 @@ type alert struct {
166167
Value string `json:"value"`
167168
}
168169

170+
// errModifyResponseFailed is returned when the proxy failed to modify the
171+
// response from the backend.
172+
var errModifyResponseFailed = errors.New("failed to process the API response")
173+
169174
// modifyAPIResponse unwraps the Prometheus API response, passes the enforced
170175
// label value and the response to the given function and finally replaces the
171176
// result in the response.
@@ -178,23 +183,24 @@ func modifyAPIResponse(f func([]string, *apiResponse) (interface{}, error)) func
178183

179184
apir, err := getAPIResponse(resp)
180185
if err != nil {
181-
return fmt.Errorf("can't decode API response: %w", err)
186+
return fmt.Errorf("can't decode the response: %w", err)
182187
}
183188

184189
v, err := f(MustLabelValues(resp.Request.Context()), apir)
185190
if err != nil {
186-
return err
191+
return fmt.Errorf("%w: %w", errModifyResponseFailed, err)
187192
}
188193

189194
b, err := json.Marshal(v)
190195
if err != nil {
191-
return fmt.Errorf("can't replace data: %w", err)
196+
return fmt.Errorf("can't encode the data: %w", err)
192197
}
198+
193199
apir.Data = json.RawMessage(b)
194200

195201
var buf bytes.Buffer
196202
if err = json.NewEncoder(&buf).Encode(apir); err != nil {
197-
return fmt.Errorf("can't encode API response: %w", err)
203+
return fmt.Errorf("can't encode the response: %w", err)
198204
}
199205
resp.Body = io.NopCloser(&buf)
200206
resp.Header["Content-Length"] = []string{fmt.Sprint(buf.Len())}

injectproxy/rules_test.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ func TestRules(t *testing.T) {
469469
upstream: validRules(),
470470
opts: []Option{WithRegexMatch()},
471471

472-
expCode: http.StatusBadGateway,
472+
expCode: http.StatusBadRequest,
473473
golden: "rules_invalid_upstream_response.golden",
474474
},
475475
} {
@@ -513,7 +513,10 @@ func TestRules(t *testing.T) {
513513
t.Fatalf("expected status code %d, got %d", tc.expCode, resp.StatusCode)
514514
}
515515

516-
body, _ := io.ReadAll(resp.Body)
516+
body, err := io.ReadAll(resp.Body)
517+
if err != nil {
518+
t.Fatalf("expected no error, got %s", err)
519+
}
517520
if resp.StatusCode != http.StatusOK {
518521
golden.Assert(t, string(body), tc.golden)
519522
return
@@ -613,7 +616,7 @@ func TestAlerts(t *testing.T) {
613616
upstream: validAlerts(),
614617
opts: []Option{WithRegexMatch()},
615618

616-
expCode: http.StatusBadGateway,
619+
expCode: http.StatusBadRequest,
617620
golden: "alerts_invalid_upstream_response.golden",
618621
},
619622
} {
@@ -650,7 +653,10 @@ func TestAlerts(t *testing.T) {
650653
t.Fatalf("expected status code %d, got %d", tc.expCode, resp.StatusCode)
651654
}
652655

653-
body, _ := io.ReadAll(resp.Body)
656+
body, err := io.ReadAll(resp.Body)
657+
if err != nil {
658+
t.Fatalf("expected no error, got %s", err)
659+
}
654660
if resp.StatusCode != http.StatusOK {
655661
golden.Assert(t, string(body), tc.golden)
656662
return

0 commit comments

Comments
 (0)