From 577134a6d027efdd16607abdfad76e6edf7cdd6e Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 31 Mar 2016 18:33:28 +1100 Subject: [PATCH 1/9] add http_response plugin --- plugins/inputs/http_response/README.md | 36 +++++++ plugins/inputs/http_response/http_response.go | 95 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 plugins/inputs/http_response/README.md create mode 100644 plugins/inputs/http_response/http_response.go diff --git a/plugins/inputs/http_response/README.md b/plugins/inputs/http_response/README.md new file mode 100644 index 0000000000000..13da76097d9f1 --- /dev/null +++ b/plugins/inputs/http_response/README.md @@ -0,0 +1,36 @@ +# Example Input Plugin + +This input plugin will test HTTP/HTTPS connections. + +### Configuration: + +``` +# List of UDP/TCP connections you want to check +[[inputs.http_response]] + # Server address (default http://localhost) + address = "http://github.com:80" + # Set http response timeout (default 1.0) + response_timeout = 1.0 + # HTTP Method (default "GET") + method = "GET" +``` + +### Measurements & Fields: + +- http_response + - response_time (float, seconds) + - http_response_code (int) #The code received + +### Tags: + +- All measurements have the following tags: + - server + - port + - protocol + +### Example Output: + +``` +$ ./telegraf -config telegraf.conf -input-filter http_response -test +http_response,server=http://192.168.2.2:2000,method=GET response_time=0.18070360500000002,http_response_code=200 1454785464182527094 +``` diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go new file mode 100644 index 0000000000000..e19c698a82d89 --- /dev/null +++ b/plugins/inputs/http_response/http_response.go @@ -0,0 +1,95 @@ +package http_response + +import ( + "errors" + "net/http" + "net/url" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" +) + +// HttpResponses struct +type HttpResponse struct { + Address string + Method string + ResponseTimeout int +} + +func (_ *HttpResponse) Description() string { + return "HTTP/HTTPS request given an address a method and a timeout" +} + +var sampleConfig = ` + ## Server address (default http://localhost) + address = "http://github.com:80" + ## Set response_timeout (default 1 seconds) + response_timeout = 1 + ## HTTP Method + method = "GET" +` + +func (_ *HttpResponse) SampleConfig() string { + return sampleConfig +} + +func (h *HttpResponse) HttpGather() (map[string]interface{}, error) { + // Prepare fields + fields := make(map[string]interface{}) + + client := &http.Client{ + Timeout: time.Second * time.Duration(h.ResponseTimeout), + } + request, err := http.NewRequest(h.Method, h.Address, nil) + if err != nil { + return nil, err + } + // Start Timer + start := time.Now() + resp, err := client.Do(request) + if err != nil { + return nil, err + } + fields["response_time"] = time.Since(start).Seconds() + fields["http_response_code"] = resp.StatusCode + return fields, nil +} + +func (c *HttpResponse) Gather(acc telegraf.Accumulator) error { + // Set default values + if c.ResponseTimeout < 1 { + c.ResponseTimeout = 1 + } + // Check send and expected string + if c.Method == "" { + c.Method = "GET" + } + if c.Address == "" { + c.Address = "http://localhost" + } + addr, err := url.Parse(c.Address) + if err != nil { + return err + } + if addr.Scheme != "http" && addr.Scheme != "https" { + return errors.New("Only http and https are supported") + } + // Prepare data + tags := map[string]string{"server": c.Address, "method": c.Method} + var fields map[string]interface{} + // Gather data + fields, err = c.HttpGather() + if err != nil { + return err + } + // Add metrics + acc.AddFields("http_response", fields, tags) + return nil +} + +func init() { + inputs.Add("http_response", func() telegraf.Input { + return &HttpResponse{} + }) +} From e9227889a598b6be086f387282517c57c8d470d3 Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 31 Mar 2016 19:47:10 +1100 Subject: [PATCH 2/9] add plugin to all --- plugins/inputs/all/all.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 55a932df204fe..8368903fde977 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -15,6 +15,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks" _ "github.com/influxdata/telegraf/plugins/inputs/haproxy" _ "github.com/influxdata/telegraf/plugins/inputs/httpjson" + _ "github.com/influxdata/telegraf/plugins/inputs/http_response" _ "github.com/influxdata/telegraf/plugins/inputs/influxdb" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia" _ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer" From 0cf4b671fded855eced87e717fb958880c0a487f Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 31 Mar 2016 19:54:08 +1100 Subject: [PATCH 3/9] update to make a working sample_config --- plugins/inputs/http_response/README.md | 6 +++--- plugins/inputs/http_response/http_response.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/http_response/README.md b/plugins/inputs/http_response/README.md index 13da76097d9f1..b70bbde72c2ad 100644 --- a/plugins/inputs/http_response/README.md +++ b/plugins/inputs/http_response/README.md @@ -8,9 +8,9 @@ This input plugin will test HTTP/HTTPS connections. # List of UDP/TCP connections you want to check [[inputs.http_response]] # Server address (default http://localhost) - address = "http://github.com:80" - # Set http response timeout (default 1.0) - response_timeout = 1.0 + address = "https://github.com" + # Set http response timeout (default 10) + response_timeout = 10 # HTTP Method (default "GET") method = "GET" ``` diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go index e19c698a82d89..d2d35025adf3e 100644 --- a/plugins/inputs/http_response/http_response.go +++ b/plugins/inputs/http_response/http_response.go @@ -23,9 +23,9 @@ func (_ *HttpResponse) Description() string { var sampleConfig = ` ## Server address (default http://localhost) - address = "http://github.com:80" + address = "https://github.com" ## Set response_timeout (default 1 seconds) - response_timeout = 1 + response_timeout = 10 ## HTTP Method method = "GET" ` From 67b81a658f653dcc68bf5d864cdd0d46c2531b1b Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 31 Mar 2016 19:55:17 +1100 Subject: [PATCH 4/9] fmt --- plugins/inputs/all/all.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 8368903fde977..bed1b6444ce21 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -14,8 +14,8 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/exec" _ "github.com/influxdata/telegraf/plugins/inputs/github_webhooks" _ "github.com/influxdata/telegraf/plugins/inputs/haproxy" - _ "github.com/influxdata/telegraf/plugins/inputs/httpjson" _ "github.com/influxdata/telegraf/plugins/inputs/http_response" + _ "github.com/influxdata/telegraf/plugins/inputs/httpjson" _ "github.com/influxdata/telegraf/plugins/inputs/influxdb" _ "github.com/influxdata/telegraf/plugins/inputs/jolokia" _ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer" From b480e2ff20ecc305d466d6beae85a4f0b0b24503 Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 31 Mar 2016 20:53:51 +1100 Subject: [PATCH 5/9] add the ability to parse http headers --- plugins/inputs/http_response/http_response.go | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go index d2d35025adf3e..df21311ae52e7 100644 --- a/plugins/inputs/http_response/http_response.go +++ b/plugins/inputs/http_response/http_response.go @@ -1,23 +1,28 @@ package http_response import ( + "bufio" "errors" "net/http" + "net/textproto" "net/url" + "strings" "time" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" ) -// HttpResponses struct -type HttpResponse struct { +// HTTPResponse struct +type HTTPResponse struct { Address string Method string ResponseTimeout int + Headers string } -func (_ *HttpResponse) Description() string { +// Description returns the plugin Description +func (h *HTTPResponse) Description() string { return "HTTP/HTTPS request given an address a method and a timeout" } @@ -28,13 +33,19 @@ var sampleConfig = ` response_timeout = 10 ## HTTP Method method = "GET" + ## HTTP Request Headers + headers = ''' + Host: github.com + ''' ` -func (_ *HttpResponse) SampleConfig() string { +// SampleConfig returns the plugin SampleConfig +func (h *HTTPResponse) SampleConfig() string { return sampleConfig } -func (h *HttpResponse) HttpGather() (map[string]interface{}, error) { +// HTTPGather gathers all fields and returns any errors it encounters +func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { // Prepare fields fields := make(map[string]interface{}) @@ -45,6 +56,14 @@ func (h *HttpResponse) HttpGather() (map[string]interface{}, error) { if err != nil { return nil, err } + h.Headers = strings.TrimSpace(h.Headers) + "\n\n" + reader := bufio.NewReader(strings.NewReader(h.Headers)) + tp := textproto.NewReader(reader) + mimeHeader, err := tp.ReadMIMEHeader() + if err != nil { + return nil, err + } + request.Header = http.Header(mimeHeader) // Start Timer start := time.Now() resp, err := client.Do(request) @@ -56,19 +75,20 @@ func (h *HttpResponse) HttpGather() (map[string]interface{}, error) { return fields, nil } -func (c *HttpResponse) Gather(acc telegraf.Accumulator) error { +// Gather gets all metric fields and tags and returns any errors it encounters +func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { // Set default values - if c.ResponseTimeout < 1 { - c.ResponseTimeout = 1 + if h.ResponseTimeout < 1 { + h.ResponseTimeout = 1 } // Check send and expected string - if c.Method == "" { - c.Method = "GET" + if h.Method == "" { + h.Method = "GET" } - if c.Address == "" { - c.Address = "http://localhost" + if h.Address == "" { + h.Address = "http://localhost" } - addr, err := url.Parse(c.Address) + addr, err := url.Parse(h.Address) if err != nil { return err } @@ -76,10 +96,10 @@ func (c *HttpResponse) Gather(acc telegraf.Accumulator) error { return errors.New("Only http and https are supported") } // Prepare data - tags := map[string]string{"server": c.Address, "method": c.Method} + tags := map[string]string{"server": h.Address, "method": h.Method} var fields map[string]interface{} // Gather data - fields, err = c.HttpGather() + fields, err = h.HTTPGather() if err != nil { return err } @@ -90,6 +110,6 @@ func (c *HttpResponse) Gather(acc telegraf.Accumulator) error { func init() { inputs.Add("http_response", func() telegraf.Input { - return &HttpResponse{} + return &HTTPResponse{} }) } From a47b36172220d45d223ace392a71edcf6fc0023c Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 31 Mar 2016 21:18:19 +1100 Subject: [PATCH 6/9] update to allow for following redirects --- plugins/inputs/http_response/README.md | 19 +++++++---- plugins/inputs/http_response/http_response.go | 33 ++++++++++++++++--- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/plugins/inputs/http_response/README.md b/plugins/inputs/http_response/README.md index b70bbde72c2ad..99770e52606f2 100644 --- a/plugins/inputs/http_response/README.md +++ b/plugins/inputs/http_response/README.md @@ -7,12 +7,18 @@ This input plugin will test HTTP/HTTPS connections. ``` # List of UDP/TCP connections you want to check [[inputs.http_response]] - # Server address (default http://localhost) - address = "https://github.com" - # Set http response timeout (default 10) + ## Server address (default http://localhost) + address = "http://github.com" + ## Set response_timeout (default 10 seconds) response_timeout = 10 - # HTTP Method (default "GET") + ## HTTP Method method = "GET" + ## HTTP Request Headers + headers = ''' + Host: github.com + ''' + ## Whether to follow redirects from the server (defaults to false) + follow_redirects = true ``` ### Measurements & Fields: @@ -25,12 +31,11 @@ This input plugin will test HTTP/HTTPS connections. - All measurements have the following tags: - server - - port - - protocol + - method ### Example Output: ``` $ ./telegraf -config telegraf.conf -input-filter http_response -test -http_response,server=http://192.168.2.2:2000,method=GET response_time=0.18070360500000002,http_response_code=200 1454785464182527094 +http_response,method=GET,server=http://www.github.com http_response_code=200i,response_time=6.223266528 1459419354977857955 ``` diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go index df21311ae52e7..09569fe730c6e 100644 --- a/plugins/inputs/http_response/http_response.go +++ b/plugins/inputs/http_response/http_response.go @@ -3,9 +3,11 @@ package http_response import ( "bufio" "errors" + "fmt" "net/http" "net/textproto" "net/url" + "os" "strings" "time" @@ -19,6 +21,7 @@ type HTTPResponse struct { Method string ResponseTimeout int Headers string + FollowRedirects bool } // Description returns the plugin Description @@ -28,8 +31,8 @@ func (h *HTTPResponse) Description() string { var sampleConfig = ` ## Server address (default http://localhost) - address = "https://github.com" - ## Set response_timeout (default 1 seconds) + address = "http://github.com" + ## Set response_timeout (default 10 seconds) response_timeout = 10 ## HTTP Method method = "GET" @@ -37,6 +40,8 @@ var sampleConfig = ` headers = ''' Host: github.com ''' + ## Whether to follow redirects from the server (defaults to false) + follow_redirects = true ` // SampleConfig returns the plugin SampleConfig @@ -44,6 +49,8 @@ func (h *HTTPResponse) SampleConfig() string { return sampleConfig } +var ErrRedirectAttempted = errors.New("redirect") + // HTTPGather gathers all fields and returns any errors it encounters func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { // Prepare fields @@ -52,6 +59,14 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { client := &http.Client{ Timeout: time.Second * time.Duration(h.ResponseTimeout), } + + if h.FollowRedirects == false { + fmt.Println(h.FollowRedirects) + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return ErrRedirectAttempted + } + } + request, err := http.NewRequest(h.Method, h.Address, nil) if err != nil { return nil, err @@ -66,9 +81,19 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { request.Header = http.Header(mimeHeader) // Start Timer start := time.Now() + request.Write(os.Stdout) resp, err := client.Do(request) if err != nil { - return nil, err + if h.FollowRedirects { + return nil, err + } + if urlError, ok := err.(*url.Error); ok && + urlError.Err == ErrRedirectAttempted { + fmt.Println(err) + err = nil + } else { + return nil, err + } } fields["response_time"] = time.Since(start).Seconds() fields["http_response_code"] = resp.StatusCode @@ -79,7 +104,7 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { // Set default values if h.ResponseTimeout < 1 { - h.ResponseTimeout = 1 + h.ResponseTimeout = 10 } // Check send and expected string if h.Method == "" { From 8dd13008d9982824fde0675372a7c1f6d4d74906 Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 31 Mar 2016 22:06:47 +1100 Subject: [PATCH 7/9] take a request body as a param --- plugins/inputs/http_response/README.md | 6 +++++- plugins/inputs/http_response/http_response.go | 20 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/plugins/inputs/http_response/README.md b/plugins/inputs/http_response/README.md index 99770e52606f2..f2f45b2af85b1 100644 --- a/plugins/inputs/http_response/README.md +++ b/plugins/inputs/http_response/README.md @@ -11,7 +11,7 @@ This input plugin will test HTTP/HTTPS connections. address = "http://github.com" ## Set response_timeout (default 10 seconds) response_timeout = 10 - ## HTTP Method + ## HTTP Request Method method = "GET" ## HTTP Request Headers headers = ''' @@ -19,6 +19,10 @@ This input plugin will test HTTP/HTTPS connections. ''' ## Whether to follow redirects from the server (defaults to false) follow_redirects = true + ## Optional HTTP Request Body + body = ''' + {'fake':'data'} + ''' ``` ### Measurements & Fields: diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go index 09569fe730c6e..dc4b2df60b21b 100644 --- a/plugins/inputs/http_response/http_response.go +++ b/plugins/inputs/http_response/http_response.go @@ -3,11 +3,10 @@ package http_response import ( "bufio" "errors" - "fmt" + "io" "net/http" "net/textproto" "net/url" - "os" "strings" "time" @@ -18,6 +17,7 @@ import ( // HTTPResponse struct type HTTPResponse struct { Address string + Body string Method string ResponseTimeout int Headers string @@ -34,7 +34,7 @@ var sampleConfig = ` address = "http://github.com" ## Set response_timeout (default 10 seconds) response_timeout = 10 - ## HTTP Method + ## HTTP Request Method method = "GET" ## HTTP Request Headers headers = ''' @@ -42,6 +42,10 @@ var sampleConfig = ` ''' ## Whether to follow redirects from the server (defaults to false) follow_redirects = true + ## Optional HTTP Request Body + body = ''' + {'fake':'data'} + ''' ` // SampleConfig returns the plugin SampleConfig @@ -49,6 +53,7 @@ func (h *HTTPResponse) SampleConfig() string { return sampleConfig } +// ErrRedirectAttempted indicates that a redirect occurred var ErrRedirectAttempted = errors.New("redirect") // HTTPGather gathers all fields and returns any errors it encounters @@ -61,13 +66,16 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { } if h.FollowRedirects == false { - fmt.Println(h.FollowRedirects) client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return ErrRedirectAttempted } } - request, err := http.NewRequest(h.Method, h.Address, nil) + var body io.Reader + if h.Body != "" { + body = strings.NewReader(h.Body) + } + request, err := http.NewRequest(h.Method, h.Address, body) if err != nil { return nil, err } @@ -81,7 +89,6 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { request.Header = http.Header(mimeHeader) // Start Timer start := time.Now() - request.Write(os.Stdout) resp, err := client.Do(request) if err != nil { if h.FollowRedirects { @@ -89,7 +96,6 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { } if urlError, ok := err.(*url.Error); ok && urlError.Err == ErrRedirectAttempted { - fmt.Println(err) err = nil } else { return nil, err From 6feef3f97d38a626ed0d32adc2bf588b3931c883 Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Mon, 4 Apr 2016 12:20:07 +1000 Subject: [PATCH 8/9] added tests and did some refactoring --- plugins/inputs/http_response/http_response.go | 52 ++-- .../http_response/http_response_test.go | 245 ++++++++++++++++++ 2 files changed, 279 insertions(+), 18 deletions(-) create mode 100644 plugins/inputs/http_response/http_response_test.go diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go index dc4b2df60b21b..cee33795aad02 100644 --- a/plugins/inputs/http_response/http_response.go +++ b/plugins/inputs/http_response/http_response.go @@ -40,12 +40,12 @@ var sampleConfig = ` headers = ''' Host: github.com ''' - ## Whether to follow redirects from the server (defaults to false) - follow_redirects = true - ## Optional HTTP Request Body - body = ''' - {'fake':'data'} - ''' + ## Whether to follow redirects from the server (defaults to false) + follow_redirects = true + ## Optional HTTP Request Body + body = ''' + {'fake':'data'} + ''' ` // SampleConfig returns the plugin SampleConfig @@ -56,20 +56,40 @@ func (h *HTTPResponse) SampleConfig() string { // ErrRedirectAttempted indicates that a redirect occurred var ErrRedirectAttempted = errors.New("redirect") -// HTTPGather gathers all fields and returns any errors it encounters -func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { - // Prepare fields - fields := make(map[string]interface{}) - +// CreateHttpClient creates an http client which will timeout at the specified +// timeout period and can follow redirects if specified +func CreateHttpClient(followRedirects bool, ResponseTimeout time.Duration) *http.Client { client := &http.Client{ - Timeout: time.Second * time.Duration(h.ResponseTimeout), + Timeout: time.Second * ResponseTimeout, } - if h.FollowRedirects == false { + if followRedirects == false { client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return ErrRedirectAttempted } } + return client +} + +// ParseHeaders takes a string of newline seperated http headers and returns a +// http.Header object. An error is returned if the headers cannot be parsed. +func ParseHeaders(headers string) (http.Header, error) { + headers = strings.TrimSpace(headers) + "\n\n" + reader := bufio.NewReader(strings.NewReader(headers)) + tp := textproto.NewReader(reader) + mimeHeader, err := tp.ReadMIMEHeader() + if err != nil { + return nil, err + } + return http.Header(mimeHeader), nil +} + +// HTTPGather gathers all fields and returns any errors it encounters +func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { + // Prepare fields + fields := make(map[string]interface{}) + + client := CreateHttpClient(h.FollowRedirects, time.Duration(h.ResponseTimeout)) var body io.Reader if h.Body != "" { @@ -79,14 +99,10 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { if err != nil { return nil, err } - h.Headers = strings.TrimSpace(h.Headers) + "\n\n" - reader := bufio.NewReader(strings.NewReader(h.Headers)) - tp := textproto.NewReader(reader) - mimeHeader, err := tp.ReadMIMEHeader() + request.Header, err = ParseHeaders(h.Headers) if err != nil { return nil, err } - request.Header = http.Header(mimeHeader) // Start Timer start := time.Now() resp, err := client.Do(request) diff --git a/plugins/inputs/http_response/http_response_test.go b/plugins/inputs/http_response/http_response_test.go new file mode 100644 index 0000000000000..0f568e3b4b648 --- /dev/null +++ b/plugins/inputs/http_response/http_response_test.go @@ -0,0 +1,245 @@ +package http_response + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestParseHeaders(t *testing.T) { + fakeHeaders := ` +Accept: text/plain +Content-Type: application/json +Cache-Control: no-cache +` + headers, err := ParseHeaders(fakeHeaders) + require.NoError(t, err) + testHeaders := make(http.Header) + testHeaders.Add("Accept", "text/plain") + testHeaders.Add("Content-Type", "application/json") + testHeaders.Add("Cache-Control", "no-cache") + assert.Equal(t, testHeaders, headers) + + headers, err = ParseHeaders("Accept text/plain") + require.Error(t, err) +} + +func setUpTestMux() http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) { + http.Redirect(w, req, "/good", http.StatusMovedPermanently) + }) + mux.HandleFunc("/good", func(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "hit the good page!") + }) + mux.HandleFunc("/badredirect", func(w http.ResponseWriter, req *http.Request) { + http.Redirect(w, req, "/badredirect", http.StatusMovedPermanently) + }) + mux.HandleFunc("/mustbepostmethod", func(w http.ResponseWriter, req *http.Request) { + if req.Method != "POST" { + http.Error(w, "method wasn't post", http.StatusMethodNotAllowed) + return + } + fmt.Fprintf(w, "used post correctly!") + }) + mux.HandleFunc("/musthaveabody", func(w http.ResponseWriter, req *http.Request) { + body, err := ioutil.ReadAll(req.Body) + req.Body.Close() + if err != nil { + http.Error(w, "couldn't read request body", http.StatusBadRequest) + return + } + if string(body) == "" { + http.Error(w, "body was empty", http.StatusBadRequest) + return + } + fmt.Fprintf(w, "sent a body!") + }) + mux.HandleFunc("/twosecondnap", func(w http.ResponseWriter, req *http.Request) { + time.Sleep(time.Second * 2) + return + }) + return mux +} + +func TestFields(t *testing.T) { + mux := setUpTestMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + h := &HTTPResponse{ + Address: ts.URL + "/good", + Body: "{ 'test': 'data'}", + Method: "GET", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err := h.HTTPGather() + require.NoError(t, err) + assert.NotEmpty(t, fields) + if assert.NotNil(t, fields["http_response_code"]) { + assert.Equal(t, http.StatusOK, fields["http_response_code"]) + } + assert.NotNil(t, fields["response_time"]) + +} + +func TestRedirects(t *testing.T) { + mux := setUpTestMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + h := &HTTPResponse{ + Address: ts.URL + "/redirect", + Body: "{ 'test': 'data'}", + Method: "GET", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err := h.HTTPGather() + require.NoError(t, err) + assert.NotEmpty(t, fields) + if assert.NotNil(t, fields["http_response_code"]) { + assert.Equal(t, http.StatusOK, fields["http_response_code"]) + } + + h = &HTTPResponse{ + Address: ts.URL + "/badredirect", + Body: "{ 'test': 'data'}", + Method: "GET", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err = h.HTTPGather() + require.Error(t, err) +} + +func TestMethod(t *testing.T) { + mux := setUpTestMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + h := &HTTPResponse{ + Address: ts.URL + "/mustbepostmethod", + Body: "{ 'test': 'data'}", + Method: "POST", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err := h.HTTPGather() + require.NoError(t, err) + assert.NotEmpty(t, fields) + if assert.NotNil(t, fields["http_response_code"]) { + assert.Equal(t, http.StatusOK, fields["http_response_code"]) + } + + h = &HTTPResponse{ + Address: ts.URL + "/mustbepostmethod", + Body: "{ 'test': 'data'}", + Method: "GET", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err = h.HTTPGather() + require.NoError(t, err) + assert.NotEmpty(t, fields) + if assert.NotNil(t, fields["http_response_code"]) { + assert.Equal(t, http.StatusMethodNotAllowed, fields["http_response_code"]) + } + + //check that lowercase methods work correctly + h = &HTTPResponse{ + Address: ts.URL + "/mustbepostmethod", + Body: "{ 'test': 'data'}", + Method: "head", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err = h.HTTPGather() + require.NoError(t, err) + assert.NotEmpty(t, fields) + if assert.NotNil(t, fields["http_response_code"]) { + assert.Equal(t, http.StatusMethodNotAllowed, fields["http_response_code"]) + } +} + +func TestBody(t *testing.T) { + mux := setUpTestMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + h := &HTTPResponse{ + Address: ts.URL + "/musthaveabody", + Body: "{ 'test': 'data'}", + Method: "GET", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err := h.HTTPGather() + require.NoError(t, err) + assert.NotEmpty(t, fields) + if assert.NotNil(t, fields["http_response_code"]) { + assert.Equal(t, http.StatusOK, fields["http_response_code"]) + } + + h = &HTTPResponse{ + Address: ts.URL + "/musthaveabody", + Method: "GET", + ResponseTimeout: 20, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + fields, err = h.HTTPGather() + require.NoError(t, err) + assert.NotEmpty(t, fields) + if assert.NotNil(t, fields["http_response_code"]) { + assert.Equal(t, http.StatusBadRequest, fields["http_response_code"]) + } +} + +func TestTimeout(t *testing.T) { + mux := setUpTestMux() + ts := httptest.NewServer(mux) + defer ts.Close() + + h := &HTTPResponse{ + Address: ts.URL + "/twosecondnap", + Body: "{ 'test': 'data'}", + Method: "GET", + ResponseTimeout: 1, + Headers: ` +Content-Type: application/json +`, + FollowRedirects: true, + } + _, err := h.HTTPGather() + require.Error(t, err) +} From bf09b4f660b658e92f33c11bab6fb282a32e6fd6 Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Thu, 7 Apr 2016 11:57:49 +1000 Subject: [PATCH 9/9] update to 5 second default and string map for headers --- plugins/inputs/http_response/README.md | 9 ++- plugins/inputs/http_response/http_response.go | 40 +++++------ .../http_response/http_response_test.go | 72 +++++++++---------- 3 files changed, 54 insertions(+), 67 deletions(-) diff --git a/plugins/inputs/http_response/README.md b/plugins/inputs/http_response/README.md index f2f45b2af85b1..e2bf75b5fd788 100644 --- a/plugins/inputs/http_response/README.md +++ b/plugins/inputs/http_response/README.md @@ -9,14 +9,13 @@ This input plugin will test HTTP/HTTPS connections. [[inputs.http_response]] ## Server address (default http://localhost) address = "http://github.com" - ## Set response_timeout (default 10 seconds) - response_timeout = 10 + ## Set response_timeout (default 5 seconds) + response_timeout = 5 ## HTTP Request Method method = "GET" ## HTTP Request Headers - headers = ''' - Host: github.com - ''' + [inputs.http_response.headers] + Host = github.com ## Whether to follow redirects from the server (defaults to false) follow_redirects = true ## Optional HTTP Request Body diff --git a/plugins/inputs/http_response/http_response.go b/plugins/inputs/http_response/http_response.go index cee33795aad02..73533fed4c27c 100644 --- a/plugins/inputs/http_response/http_response.go +++ b/plugins/inputs/http_response/http_response.go @@ -1,11 +1,9 @@ package http_response import ( - "bufio" "errors" "io" "net/http" - "net/textproto" "net/url" "strings" "time" @@ -20,7 +18,7 @@ type HTTPResponse struct { Body string Method string ResponseTimeout int - Headers string + Headers map[string]string FollowRedirects bool } @@ -32,14 +30,13 @@ func (h *HTTPResponse) Description() string { var sampleConfig = ` ## Server address (default http://localhost) address = "http://github.com" - ## Set response_timeout (default 10 seconds) - response_timeout = 10 + ## Set response_timeout (default 5 seconds) + response_timeout = 5 ## HTTP Request Method method = "GET" - ## HTTP Request Headers - headers = ''' - Host: github.com - ''' + ## HTTP Request Headers (all values must be strings) + [inputs.http_response.headers] + # Host = "github.com" ## Whether to follow redirects from the server (defaults to false) follow_redirects = true ## Optional HTTP Request Body @@ -71,17 +68,14 @@ func CreateHttpClient(followRedirects bool, ResponseTimeout time.Duration) *http return client } -// ParseHeaders takes a string of newline seperated http headers and returns a -// http.Header object. An error is returned if the headers cannot be parsed. -func ParseHeaders(headers string) (http.Header, error) { - headers = strings.TrimSpace(headers) + "\n\n" - reader := bufio.NewReader(strings.NewReader(headers)) - tp := textproto.NewReader(reader) - mimeHeader, err := tp.ReadMIMEHeader() - if err != nil { - return nil, err +// CreateHeaders takes a map of header strings and puts them +// into a http.Header Object +func CreateHeaders(headers map[string]string) http.Header { + httpHeaders := make(http.Header) + for key := range headers { + httpHeaders.Add(key, headers[key]) } - return http.Header(mimeHeader), nil + return httpHeaders } // HTTPGather gathers all fields and returns any errors it encounters @@ -99,10 +93,8 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { if err != nil { return nil, err } - request.Header, err = ParseHeaders(h.Headers) - if err != nil { - return nil, err - } + request.Header = CreateHeaders(h.Headers) + // Start Timer start := time.Now() resp, err := client.Do(request) @@ -126,7 +118,7 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) { func (h *HTTPResponse) Gather(acc telegraf.Accumulator) error { // Set default values if h.ResponseTimeout < 1 { - h.ResponseTimeout = 10 + h.ResponseTimeout = 5 } // Check send and expected string if h.Method == "" { diff --git a/plugins/inputs/http_response/http_response_test.go b/plugins/inputs/http_response/http_response_test.go index 0f568e3b4b648..acdfeac7598b8 100644 --- a/plugins/inputs/http_response/http_response_test.go +++ b/plugins/inputs/http_response/http_response_test.go @@ -11,22 +11,18 @@ import ( "time" ) -func TestParseHeaders(t *testing.T) { - fakeHeaders := ` -Accept: text/plain -Content-Type: application/json -Cache-Control: no-cache -` - headers, err := ParseHeaders(fakeHeaders) - require.NoError(t, err) +func TestCreateHeaders(t *testing.T) { + fakeHeaders := map[string]string{ + "Accept": "text/plain", + "Content-Type": "application/json", + "Cache-Control": "no-cache", + } + headers := CreateHeaders(fakeHeaders) testHeaders := make(http.Header) testHeaders.Add("Accept", "text/plain") testHeaders.Add("Content-Type", "application/json") testHeaders.Add("Cache-Control", "no-cache") assert.Equal(t, testHeaders, headers) - - headers, err = ParseHeaders("Accept text/plain") - require.Error(t, err) } func setUpTestMux() http.Handler { @@ -77,9 +73,9 @@ func TestFields(t *testing.T) { Body: "{ 'test': 'data'}", Method: "GET", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err := h.HTTPGather() @@ -102,9 +98,9 @@ func TestRedirects(t *testing.T) { Body: "{ 'test': 'data'}", Method: "GET", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err := h.HTTPGather() @@ -119,9 +115,9 @@ Content-Type: application/json Body: "{ 'test': 'data'}", Method: "GET", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err = h.HTTPGather() @@ -138,9 +134,9 @@ func TestMethod(t *testing.T) { Body: "{ 'test': 'data'}", Method: "POST", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err := h.HTTPGather() @@ -155,9 +151,9 @@ Content-Type: application/json Body: "{ 'test': 'data'}", Method: "GET", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err = h.HTTPGather() @@ -173,9 +169,9 @@ Content-Type: application/json Body: "{ 'test': 'data'}", Method: "head", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err = h.HTTPGather() @@ -196,9 +192,9 @@ func TestBody(t *testing.T) { Body: "{ 'test': 'data'}", Method: "GET", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err := h.HTTPGather() @@ -212,9 +208,9 @@ Content-Type: application/json Address: ts.URL + "/musthaveabody", Method: "GET", ResponseTimeout: 20, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } fields, err = h.HTTPGather() @@ -235,9 +231,9 @@ func TestTimeout(t *testing.T) { Body: "{ 'test': 'data'}", Method: "GET", ResponseTimeout: 1, - Headers: ` -Content-Type: application/json -`, + Headers: map[string]string{ + "Content-Type": "application/json", + }, FollowRedirects: true, } _, err := h.HTTPGather()