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

Support multipart/form-data #1115

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
20 changes: 18 additions & 2 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ modules:

```


### `<module>`

```yml

# The protocol over which the probe will take place (http, tcp, dns, icmp, grpc).
Expand All @@ -45,6 +45,7 @@ modules:
```

### `<http_probe>`

```yml

# Accepted status codes for this probe. Defaults to 2xx.
Expand Down Expand Up @@ -157,9 +158,13 @@ modules:
[ body: <string> ]

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

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

#### `<http_header_match_spec>`
Expand All @@ -170,6 +175,17 @@ regexp: <regex>,
[ allow_missing: <boolean> | default = false ]
```

#### `<body_multipart_spec>`

```yml
# The type of the multipart body part. Valid values are "file" and "text".
type: <string>
# The name of the multipart body part.
key: <string>
# The value of the multipart body part. It can be a file path or a string.
value: <string>
```

### `<tcp_probe>`

```yml
Expand Down
7 changes: 7 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,18 @@ type HTTPProbe struct {
FailIfHeaderNotMatchesRegexp []HeaderMatch `yaml:"fail_if_header_not_matches,omitempty"`
Body string `yaml:"body,omitempty"`
BodyFile string `yaml:"body_file,omitempty"`
BodyMultipart []Multipart `yaml:"body_multipart,omitempty"`
HTTPClientConfig config.HTTPClientConfig `yaml:"http_client_config,inline"`
Compression string `yaml:"compression,omitempty"`
BodySizeLimit units.Base2Bytes `yaml:"body_size_limit,omitempty"`
}

type Multipart struct {
Type string `yaml:"type,omitempty"`
Key string `yaml:"key,omitempty"`
Value string `yaml:"value,omitempty"`
}

type GRPCProbe struct {
Service string `yaml:"service,omitempty"`
TLS bool `yaml:"tls,omitempty"`
Expand Down
12 changes: 12 additions & 0 deletions example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ modules:
http:
method: POST
body_file: "/files/body.txt"
http_post_body_multipart:
prober: http
timeout: 5s
http:
method: POST
body_multipart:
- type: "file"
key: "file"
value: "/files/body.txt"
- type: "text"
key: "name"
value: "arash"
http_basic_auth_example:
prober: http
timeout: 5s
Expand Down
52 changes: 52 additions & 0 deletions prober/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
package prober

import (
"bytes"
"compress/flate"
"compress/gzip"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"net/http"
"net/http/cookiejar"
Expand Down Expand Up @@ -420,6 +422,56 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
body = body_file
}

// If a multipart form is configured, add it to the request.
if httpConfig.BodyMultipart != nil {
var (
b bytes.Buffer
r io.Reader
)
w := multipart.NewWriter(&b)
for _, part := range httpConfig.BodyMultipart {
if part.Type == "file" {
r, err = os.Open(part.Value)
if err != nil {
level.Error(logger).Log("msg", "Error reading body file", "err", err)
return
}
} else {
r = strings.NewReader(part.Value)
}

var fw io.Writer
if x, ok := r.(io.Closer); ok {
defer x.Close()
}
// Add a file field
if x, ok := r.(*os.File); ok {
if fw, err = w.CreateFormFile(part.Key, x.Name()); err != nil {
level.Error(logger).Log("msg", "Error creating request", "err", err)
return
}
} else {
// Add a text fields
if fw, err = w.CreateFormField(part.Key); err != nil {
level.Error(logger).Log("msg", "Error creating request", "err", err)
return
}
}
if _, err = io.Copy(fw, r); err != nil {
level.Error(logger).Log("msg", "Error creating request", "err", err)
return
}
}
// We should close the multipart writer.
// If we don't close it, your request will be missing the terminating boundary.
w.Close()

body = &b

// Set the proper content-type header containing the boundary.
httpConfig.Headers["Content-Type"] = w.FormDataContentType()
}

request, err := http.NewRequest(httpConfig.Method, targetURL.String(), body)
if err != nil {
level.Error(logger).Log("msg", "Error creating request", "err", err)
Expand Down