diff --git a/plugins/inputs/http/README.md b/plugins/inputs/http/README.md index 25d3d2b2d8960..240fd90c98b20 100644 --- a/plugins/inputs/http/README.md +++ b/plugins/inputs/http/README.md @@ -19,6 +19,13 @@ The HTTP input plugin collects metrics from one or more HTTP(S) endpoints. The ## Optional HTTP headers # headers = {"X-Special-Header" = "Special-Value"} + ## HTTP entity-body to send with POST/PUT requests. + # body = "" + + ## HTTP Content-Encoding for write request body, can be set to "gzip" to + ## compress body or "identity" to apply no encoding. + # content_encoding = "identity" + ## Optional HTTP Basic Auth Credentials # username = "username" # password = "pa$$word" diff --git a/plugins/inputs/http/http.go b/plugins/inputs/http/http.go index f5a2544c82819..6d2d528baa820 100644 --- a/plugins/inputs/http/http.go +++ b/plugins/inputs/http/http.go @@ -3,6 +3,7 @@ package http import ( "errors" "fmt" + "io" "io/ioutil" "net/http" "strings" @@ -17,17 +18,19 @@ import ( ) type HTTP struct { - URLs []string `toml:"urls"` - Method string + URLs []string `toml:"urls"` + Method string `toml:"method"` + Body string `toml:"body"` + ContentEncoding string `toml:"content_encoding"` - Headers map[string]string + Headers map[string]string `toml:"headers"` // HTTP Basic Auth Credentials - Username string - Password string + Username string `toml:"username"` + Password string `toml:"password"` tls.ClientConfig - Timeout internal.Duration + Timeout internal.Duration `toml:"timeout"` client *http.Client @@ -52,6 +55,13 @@ var sampleConfig = ` # username = "username" # password = "pa$$word" + ## HTTP entity-body to send with POST/PUT requests. + # body = "" + + ## HTTP Content-Encoding for write request body, can be set to "gzip" to + ## compress body or "identity" to apply no encoding. + # content_encoding = "identity" + ## Optional TLS Config # tls_ca = "/etc/telegraf/ca.pem" # tls_cert = "/etc/telegraf/cert.pem" @@ -132,11 +142,20 @@ func (h *HTTP) gatherURL( acc telegraf.Accumulator, url string, ) error { - request, err := http.NewRequest(h.Method, url, nil) + body, err := makeRequestBodyReader(h.ContentEncoding, h.Body) if err != nil { return err } + request, err := http.NewRequest(h.Method, url, body) + if err != nil { + return err + } + + if h.ContentEncoding == "gzip" { + request.Header.Set("Content-Encoding", "gzip") + } + for k, v := range h.Headers { if strings.ToLower(k) == "host" { request.Host = v @@ -183,6 +202,18 @@ func (h *HTTP) gatherURL( return nil } +func makeRequestBodyReader(contentEncoding, body string) (io.Reader, error) { + var err error + var reader io.Reader = strings.NewReader(body) + if contentEncoding == "gzip" { + reader, err = internal.CompressWithGzip(reader) + if err != nil { + return nil, err + } + } + return reader, nil +} + func init() { inputs.Add("http", func() telegraf.Input { return &HTTP{ diff --git a/plugins/inputs/http/http_test.go b/plugins/inputs/http/http_test.go index 4cd465bceb1a4..7ac05e1356c32 100644 --- a/plugins/inputs/http/http_test.go +++ b/plugins/inputs/http/http_test.go @@ -1,6 +1,9 @@ package http_test import ( + "compress/gzip" + "fmt" + "io/ioutil" "net/http" "net/http/httptest" "testing" @@ -149,3 +152,93 @@ const simpleJSON = ` "a": 1.2 } ` + +func TestBodyAndContentEncoding(t *testing.T) { + ts := httptest.NewServer(http.NotFoundHandler()) + defer ts.Close() + + url := fmt.Sprintf("http://%s", ts.Listener.Addr().String()) + + tests := []struct { + name string + plugin *plugin.HTTP + queryHandlerFunc func(t *testing.T, w http.ResponseWriter, r *http.Request) + }{ + { + name: "no body", + plugin: &plugin.HTTP{ + Method: "POST", + URLs: []string{url}, + }, + queryHandlerFunc: func(t *testing.T, w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + require.Equal(t, []byte(""), body) + w.WriteHeader(http.StatusOK) + }, + }, + { + name: "post body", + plugin: &plugin.HTTP{ + URLs: []string{url}, + Method: "POST", + Body: "test", + }, + queryHandlerFunc: func(t *testing.T, w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + require.Equal(t, []byte("test"), body) + w.WriteHeader(http.StatusOK) + }, + }, + { + name: "get method body is sent", + plugin: &plugin.HTTP{ + URLs: []string{url}, + Method: "GET", + Body: "test", + }, + queryHandlerFunc: func(t *testing.T, w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + require.Equal(t, []byte("test"), body) + w.WriteHeader(http.StatusOK) + }, + }, + { + name: "gzip encoding", + plugin: &plugin.HTTP{ + URLs: []string{url}, + Method: "GET", + Body: "test", + ContentEncoding: "gzip", + }, + queryHandlerFunc: func(t *testing.T, w http.ResponseWriter, r *http.Request) { + require.Equal(t, r.Header.Get("Content-Encoding"), "gzip") + + gr, err := gzip.NewReader(r.Body) + require.NoError(t, err) + body, err := ioutil.ReadAll(gr) + require.NoError(t, err) + require.Equal(t, []byte("test"), body) + w.WriteHeader(http.StatusOK) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tt.queryHandlerFunc(t, w, r) + }) + + parser, err := parsers.NewParser(&parsers.Config{DataFormat: "influx"}) + require.NoError(t, err) + + tt.plugin.SetParser(parser) + + var acc testutil.Accumulator + err = tt.plugin.Gather(&acc) + require.NoError(t, err) + }) + } +}