Skip to content

Commit 0f6efe0

Browse files
authored
add cookie_file config option (netdata#1133)
1 parent ea3c998 commit 0f6efe0

File tree

8 files changed

+443
-285
lines changed

8 files changed

+443
-285
lines changed

modules/httpcheck/charts.go

-4
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ var responseStatusChart = module.Chart{
5757
{ID: "timeout"},
5858
{ID: "bad_content"},
5959
{ID: "bad_status"},
60-
//{ID: "dns_lookup_error", Name: "dns lookup error"},
61-
//{ID: "address_parse_error", Name: "address parse error"},
62-
//{ID: "redirect_error", Name: "redirect error"},
63-
//{ID: "body_read_error", Name: "body read error"},
6460
},
6561
}
6662

modules/httpcheck/collect.go

+41-47
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"net"
99
"net/http"
10+
"os"
1011
"time"
1112

1213
"github.com/netdata/go.d.plugin/pkg/stm"
@@ -17,9 +18,6 @@ type reqErrCode int
1718

1819
const (
1920
codeTimeout reqErrCode = iota
20-
//codeDNSLookup
21-
//codeParseAddress
22-
//codeRedirect
2321
codeNoConnection
2422
)
2523

@@ -29,13 +27,20 @@ func (hc *HTTPCheck) collect() (map[string]int64, error) {
2927
return nil, fmt.Errorf("error on creating HTTP requests to %s : %v", hc.Request.URL, err)
3028
}
3129

32-
var mx metrics
30+
if hc.CookieFile != "" {
31+
if err := hc.readCookieFile(); err != nil {
32+
return nil, fmt.Errorf("error on reading cookie file '%s': %v", hc.CookieFile, err)
33+
}
34+
}
3335

3436
start := time.Now()
35-
resp, err := hc.client.Do(req)
37+
resp, err := hc.httpClient.Do(req)
3638
dur := time.Since(start)
39+
3740
defer closeBody(resp)
3841

42+
var mx metrics
43+
3944
if err != nil {
4045
hc.Warning(err)
4146
hc.collectErrResponse(&mx, err)
@@ -44,35 +49,24 @@ func (hc *HTTPCheck) collect() (map[string]int64, error) {
4449
hc.collectOKResponse(&mx, resp)
4550
}
4651

47-
changed := hc.metrics.Status != mx.Status
48-
if changed {
52+
if hc.metrics.Status != mx.Status {
4953
mx.InState = hc.UpdateEvery
5054
} else {
5155
mx.InState = hc.metrics.InState + hc.UpdateEvery
5256
}
5357
hc.metrics = mx
5458

55-
//if err == nil || mx.Status.RedirectError {
56-
// mx.ResponseTime = durationToMs(end)
57-
//}
58-
5959
return stm.ToMap(mx), nil
6060
}
6161

6262
func (hc *HTTPCheck) collectErrResponse(mx *metrics, err error) {
6363
switch code := decodeReqError(err); code {
64-
default:
65-
panic(fmt.Sprintf("unknown request error code : %d", code))
6664
case codeNoConnection:
6765
mx.Status.NoConnection = true
68-
//case codeDNSLookup:
69-
// mx.Status.DNSLookupError = true
70-
//case codeParseAddress:
71-
// mx.Status.ParseAddressError = true
72-
//case codeRedirect:
73-
// mx.Status.RedirectError = true
7466
case codeTimeout:
7567
mx.Status.Timeout = true
68+
default:
69+
panic(fmt.Sprintf("unknown request error code : %d", code))
7670
}
7771
}
7872

@@ -109,34 +103,34 @@ func decodeReqError(err error) reqErrCode {
109103
return codeTimeout
110104
}
111105
return codeNoConnection
112-
//
113-
//netErr, isNetErr := err.(net.Error)
114-
//if isNetErr && netErr.Timeout() {
115-
// return codeTimeout
116-
//}
117-
//
118-
//urlErr, isURLErr := err.(*url.Error)
119-
//if !isURLErr {
120-
// return codeNoConnection
121-
//}
122-
//
123-
//if urlErr.Err == web.ErrRedirectAttempted {
124-
// return codeRedirect
125-
//}
126-
//
127-
//opErr, isOpErr := (urlErr.Err).(*net.OpError)
128-
//if !isOpErr {
129-
// return codeNoConnection
130-
//}
131-
//
132-
//switch (opErr.Err).(type) {
133-
//case *net.DNSError:
134-
// return codeDNSLookup
135-
//case *net.ParseError:
136-
// return codeParseAddress
137-
//}
138-
//
139-
//return codeNoConnection
106+
}
107+
108+
func (hc *HTTPCheck) readCookieFile() error {
109+
if hc.CookieFile == "" {
110+
return nil
111+
}
112+
113+
fi, err := os.Stat(hc.CookieFile)
114+
if err != nil {
115+
return err
116+
}
117+
118+
if hc.cookieFileModTime.Equal(fi.ModTime()) {
119+
hc.Debugf("cookie file '%s' modification time has not changed, using previously read data", hc.CookieFile)
120+
return nil
121+
}
122+
123+
hc.Debugf("reading cookie file '%s'", hc.CookieFile)
124+
125+
jar, err := loadCookieJar(hc.CookieFile)
126+
if err != nil {
127+
return err
128+
}
129+
130+
hc.httpClient.Jar = jar
131+
hc.cookieFileModTime = fi.ModTime()
132+
133+
return nil
140134
}
141135

142136
func closeBody(resp *http.Response) {

modules/httpcheck/cookiejar.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package httpcheck
4+
5+
import (
6+
"bufio"
7+
"fmt"
8+
"net/http"
9+
"net/http/cookiejar"
10+
"net/url"
11+
"os"
12+
"strconv"
13+
"strings"
14+
"time"
15+
16+
"golang.org/x/net/publicsuffix"
17+
)
18+
19+
// TODO: implement proper cookie auth support
20+
// relevant forum topic: https://community.netdata.cloud/t/howto-http-endpoint-collector-with-cookie-and-user-pass/3981/5?u=ilyam8
21+
22+
// cookie file format: https://everything.curl.dev/http/cookies/fileformat
23+
func loadCookieJar(path string) (http.CookieJar, error) {
24+
file, err := os.Open(path)
25+
if err != nil {
26+
return nil, err
27+
}
28+
defer func() { _ = file.Close() }()
29+
30+
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
sc := bufio.NewScanner(file)
36+
37+
for sc.Scan() {
38+
line, httpOnly := strings.CutPrefix(strings.TrimSpace(sc.Text()), "#HttpOnly_")
39+
40+
if strings.HasPrefix(line, "#") || line == "" {
41+
continue
42+
}
43+
44+
parts := strings.Fields(line)
45+
if len(parts) != 6 && len(parts) != 7 {
46+
return nil, fmt.Errorf("got %d fields in line '%s', want 6 or 7", len(parts), line)
47+
}
48+
49+
for i, v := range parts {
50+
parts[i] = strings.TrimSpace(v)
51+
}
52+
53+
cookie := &http.Cookie{
54+
Domain: parts[0],
55+
Path: parts[2],
56+
Name: parts[5],
57+
HttpOnly: httpOnly,
58+
}
59+
cookie.Secure, err = strconv.ParseBool(parts[3])
60+
if err != nil {
61+
return nil, err
62+
}
63+
expires, err := strconv.ParseInt(parts[4], 10, 64)
64+
if err != nil {
65+
return nil, err
66+
}
67+
if expires > 0 {
68+
cookie.Expires = time.Unix(expires, 0)
69+
}
70+
if len(parts) == 7 {
71+
cookie.Value = parts[6]
72+
}
73+
74+
scheme := "http"
75+
if cookie.Secure {
76+
scheme = "https"
77+
}
78+
cookieURL := &url.URL{
79+
Scheme: scheme,
80+
Host: cookie.Domain,
81+
}
82+
83+
cookies := jar.Cookies(cookieURL)
84+
cookies = append(cookies, cookie)
85+
jar.SetCookies(cookieURL, cookies)
86+
}
87+
88+
return jar, nil
89+
}

modules/httpcheck/httpcheck.go

+21-17
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,27 @@ func New() *HTTPCheck {
3737

3838
type Config struct {
3939
web.HTTP `yaml:",inline"`
40+
UpdateEvery int `yaml:"update_every"`
4041
AcceptedStatuses []int `yaml:"status_accepted"`
4142
ResponseMatch string `yaml:"response_match"`
43+
CookieFile string `yaml:"cookie_file"`
4244
}
4345

44-
type (
45-
HTTPCheck struct {
46-
module.Base
47-
Config `yaml:",inline"`
48-
UpdateEvery int `yaml:"update_every"`
46+
type HTTPCheck struct {
47+
module.Base
48+
Config `yaml:",inline"`
4949

50-
charts *module.Charts
50+
httpClient *http.Client
5151

52-
acceptedStatuses map[int]bool
53-
reResponse *regexp.Regexp
54-
client client
55-
metrics metrics
56-
}
57-
client interface {
58-
Do(*http.Request) (*http.Response, error)
59-
}
60-
)
52+
charts *module.Charts
53+
54+
acceptedStatuses map[int]bool
55+
reResponse *regexp.Regexp
56+
57+
cookieFileModTime time.Time
58+
59+
metrics metrics
60+
}
6161

6262
func (hc *HTTPCheck) Init() bool {
6363
if err := hc.validateConfig(); err != nil {
@@ -72,7 +72,7 @@ func (hc *HTTPCheck) Init() bool {
7272
hc.Errorf("init HTTP client: %v", err)
7373
return false
7474
}
75-
hc.client = httpClient
75+
hc.httpClient = httpClient
7676

7777
re, err := hc.initResponseMatchRegexp()
7878
if err != nil {
@@ -115,4 +115,8 @@ func (hc *HTTPCheck) Collect() map[string]int64 {
115115
return mx
116116
}
117117

118-
func (hc *HTTPCheck) Cleanup() {}
118+
func (hc *HTTPCheck) Cleanup() {
119+
if hc.httpClient != nil {
120+
hc.httpClient.CloseIdleConnections()
121+
}
122+
}

0 commit comments

Comments
 (0)