summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/httpcheck/collect.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/go/collectors/go.d.plugin/modules/httpcheck/collect.go189
1 files changed, 189 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/httpcheck/collect.go b/src/go/collectors/go.d.plugin/modules/httpcheck/collect.go
new file mode 100644
index 000000000..8d88dc02f
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/httpcheck/collect.go
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package httpcheck
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/stm"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+)
+
+type reqErrCode int
+
+const (
+ codeTimeout reqErrCode = iota
+ codeRedirect
+ codeNoConnection
+)
+
+func (hc *HTTPCheck) collect() (map[string]int64, error) {
+ req, err := web.NewHTTPRequest(hc.Request)
+ if err != nil {
+ return nil, fmt.Errorf("error on creating HTTP requests to %s : %v", hc.Request.URL, err)
+ }
+
+ if hc.CookieFile != "" {
+ if err := hc.readCookieFile(); err != nil {
+ return nil, fmt.Errorf("error on reading cookie file '%s': %v", hc.CookieFile, err)
+ }
+ }
+
+ start := time.Now()
+ resp, err := hc.httpClient.Do(req)
+ dur := time.Since(start)
+
+ defer closeBody(resp)
+
+ var mx metrics
+
+ if hc.isError(err, resp) {
+ hc.Debug(err)
+ hc.collectErrResponse(&mx, err)
+ } else {
+ mx.ResponseTime = durationToMs(dur)
+ hc.collectOKResponse(&mx, resp)
+ }
+
+ if hc.metrics.Status != mx.Status {
+ mx.InState = hc.UpdateEvery
+ } else {
+ mx.InState = hc.metrics.InState + hc.UpdateEvery
+ }
+ hc.metrics = mx
+
+ return stm.ToMap(mx), nil
+}
+
+func (hc *HTTPCheck) isError(err error, resp *http.Response) bool {
+ return err != nil && !(errors.Is(err, web.ErrRedirectAttempted) && hc.acceptedStatuses[resp.StatusCode])
+}
+
+func (hc *HTTPCheck) collectErrResponse(mx *metrics, err error) {
+ switch code := decodeReqError(err); code {
+ case codeNoConnection:
+ mx.Status.NoConnection = true
+ case codeTimeout:
+ mx.Status.Timeout = true
+ case codeRedirect:
+ mx.Status.Redirect = true
+ default:
+ panic(fmt.Sprintf("unknown request error code : %d", code))
+ }
+}
+
+func (hc *HTTPCheck) collectOKResponse(mx *metrics, resp *http.Response) {
+ hc.Debugf("endpoint '%s' returned %d (%s) HTTP status code", hc.URL, resp.StatusCode, resp.Status)
+
+ if !hc.acceptedStatuses[resp.StatusCode] {
+ mx.Status.BadStatusCode = true
+ return
+ }
+
+ bs, err := io.ReadAll(resp.Body)
+ // golang net/http closes body on redirect
+ if err != nil && !errors.Is(err, io.EOF) && !strings.Contains(err.Error(), "read on closed response body") {
+ hc.Warningf("error on reading body : %v", err)
+ mx.Status.BadContent = true
+ return
+ }
+
+ mx.ResponseLength = len(bs)
+
+ if hc.reResponse != nil && !hc.reResponse.Match(bs) {
+ mx.Status.BadContent = true
+ return
+ }
+
+ if ok := hc.checkHeader(resp); !ok {
+ mx.Status.BadHeader = true
+ return
+ }
+
+ mx.Status.Success = true
+}
+
+func (hc *HTTPCheck) checkHeader(resp *http.Response) bool {
+ for _, m := range hc.headerMatch {
+ value := resp.Header.Get(m.key)
+
+ var ok bool
+ switch {
+ case value == "":
+ ok = m.exclude
+ case m.valMatcher == nil:
+ ok = !m.exclude
+ default:
+ ok = m.valMatcher.MatchString(value)
+ }
+
+ if !ok {
+ hc.Debugf("header match: bad header: exlude '%v' key '%s' value '%s'", m.exclude, m.key, value)
+ return false
+ }
+ }
+
+ return true
+}
+
+func decodeReqError(err error) reqErrCode {
+ if err == nil {
+ panic("nil error")
+ }
+
+ if errors.Is(err, web.ErrRedirectAttempted) {
+ return codeRedirect
+ }
+ var v net.Error
+ if errors.As(err, &v) && v.Timeout() {
+ return codeTimeout
+ }
+ return codeNoConnection
+}
+
+func (hc *HTTPCheck) readCookieFile() error {
+ if hc.CookieFile == "" {
+ return nil
+ }
+
+ fi, err := os.Stat(hc.CookieFile)
+ if err != nil {
+ return err
+ }
+
+ if hc.cookieFileModTime.Equal(fi.ModTime()) {
+ hc.Debugf("cookie file '%s' modification time has not changed, using previously read data", hc.CookieFile)
+ return nil
+ }
+
+ hc.Debugf("reading cookie file '%s'", hc.CookieFile)
+
+ jar, err := loadCookieJar(hc.CookieFile)
+ if err != nil {
+ return err
+ }
+
+ hc.httpClient.Jar = jar
+ hc.cookieFileModTime = fi.ModTime()
+
+ return nil
+}
+
+func closeBody(resp *http.Response) {
+ if resp == nil || resp.Body == nil {
+ return
+ }
+ _, _ = io.Copy(io.Discard, resp.Body)
+ _ = resp.Body.Close()
+}
+
+func durationToMs(duration time.Duration) int {
+ return int(duration) / (int(time.Millisecond) / int(time.Nanosecond))
+}