Skip to content
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,11 @@ endpoints:
conditions:
- "[DOMAIN_EXPIRATION] > 720h"
- "[CERTIFICATE_EXPIRATION] > 240h"
- name: check-domain-expiration-only
url: "domain://example.org"
interval: 1h
conditions:
- "[DOMAIN_EXPIRATION] > 720h"
```

> ⚠ The usage of the `[DOMAIN_EXPIRATION]` placeholder requires Gatus to use RDAP, or as a fallback, send a request to the official IANA WHOIS service
Expand Down
71 changes: 40 additions & 31 deletions config/endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"math/rand"
"net"
"net/http"
Expand Down Expand Up @@ -53,6 +54,7 @@ const (
TypeGRPC Type = "GRPC"
TypeWS Type = "WEBSOCKET"
TypeSSH Type = "SSH"
TypeDomain Type = "DOMAIN"
TypeUNKNOWN Type = "UNKNOWN"
)

Expand Down Expand Up @@ -161,29 +163,36 @@ func (e *Endpoint) IsEnabled() bool {

// Type returns the endpoint type
func (e *Endpoint) Type() Type {
switch {
case e.DNSConfig != nil:
if e.DNSConfig != nil {
return TypeDNS
case strings.HasPrefix(e.URL, "tcp://"):
}
before, _, ok := strings.Cut(e.URL, ":")
if !ok {
return TypeUNKNOWN
}
switch before {
case "tcp":
return TypeTCP
case strings.HasPrefix(e.URL, "sctp://"):
case "sctp":
return TypeSCTP
case strings.HasPrefix(e.URL, "udp://"):
case "udp":
return TypeUDP
case strings.HasPrefix(e.URL, "icmp://"):
case "icmp":
return TypeICMP
case strings.HasPrefix(e.URL, "starttls://"):
case "starttls":
return TypeSTARTTLS
case strings.HasPrefix(e.URL, "tls://"):
case "tls":
return TypeTLS
case strings.HasPrefix(e.URL, "http://") || strings.HasPrefix(e.URL, "https://"):
case "http", "https":
return TypeHTTP
case strings.HasPrefix(e.URL, "grpc://") || strings.HasPrefix(e.URL, "grpcs://"):
case "grpc", "grpcs":
return TypeGRPC
case strings.HasPrefix(e.URL, "ws://") || strings.HasPrefix(e.URL, "wss://"):
case "ws", "wss":
return TypeWS
case strings.HasPrefix(e.URL, "ssh://"):
case "ssh":
return TypeSSH
case "domain":
return TypeDomain
default:
return TypeUNKNOWN
}
Expand Down Expand Up @@ -452,18 +461,16 @@ func (e *Endpoint) call(result *Result) {
var err error
var certificate *x509.Certificate
endpointType := e.Type()
if endpointType == TypeHTTP {
request = e.buildHTTPRequest()
}
startTime := time.Now()
if endpointType == TypeDNS {
switch endpointType {
case TypeDNS:
result.Connected, result.DNSRCode, result.Body, err = client.QueryDNS(e.DNSConfig.QueryType, e.DNSConfig.QueryName, e.URL)
if err != nil {
result.AddError(err.Error())
return
}
result.Duration = time.Since(startTime)
} else if endpointType == TypeSTARTTLS || endpointType == TypeTLS {
case TypeSTARTTLS, TypeTLS:
if endpointType == TypeSTARTTLS {
result.Connected, certificate, err = client.CanPerformStartTLS(strings.TrimPrefix(e.URL, "starttls://"), e.ClientConfig)
} else {
Expand All @@ -475,24 +482,20 @@ func (e *Endpoint) call(result *Result) {
}
result.Duration = time.Since(startTime)
result.CertificateExpiration = time.Until(certificate.NotAfter)
} else if endpointType == TypeTCP {
case TypeTCP:
result.Connected, result.Body = client.CanCreateNetworkConnection("tcp", strings.TrimPrefix(e.URL, "tcp://"), e.getParsedBody(), e.ClientConfig)
result.Duration = time.Since(startTime)
} else if endpointType == TypeUDP {
case TypeUDP:
result.Connected, result.Body = client.CanCreateNetworkConnection("udp", strings.TrimPrefix(e.URL, "udp://"), e.getParsedBody(), e.ClientConfig)
result.Duration = time.Since(startTime)
} else if endpointType == TypeSCTP {
case TypeSCTP:
result.Connected = client.CanCreateSCTPConnection(strings.TrimPrefix(e.URL, "sctp://"), e.ClientConfig)
result.Duration = time.Since(startTime)
} else if endpointType == TypeICMP {
case TypeICMP:
result.Connected, result.Duration = client.Ping(strings.TrimPrefix(e.URL, "icmp://"), e.ClientConfig)
} else if endpointType == TypeWS {
case TypeWS:
wsHeaders := map[string]string{}
if e.Headers != nil {
for k, v := range e.Headers {
wsHeaders[k] = v
}
}
maps.Copy(wsHeaders, e.Headers)
if _, exists := wsHeaders["User-Agent"]; !exists {
wsHeaders["User-Agent"] = GatusUserAgent
}
Expand All @@ -502,7 +505,7 @@ func (e *Endpoint) call(result *Result) {
return
}
result.Duration = time.Since(startTime)
} else if endpointType == TypeSSH {
case TypeSSH:
// If there's no username, password or private key specified, attempt to validate just the SSH banner
if e.SSHConfig == nil || (len(e.SSHConfig.Username) == 0 && len(e.SSHConfig.Password) == 0 && len(e.SSHConfig.PrivateKey) == 0) {
result.Connected, result.HTTPStatus, err = client.CheckSSHBanner(strings.TrimPrefix(e.URL, "ssh://"), e.ClientConfig)
Expand Down Expand Up @@ -531,7 +534,7 @@ func (e *Endpoint) call(result *Result) {
result.Body = output
}
result.Duration = time.Since(startTime)
} else if endpointType == TypeGRPC {
case TypeGRPC:
useTLS := strings.HasPrefix(e.URL, "grpcs://")
address := strings.TrimPrefix(strings.TrimPrefix(e.URL, "grpcs://"), "grpc://")
connected, status, err, duration := client.PerformGRPCHealthCheck(address, useTLS, e.ClientConfig)
Expand All @@ -542,9 +545,15 @@ func (e *Endpoint) call(result *Result) {
result.Connected = connected
result.Duration = duration
if e.needsToReadBody() {
result.Body = []byte(fmt.Sprintf("{\"status\":\"%s\"}", status))
result.Body = fmt.Appendf(nil, "{\"status\":\"%s\"}", status)
}
} else {
case TypeDomain:
// domain expiration checked before call `call`
return
case TypeHTTP:
request = e.buildHTTPRequest()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed my previous refactoring introduces one unintended (and maybe unwanted change). The startTime is now set before starting to build the http request.

To restore the original behavior we should update the startTime after building the request:

Suggested change
request = e.buildHTTPRequest()
request = e.buildHTTPRequest()
startTime = time.Now()

fallthrough
default:
response, err = client.GetHTTPClient(e.ClientConfig).Do(request)
result.Duration = time.Since(startTime)
if err != nil {
Expand Down
19 changes: 19 additions & 0 deletions config/endpoint/endpoint_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package endpoint

import (
"testing"
)

func BenchmarkEndpoint_Type(b *testing.B) {
for b.Loop() {
for _, tt := range testEndpoint_typeData {
endpoint := Endpoint{
URL: tt.args.URL,
DNSConfig: tt.args.DNS,
}
if got := endpoint.Type(); got != tt.want {
b.Errorf("Endpoint.Type() = %v, want %v", got, tt.want)
}
}
}
}
Loading