Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for HTTP request body as file #987

Merged
merged 3 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> ]
electron0zero marked this conversation as resolved.
Show resolved Hide resolved

```

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
49 changes: 49 additions & 0 deletions prober/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/textproto"
Expand Down Expand Up @@ -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)
}
}
}