api: add /v1/health/ws for health bubbles on dashboard

This commit is contained in:
yusing 2025-01-19 04:34:20 +08:00
parent fe7740f1b0
commit 1adba05065
13 changed files with 89 additions and 23 deletions

View file

@ -37,6 +37,7 @@ func NewHandler(cfg config.ConfigInstance) http.Handler {
mux.HandleFunc("GET", "/v1/schema/{filename...}", v1.GetSchemaFile)
mux.HandleFunc("GET", "/v1/stats", useCfg(cfg, v1.Stats))
mux.HandleFunc("GET", "/v1/stats/ws", useCfg(cfg, v1.StatsWS))
mux.HandleFunc("GET", "/v1/health/ws", useCfg(cfg, v1.HealthWS))
mux.HandleFunc("GET", "/v1/favicon/{alias}", auth.RequireAuth(favicon.GetFavIcon))
return mux
}

18
internal/api/v1/health.go Normal file
View file

@ -0,0 +1,18 @@
package v1
import (
"net/http"
"time"
"github.com/coder/websocket"
"github.com/coder/websocket/wsjson"
U "github.com/yusing/go-proxy/internal/api/v1/utils"
config "github.com/yusing/go-proxy/internal/config/types"
"github.com/yusing/go-proxy/internal/route/routes"
)
func HealthWS(cfg config.ConfigInstance, w http.ResponseWriter, r *http.Request) {
U.PeriodicWS(cfg, w, r, 1*time.Second, func(conn *websocket.Conn) error {
return wsjson.Write(r.Context(), conn, routes.HealthMap())
})
}

View file

@ -1,6 +1,7 @@
package config
import (
"context"
"os"
"strconv"
"strings"
@ -146,6 +147,10 @@ func (cfg *Config) Task() *task.Task {
return cfg.task
}
func (cfg *Config) Context() context.Context {
return cfg.task.Context()
}
func (cfg *Config) Start() {
cfg.StartAutoCert()
cfg.StartProxyProviders()

View file

@ -1,6 +1,8 @@
package types
import (
"context"
"github.com/yusing/go-proxy/internal/net/http/accesslog"
"github.com/yusing/go-proxy/internal/utils"
@ -31,6 +33,7 @@ type (
Value() *Config
Reload() E.Error
Statistics() map[string]any
Context() context.Context
}
)

View file

@ -117,6 +117,11 @@ func (w *Watcher) Uptime() time.Duration {
return 0
}
// Latency implements health.HealthMonitor.
func (w *Watcher) Latency() time.Duration {
return 0
}
// Status implements health.HealthMonitor.
func (w *Watcher) Status() health.Status {
status := w.getStatusUpdateReady()

View file

@ -28,7 +28,7 @@ type (
Name() string
URL() types.URL
Weight() Weight
SetWeight(Weight)
SetWeight(weight Weight)
TryWake() error
}

View file

@ -30,7 +30,7 @@ type (
HealthMon health.HealthMonitor `json:"health,omitempty"`
loadBalancer *loadbalancer.LoadBalancer
server *loadbalancer.Server
server loadbalancer.Server
handler http.Handler
rp *reverseproxy.ReverseProxy
@ -180,11 +180,8 @@ func (r *HTTPRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.handler.ServeHTTP(w, req)
}
func (r *HTTPRoute) Health() health.Status {
if r.HealthMon != nil {
return r.HealthMon.Status()
}
return health.StatusUnknown
func (r *HTTPRoute) HealthMonitor() health.HealthMonitor {
return r.HealthMon
}
func (r *HTTPRoute) addToLoadBalancer(parent task.Parent) {

View file

@ -26,7 +26,12 @@ type (
func (stats *RouteStats) Add(r *R.Route) {
stats.Total++
switch r.Health() {
mon := r.HealthMonitor()
if mon == nil {
stats.NumUnknown++
return
}
switch mon.Status() {
case health.StatusHealthy:
stats.NumHealthy++
case health.StatusUnhealthy:

View file

@ -11,7 +11,6 @@ import (
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"
"github.com/yusing/go-proxy/internal/watcher/health"
)
type (
@ -24,12 +23,11 @@ type (
Routes = F.Map[string, *Route]
impl interface {
entry.Entry
types.Route
task.TaskStarter
task.TaskFinisher
String() string
TargetURL() url.URL
Health() health.Status
}
RawEntry = types.RawEntry
RawEntries = types.RawEntries

View file

@ -2,6 +2,7 @@ package routes
import (
"strings"
"time"
"github.com/yusing/go-proxy/internal/homepage"
"github.com/yusing/go-proxy/internal/route/entry"
@ -10,6 +11,33 @@ import (
"github.com/yusing/go-proxy/internal/utils/strutils"
)
func getHealthInfo(r route.Route) map[string]string {
mon := r.HealthMonitor()
if mon == nil {
return map[string]string{
"status": "unknown",
"uptime": "n/a",
"latency": "n/a",
}
}
return map[string]string{
"status": mon.Status().String(),
"uptime": mon.Uptime().Round(time.Second).String(),
"latency": mon.Latency().Round(time.Microsecond).String(),
}
}
func HealthMap() map[string]map[string]string {
healthMap := make(map[string]map[string]string)
httpRoutes.RangeAll(func(alias string, r route.HTTPRoute) {
healthMap[alias] = getHealthInfo(r)
})
streamRoutes.RangeAll(func(alias string, r route.StreamRoute) {
healthMap[alias] = getHealthInfo(r)
})
return healthMap
}
func HomepageConfig(useDefaultCategories bool) homepage.Config {
hpCfg := homepage.NewHomePageConfig()
GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
@ -77,8 +105,8 @@ func HomepageConfig(useDefaultCategories bool) homepage.Config {
return hpCfg
}
func RoutesByAlias(typeFilter ...route.RouteType) map[string]any {
rts := make(map[string]any)
func RoutesByAlias(typeFilter ...route.RouteType) map[string]route.Route {
rts := make(map[string]route.Route)
if len(typeFilter) == 0 || typeFilter[0] == "" {
typeFilter = []route.RouteType{route.RouteTypeReverseProxy, route.RouteTypeStream}
}

View file

@ -116,12 +116,8 @@ func (r *StreamRoute) Finish(reason any) {
r.task.Finish(reason)
}
func (r *StreamRoute) Health() health.Status {
if r.HealthMon != nil {
return r.HealthMon.Status()
}
return health.StatusUnknown
func (r *StreamRoute) HealthMonitor() health.HealthMonitor {
return r.HealthMon
}
func (r *StreamRoute) acceptConnections() {

View file

@ -8,14 +8,16 @@ import (
)
type (
HTTPRoute interface {
Route interface {
Entry
HealthMonitor() health.HealthMonitor
}
HTTPRoute interface {
Route
http.Handler
Health() health.Status
}
StreamRoute interface {
Entry
Route
net.Stream
Health() health.Status
}
)

View file

@ -142,6 +142,14 @@ func (mon *monitor) Uptime() time.Duration {
return time.Since(mon.startTime)
}
// Latency implements HealthMonitor.
func (mon *monitor) Latency() time.Duration {
if mon.lastResult == nil {
return 0
}
return mon.lastResult.Latency
}
// Name implements HealthMonitor.
func (mon *monitor) Name() string {
parts := strutils.SplitRune(mon.service, '/')