From: Daniel Teunis Date: Wed, 29 Mar 2023 16:50:09 +0000 (+0200) Subject: Add support for HTTP request body as file (#987) X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=46020dddc2a82c63fadce0bb861269e902d1d39b;p=blackbox_exporter.git Add support for HTTP request body as file (#987) * Add HTTP request body test This commit adds a test for specifying a request body for HTTP probes. Signed-off-by: Daniel Teunis * Add support for HTTP request body as file Resolves #391 Signed-off-by: Daniel Teunis * Use io instead of io/ioutil Signed-off-by: Marcelo Magallon --------- Signed-off-by: Daniel Teunis Signed-off-by: Marcelo Magallon Co-authored-by: Marcelo Magallon --- diff --git a/CONFIGURATION.md b/CONFIGURATION.md index a3347c3..604ee89 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -142,7 +142,11 @@ modules: [ 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 246e52d..9933884 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("setting body and body_file both are not allowed") + } + for key, value := range s.Headers { switch textproto.CanonicalMIMEHeaderKey(key) { case "Accept-Encoding": diff --git a/config/config_test.go b/config/config_test.go index e1b57a3..65e3fe8 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -99,6 +99,10 @@ func TestLoadBadConfigs(t *testing.T) { input: "testdata/invalid-tcp-query-response-regexp.yml", want: `error parsing config file: "Could not compile regular expression" regexp=":["`, }, + { + input: "testdata/invalid-http-body-config.yml", + want: `error parsing config file: setting body and body_file both are not allowed`, + }, } for _, test := range tests { t.Run(test.input, func(t *testing.T) { diff --git a/config/testdata/invalid-http-body-config.yml b/config/testdata/invalid-http-body-config.yml new file mode 100644 index 0000000..16c4784 --- /dev/null +++ b/config/testdata/invalid-http-body-config.yml @@ -0,0 +1,7 @@ +modules: + http_test: + prober: http + timeout: 5s + http: + body: "Test body" + body_file: "test_body.txt" diff --git a/example.yml b/example.yml index b7ac278..5e887a5 100644 --- a/example.yml +++ b/example.yml @@ -48,6 +48,12 @@ modules: headers: Content-Type: application/json body: '{}' + http_post_body_file: + prober: http + timeout: 5s + http: + method: POST + body_file: "/files/body.txt" http_basic_auth_example: prober: http timeout: 5s diff --git a/prober/http.go b/prober/http.go index af12c7e..5e452cd 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 69fccee..3d7dfdb 100644 --- a/prober/http_test.go +++ b/prober/http_test.go @@ -22,6 +22,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "io" "net/http" "net/http/httptest" "net/textproto" @@ -1405,3 +1406,51 @@ func TestSkipResolvePhase(t *testing.T) { checkMetrics(expectedMetrics, mfs, t) }) } + +func TestBody(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) + } + + tests := []config.HTTPProbe{ + {IPProtocolFallback: true, Body: body}, + {IPProtocolFallback: true, BodyFile: tmpBodyFile.Name()}, + } + + for i, test := range tests { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("Body test %d failed unexpectedly.", i) + } + if string(b) != body { + t.Fatalf("Body test %d failed unexpectedly.", i) + } + })) + 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: test}, + registry, + log.NewNopLogger(), + ) + if !result { + t.Fatalf("Body test %d failed unexpectedly.", i) + } + } +}