Skip to content

Commit

Permalink
change: return 400 when the response can't be modified (#228)
Browse files Browse the repository at this point in the history
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 <spasquie@redhat.com>
  • Loading branch information
simonpasquier authored Jul 4, 2024
1 parent 97e4565 commit c33000b
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 11 deletions.
20 changes: 17 additions & 3 deletions injectproxy/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
Expand Down Expand Up @@ -48,6 +49,8 @@ type routes struct {
modifiers map[string]func(*http.Response) error
errorOnReplace bool
regexMatch bool

logger *log.Logger
}

type options struct {
Expand Down Expand Up @@ -297,6 +300,7 @@ func NewRoutes(upstream *url.URL, label string, extractLabeler ExtractLabeler, o
el: extractLabeler,
errorOnReplace: opt.errorOnReplace,
regexMatch: opt.regexMatch,
logger: log.Default(),
}
mux := newStrictMux(newInstrumentedMux(http.NewServeMux(), opt.registerer))

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

return r, nil
}

Expand All @@ -395,9 +399,19 @@ func (r *routes) ModifyResponse(resp *http.Response) error {
// Return the server's response unmodified.
return nil
}

return m(resp)
}

func (r *routes) errorHandler(rw http.ResponseWriter, _ *http.Request, err error) {
r.logger.Printf("http: proxy error: %v", err)
if errors.Is(err, errModifyResponseFailed) {
rw.WriteHeader(http.StatusBadRequest)
}

rw.WriteHeader(http.StatusBadGateway)
}

func enforceMethods(h http.HandlerFunc, methods ...string) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
for _, m := range methods {
Expand Down
14 changes: 10 additions & 4 deletions injectproxy/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -166,6 +167,10 @@ type alert struct {
Value string `json:"value"`
}

// errModifyResponseFailed is returned when the proxy failed to modify the
// response from the backend.
var errModifyResponseFailed = errors.New("failed to process the API response")

// modifyAPIResponse unwraps the Prometheus API response, passes the enforced
// label value and the response to the given function and finally replaces the
// result in the response.
Expand All @@ -178,23 +183,24 @@ func modifyAPIResponse(f func([]string, *apiResponse) (interface{}, error)) func

apir, err := getAPIResponse(resp)
if err != nil {
return fmt.Errorf("can't decode API response: %w", err)
return fmt.Errorf("can't decode the response: %w", err)
}

v, err := f(MustLabelValues(resp.Request.Context()), apir)
if err != nil {
return err
return fmt.Errorf("%w: %w", errModifyResponseFailed, err)
}

b, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("can't replace data: %w", err)
return fmt.Errorf("can't encode the data: %w", err)
}

apir.Data = json.RawMessage(b)

var buf bytes.Buffer
if err = json.NewEncoder(&buf).Encode(apir); err != nil {
return fmt.Errorf("can't encode API response: %w", err)
return fmt.Errorf("can't encode the response: %w", err)
}
resp.Body = io.NopCloser(&buf)
resp.Header["Content-Length"] = []string{fmt.Sprint(buf.Len())}
Expand Down
14 changes: 10 additions & 4 deletions injectproxy/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ func TestRules(t *testing.T) {
upstream: validRules(),
opts: []Option{WithRegexMatch()},

expCode: http.StatusBadGateway,
expCode: http.StatusBadRequest,
golden: "rules_invalid_upstream_response.golden",
},
} {
Expand Down Expand Up @@ -513,7 +513,10 @@ func TestRules(t *testing.T) {
t.Fatalf("expected status code %d, got %d", tc.expCode, resp.StatusCode)
}

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

expCode: http.StatusBadGateway,
expCode: http.StatusBadRequest,
golden: "alerts_invalid_upstream_response.golden",
},
} {
Expand Down Expand Up @@ -650,7 +653,10 @@ func TestAlerts(t *testing.T) {
t.Fatalf("expected status code %d, got %d", tc.expCode, resp.StatusCode)
}

body, _ := io.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("expected no error, got %s", err)
}
if resp.StatusCode != http.StatusOK {
golden.Assert(t, string(body), tc.golden)
return
Expand Down

0 comments on commit c33000b

Please sign in to comment.