Skip to content

Commit

Permalink
Add support for HTTP request body as file
Browse files Browse the repository at this point in the history
Resolves #391

Signed-off-by: Daniel Teunis <daniel@teunis.cc>
  • Loading branch information
danteu committed Nov 15, 2022
1 parent 3025186 commit 60af0b8
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 20 deletions.
6 changes: 5 additions & 1 deletion CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ The other placeholders are specified separately.
[ ip_protocol_fallback: <boolean> | default = true ]

# The body of the HTTP request used in probe.
body: [ <string> ]
[ body: <string> ]

# Read the HTTP request body from from a file.
# It is mutually exclusive with `body`.
[ body_file: <filename> ]

```

Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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":
Expand Down
4 changes: 4 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions config/testdata/invalid-http-body-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
modules:
http_test:
prober: http
timeout: 5s
http:
body: "Test body"
body_file: "test_body.txt"
6 changes: 6 additions & 0 deletions example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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
Expand Down
12 changes: 12 additions & 0 deletions prober/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"net/http/httptrace"
"net/textproto"
"net/url"
"os"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -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)
Expand Down
61 changes: 42 additions & 19 deletions prober/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1409,25 +1409,48 @@ func TestSkipResolvePhase(t *testing.T) {

func TestBody(t *testing.T) {
body := "Test Body"
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("Body test failed unexpectedly.")
}
if string(b) != body {
t.Fatalf("Body test failed unexpectedly.")
}
}))
defer ts.Close()
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)
}

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,
Body: body,
}}, registry, log.NewNopLogger())
if !result {
t.Fatalf("Body test failed unexpectedly.")
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 := ioutil.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)
}
}
}

0 comments on commit 60af0b8

Please sign in to comment.