Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
httpcheck: add header_match
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyam8 committed Jan 3, 2024
1 parent 79303d6 commit 22c3a7b
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 8 deletions.
1 change: 1 addition & 0 deletions modules/httpcheck/charts.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var responseStatusChart = module.Chart{
{ID: "redirect"},
{ID: "bad_content"},
{ID: "bad_status"},
{ID: "bad_header"},
},
}

Expand Down
28 changes: 28 additions & 0 deletions modules/httpcheck/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,37 @@ func (hc *HTTPCheck) collectOKResponse(mx *metrics, resp *http.Response) {
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("headers 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")
Expand Down
30 changes: 23 additions & 7 deletions modules/httpcheck/httpcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,21 @@ func New() *HTTPCheck {
}
}

type Config struct {
web.HTTP `yaml:",inline"`
UpdateEvery int `yaml:"update_every"`
AcceptedStatuses []int `yaml:"status_accepted"`
ResponseMatch string `yaml:"response_match"`
CookieFile string `yaml:"cookie_file"`
}
type (
Config struct {
web.HTTP `yaml:",inline"`
UpdateEvery int `yaml:"update_every"`
AcceptedStatuses []int `yaml:"status_accepted"`
ResponseMatch string `yaml:"response_match"`
CookieFile string `yaml:"cookie_file"`
HeaderMatch []HeaderMatchConfig `yaml:"header_match"`
}
HeaderMatchConfig struct {
Exclude bool `yaml:"exclude"`
Key string `yaml:"key"`
Value string `yaml:"value"`
}
)

type HTTPCheck struct {
module.Base
Expand All @@ -58,6 +66,7 @@ type HTTPCheck struct {

acceptedStatuses map[int]bool
reResponse *regexp.Regexp
headerMatch []headersMatch

cookieFileModTime time.Time

Expand Down Expand Up @@ -86,6 +95,13 @@ func (hc *HTTPCheck) Init() bool {
}
hc.reResponse = re

hm, err := hc.initHeadersMatch()
if err != nil {
hc.Errorf("init headers match: %v", err)
return false
}
hc.headerMatch = hm

for _, v := range hc.AcceptedStatuses {
hc.acceptedStatuses[v] = true
}
Expand Down
175 changes: 175 additions & 0 deletions modules/httpcheck/httpcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,14 @@ func TestHTTPCheck_Check(t *testing.T) {
func TestHTTPCheck_Collect(t *testing.T) {
tests := map[string]struct {
prepare func() (httpCheck *HTTPCheck, cleanup func())
update func(check *HTTPCheck)
wantMetrics map[string]int64
}{
"success case": {
prepare: prepareSuccessCase,
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 5,
Expand All @@ -164,6 +166,7 @@ func TestHTTPCheck_Collect(t *testing.T) {
prepare: prepareTimeoutCase,
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 0,
Expand All @@ -178,6 +181,7 @@ func TestHTTPCheck_Collect(t *testing.T) {
prepare: prepareRedirectSuccessCase,
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 0,
Expand All @@ -192,6 +196,7 @@ func TestHTTPCheck_Collect(t *testing.T) {
prepare: prepareRedirectFailCase,
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 0,
Expand All @@ -206,6 +211,7 @@ func TestHTTPCheck_Collect(t *testing.T) {
prepare: prepareBadStatusCase,
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 1,
"in_state": 2,
"length": 0,
Expand All @@ -220,6 +226,7 @@ func TestHTTPCheck_Collect(t *testing.T) {
prepare: prepareBadContentCase,
wantMetrics: map[string]int64{
"bad_content": 1,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 17,
Expand All @@ -234,6 +241,7 @@ func TestHTTPCheck_Collect(t *testing.T) {
prepare: prepareNoConnectionCase,
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 0,
Expand All @@ -244,10 +252,171 @@ func TestHTTPCheck_Collect(t *testing.T) {
"timeout": 0,
},
},
"headers match include no value success case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Key: "header-key2"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 1,
"time": 0,
"timeout": 0,
},
},
"headers match include with value success case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Key: "header-key2", Value: "= header-value"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 1,
"time": 0,
"timeout": 0,
},
},
"headers match include no value bad headers case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Key: "header-key99"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 1,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 0,
"time": 0,
"timeout": 0,
},
},
"headers match include with value bad headers case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Key: "header-key2", Value: "= header-value99"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 1,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 0,
"time": 0,
"timeout": 0,
},
},
"headers match exclude no value success case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Exclude: true, Key: "header-key99"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 1,
"time": 0,
"timeout": 0,
},
},
"headers match exclude with value success case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Exclude: true, Key: "header-key2", Value: "= header-value99"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 1,
"time": 0,
"timeout": 0,
},
},
"headers match exclude no value bad headers case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Exclude: true, Key: "header-key2"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 1,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 0,
"time": 0,
"timeout": 0,
},
},
"headers match exclude with value bad headers case": {
prepare: prepareSuccessCase,
update: func(httpCheck *HTTPCheck) {
httpCheck.HeaderMatch = []HeaderMatchConfig{
{Exclude: true, Key: "header-key2", Value: "= header-value"},
}
},
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 1,
"bad_status": 0,
"in_state": 2,
"length": 5,
"no_connection": 0,
"redirect": 0,
"success": 0,
"time": 0,
"timeout": 0,
},
},
"cookie auth case": {
prepare: prepareCookieAuthCase,
wantMetrics: map[string]int64{
"bad_content": 0,
"bad_headers": 0,
"bad_status": 0,
"in_state": 2,
"length": 0,
Expand All @@ -265,6 +434,10 @@ func TestHTTPCheck_Collect(t *testing.T) {
httpCheck, cleanup := test.prepare()
defer cleanup()

if test.update != nil {
test.update(httpCheck)
}

require.True(t, httpCheck.Init())

var mx map[string]int64
Expand All @@ -288,6 +461,8 @@ func prepareSuccessCase() (*HTTPCheck, func()) {

srv := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("header-key1", "header-value")
w.Header().Set("header-key2", "header-value")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("match"))
}))
Expand Down
Loading

0 comments on commit 22c3a7b

Please sign in to comment.