http_response : Add in support for looking for substring in response (#2204)

* Add in support for looking for substring in response

* Add note to CHANGELOG.md

* Switch from substring match to regex match

* Requested code changes

* Make requested changes and refactor to avoid nested if-else.

* Convert tabs to space and compile regex once
This commit is contained in:
Len Smith
2017-02-01 09:21:08 -05:00
committed by Cameron Sparr
parent 32e06a489d
commit 9003efc3fa
4 changed files with 135 additions and 6 deletions

View File

@@ -23,6 +23,11 @@ This input plugin will test HTTP/HTTPS connections.
# {'fake':'data'}
# '''
## Optional substring or regex match in body of the response
## response_string_match = "\"service_status\": \"up\""
## response_string_match = "ok"
## response_string_match = "\".*_status\".?:.?\"up\""
## Optional SSL Config
# ssl_ca = "/etc/telegraf/ca.pem"
# ssl_cert = "/etc/telegraf/cert.pem"

View File

@@ -3,8 +3,11 @@ package http_response
import (
"errors"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"time"
@@ -15,12 +18,14 @@ import (
// HTTPResponse struct
type HTTPResponse struct {
Address string
Body string
Method string
ResponseTimeout internal.Duration
Headers map[string]string
FollowRedirects bool
Address string
Body string
Method string
ResponseTimeout internal.Duration
Headers map[string]string
FollowRedirects bool
ResponseStringMatch string
compiledStringMatch *regexp.Regexp
// Path to CA file
SSLCA string `toml:"ssl_ca"`
@@ -54,6 +59,11 @@ var sampleConfig = `
# {'fake':'data'}
# '''
## Optional substring or regex match in body of the response
## response_string_match = "\"service_status\": \"up\""
## response_string_match = "ok"
## response_string_match = "\".*_status\".?:.?\"up\""
## Optional SSL Config
# ssl_ca = "/etc/telegraf/ca.pem"
# ssl_cert = "/etc/telegraf/cert.pem"
@@ -137,6 +147,35 @@ func (h *HTTPResponse) HTTPGather() (map[string]interface{}, error) {
}
fields["response_time"] = time.Since(start).Seconds()
fields["http_response_code"] = resp.StatusCode
// Check the response for a regex match.
if h.ResponseStringMatch != "" {
// Compile once and reuse
if h.compiledStringMatch == nil {
h.compiledStringMatch = regexp.MustCompile(h.ResponseStringMatch)
if err != nil {
log.Printf("E! Failed to compile regular expression %s : %s", h.ResponseStringMatch, err)
fields["response_string_match"] = 0
return fields, nil
}
}
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("E! Failed to read body of HTTP Response : %s", err)
fields["response_string_match"] = 0
return fields, nil
}
if h.compiledStringMatch.Match(bodyBytes) {
fields["response_string_match"] = 1
} else {
fields["response_string_match"] = 0
}
}
return fields, nil
}

View File

@@ -22,6 +22,9 @@ func setUpTestMux() http.Handler {
mux.HandleFunc("/good", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hit the good page!")
})
mux.HandleFunc("/jsonresponse", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "\"service_status\": \"up\", \"healthy\" : \"true\"")
})
mux.HandleFunc("/badredirect", func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, "/badredirect", http.StatusMovedPermanently)
})
@@ -236,6 +239,87 @@ func TestBody(t *testing.T) {
}
}
func TestStringMatch(t *testing.T) {
mux := setUpTestMux()
ts := httptest.NewServer(mux)
defer ts.Close()
h := &HTTPResponse{
Address: ts.URL + "/good",
Body: "{ 'test': 'data'}",
Method: "GET",
ResponseStringMatch: "hit the good page",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
Headers: map[string]string{
"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.Equal(t, 1, fields["response_string_match"])
assert.NotNil(t, fields["response_time"])
}
func TestStringMatchJson(t *testing.T) {
mux := setUpTestMux()
ts := httptest.NewServer(mux)
defer ts.Close()
h := &HTTPResponse{
Address: ts.URL + "/jsonresponse",
Body: "{ 'test': 'data'}",
Method: "GET",
ResponseStringMatch: "\"service_status\": \"up\"",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
Headers: map[string]string{
"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.Equal(t, 1, fields["response_string_match"])
assert.NotNil(t, fields["response_time"])
}
func TestStringMatchFail(t *testing.T) {
mux := setUpTestMux()
ts := httptest.NewServer(mux)
defer ts.Close()
h := &HTTPResponse{
Address: ts.URL + "/good",
Body: "{ 'test': 'data'}",
Method: "GET",
ResponseStringMatch: "hit the bad page",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
Headers: map[string]string{
"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.Equal(t, 0, fields["response_string_match"])
assert.NotNil(t, fields["response_time"])
}
func TestTimeout(t *testing.T) {
mux := setUpTestMux()
ts := httptest.NewServer(mux)