From 3fcc81866a873cde4162d515d4ea2cd3349b46b5 Mon Sep 17 00:00:00 2001 From: Luke Swithenbank Date: Mon, 4 Apr 2016 12:20:07 +1000 Subject: [PATCH] added tests and did some refactoring --- plugins/inputs/http_response/http_response.go | 56 ++-- .../http_response/http_response_test.go | 245 ++++++++++++++++++ 2 files changed, 281 insertions(+), 20 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 dc4b2df60..cee33795a 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") +// 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 * ResponseTimeout, + } + + 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 := &http.Client{ - Timeout: time.Second * time.Duration(h.ResponseTimeout), - } - - if h.FollowRedirects == false { - client.CheckRedirect = func(req *http.Request, via []*http.Request) error { - return ErrRedirectAttempted - } - } + 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 000000000..0f568e3b4 --- /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) +}