Skip to content

Commit 8d04cb7

Browse files
GeorgeMacdanielnelson
authored andcommitted
Add support for interface field in http_response input plugin (#6006)
1 parent f8bef14 commit 8d04cb7

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

plugins/inputs/http_response/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ This input plugin checks HTTP/HTTPS connections.
4646
## HTTP Request Headers (all values must be strings)
4747
# [inputs.http_response.headers]
4848
# Host = "github.com"
49+
50+
## Interface to use when dialing an address
51+
# interface = "eth0"
4952
```
5053

5154
### Metrics:

plugins/inputs/http_response/http_response.go

+37-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type HTTPResponse struct {
3131
Headers map[string]string
3232
FollowRedirects bool
3333
ResponseStringMatch string
34+
Interface string
3435
tls.ClientConfig
3536

3637
compiledStringMatch *regexp.Regexp
@@ -82,6 +83,9 @@ var sampleConfig = `
8283
## HTTP Request Headers (all values must be strings)
8384
# [inputs.http_response.headers]
8485
# Host = "github.com"
86+
87+
## Interface to use when dialing an address
88+
# interface = "eth0"
8589
`
8690

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

111-
// CreateHttpClient creates an http client which will timeout at the specified
115+
// createHttpClient creates an http client which will timeout at the specified
112116
// timeout period and can follow redirects if specified
113117
func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
114118
tlsCfg, err := h.ClientConfig.TLSConfig()
115119
if err != nil {
116120
return nil, err
117121
}
122+
123+
dialer := &net.Dialer{}
124+
125+
if h.Interface != "" {
126+
dialer.LocalAddr, err = localAddress(h.Interface)
127+
if err != nil {
128+
return nil, err
129+
}
130+
}
131+
118132
client := &http.Client{
119133
Transport: &http.Transport{
120134
Proxy: getProxyFunc(h.HTTPProxy),
135+
DialContext: dialer.DialContext,
121136
DisableKeepAlives: true,
122137
TLSClientConfig: tlsCfg,
123138
},
@@ -132,6 +147,27 @@ func (h *HTTPResponse) createHttpClient() (*http.Client, error) {
132147
return client, nil
133148
}
134149

150+
func localAddress(interfaceName string) (net.Addr, error) {
151+
i, err := net.InterfaceByName(interfaceName)
152+
if err != nil {
153+
return nil, err
154+
}
155+
156+
addrs, err := i.Addrs()
157+
if err != nil {
158+
return nil, err
159+
}
160+
161+
for _, addr := range addrs {
162+
if naddr, ok := addr.(*net.IPNet); ok {
163+
// leaving port set to zero to let kernel pick
164+
return &net.TCPAddr{IP: naddr.IP}, nil
165+
}
166+
}
167+
168+
return nil, fmt.Errorf("cannot create local address for interface %q", interfaceName)
169+
}
170+
135171
func setResult(result_string string, fields map[string]interface{}, tags map[string]string) {
136172
result_codes := map[string]int{
137173
"success": 0,

plugins/inputs/http_response/http_response_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package http_response
22

33
import (
4+
"errors"
45
"fmt"
56
"io/ioutil"
7+
"net"
68
"net/http"
79
"net/http/httptest"
810
"testing"
@@ -210,6 +212,67 @@ func TestFields(t *testing.T) {
210212
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
211213
}
212214

215+
func findInterface() (net.Interface, error) {
216+
potential, _ := net.Interfaces()
217+
218+
for _, i := range potential {
219+
// we are only interest in loopback interfaces which are up
220+
if (i.Flags&net.FlagUp == 0) || (i.Flags&net.FlagLoopback == 0) {
221+
continue
222+
}
223+
224+
if addrs, _ := i.Addrs(); len(addrs) > 0 {
225+
// return interface if it has at least one unicast address
226+
return i, nil
227+
}
228+
}
229+
230+
return net.Interface{}, errors.New("cannot find suitable loopback interface")
231+
}
232+
233+
func TestInterface(t *testing.T) {
234+
var (
235+
mux = setUpTestMux()
236+
ts = httptest.NewServer(mux)
237+
)
238+
239+
defer ts.Close()
240+
241+
intf, err := findInterface()
242+
require.NoError(t, err)
243+
244+
h := &HTTPResponse{
245+
Address: ts.URL + "/good",
246+
Body: "{ 'test': 'data'}",
247+
Method: "GET",
248+
ResponseTimeout: internal.Duration{Duration: time.Second * 20},
249+
Headers: map[string]string{
250+
"Content-Type": "application/json",
251+
},
252+
FollowRedirects: true,
253+
Interface: intf.Name,
254+
}
255+
256+
var acc testutil.Accumulator
257+
err = h.Gather(&acc)
258+
require.NoError(t, err)
259+
260+
expectedFields := map[string]interface{}{
261+
"http_response_code": http.StatusOK,
262+
"result_type": "success",
263+
"result_code": 0,
264+
"response_time": nil,
265+
}
266+
expectedTags := map[string]interface{}{
267+
"server": nil,
268+
"method": "GET",
269+
"status_code": "200",
270+
"result": "success",
271+
}
272+
absentFields := []string{"response_string_match"}
273+
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
274+
}
275+
213276
func TestRedirects(t *testing.T) {
214277
mux := setUpTestMux()
215278
ts := httptest.NewServer(mux)

0 commit comments

Comments
 (0)