feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
179
checks/chttp/http.go
Обычный файл
179
checks/chttp/http.go
Обычный файл
@@ -0,0 +1,179 @@
|
||||
// Package chttp provides HTTP check functionality for RSMon.
|
||||
package chttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"rsgit.ru/rsmon/rsmon/app/models"
|
||||
)
|
||||
|
||||
const (
|
||||
stateERR = "ERR"
|
||||
stateFAIL = "FAIL"
|
||||
)
|
||||
|
||||
// UserAgent is the HTTP User-Agent header sent with all HTTP checks.
|
||||
const UserAgent = "Mozilla/5.0 (compatible; RSMon/1.0; +https://rsmon.ru/bot)"
|
||||
|
||||
func ipv6(ip []string) []string {
|
||||
var ips []string
|
||||
for _, a := range ip {
|
||||
ip := net.ParseIP(a)
|
||||
check := ip.To4()
|
||||
if check == nil {
|
||||
ips = append(ips, a)
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
// Perform executes an HTTP check.
|
||||
func Perform(c *models.Check) *Result {
|
||||
result := &Result{}
|
||||
result.State = "START"
|
||||
if c.URL == nil {
|
||||
result.State = stateFAIL
|
||||
result.Error = errors.New("no url or bad url")
|
||||
return result
|
||||
}
|
||||
|
||||
var requestBody io.Reader
|
||||
|
||||
settings := c.GetSettings()
|
||||
var err error
|
||||
var to time.Duration
|
||||
var slow time.Duration
|
||||
|
||||
if settings.Timeout > 0 && settings.Timeout < 300000 {
|
||||
to = time.Millisecond * time.Duration(settings.Timeout)
|
||||
} else {
|
||||
to = time.Second * 60
|
||||
}
|
||||
|
||||
if settings.SlowTime > 0 {
|
||||
slow = time.Millisecond * time.Duration(settings.SlowTime)
|
||||
} else {
|
||||
slow = time.Second * 5
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: to,
|
||||
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
if settings.CheckIp {
|
||||
addr, err := net.LookupHost(*c.URL)
|
||||
if err != nil {
|
||||
result.Status = stateFAIL
|
||||
result.Error = err
|
||||
}
|
||||
if settings.CheckIPv6 {
|
||||
addr = ipv6(addr)
|
||||
}
|
||||
for _, a := range addr {
|
||||
_, err := net.Dial("tcp", net.JoinHostPort(a, "80"))
|
||||
if err != nil {
|
||||
result.Error = err
|
||||
break
|
||||
}
|
||||
result.State = "OK"
|
||||
}
|
||||
}
|
||||
switch settings.RequestType {
|
||||
case "application/json":
|
||||
body, _ := json.Marshal(settings.RequestContent)
|
||||
requestBody = bytes.NewReader(body)
|
||||
case "application/x-www-form-urlencoded":
|
||||
requestBody = strings.NewReader(settings.RequestContent)
|
||||
case "multipart/form-data":
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
writer := multipart.NewWriter(buf)
|
||||
_, _ = writer.CreateFormField(settings.RequestContent)
|
||||
_ = writer.Close()
|
||||
requestBody = bytes.NewReader(buf.Bytes())
|
||||
settings.RequestType = writer.FormDataContentType()
|
||||
case "text/plain":
|
||||
requestBody = bytes.NewBufferString(settings.RequestContent)
|
||||
}
|
||||
if settings.RequestMethod == "" {
|
||||
settings.RequestMethod = "GET"
|
||||
}
|
||||
request, err := http.NewRequest(strings.ToUpper(settings.RequestMethod), *c.URL, requestBody)
|
||||
if err != nil {
|
||||
result.State = stateFAIL
|
||||
result.Error = err
|
||||
return result
|
||||
}
|
||||
|
||||
if settings.HTTPUsername != "" && settings.HTTPPassword != "" {
|
||||
request.SetBasicAuth(settings.HTTPUsername, settings.HTTPPassword)
|
||||
}
|
||||
|
||||
request.Header.Set("User-Agent", UserAgent)
|
||||
request.Header.Set("Cache-Control", "max-age=0")
|
||||
request.Header.Set("Connection", "close")
|
||||
if settings.RequestType != "" {
|
||||
request.Header.Set("Content-Type", settings.RequestType)
|
||||
}
|
||||
if len(settings.RequestHeader) != 0 {
|
||||
for _, h := range settings.RequestHeader {
|
||||
request.Header.Set(h.Key, h.Value)
|
||||
}
|
||||
}
|
||||
start := time.Now()
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
result.State = stateERR
|
||||
result.Error = errors.Wrap(err, "request exec")
|
||||
return result
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck // accepted lint exception
|
||||
|
||||
result.Status = resp.Status
|
||||
result.StatusCode = resp.StatusCode
|
||||
result.Headers = resp.Header
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
result.Duration = time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
result.State = stateERR
|
||||
result.Error = errors.Wrap(err, "read body")
|
||||
return result
|
||||
}
|
||||
result.Length = len(body)
|
||||
result.Body = body
|
||||
|
||||
warns, err := settings.CheckAnswer(resp, body)
|
||||
|
||||
for _, w := range warns {
|
||||
result.State = "WARN"
|
||||
result.Warnings = append(result.Warnings, w)
|
||||
}
|
||||
if err != nil {
|
||||
result.State = stateERR
|
||||
result.Error = errors.Wrap(err, "response check")
|
||||
}
|
||||
|
||||
if result.Error == nil {
|
||||
if result.Duration > slow {
|
||||
result.State = "WARN"
|
||||
result.Warnings = append(result.Warnings, "slow")
|
||||
} else if result.State == "START" {
|
||||
result.State = "OK"
|
||||
}
|
||||
} else {
|
||||
result.State = stateERR
|
||||
}
|
||||
return result
|
||||
}
|
||||
41
checks/chttp/result.go
Обычный файл
41
checks/chttp/result.go
Обычный файл
@@ -0,0 +1,41 @@
|
||||
package chttp
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"rsgit.ru/rsmon/rsmon/app/models"
|
||||
"rsgit.ru/rsmon/rsmon/internal/checkresult"
|
||||
)
|
||||
|
||||
// Result provides functionality.
|
||||
type Result struct {
|
||||
checkresult.CheckResult
|
||||
StatusCode int
|
||||
Status string
|
||||
Body []byte
|
||||
Headers map[string][]string
|
||||
Length int
|
||||
}
|
||||
|
||||
// InfluxFields provides functionality.
|
||||
func (hr *Result) InfluxFields() map[string]interface{} {
|
||||
ret := make(map[string]interface{}, 0)
|
||||
ret["took"] = int64(hr.Duration / time.Millisecond)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// InfluxTags provides functionality.
|
||||
func (hr *Result) InfluxTags(c models.Check) map[string]string { //nolint:gocritic // hugeParam: accepted for interface compatibility
|
||||
ret := make(map[string]string, 0)
|
||||
ret["check"] = strconv.FormatInt(c.ID, 10)
|
||||
ret["state"] = hr.State
|
||||
ret["code"] = strconv.Itoa(hr.StatusCode)
|
||||
if hr.Error != nil {
|
||||
ret["error"] = hr.Error.Error()
|
||||
}
|
||||
ret["warnings"] = strings.Join(hr.Warnings, ",")
|
||||
return ret
|
||||
}
|
||||
Ссылка в новой задаче
Block a user