diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 1143c8961..f1e1d43fc 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -129,7 +129,11 @@ The other placeholders are specified separately. [ ip_protocol_fallback: | default = true ] # The body of the HTTP request used in probe. - body: [ ] + [ body: ] + + # Read the HTTP request body from from a file. + # It is mutually exclusive with `body`. + [ body_file: ] ``` diff --git a/config/config.go b/config/config.go index 246e52d80..9305f3170 100644 --- a/config/config.go +++ b/config/config.go @@ -218,6 +218,7 @@ type HTTPProbe struct { FailIfHeaderMatchesRegexp []HeaderMatch `yaml:"fail_if_header_matches,omitempty"` FailIfHeaderNotMatchesRegexp []HeaderMatch `yaml:"fail_if_header_not_matches,omitempty"` Body string `yaml:"body,omitempty"` + BodyFile string `yaml:"body_file,omitempty"` HTTPClientConfig config.HTTPClientConfig `yaml:"http_client_config,inline"` Compression string `yaml:"compression,omitempty"` BodySizeLimit units.Base2Bytes `yaml:"body_size_limit,omitempty"` @@ -330,6 +331,10 @@ func (s *HTTPProbe) UnmarshalYAML(unmarshal func(interface{}) error) error { s.HTTPClientConfig.FollowRedirects = !*s.NoFollowRedirects } + if s.Body != "" && s.BodyFile != "" { + return errors.New("body and body_file must not both be set") + } + for key, value := range s.Headers { switch textproto.CanonicalMIMEHeaderKey(key) { case "Accept-Encoding": diff --git a/prober/http.go b/prober/http.go index af12c7e42..5e452cd94 100644 --- a/prober/http.go +++ b/prober/http.go @@ -27,6 +27,7 @@ import ( "net/http/httptrace" "net/textproto" "net/url" + "os" "strconv" "strings" "sync" @@ -408,6 +409,17 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr body = strings.NewReader(httpConfig.Body) } + // If a body file is configured, add its content to the request. + if httpConfig.BodyFile != "" { + body_file, err := os.Open(httpConfig.BodyFile) + if err != nil { + level.Error(logger).Log("msg", "Error creating request", "err", err) + return + } + defer body_file.Close() + body = body_file + } + request, err := http.NewRequest(httpConfig.Method, targetURL.String(), body) if err != nil { level.Error(logger).Log("msg", "Error creating request", "err", err) diff --git a/prober/http_test.go b/prober/http_test.go index 430b2b9a3..de28bc306 100644 --- a/prober/http_test.go +++ b/prober/http_test.go @@ -1431,3 +1431,39 @@ func TestBody(t *testing.T) { t.Fatalf("Body test failed unexpectedly.") } } + +func TestBodyFile(t *testing.T) { + body := "Test Body" + tmpBodyFile, err := os.CreateTemp("", "body.txt") + if err != nil { + t.Fatalf("Error creating body tempfile: %s", err) + } + if _, err := tmpBodyFile.Write([]byte(body)); err != nil { + t.Fatalf("Error writing body tempfile: %s", err) + } + if err := tmpBodyFile.Close(); err != nil { + t.Fatalf("Error closing body tempfie: %s", err) + } + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatalf("Body file test failed unexpectedly.") + } + if string(b) != body { + t.Fatalf("Body file test failed unexpectedly.") + } + })) + defer ts.Close() + + registry := prometheus.NewRegistry() + testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + result := ProbeHTTP(testCTX, ts.URL, config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{ + IPProtocolFallback: true, + BodyFile: tmpBodyFile.Name(), + }}, registry, log.NewNopLogger()) + if !result { + t.Fatalf("Body file test failed unexpectedly.") + } +}