statping-ng/utils/utils.go

282 lines
6.8 KiB
Go

package utils
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/go-ping/ping"
"github.com/statping-ng/statping-ng/types/metrics"
)
var (
// Directory returns the current path or the STATPING_DIR environment variable
Directory string
)
func NotNumber(val string) bool {
_, err := strconv.ParseInt(val, 10, 64)
return err != nil
}
// ToInt converts a int to a string
func ToInt(s interface{}) int64 {
switch v := s.(type) {
case string:
val, _ := strconv.Atoi(v)
return int64(val)
case []byte:
val, _ := strconv.Atoi(string(v))
return int64(val)
case float32:
return int64(v)
case float64:
return int64(v)
case int:
return int64(v)
case int16:
return int64(v)
case int32:
return int64(v)
case int64:
return v
case uint:
return int64(v)
default:
return 0
}
}
// ToString converts a int to a string
func ToString(s interface{}) string {
switch v := s.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return fmt.Sprintf("%v", v)
case float32, float64:
return fmt.Sprintf("%f", v)
case []byte:
return string(v)
case bool:
return fmt.Sprintf("%t", v)
case time.Time:
return v.Format("Monday January _2, 2006 at 03:04PM")
case time.Duration:
return v.String()
default:
return fmt.Sprintf("%v", v)
}
}
// Command will run a terminal command with 'sh -c COMMAND' and return stdout and errOut as strings
//
// in, out, err := Command("sass assets/scss assets/css/base.css")
func Command(name string, args ...string) (string, string, error) {
testCmd := exec.Command(name, args...)
var stdout, stderr []byte
var errStdout, errStderr error
stdoutIn, _ := testCmd.StdoutPipe()
stderrIn, _ := testCmd.StderrPipe()
err := testCmd.Start()
if err != nil {
return "", "", err
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn)
}()
stderr, errStderr = copyAndCapture(os.Stderr, stderrIn)
// call testCmd.Wait() only after reads from all pipes have completed
wg.Wait()
err = testCmd.Wait()
if err != nil {
return string(stdout), string(stderr), err
}
if errStdout != nil || errStderr != nil {
return string(stdout), string(stderr), errors.New("failed to capture stdout or stderr")
}
outStr, errStr := string(stdout), string(stderr)
return outStr, errStr, err
}
// copyAndCapture will read a terminal command into bytes
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
var out []byte
buf := make([]byte, 1024, 1024)
for {
n, err := r.Read(buf[:])
if n > 0 {
d := buf[:n]
out = append(out, d...)
_, err := w.Write(d)
if err != nil {
return out, err
}
}
if err != nil {
// Read returns io.EOF at the end of file, which is not an error for us
if err == io.EOF {
err = nil
}
return out, err
}
}
}
// DurationReadable will return a time.Duration into a human readable string
// // t := time.Duration(5 * time.Minute)
// // DurationReadable(t)
// // returns: 5 minutes
func DurationReadable(d time.Duration) string {
if d.Hours() >= 1 {
return fmt.Sprintf("%0.0f hours", d.Hours())
} else if d.Minutes() >= 1 {
return fmt.Sprintf("%0.0f minutes", d.Minutes())
} else if d.Seconds() >= 1 {
return fmt.Sprintf("%0.0f seconds", d.Seconds())
}
return d.String()
}
// HttpRequest is a global function to send a HTTP request
// // url - The URL for HTTP request
// // method - GET, POST, DELETE, PATCH
// // content - The HTTP request content type (text/plain, application/json, or nil)
// // headers - An array of Headers to be sent (KEY=VALUE) []string{"Authentication=12345", ...}
// // body - The body or form data to send with HTTP request
// // timeout - Specific duration to timeout on. time.Duration(30 * time.Seconds)
// // You can use a HTTP Proxy if you HTTP_PROXY environment variable
func HttpRequest(endpoint, method string, contentType interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool, customTLS *tls.Config) ([]byte, *http.Response, error) {
var err error
var req *http.Request
if method == "" {
method = "GET"
}
t1 := Now()
if req, err = http.NewRequest(method, endpoint, body); err != nil {
return nil, nil, err
}
// set default headers so end user can overwrite them if needed
req.Header.Set("User-Agent", "Statping-ng")
req.Header.Set("Statping-Version", Params.GetString("VERSION"))
req.Header.Set("Accept", "text/html")
if contentType != nil {
req.Header.Set("Content-Type", contentType.(string))
}
verifyHost := req.URL.Hostname()
for _, h := range headers {
keyVal := strings.SplitN(h, "=", 2)
if len(keyVal) == 2 {
if keyVal[0] != "" && keyVal[1] != "" {
if strings.ToLower(keyVal[0]) == "host" {
req.Host = strings.TrimSpace(keyVal[1])
verifyHost = req.Host
} else {
req.Header.Set(keyVal[0], keyVal[1])
}
}
}
}
var resp *http.Response
dialer := &net.Dialer{
Timeout: timeout,
KeepAlive: timeout,
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: !verifySSL,
ServerName: verifyHost,
Renegotiation: tls.RenegotiateOnceAsClient,
},
DisableKeepAlives: true,
ResponseHeaderTimeout: timeout,
TLSHandshakeTimeout: timeout,
Proxy: http.ProxyFromEnvironment,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// redirect all connections to host specified in url
return dialer.DialContext(ctx, network, addr)
},
}
if customTLS != nil {
transport.TLSClientConfig.RootCAs = customTLS.RootCAs
transport.TLSClientConfig.Certificates = customTLS.Certificates
}
client := &http.Client{
Transport: transport,
Timeout: timeout,
}
if req.Header.Get("Redirect") != "true" {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
req.Header.Del("Redirect")
}
if resp, err = client.Do(req); err != nil {
return nil, resp, err
}
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, resp, err
}
// record HTTP metrics
metrics.Histo("bytes", float64(len(contents)), endpoint, method)
metrics.Histo("duration", Now().Sub(t1).Seconds(), endpoint, method)
return contents, resp, err
}
func Ping(address string, secondsTimeout int) (int64, error) {
ping, err := ping.NewPinger(address)
if err != nil {
return 0, err
}
ping.Count = 1
ping.Timeout = time.Second * time.Duration(secondsTimeout)
if runtime.GOOS == "windows" {
ping.SetPrivileged(true)
}
err = ping.Run()
if err != nil {
return 0, err
}
stats := ping.Statistics()
if stats.PacketsSent-stats.PacketsRecv != 0 {
return 0, errors.New("destination host unreachable")
}
return stats.MinRtt.Microseconds(), nil
}