diff --git a/apps/checker/checker/http.go b/apps/checker/checker/http.go index f85f0a0f..aef56126 100644 --- a/apps/checker/checker/http.go +++ b/apps/checker/checker/http.go @@ -43,6 +43,10 @@ type Response struct { Timing Timing `json:"timing"` } +// maxResponseBodyBytes caps the read of a probed response body so a large body +// cannot OOM the 512 MB checker machines (exit 137 waves). +const maxResponseBodyBytes = 10 << 20 // 10 MiB + // decodeBase64Body decodes a data URL base64 body if needed func decodeBase64Body(body string) ([]byte, error) { data := strings.Split(body, ",") @@ -145,7 +149,9 @@ func Http(ctx context.Context, client *http.Client, inputData request.HttpChecke defer response.Body.Close() - body, err := io.ReadAll(response.Body) + // Cap the response body: an endpoint returning a large body would + // otherwise OOM these 512 MB machines (fleet-wide exit 137 waves). + body, err := io.ReadAll(io.LimitReader(response.Body, maxResponseBodyBytes)) timing.TransferDone = time.Now().UTC().UnixMilli() diff --git a/apps/checker/checker/http_test.go b/apps/checker/checker/http_test.go index 070717a9..b09c89fc 100644 --- a/apps/checker/checker/http_test.go +++ b/apps/checker/checker/http_test.go @@ -28,6 +28,24 @@ func NewTestClient(fn RoundTripFunc) *http.Client { } } +func Test_HttpCapsResponseBody(t *testing.T) { + client := NewTestClient(func(req *http.Request) *http.Response { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(bytes.Repeat([]byte("b"), 20<<20))), + Header: make(http.Header), + } + }) + + got, err := checker.Http(context.Background(), client, request.HttpCheckerRequest{URL: "https://openstat.us", CronTimestamp: 1}) + if err != nil { + t.Fatalf("Http() error = %v", err) + } + if got.Body != string(bytes.Repeat([]byte("b"), 10<<20)) { + t.Errorf("Http() body length = %d, want %d (capped by maxResponseBodyBytes)", len(got.Body), 10<<20) + } +} + func Test_ping(t *testing.T) { type args struct {