Skip to content

Commit

Permalink
Add support for interface field in http_response input plugin (#6006)
Browse files Browse the repository at this point in the history
  • Loading branch information
GeorgeMac authored and danielnelson committed Jun 19, 2019
1 parent f8bef14 commit 8d04cb7
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 1 deletion.
3 changes: 3 additions & 0 deletions plugins/inputs/http_response/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ This input plugin checks HTTP/HTTPS connections.
## HTTP Request Headers (all values must be strings)
# [inputs.http_response.headers]
# Host = "github.com"
## Interface to use when dialing an address
# interface = "eth0"
```

### Metrics:
Expand Down
38 changes: 37 additions & 1 deletion plugins/inputs/http_response/http_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type HTTPResponse struct {
Headers map[string]string
FollowRedirects bool
ResponseStringMatch string
Interface string
tls.ClientConfig

compiledStringMatch *regexp.Regexp
Expand Down Expand Up @@ -82,6 +83,9 @@ var sampleConfig = `
## HTTP Request Headers (all values must be strings)
# [inputs.http_response.headers]
# Host = "github.com"
## Interface to use when dialing an address
# interface = "eth0"
`

// SampleConfig returns the plugin SampleConfig
Expand All @@ -108,16 +112,27 @@ func getProxyFunc(http_proxy string) func(*http.Request) (*url.URL, error) {
}
}

// CreateHttpClient creates an http client which will timeout at the specified
// createHttpClient creates an http client which will timeout at the specified
// timeout period and can follow redirects if specified
func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
tlsCfg, err := h.ClientConfig.TLSConfig()
if err != nil {
return nil, err
}

dialer := &net.Dialer{}

if h.Interface != "" {
dialer.LocalAddr, err = localAddress(h.Interface)
if err != nil {
return nil, err
}
}

client := &http.Client{
Transport: &http.Transport{
Proxy: getProxyFunc(h.HTTPProxy),
DialContext: dialer.DialContext,
DisableKeepAlives: true,
TLSClientConfig: tlsCfg,
},
Expand All @@ -132,6 +147,27 @@ func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
return client, nil
}

func localAddress(interfaceName string) (net.Addr, error) {
i, err := net.InterfaceByName(interfaceName)
if err != nil {
return nil, err
}

addrs, err := i.Addrs()
if err != nil {
return nil, err
}

for _, addr := range addrs {
if naddr, ok := addr.(*net.IPNet); ok {
// leaving port set to zero to let kernel pick
return &net.TCPAddr{IP: naddr.IP}, nil
}
}

return nil, fmt.Errorf("cannot create local address for interface %q", interfaceName)
}

func setResult(result_string string, fields map[string]interface{}, tags map[string]string) {
result_codes := map[string]int{
"success": 0,
Expand Down
63 changes: 63 additions & 0 deletions plugins/inputs/http_response/http_response_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package http_response

import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"testing"
Expand Down Expand Up @@ -210,6 +212,67 @@ func TestFields(t *testing.T) {
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
}

func findInterface() (net.Interface, error) {
potential, _ := net.Interfaces()

for _, i := range potential {
// we are only interest in loopback interfaces which are up
if (i.Flags&net.FlagUp == 0) || (i.Flags&net.FlagLoopback == 0) {
continue
}

if addrs, _ := i.Addrs(); len(addrs) > 0 {
// return interface if it has at least one unicast address
return i, nil
}
}

return net.Interface{}, errors.New("cannot find suitable loopback interface")
}

func TestInterface(t *testing.T) {
var (
mux = setUpTestMux()
ts = httptest.NewServer(mux)
)

defer ts.Close()

intf, err := findInterface()
require.NoError(t, err)

h := &HTTPResponse{
Address: ts.URL + "/good",
Body: "{ 'test': 'data'}",
Method: "GET",
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
Headers: map[string]string{
"Content-Type": "application/json",
},
FollowRedirects: true,
Interface: intf.Name,
}

var acc testutil.Accumulator
err = h.Gather(&acc)
require.NoError(t, err)

expectedFields := map[string]interface{}{
"http_response_code": http.StatusOK,
"result_type": "success",
"result_code": 0,
"response_time": nil,
}
expectedTags := map[string]interface{}{
"server": nil,
"method": "GET",
"status_code": "200",
"result": "success",
}
absentFields := []string{"response_string_match"}
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
}

func TestRedirects(t *testing.T) {
mux := setUpTestMux()
ts := httptest.NewServer(mux)
Expand Down

0 comments on commit 8d04cb7

Please sign in to comment.