refactor: remove net.URL and net.CIDR types, improved unmarshal handling

This commit is contained in:
yusing 2025-04-13 07:06:21 +08:00
parent 1eac48e899
commit fce96ff3be
37 changed files with 236 additions and 292 deletions

View file

@ -8,7 +8,6 @@ import (
"strings" "strings"
"github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
"github.com/yusing/go-proxy/internal/watcher/health/monitor" "github.com/yusing/go-proxy/internal/watcher/health/monitor"
) )
@ -44,11 +43,11 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return return
} }
result, err = monitor.NewHTTPHealthMonitor(types.NewURL(&url.URL{ result, err = monitor.NewHTTPHealthMonitor(&url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: host,
Path: path, Path: path,
}), defaultHealthConfig).CheckHealth() }, defaultHealthConfig).CheckHealth()
case "tcp", "udp": case "tcp", "udp":
host := query.Get("host") host := query.Get("host")
if host == "" { if host == "" {
@ -63,10 +62,10 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return return
} }
result, err = monitor.NewRawHealthMonitor(types.NewURL(&url.URL{ result, err = monitor.NewRawHealthMonitor(&url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: host,
}), defaultHealthConfig).CheckHealth() }, defaultHealthConfig).CheckHealth()
} }
if err != nil { if err != nil {

View file

@ -9,7 +9,6 @@ import (
"github.com/yusing/go-proxy/internal/docker" "github.com/yusing/go-proxy/internal/docker"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy" "github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
"github.com/yusing/go-proxy/internal/net/types"
) )
func serviceUnavailable(w http.ResponseWriter, r *http.Request) { func serviceUnavailable(w http.ResponseWriter, r *http.Request) {
@ -22,10 +21,10 @@ func DockerSocketHandler() http.HandlerFunc {
logging.Warn().Err(err).Msg("failed to connect to docker client") logging.Warn().Err(err).Msg("failed to connect to docker client")
return serviceUnavailable return serviceUnavailable
} }
rp := reverseproxy.NewReverseProxy("docker", types.NewURL(&url.URL{ rp := reverseproxy.NewReverseProxy("docker", &url.URL{
Scheme: "http", Scheme: "http",
Host: client.DummyHost, Host: client.DummyHost,
}), dockerClient.HTTPClient().Transport) }, dockerClient.HTTPClient().Transport)
return rp.ServeHTTP return rp.ServeHTTP
} }

View file

@ -12,7 +12,6 @@ import (
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/gphttp" "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy" "github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
) )
@ -55,9 +54,9 @@ func ProxyHTTP(w http.ResponseWriter, r *http.Request) {
logging.Debug().Msgf("proxy http request: %s %s", r.Method, r.URL.String()) logging.Debug().Msgf("proxy http request: %s %s", r.Method, r.URL.String())
rp := reverseproxy.NewReverseProxy("agent", types.NewURL(&url.URL{ rp := reverseproxy.NewReverseProxy("agent", &url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: host,
}), transport) }, transport)
rp.ServeHTTP(w, r) rp.ServeHTTP(w, r)
} }

View file

@ -2,15 +2,14 @@ package homepage
import ( import (
"net/http" "net/http"
"net/url"
net "github.com/yusing/go-proxy/internal/net/types"
) )
type route interface { type route interface {
TargetName() string TargetName() string
ProviderName() string ProviderName() string
Reference() string Reference() string
TargetURL() *net.URL TargetURL() *url.URL
} }
type httpRoute interface { type httpRoute interface {

View file

@ -6,7 +6,6 @@ import (
"strings" "strings"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
) )
@ -24,7 +23,7 @@ type (
Key, Value string Key, Value string
} }
Host string Host string
CIDR struct{ types.CIDR } CIDR struct{ net.IPNet }
) )
var ErrInvalidHTTPHeaderFilter = gperr.New("invalid http header filter") var ErrInvalidHTTPHeaderFilter = gperr.New("invalid http header filter")

View file

@ -1,6 +1,7 @@
package accesslog_test package accesslog_test
import ( import (
"net"
"net/http" "net/http"
"testing" "testing"
@ -155,8 +156,11 @@ func TestHeaderFilter(t *testing.T) {
} }
func TestCIDRFilter(t *testing.T) { func TestCIDRFilter(t *testing.T) {
cidr := []*CIDR{ cidr := []*CIDR{{
strutils.MustParse[*CIDR]("192.168.10.0/24"), net.IPNet{
IP: net.ParseIP("192.168.10.0"),
Mask: net.CIDRMask(24, 32),
}},
} }
ExpectEqual(t, cidr[0].String(), "192.168.10.0/24") ExpectEqual(t, cidr[0].String(), "192.168.10.0/24")
inCIDR := &http.Request{ inCIDR := &http.Request{

View file

@ -13,7 +13,6 @@ import (
"github.com/yusing/go-proxy/internal/route/routes" "github.com/yusing/go-proxy/internal/route/routes"
"github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
) )
// TODO: stats of each server. // TODO: stats of each server.
@ -240,14 +239,14 @@ func (lb *LoadBalancer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
lb.impl.ServeHTTP(srvs, rw, r) lb.impl.ServeHTTP(srvs, rw, r)
} }
// MarshalJSON implements health.HealthMonitor. // MarshalMap implements health.HealthMonitor.
func (lb *LoadBalancer) MarshalJSON() ([]byte, error) { func (lb *LoadBalancer) MarshalMap() map[string]any {
extra := make(map[string]any) extra := make(map[string]any)
lb.pool.RangeAll(func(k string, v Server) { lb.pool.RangeAll(func(k string, v Server) {
extra[v.Key()] = v extra[v.Key()] = v
}) })
return (&monitor.JSONRepresentation{ return (&health.JSONRepresentation{
Name: lb.Name(), Name: lb.Name(),
Status: lb.Status(), Status: lb.Status(),
Started: lb.startTime, Started: lb.startTime,
@ -256,7 +255,7 @@ func (lb *LoadBalancer) MarshalJSON() ([]byte, error) {
"config": lb.Config, "config": lb.Config,
"pool": extra, "pool": extra,
}, },
}).MarshalJSON() }).MarshalMap()
} }
// Name implements health.HealthMonitor. // Name implements health.HealthMonitor.

View file

@ -2,9 +2,9 @@ package types
import ( import (
"net/http" "net/http"
"net/url"
idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types" idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
net "github.com/yusing/go-proxy/internal/net/types"
U "github.com/yusing/go-proxy/internal/utils" U "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional" F "github.com/yusing/go-proxy/internal/utils/functional"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
@ -15,7 +15,7 @@ type (
_ U.NoCopy _ U.NoCopy
name string name string
url *net.URL url *url.URL
weight Weight weight Weight
http.Handler `json:"-"` http.Handler `json:"-"`
@ -27,7 +27,7 @@ type (
health.HealthMonitor health.HealthMonitor
Name() string Name() string
Key() string Key() string
URL() *net.URL URL() *url.URL
Weight() Weight Weight() Weight
SetWeight(weight Weight) SetWeight(weight Weight)
TryWake() error TryWake() error
@ -38,7 +38,7 @@ type (
var NewServerPool = F.NewMap[Pool] var NewServerPool = F.NewMap[Pool]
func NewServer(name string, url *net.URL, weight Weight, handler http.Handler, healthMon health.HealthMonitor) Server { func NewServer(name string, url *url.URL, weight Weight, handler http.Handler, healthMon health.HealthMonitor) Server {
srv := &server{ srv := &server{
name: name, name: name,
url: url, url: url,
@ -52,7 +52,7 @@ func NewServer(name string, url *net.URL, weight Weight, handler http.Handler, h
func TestNewServer[T ~int | ~float32 | ~float64](weight T) Server { func TestNewServer[T ~int | ~float32 | ~float64](weight T) Server {
srv := &server{ srv := &server{
weight: Weight(weight), weight: Weight(weight),
url: net.MustParseURL("http://localhost"), url: &url.URL{Scheme: "http", Host: "localhost"},
} }
return srv return srv
} }
@ -61,7 +61,7 @@ func (srv *server) Name() string {
return srv.name return srv.name
} }
func (srv *server) URL() *net.URL { func (srv *server) URL() *url.URL {
return srv.url return srv.url
} }

View file

@ -6,7 +6,6 @@ import (
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp" gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils" "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional" F "github.com/yusing/go-proxy/internal/utils/functional"
) )
@ -18,8 +17,8 @@ type (
cachedAddr F.Map[string, bool] // cache for trusted IPs cachedAddr F.Map[string, bool] // cache for trusted IPs
} }
CIDRWhitelistOpts struct { CIDRWhitelistOpts struct {
Allow []*types.CIDR `validate:"min=1"` Allow []*net.IPNet `validate:"min=1"`
StatusCode int `json:"status_code" aliases:"status" validate:"omitempty,status_code"` StatusCode int `json:"status_code" aliases:"status" validate:"omitempty,status_code"`
Message string Message string
} }
) )
@ -27,7 +26,7 @@ type (
var ( var (
CIDRWhiteList = NewMiddleware[cidrWhitelist]() CIDRWhiteList = NewMiddleware[cidrWhitelist]()
cidrWhitelistDefaults = CIDRWhitelistOpts{ cidrWhitelistDefaults = CIDRWhitelistOpts{
Allow: []*types.CIDR{}, Allow: []*net.IPNet{},
StatusCode: http.StatusForbidden, StatusCode: http.StatusForbidden,
Message: "IP not allowed", Message: "IP not allowed",
} }

View file

@ -11,7 +11,6 @@ import (
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/atomic" "github.com/yusing/go-proxy/internal/utils/atomic"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
) )
@ -33,7 +32,7 @@ var (
cfCIDRsMu sync.Mutex cfCIDRsMu sync.Mutex
// RFC 1918. // RFC 1918.
localCIDRs = []*types.CIDR{ localCIDRs = []*net.IPNet{
{IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 255)}, // 127.0.0.1/32 {IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 255, 255, 255)}, // 127.0.0.1/32
{IP: net.IPv4(10, 0, 0, 0), Mask: net.IPv4Mask(255, 0, 0, 0)}, // 10.0.0.0/8 {IP: net.IPv4(10, 0, 0, 0), Mask: net.IPv4Mask(255, 0, 0, 0)}, // 10.0.0.0/8
{IP: net.IPv4(172, 16, 0, 0), Mask: net.IPv4Mask(255, 240, 0, 0)}, // 172.16.0.0/12 {IP: net.IPv4(172, 16, 0, 0), Mask: net.IPv4Mask(255, 240, 0, 0)}, // 172.16.0.0/12
@ -68,7 +67,7 @@ func (cri *cloudflareRealIP) getTracer() *Tracer {
return cri.realIP.getTracer() return cri.realIP.getTracer()
} }
func tryFetchCFCIDR() (cfCIDRs []*types.CIDR) { func tryFetchCFCIDR() (cfCIDRs []*net.IPNet) {
if time.Since(cfCIDRsLastUpdate.Load()) < cfCIDRsUpdateInterval { if time.Since(cfCIDRsLastUpdate.Load()) < cfCIDRsUpdateInterval {
return return
} }
@ -83,7 +82,7 @@ func tryFetchCFCIDR() (cfCIDRs []*types.CIDR) {
if common.IsTest { if common.IsTest {
cfCIDRs = localCIDRs cfCIDRs = localCIDRs
} else { } else {
cfCIDRs = make([]*types.CIDR, 0, 30) cfCIDRs = make([]*net.IPNet, 0, 30)
err := errors.Join( err := errors.Join(
fetchUpdateCFIPRange(cfIPv4CIDRsEndpoint, &cfCIDRs), fetchUpdateCFIPRange(cfIPv4CIDRsEndpoint, &cfCIDRs),
fetchUpdateCFIPRange(cfIPv6CIDRsEndpoint, &cfCIDRs), fetchUpdateCFIPRange(cfIPv6CIDRsEndpoint, &cfCIDRs),
@ -103,7 +102,7 @@ func tryFetchCFCIDR() (cfCIDRs []*types.CIDR) {
return return
} }
func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*types.CIDR) error { func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*net.IPNet) error {
resp, err := http.Get(endpoint) resp, err := http.Get(endpoint)
if err != nil { if err != nil {
return err return err
@ -124,7 +123,7 @@ func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*types.CIDR) error {
return fmt.Errorf("cloudflare responeded an invalid CIDR: %s", line) return fmt.Errorf("cloudflare responeded an invalid CIDR: %s", line)
} }
*cfCIDRs = append(*cfCIDRs, (*types.CIDR)(cidr)) *cfCIDRs = append(*cfCIDRs, (*net.IPNet)(cidr))
} }
*cfCIDRs = append(*cfCIDRs, localCIDRs...) *cfCIDRs = append(*cfCIDRs, localCIDRs...)
return nil return nil

View file

@ -4,10 +4,10 @@ import (
"bytes" "bytes"
"net" "net"
"net/http" "net/http"
"net/url"
"slices" "slices"
"testing" "testing"
"github.com/yusing/go-proxy/internal/net/types"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
) )
@ -51,8 +51,8 @@ func TestModifyRequest(t *testing.T) {
}) })
t.Run("request_headers", func(t *testing.T) { t.Run("request_headers", func(t *testing.T) {
reqURL := types.MustParseURL("https://my.app/?arg_1=b") reqURL := Must(url.Parse("https://my.app/?arg_1=b"))
upstreamURL := types.MustParseURL("http://test.example.com") upstreamURL := Must(url.Parse("http://test.example.com"))
result, err := newMiddlewareTest(ModifyRequest, &testArgs{ result, err := newMiddlewareTest(ModifyRequest, &testArgs{
middlewareOpt: opts, middlewareOpt: opts,
reqURL: reqURL, reqURL: reqURL,
@ -128,8 +128,8 @@ func TestModifyRequest(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
reqURL := types.MustParseURL("https://my.app" + tt.path) reqURL := Must(url.Parse("https://my.app" + tt.path))
upstreamURL := types.MustParseURL(tt.upstreamURL) upstreamURL := Must(url.Parse(tt.upstreamURL))
opts["add_prefix"] = tt.addPrefix opts["add_prefix"] = tt.addPrefix
result, err := newMiddlewareTest(ModifyRequest, &testArgs{ result, err := newMiddlewareTest(ModifyRequest, &testArgs{

View file

@ -4,10 +4,10 @@ import (
"bytes" "bytes"
"net" "net"
"net/http" "net/http"
"net/url"
"slices" "slices"
"testing" "testing"
"github.com/yusing/go-proxy/internal/net/types"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
) )
@ -54,8 +54,8 @@ func TestModifyResponse(t *testing.T) {
}) })
t.Run("response_headers", func(t *testing.T) { t.Run("response_headers", func(t *testing.T) {
reqURL := types.MustParseURL("https://my.app/?arg_1=b") reqURL := Must(url.Parse("https://my.app/?arg_1=b"))
upstreamURL := types.MustParseURL("http://test.example.com") upstreamURL := Must(url.Parse("http://test.example.com"))
result, err := newMiddlewareTest(ModifyResponse, &testArgs{ result, err := newMiddlewareTest(ModifyResponse, &testArgs{
middlewareOpt: opts, middlewareOpt: opts,
reqURL: reqURL, reqURL: reqURL,

View file

@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
"github.com/yusing/go-proxy/internal/net/types"
) )
// https://nginx.org/en/docs/http/ngx_http_realip_module.html // https://nginx.org/en/docs/http/ngx_http_realip_module.html
@ -19,7 +18,7 @@ type (
// Header is the name of the header to use for the real client IP // Header is the name of the header to use for the real client IP
Header string `validate:"required"` Header string `validate:"required"`
// From is a list of Address / CIDRs to trust // From is a list of Address / CIDRs to trust
From []*types.CIDR `validate:"required,min=1"` From []*net.IPNet `validate:"required,min=1"`
/* /*
If recursive search is disabled, If recursive search is disabled,
the original client address that matches one of the trusted addresses is replaced by the original client address that matches one of the trusted addresses is replaced by
@ -36,7 +35,7 @@ var (
RealIP = NewMiddleware[realIP]() RealIP = NewMiddleware[realIP]()
realIPOptsDefault = RealIPOpts{ realIPOptsDefault = RealIPOpts{
Header: "X-Real-IP", Header: "X-Real-IP",
From: []*types.CIDR{}, From: []*net.IPNet{},
} }
) )

View file

@ -7,7 +7,6 @@ import (
"testing" "testing"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
"github.com/yusing/go-proxy/internal/net/types"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
) )
@ -23,7 +22,7 @@ func TestSetRealIPOpts(t *testing.T) {
} }
optExpected := &RealIPOpts{ optExpected := &RealIPOpts{
Header: httpheaders.HeaderXRealIP, Header: httpheaders.HeaderXRealIP,
From: []*types.CIDR{ From: []*net.IPNet{
{ {
IP: net.ParseIP("127.0.0.0"), IP: net.ParseIP("127.0.0.0"),
Mask: net.IPv4Mask(255, 0, 0, 0), Mask: net.IPv4Mask(255, 0, 0, 0),

View file

@ -2,15 +2,15 @@ package middleware
import ( import (
"net/http" "net/http"
"net/url"
"testing" "testing"
"github.com/yusing/go-proxy/internal/net/types"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
) )
func TestRedirectToHTTPs(t *testing.T) { func TestRedirectToHTTPs(t *testing.T) {
result, err := newMiddlewareTest(RedirectHTTP, &testArgs{ result, err := newMiddlewareTest(RedirectHTTP, &testArgs{
reqURL: types.MustParseURL("http://example.com"), reqURL: Must(url.Parse("http://example.com")),
}) })
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, result.ResponseStatus, http.StatusPermanentRedirect) ExpectEqual(t, result.ResponseStatus, http.StatusPermanentRedirect)
@ -19,7 +19,7 @@ func TestRedirectToHTTPs(t *testing.T) {
func TestNoRedirect(t *testing.T) { func TestNoRedirect(t *testing.T) {
result, err := newMiddlewareTest(RedirectHTTP, &testArgs{ result, err := newMiddlewareTest(RedirectHTTP, &testArgs{
reqURL: types.MustParseURL("https://example.com"), reqURL: Must(url.Parse("https://example.com")),
}) })
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, result.ResponseStatus, http.StatusOK) ExpectEqual(t, result.ResponseStatus, http.StatusOK)

View file

@ -7,11 +7,11 @@ import (
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy" "github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
"github.com/yusing/go-proxy/internal/net/types"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
) )
@ -80,11 +80,11 @@ type TestResult struct {
type testArgs struct { type testArgs struct {
middlewareOpt OptionsRaw middlewareOpt OptionsRaw
upstreamURL *types.URL upstreamURL *url.URL
realRoundTrip bool realRoundTrip bool
reqURL *types.URL reqURL *url.URL
reqMethod string reqMethod string
headers http.Header headers http.Header
body []byte body []byte
@ -96,13 +96,13 @@ type testArgs struct {
func (args *testArgs) setDefaults() { func (args *testArgs) setDefaults() {
if args.reqURL == nil { if args.reqURL == nil {
args.reqURL = Must(types.ParseURL("https://example.com")) args.reqURL = Must(url.Parse("https://example.com"))
} }
if args.reqMethod == "" { if args.reqMethod == "" {
args.reqMethod = http.MethodGet args.reqMethod = http.MethodGet
} }
if args.upstreamURL == nil { if args.upstreamURL == nil {
args.upstreamURL = Must(types.ParseURL("https://10.0.0.1:8443")) // dummy url, no actual effect args.upstreamURL = Must(url.Parse("https://10.0.0.1:8443")) // dummy url, no actual effect
} }
if args.respHeaders == nil { if args.respHeaders == nil {
args.respHeaders = http.Header{} args.respHeaders = http.Header{}

View file

@ -28,7 +28,6 @@ import (
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog" "github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
"github.com/yusing/go-proxy/internal/net/gphttp/httpheaders" "github.com/yusing/go-proxy/internal/net/gphttp/httpheaders"
"github.com/yusing/go-proxy/internal/net/types"
U "github.com/yusing/go-proxy/internal/utils" U "github.com/yusing/go-proxy/internal/utils"
"golang.org/x/net/http/httpguts" "golang.org/x/net/http/httpguts"
) )
@ -93,7 +92,7 @@ type ReverseProxy struct {
HandlerFunc http.HandlerFunc HandlerFunc http.HandlerFunc
TargetName string TargetName string
TargetURL *types.URL TargetURL *url.URL
} }
func singleJoiningSlash(a, b string) string { func singleJoiningSlash(a, b string) string {
@ -133,7 +132,7 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
// URLs to the scheme, host, and base path provided in target. If the // URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir", // target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir. // the target request will be for /base/dir.
func NewReverseProxy(name string, target *types.URL, transport http.RoundTripper) *ReverseProxy { func NewReverseProxy(name string, target *url.URL, transport http.RoundTripper) *ReverseProxy {
if transport == nil { if transport == nil {
panic("nil transport") panic("nil transport")
} }
@ -151,7 +150,7 @@ func (p *ReverseProxy) rewriteRequestURL(req *http.Request) {
targetQuery := p.TargetURL.RawQuery targetQuery := p.TargetURL.RawQuery
req.URL.Scheme = p.TargetURL.Scheme req.URL.Scheme = p.TargetURL.Scheme
req.URL.Host = p.TargetURL.Host req.URL.Host = p.TargetURL.Host
req.URL.Path, req.URL.RawPath = joinURLPath(&p.TargetURL.URL, req.URL) req.URL.Path, req.URL.RawPath = joinURLPath(p.TargetURL, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" { if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else { } else {

View file

@ -1,39 +0,0 @@
package types
import (
"net"
"strings"
)
//nolint:recvcheck
type CIDR net.IPNet
func ParseCIDR(v string) (cidr CIDR, err error) {
err = cidr.Parse(v)
return
}
func (cidr *CIDR) Parse(v string) error {
if !strings.Contains(v, "/") {
v += "/32" // single IP
}
_, ipnet, err := net.ParseCIDR(v)
if err != nil {
return err
}
cidr.IP = ipnet.IP
cidr.Mask = ipnet.Mask
return nil
}
func (cidr CIDR) Contains(ip net.IP) bool {
return (*net.IPNet)(&cidr).Contains(ip)
}
func (cidr CIDR) String() string {
return (*net.IPNet)(&cidr).String()
}
func (cidr CIDR) MarshalText() ([]byte, error) {
return []byte(cidr.String()), nil
}

View file

@ -1,56 +0,0 @@
package types
import (
urlPkg "net/url"
"github.com/yusing/go-proxy/internal/utils"
)
type URL struct {
_ utils.NoCopy
urlPkg.URL
}
func MustParseURL(url string) *URL {
u, err := ParseURL(url)
if err != nil {
panic(err)
}
return u
}
func ParseURL(url string) (*URL, error) {
u := &URL{}
return u, u.Parse(url)
}
func NewURL(url *urlPkg.URL) *URL {
return &URL{URL: *url}
}
func (u *URL) Parse(url string) error {
uu, err := urlPkg.Parse(url)
if err != nil {
return err
}
u.URL = *uu
return nil
}
func (u *URL) String() string {
if u == nil {
return "nil"
}
return u.URL.String()
}
func (u *URL) MarshalJSON() (text []byte, err error) {
if u == nil {
return []byte("null"), nil
}
return []byte("\"" + u.URL.String() + "\""), nil
}
func (u *URL) Equals(other *URL) bool {
return u.String() == other.String()
}

View file

@ -4,7 +4,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
@ -21,7 +21,7 @@ const (
testDockerIP = "172.17.0.123" testDockerIP = "172.17.0.123"
) )
func makeRoutes(cont *types.Container, dockerHostIP ...string) route.Routes { func makeRoutes(cont *container.Summary, dockerHostIP ...string) route.Routes {
var p DockerProvider var p DockerProvider
var host string var host string
if len(dockerHostIP) > 0 { if len(dockerHostIP) > 0 {
@ -64,7 +64,7 @@ func TestApplyLabel(t *testing.T) {
}, },
}, },
} }
entries := makeRoutes(&types.Container{ entries := makeRoutes(&container.Summary{
Names: dummyNames, Names: dummyNames,
Labels: map[string]string{ Labels: map[string]string{
D.LabelAliases: "a,b", D.LabelAliases: "a,b",
@ -135,7 +135,7 @@ func TestApplyLabel(t *testing.T) {
} }
func TestApplyLabelWithAlias(t *testing.T) { func TestApplyLabelWithAlias(t *testing.T) {
entries := makeRoutes(&types.Container{ entries := makeRoutes(&container.Summary{
Names: dummyNames, Names: dummyNames,
State: "running", State: "running",
Labels: map[string]string{ Labels: map[string]string{
@ -162,7 +162,7 @@ func TestApplyLabelWithAlias(t *testing.T) {
} }
func TestApplyLabelWithRef(t *testing.T) { func TestApplyLabelWithRef(t *testing.T) {
entries := makeRoutes(&types.Container{ entries := makeRoutes(&container.Summary{
Names: dummyNames, Names: dummyNames,
State: "running", State: "running",
Labels: map[string]string{ Labels: map[string]string{
@ -190,7 +190,7 @@ func TestApplyLabelWithRef(t *testing.T) {
} }
func TestApplyLabelWithRefIndexError(t *testing.T) { func TestApplyLabelWithRefIndexError(t *testing.T) {
c := D.FromDocker(&types.Container{ c := D.FromDocker(&container.Summary{
Names: dummyNames, Names: dummyNames,
State: "running", State: "running",
Labels: map[string]string{ Labels: map[string]string{
@ -204,7 +204,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) {
_, err := p.routesFromContainerLabels(c) _, err := p.routesFromContainerLabels(c)
ExpectError(t, ErrAliasRefIndexOutOfRange, err) ExpectError(t, ErrAliasRefIndexOutOfRange, err)
c = D.FromDocker(&types.Container{ c = D.FromDocker(&container.Summary{
Names: dummyNames, Names: dummyNames,
State: "running", State: "running",
Labels: map[string]string{ Labels: map[string]string{
@ -217,7 +217,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) {
} }
func TestDynamicAliases(t *testing.T) { func TestDynamicAliases(t *testing.T) {
c := &types.Container{ c := &container.Summary{
Names: []string{"app1"}, Names: []string{"app1"},
State: "running", State: "running",
Labels: map[string]string{ Labels: map[string]string{
@ -240,7 +240,7 @@ func TestDynamicAliases(t *testing.T) {
} }
func TestDisableHealthCheck(t *testing.T) { func TestDisableHealthCheck(t *testing.T) {
c := &types.Container{ c := &container.Summary{
Names: dummyNames, Names: dummyNames,
State: "running", State: "running",
Labels: map[string]string{ Labels: map[string]string{
@ -254,7 +254,7 @@ func TestDisableHealthCheck(t *testing.T) {
} }
func TestPublicIPLocalhost(t *testing.T) { func TestPublicIPLocalhost(t *testing.T) {
c := &types.Container{Names: dummyNames, State: "running"} c := &container.Summary{Names: dummyNames, State: "running"}
r, ok := makeRoutes(c)["a"] r, ok := makeRoutes(c)["a"]
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectEqual(t, r.Container.PublicHostname, "127.0.0.1") ExpectEqual(t, r.Container.PublicHostname, "127.0.0.1")
@ -262,7 +262,7 @@ func TestPublicIPLocalhost(t *testing.T) {
} }
func TestPublicIPRemote(t *testing.T) { func TestPublicIPRemote(t *testing.T) {
c := &types.Container{Names: dummyNames, State: "running"} c := &container.Summary{Names: dummyNames, State: "running"}
raw, ok := makeRoutes(c, testIP)["a"] raw, ok := makeRoutes(c, testIP)["a"]
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectEqual(t, raw.Container.PublicHostname, testIP) ExpectEqual(t, raw.Container.PublicHostname, testIP)
@ -270,9 +270,9 @@ func TestPublicIPRemote(t *testing.T) {
} }
func TestPrivateIPLocalhost(t *testing.T) { func TestPrivateIPLocalhost(t *testing.T) {
c := &types.Container{ c := &container.Summary{
Names: dummyNames, Names: dummyNames,
NetworkSettings: &types.SummaryNetworkSettings{ NetworkSettings: &container.NetworkSettingsSummary{
Networks: map[string]*network.EndpointSettings{ Networks: map[string]*network.EndpointSettings{
"network": { "network": {
IPAddress: testDockerIP, IPAddress: testDockerIP,
@ -287,10 +287,10 @@ func TestPrivateIPLocalhost(t *testing.T) {
} }
func TestPrivateIPRemote(t *testing.T) { func TestPrivateIPRemote(t *testing.T) {
c := &types.Container{ c := &container.Summary{
Names: dummyNames, Names: dummyNames,
State: "running", State: "running",
NetworkSettings: &types.SummaryNetworkSettings{ NetworkSettings: &container.NetworkSettingsSummary{
Networks: map[string]*network.EndpointSettings{ Networks: map[string]*network.EndpointSettings{
"network": { "network": {
IPAddress: testDockerIP, IPAddress: testDockerIP,
@ -309,17 +309,17 @@ func TestStreamDefaultValues(t *testing.T) {
privPort := uint16(1234) privPort := uint16(1234)
pubPort := uint16(4567) pubPort := uint16(4567)
privIP := "172.17.0.123" privIP := "172.17.0.123"
cont := &types.Container{ cont := &container.Summary{
Names: []string{"a"}, Names: []string{"a"},
State: "running", State: "running",
NetworkSettings: &types.SummaryNetworkSettings{ NetworkSettings: &container.NetworkSettingsSummary{
Networks: map[string]*network.EndpointSettings{ Networks: map[string]*network.EndpointSettings{
"network": { "network": {
IPAddress: privIP, IPAddress: privIP,
}, },
}, },
}, },
Ports: []types.Port{ Ports: []container.Port{
{Type: "udp", PrivatePort: privPort, PublicPort: pubPort}, {Type: "udp", PrivatePort: privPort, PublicPort: pubPort},
}, },
} }
@ -346,7 +346,7 @@ func TestStreamDefaultValues(t *testing.T) {
} }
func TestExplicitExclude(t *testing.T) { func TestExplicitExclude(t *testing.T) {
r, ok := makeRoutes(&types.Container{ r, ok := makeRoutes(&container.Summary{
Names: dummyNames, Names: dummyNames,
Labels: map[string]string{ Labels: map[string]string{
D.LabelAliases: "a", D.LabelAliases: "a",
@ -360,9 +360,9 @@ func TestExplicitExclude(t *testing.T) {
func TestImplicitExcludeDatabase(t *testing.T) { func TestImplicitExcludeDatabase(t *testing.T) {
t.Run("mount path detection", func(t *testing.T) { t.Run("mount path detection", func(t *testing.T) {
r, ok := makeRoutes(&types.Container{ r, ok := makeRoutes(&container.Summary{
Names: dummyNames, Names: dummyNames,
Mounts: []types.MountPoint{ Mounts: []container.MountPoint{
{Source: "/data", Destination: "/var/lib/postgresql/data"}, {Source: "/data", Destination: "/var/lib/postgresql/data"},
}, },
})["a"] })["a"]
@ -370,9 +370,9 @@ func TestImplicitExcludeDatabase(t *testing.T) {
ExpectTrue(t, r.ShouldExclude()) ExpectTrue(t, r.ShouldExclude())
}) })
t.Run("exposed port detection", func(t *testing.T) { t.Run("exposed port detection", func(t *testing.T) {
r, ok := makeRoutes(&types.Container{ r, ok := makeRoutes(&container.Summary{
Names: dummyNames, Names: dummyNames,
Ports: []types.Port{ Ports: []container.Port{
{Type: "tcp", PrivatePort: 5432, PublicPort: 5432}, {Type: "tcp", PrivatePort: 5432, PublicPort: 5432},
}, },
})["a"] })["a"]

View file

@ -128,7 +128,7 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
if err != nil && len(routes) == 0 { if err != nil && len(routes) == 0 {
return route.Routes{}, err return route.Routes{}, err
} }
errs := gperr.NewBuilder("routes error") errs := gperr.NewBuilder()
errs.Add(err) errs.Add(err)
// check for exclusion // check for exclusion
// set alias and provider, then validate // set alias and provider, then validate

View file

@ -6,10 +6,9 @@ import (
"github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/agent/pkg/agentproxy" "github.com/yusing/go-proxy/agent/pkg/agentproxy"
"github.com/yusing/go-proxy/internal/api/v1/favicon"
"github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/docker"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/homepage"
"github.com/yusing/go-proxy/internal/idlewatcher" "github.com/yusing/go-proxy/internal/idlewatcher"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp" gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
@ -104,10 +103,10 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
switch { switch {
case r.UseIdleWatcher(): case r.UseIdleWatcher():
waker, err := idlewatcher.NewHTTPWaker(parent, r, r.rp) waker, err := idlewatcher.NewWatcher(parent, r)
if err != nil { if err != nil {
r.task.Finish(err) r.task.Finish(err)
return err return gperr.Wrap(err, "idlewatcher error")
} }
r.handler = waker r.handler = waker
r.HealthMon = waker r.HealthMon = waker
@ -191,6 +190,10 @@ func (r *ReveseProxyRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.handler.ServeHTTP(w, req) r.handler.ServeHTTP(w, req)
} }
func (r *ReveseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy {
return r.rp
}
func (r *ReveseProxyRoute) HealthMonitor() health.HealthMonitor { func (r *ReveseProxyRoute) HealthMonitor() health.HealthMonitor {
return r.HealthMon return r.HealthMon
} }

View file

@ -10,7 +10,6 @@ import (
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/homepage" "github.com/yusing/go-proxy/internal/homepage"
idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types" idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
@ -52,8 +51,8 @@ type (
Provider string `json:"provider,omitempty"` Provider string `json:"provider,omitempty"`
// private fields // private fields
LisURL *net.URL `json:"lurl,omitempty"` LisURL *url.URL `json:"lurl,omitempty"`
ProxyURL *net.URL `json:"purl,omitempty"` ProxyURL *url.URL `json:"purl,omitempty"`
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"` Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
@ -88,7 +87,7 @@ func (r *Route) Validate() (err gperr.Error) {
if err != nil { if err != nil {
errs.Add(err) errs.Add(err)
} }
r.ProxyURL = gperr.Collect(errs, net.ParseURL, "file://"+r.Root) r.ProxyURL = gperr.Collect(errs, url.Parse, "file://"+r.Root)
r.Host = "" r.Host = ""
r.Port.Proxy = 0 r.Port.Proxy = 0
} else { } else {
@ -98,9 +97,9 @@ func (r *Route) Validate() (err gperr.Error) {
errs.Addf("unexpected listening port for %s scheme", r.Scheme) errs.Addf("unexpected listening port for %s scheme", r.Scheme)
} }
case route.SchemeTCP, route.SchemeUDP: case route.SchemeTCP, route.SchemeUDP:
r.LisURL = gperr.Collect(errs, net.ParseURL, fmt.Sprintf("%s://:%d", r.Scheme, r.Port.Listening)) r.LisURL = gperr.Collect(errs, url.Parse, fmt.Sprintf("%s://:%d", r.Scheme, r.Port.Listening))
} }
r.ProxyURL = gperr.Collect(errs, net.ParseURL, fmt.Sprintf("%s://%s:%d", r.Scheme, r.Host, r.Port.Proxy)) r.ProxyURL = gperr.Collect(errs, url.Parse, fmt.Sprintf("%s://%s:%d", r.Scheme, r.Host, r.Port.Proxy))
} }
if !r.UseHealthCheck() && (r.UseLoadBalance() || r.UseIdleWatcher()) { if !r.UseHealthCheck() && (r.UseLoadBalance() || r.UseIdleWatcher()) {
@ -160,7 +159,7 @@ func (r *Route) TargetName() string {
return r.Alias return r.Alias
} }
func (r *Route) TargetURL() *net.URL { func (r *Route) TargetURL() *url.URL {
return r.ProxyURL return r.ProxyURL
} }

View file

@ -2,6 +2,7 @@ package rules
import ( import (
"net/http" "net/http"
"net/url"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@ -9,7 +10,6 @@ import (
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp" gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy" "github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
) )
@ -95,7 +95,7 @@ var commands = map[string]struct {
}, },
validate: validateURL, validate: validateURL,
build: func(args any) CommandHandler { build: func(args any) CommandHandler {
target := args.(*types.URL).String() target := args.(*url.URL).String()
return ReturningCommand(func(w http.ResponseWriter, r *http.Request) { return ReturningCommand(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, target, http.StatusTemporaryRedirect) http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}) })
@ -160,7 +160,7 @@ var commands = map[string]struct {
}, },
validate: validateAbsoluteURL, validate: validateAbsoluteURL,
build: func(args any) CommandHandler { build: func(args any) CommandHandler {
target := args.(*types.URL) target := args.(*url.URL)
if target.Scheme == "" { if target.Scheme == "" {
target.Scheme = "http" target.Scheme = "http"
} }

View file

@ -1,10 +1,10 @@
package rules package rules
import ( import (
"net"
"net/http" "net/http"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
) )
@ -205,7 +205,7 @@ var checkers = map[string]struct {
}, },
validate: validateCIDR, validate: validateCIDR,
builder: func(args any) CheckFunc { builder: func(args any) CheckFunc {
cidr := args.(types.CIDR) cidr := args.(*net.IPNet)
return func(cached Cache, r *http.Request) bool { return func(cached Cache, r *http.Request) bool {
ip := cached.GetRemoteIP(r) ip := cached.GetRemoteIP(r)
if ip == nil { if ip == nil {

View file

@ -2,13 +2,14 @@ package rules
import ( import (
"fmt" "fmt"
"net"
"net/url"
"os" "os"
"path" "path"
"strings" "strings"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
gphttp "github.com/yusing/go-proxy/internal/net/gphttp" gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
"github.com/yusing/go-proxy/internal/net/types"
) )
type ( type (
@ -48,24 +49,24 @@ func toKVOptionalV(args []string) (any, gperr.Error) {
} }
} }
// validateURL returns types.URL with the URL validated. // validateURL returns url.URL with the URL validated.
func validateURL(args []string) (any, gperr.Error) { func validateURL(args []string) (any, gperr.Error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrExpectOneArg return nil, ErrExpectOneArg
} }
u, err := types.ParseURL(args[0]) u, err := url.Parse(args[0])
if err != nil { if err != nil {
return nil, ErrInvalidArguments.With(err) return nil, ErrInvalidArguments.With(err)
} }
return u, nil return u, nil
} }
// validateAbsoluteURL returns types.URL with the URL validated. // validateAbsoluteURL returns url.URL with the URL validated.
func validateAbsoluteURL(args []string) (any, gperr.Error) { func validateAbsoluteURL(args []string) (any, gperr.Error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrExpectOneArg return nil, ErrExpectOneArg
} }
u, err := types.ParseURL(args[0]) u, err := url.Parse(args[0])
if err != nil { if err != nil {
return nil, ErrInvalidArguments.With(err) return nil, ErrInvalidArguments.With(err)
} }
@ -86,7 +87,7 @@ func validateCIDR(args []string) (any, gperr.Error) {
if !strings.Contains(args[0], "/") { if !strings.Contains(args[0], "/") {
args[0] += "/32" args[0] += "/32"
} }
cidr, err := types.ParseCIDR(args[0]) _, cidr, err := net.ParseCIDR(args[0])
if err != nil { if err != nil {
return nil, ErrInvalidArguments.With(err) return nil, ErrInvalidArguments.With(err)
} }

View file

@ -5,7 +5,6 @@ import (
"errors" "errors"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/docker"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/idlewatcher" "github.com/yusing/go-proxy/internal/idlewatcher"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
@ -58,10 +57,10 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
switch { switch {
case r.UseIdleWatcher(): case r.UseIdleWatcher():
waker, err := idlewatcher.NewStreamWaker(parent, r, r.Stream) waker, err := idlewatcher.NewWatcher(parent, r)
if err != nil { if err != nil {
r.task.Finish(err) r.task.Finish(err)
return err return gperr.Wrap(err, "idlewatcher error")
} }
r.Stream = waker r.Stream = waker
r.HealthMon = waker r.HealthMon = waker

View file

@ -2,6 +2,7 @@ package route
import ( import (
"net/http" "net/http"
"net/url"
"github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/docker" "github.com/yusing/go-proxy/internal/docker"
@ -22,7 +23,7 @@ type (
task.TaskFinisher task.TaskFinisher
ProviderName() string ProviderName() string
TargetName() string TargetName() string
TargetURL() *net.URL TargetURL() *url.URL
HealthMonitor() health.HealthMonitor HealthMonitor() health.HealthMonitor
Reference() string Reference() string

View file

@ -1,8 +1,11 @@
package utils package utils
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"net/url"
"os" "os"
"reflect" "reflect"
"strconv" "strconv"
@ -42,7 +45,14 @@ var (
tagAliases = "aliases" // declare aliases for fields tagAliases = "aliases" // declare aliases for fields
) )
var mapUnmarshalerType = reflect.TypeFor[MapUnmarshaller]() var (
typeDuration = reflect.TypeFor[time.Duration]()
typeURL = reflect.TypeFor[url.URL]()
typeCIDR = reflect.TypeFor[*net.IPNet]()
typeMapMarshaller = reflect.TypeFor[MapMarshaller]()
typeMapUnmarshaler = reflect.TypeFor[MapUnmarshaller]()
)
var defaultValues = functional.NewMapOf[reflect.Type, func() any]() var defaultValues = functional.NewMapOf[reflect.Type, func() any]()
@ -196,7 +206,7 @@ func MapUnmarshalValidate(src SerializedObject, dst any) (err gperr.Error) {
return gperr.Errorf("unmarshal: src is %w and dst is not settable", ErrNilValue) return gperr.Errorf("unmarshal: src is %w and dst is not settable", ErrNilValue)
} }
if dstT.Implements(mapUnmarshalerType) { if dstT.Implements(typeMapUnmarshaler) {
dstV, _, err = dive(dstV) dstV, _, err = dive(dstV)
if err != nil { if err != nil {
return err return err
@ -370,7 +380,7 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error {
} }
obj, ok := src.Interface().(SerializedObject) obj, ok := src.Interface().(SerializedObject)
if !ok { if !ok {
return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String()) return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
} }
return MapUnmarshalValidate(obj, dst.Addr().Interface()) return MapUnmarshalValidate(obj, dst.Addr().Interface())
case srcKind == reflect.Slice: case srcKind == reflect.Slice:
@ -378,7 +388,7 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error {
return nil return nil
} }
if dstT.Kind() != reflect.Slice { if dstT.Kind() != reflect.Slice {
return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String()) return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
} }
sliceErrs := gperr.NewBuilder("slice conversion errors") sliceErrs := gperr.NewBuilder("slice conversion errors")
newSlice := reflect.MakeSlice(dstT, src.Len(), src.Len()) newSlice := reflect.MakeSlice(dstT, src.Len(), src.Len())
@ -402,6 +412,10 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error {
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT) return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
} }
func nilPointer[T any]() reflect.Value {
return reflect.ValueOf((*T)(nil))
}
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) { func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) {
convertible = true convertible = true
dstT := dst.Type() dstT := dst.Type()
@ -417,10 +431,10 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
return return
} }
switch dstT { switch dstT {
case reflect.TypeFor[time.Duration](): case typeDuration:
if src == "" { if src == "" {
dst.Set(reflect.Zero(dstT)) dst.Set(reflect.Zero(dstT))
return return false, nil
} }
d, err := time.ParseDuration(src) d, err := time.ParseDuration(src)
if err != nil { if err != nil {
@ -431,7 +445,31 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
} }
dst.Set(reflect.ValueOf(d)) dst.Set(reflect.ValueOf(d))
return return
default: case typeURL:
if src == "" {
dst.Addr().Set(nilPointer[*url.URL]())
return
}
u, err := url.Parse(src)
if err != nil {
return true, gperr.Wrap(err)
}
dst.Set(reflect.ValueOf(u).Elem())
return
case typeCIDR:
if src == "" {
dst.Addr().Set(nilPointer[*net.IPNet]())
return
}
if !strings.Contains(src, "/") {
src += "/32" // single IP
}
_, ipnet, err := net.ParseCIDR(src)
if err != nil {
return true, gperr.Wrap(err)
}
dst.Set(reflect.ValueOf(ipnet).Elem())
return
} }
if dstKind := dst.Kind(); isIntFloat(dstKind) { if dstKind := dst.Kind(); isIntFloat(dstKind) {
var i any var i any

View file

@ -1,10 +1,13 @@
package utils package utils
import ( import (
"net/url"
"reflect" "reflect"
"strconv" "strconv"
"testing" "testing"
"time"
"github.com/yusing/go-proxy/internal/utils/strutils"
. "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/internal/utils/testing"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -42,7 +45,7 @@ func TestDeserialize(t *testing.T) {
var s2 S var s2 S
err := MapUnmarshalValidate(testStructSerialized, &s2) err := MapUnmarshalValidate(testStructSerialized, &s2)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, s2, testStruct) ExpectEqualValues(t, s2, testStruct)
}) })
} }
@ -62,15 +65,15 @@ func TestDeserializeAnonymousField(t *testing.T) {
// t.Fatalf("anon %v, all %v", anon, all) // t.Fatalf("anon %v, all %v", anon, all)
err := MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s) err := MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, s.A, 1) ExpectEqualValues(t, s.A, 1)
ExpectEqual(t, s.B, 2) ExpectEqualValues(t, s.B, 2)
ExpectEqual(t, s.C, 3) ExpectEqualValues(t, s.C, 3)
err = MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s2) err = MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s2)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, s2.A, 1) ExpectEqualValues(t, s2.A, 1)
ExpectEqual(t, s2.B, 2) ExpectEqualValues(t, s2.B, 2)
ExpectEqual(t, s2.C, 3) ExpectEqualValues(t, s2.C, 3)
} }
func TestStringIntConvert(t *testing.T) { func TestStringIntConvert(t *testing.T) {
@ -91,42 +94,42 @@ func TestStringIntConvert(t *testing.T) {
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.i8, int8(127)) ExpectEqualValues(t, test.i8, int8(127))
ok, err = ConvertString(s, reflect.ValueOf(&test.i16)) ok, err = ConvertString(s, reflect.ValueOf(&test.i16))
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.i16, int16(127)) ExpectEqualValues(t, test.i16, int16(127))
ok, err = ConvertString(s, reflect.ValueOf(&test.i32)) ok, err = ConvertString(s, reflect.ValueOf(&test.i32))
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.i32, int32(127)) ExpectEqualValues(t, test.i32, int32(127))
ok, err = ConvertString(s, reflect.ValueOf(&test.i64)) ok, err = ConvertString(s, reflect.ValueOf(&test.i64))
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.i64, int64(127)) ExpectEqualValues(t, test.i64, int64(127))
ok, err = ConvertString(s, reflect.ValueOf(&test.u8)) ok, err = ConvertString(s, reflect.ValueOf(&test.u8))
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.u8, uint8(127)) ExpectEqualValues(t, test.u8, uint8(127))
ok, err = ConvertString(s, reflect.ValueOf(&test.u16)) ok, err = ConvertString(s, reflect.ValueOf(&test.u16))
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.u16, uint16(127)) ExpectEqualValues(t, test.u16, uint16(127))
ok, err = ConvertString(s, reflect.ValueOf(&test.u32)) ok, err = ConvertString(s, reflect.ValueOf(&test.u32))
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.u32, uint32(127)) ExpectEqualValues(t, test.u32, uint32(127))
ok, err = ConvertString(s, reflect.ValueOf(&test.u64)) ok, err = ConvertString(s, reflect.ValueOf(&test.u64))
ExpectTrue(t, ok) ExpectTrue(t, ok)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, test.u64, uint64(127)) ExpectEqualValues(t, test.u64, uint64(127))
} }
type testModel struct { type testModel struct {
@ -150,19 +153,19 @@ func TestConvertor(t *testing.T) {
m := new(testModel) m := new(testModel)
ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m)) ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m))
ExpectEqual(t, m.Test.foo, 123) ExpectEqualValues(t, m.Test.foo, 123)
ExpectEqual(t, m.Test.bar, "123") ExpectEqualValues(t, m.Test.bar, "123")
}) })
t.Run("int_to_string", func(t *testing.T) { t.Run("int_to_string", func(t *testing.T) {
m := new(testModel) m := new(testModel)
ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m)) ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m))
ExpectEqual(t, m.Test.foo, 123) ExpectEqualValues(t, m.Test.foo, 123)
ExpectEqual(t, m.Test.bar, "123") ExpectEqualValues(t, m.Test.bar, "123")
ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Baz": 123}, m)) ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Baz": 123}, m))
ExpectEqual(t, m.Baz, "123") ExpectEqualValues(t, m.Baz, "123")
}) })
t.Run("invalid", func(t *testing.T) { t.Run("invalid", func(t *testing.T) {
@ -177,21 +180,21 @@ func TestStringToSlice(t *testing.T) {
convertible, err := ConvertString("a,b,c", reflect.ValueOf(&dst)) convertible, err := ConvertString("a,b,c", reflect.ValueOf(&dst))
ExpectTrue(t, convertible) ExpectTrue(t, convertible)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, dst, []string{"a", "b", "c"}) ExpectEqualValues(t, dst, []string{"a", "b", "c"})
}) })
t.Run("yaml-like", func(t *testing.T) { t.Run("yaml-like", func(t *testing.T) {
dst := make([]string, 0) dst := make([]string, 0)
convertible, err := ConvertString("- a\n- b\n- c", reflect.ValueOf(&dst)) convertible, err := ConvertString("- a\n- b\n- c", reflect.ValueOf(&dst))
ExpectTrue(t, convertible) ExpectTrue(t, convertible)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, dst, []string{"a", "b", "c"}) ExpectEqualValues(t, dst, []string{"a", "b", "c"})
}) })
t.Run("single-line-yaml-like", func(t *testing.T) { t.Run("single-line-yaml-like", func(t *testing.T) {
dst := make([]string, 0) dst := make([]string, 0)
convertible, err := ConvertString("- a", reflect.ValueOf(&dst)) convertible, err := ConvertString("- a", reflect.ValueOf(&dst))
ExpectTrue(t, convertible) ExpectTrue(t, convertible)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, dst, []string{"a"}) ExpectEqualValues(t, dst, []string{"a"})
}) })
} }
@ -215,7 +218,7 @@ func TestStringToMap(t *testing.T) {
convertible, err := ConvertString(" a: b\n c: d", reflect.ValueOf(&dst)) convertible, err := ConvertString(" a: b\n c: d", reflect.ValueOf(&dst))
ExpectTrue(t, convertible) ExpectTrue(t, convertible)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, dst, map[string]string{"a": "b", "c": "d"}) ExpectEqualValues(t, dst, map[string]string{"a": "b", "c": "d"})
}) })
} }
@ -242,7 +245,7 @@ func TestStringToStruct(t *testing.T) {
convertible, err := ConvertString(" A: a\n B: 123", reflect.ValueOf(&dst)) convertible, err := ConvertString(" A: a\n B: 123", reflect.ValueOf(&dst))
ExpectTrue(t, convertible) ExpectTrue(t, convertible)
ExpectNoError(t, err) ExpectNoError(t, err)
ExpectEqual(t, dst, struct { ExpectEqualValues(t, dst, struct {
A string A string
B int B int
}{"a", 123}) }{"a", 123})

View file

@ -21,50 +21,55 @@ func Must[Result any](r Result, err error) Result {
return r return r
} }
func ExpectNoError(t *testing.T, err error) { func ExpectNoError(t *testing.T, err error, msgAndArgs ...any) {
t.Helper() t.Helper()
require.NoError(t, err) require.NoError(t, err, msgAndArgs...)
} }
func ExpectHasError(t *testing.T, err error) { func ExpectHasError(t *testing.T, err error, msgAndArgs ...any) {
t.Helper() t.Helper()
require.Error(t, err) require.Error(t, err, msgAndArgs...)
} }
func ExpectError(t *testing.T, expected error, err error) { func ExpectError(t *testing.T, expected error, err error, msgAndArgs ...any) {
t.Helper() t.Helper()
require.ErrorIs(t, err, expected) require.ErrorIs(t, err, expected, msgAndArgs...)
} }
func ExpectErrorT[T error](t *testing.T, err error) { func ExpectErrorT[T error](t *testing.T, err error, msgAndArgs ...any) {
t.Helper() t.Helper()
var errAs T var errAs T
require.ErrorAs(t, err, &errAs) require.ErrorAs(t, err, &errAs, msgAndArgs...)
} }
func ExpectEqual[T any](t *testing.T, got T, want T) { func ExpectEqual[T any](t *testing.T, got T, want T, msgAndArgs ...any) {
t.Helper() t.Helper()
require.EqualValues(t, got, want) require.Equal(t, want, got, msgAndArgs...)
} }
func ExpectContains[T any](t *testing.T, got T, wants []T) { func ExpectEqualValues(t *testing.T, got any, want any, msgAndArgs ...any) {
t.Helper() t.Helper()
require.Contains(t, wants, got) require.EqualValues(t, want, got, msgAndArgs...)
} }
func ExpectTrue(t *testing.T, got bool) { func ExpectContains[T any](t *testing.T, got T, wants []T, msgAndArgs ...any) {
t.Helper() t.Helper()
require.True(t, got) require.Contains(t, wants, got, msgAndArgs...)
} }
func ExpectFalse(t *testing.T, got bool) { func ExpectTrue(t *testing.T, got bool, msgAndArgs ...any) {
t.Helper() t.Helper()
require.False(t, got) require.True(t, got, msgAndArgs...)
} }
func ExpectType[T any](t *testing.T, got any) (_ T) { func ExpectFalse(t *testing.T, got bool, msgAndArgs ...any) {
t.Helper()
require.False(t, got, msgAndArgs...)
}
func ExpectType[T any](t *testing.T, got any, msgAndArgs ...any) (_ T) {
t.Helper() t.Helper()
_, ok := got.(T) _, ok := got.(T)
require.True(t, ok) require.True(t, ok, msgAndArgs...)
return got.(T) return got.(T)
} }

View file

@ -1,34 +1,32 @@
package monitor package health
import ( import (
"encoding/json" "net/url"
"strconv" "strconv"
"time" "time"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils" "github.com/yusing/go-proxy/internal/utils/strutils"
"github.com/yusing/go-proxy/internal/watcher/health"
) )
type JSONRepresentation struct { type JSONRepresentation struct {
Name string Name string
Config *health.HealthCheckConfig Config *HealthCheckConfig
Status health.Status Status Status
Started time.Time Started time.Time
Uptime time.Duration Uptime time.Duration
Latency time.Duration Latency time.Duration
LastSeen time.Time LastSeen time.Time
Detail string Detail string
URL *net.URL URL *url.URL
Extra map[string]any Extra map[string]any
} }
func (jsonRepr *JSONRepresentation) MarshalJSON() ([]byte, error) { func (jsonRepr *JSONRepresentation) MarshalMap() map[string]any {
url := jsonRepr.URL.String() url := jsonRepr.URL.String()
if url == "http://:0" { if url == "http://:0" {
url = "" url = ""
} }
return json.Marshal(map[string]any{ return map[string]any{
"name": jsonRepr.Name, "name": jsonRepr.Name,
"config": jsonRepr.Config, "config": jsonRepr.Config,
"started": jsonRepr.Started.Unix(), "started": jsonRepr.Started.Unix(),
@ -43,5 +41,5 @@ func (jsonRepr *JSONRepresentation) MarshalJSON() ([]byte, error) {
"detail": jsonRepr.Detail, "detail": jsonRepr.Detail,
"url": url, "url": url,
"extra": jsonRepr.Extra, "extra": jsonRepr.Extra,
}) }
} }

View file

@ -7,7 +7,6 @@ import (
"net/url" "net/url"
agentPkg "github.com/yusing/go-proxy/agent/pkg/agent" agentPkg "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
) )
@ -24,7 +23,7 @@ type (
} }
) )
func AgentTargetFromURL(url *types.URL) *AgentCheckHealthTarget { func AgentTargetFromURL(url *url.URL) *AgentCheckHealthTarget {
return &AgentCheckHealthTarget{ return &AgentCheckHealthTarget{
Scheme: url.Scheme, Scheme: url.Scheme,
Host: url.Host, Host: url.Host,
@ -40,12 +39,12 @@ func (target *AgentCheckHealthTarget) buildQuery() string {
return query.Encode() return query.Encode()
} }
func (target *AgentCheckHealthTarget) displayURL() *types.URL { func (target *AgentCheckHealthTarget) displayURL() *url.URL {
return types.NewURL(&url.URL{ return &url.URL{
Scheme: target.Scheme, Scheme: target.Scheme,
Host: target.Host, Host: target.Host,
Path: target.Path, Path: target.Path,
}) }
} }
func NewAgentProxiedMonitor(agent *agentPkg.AgentConfig, config *health.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor { func NewAgentProxiedMonitor(agent *agentPkg.AgentConfig, config *health.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor {

View file

@ -4,9 +4,9 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
"github.com/yusing/go-proxy/pkg" "github.com/yusing/go-proxy/pkg"
) )
@ -26,7 +26,7 @@ var pinger = &http.Client{
}, },
} }
func NewHTTPHealthMonitor(url *types.URL, config *health.HealthCheckConfig) *HTTPHealthMonitor { func NewHTTPHealthMonitor(url *url.URL, config *health.HealthCheckConfig) *HTTPHealthMonitor {
mon := new(HTTPHealthMonitor) mon := new(HTTPHealthMonitor)
mon.monitor = newMonitor(url, config, mon.CheckHealth) mon.monitor = newMonitor(url, config, mon.CheckHealth)
if config.UseGet { if config.UseGet {

View file

@ -4,12 +4,12 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/url"
"time" "time"
"github.com/yusing/go-proxy/internal/docker" "github.com/yusing/go-proxy/internal/docker"
"github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/notif" "github.com/yusing/go-proxy/internal/notif"
route "github.com/yusing/go-proxy/internal/route/types" route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/task"
@ -23,7 +23,7 @@ type (
monitor struct { monitor struct {
service string service string
config *health.HealthCheckConfig config *health.HealthCheckConfig
url atomic.Value[*types.URL] url atomic.Value[*url.URL]
status atomic.Value[health.Status] status atomic.Value[health.Status]
lastResult atomic.Value[*health.HealthCheckResult] lastResult atomic.Value[*health.HealthCheckResult]
@ -63,7 +63,7 @@ func NewMonitor(r route.Route) health.HealthMonCheck {
return mon return mon
} }
func newMonitor(url *types.URL, config *health.HealthCheckConfig, healthCheckFunc HealthCheckFunc) *monitor { func newMonitor(url *url.URL, config *health.HealthCheckConfig, healthCheckFunc HealthCheckFunc) *monitor {
mon := &monitor{ mon := &monitor{
config: config, config: config,
checkHealth: healthCheckFunc, checkHealth: healthCheckFunc,
@ -135,12 +135,12 @@ func (mon *monitor) Finish(reason any) {
} }
// UpdateURL implements HealthChecker. // UpdateURL implements HealthChecker.
func (mon *monitor) UpdateURL(url *types.URL) { func (mon *monitor) UpdateURL(url *url.URL) {
mon.url.Store(url) mon.url.Store(url)
} }
// URL implements HealthChecker. // URL implements HealthChecker.
func (mon *monitor) URL() *types.URL { func (mon *monitor) URL() *url.URL {
return mon.url.Load() return mon.url.Load()
} }
@ -179,8 +179,8 @@ func (mon *monitor) String() string {
return mon.Name() return mon.Name()
} }
// MarshalJSON implements json.Marshaler of HealthMonitor. // MarshalMap implements health.HealthMonitor.
func (mon *monitor) MarshalJSON() ([]byte, error) { func (mon *monitor) MarshalMap() map[string]any {
res := mon.lastResult.Load() res := mon.lastResult.Load()
if res == nil { if res == nil {
res = &health.HealthCheckResult{ res = &health.HealthCheckResult{
@ -188,7 +188,7 @@ func (mon *monitor) MarshalJSON() ([]byte, error) {
} }
} }
return (&JSONRepresentation{ return (&health.JSONRepresentation{
Name: mon.service, Name: mon.service,
Config: mon.config, Config: mon.config,
Status: mon.status.Load(), Status: mon.status.Load(),
@ -198,7 +198,7 @@ func (mon *monitor) MarshalJSON() ([]byte, error) {
LastSeen: GetLastSeen(mon.service), LastSeen: GetLastSeen(mon.service),
Detail: res.Detail, Detail: res.Detail,
URL: mon.url.Load(), URL: mon.url.Load(),
}).MarshalJSON() }).MarshalMap()
} }
func (mon *monitor) checkUpdateHealth() error { func (mon *monitor) checkUpdateHealth() error {

View file

@ -2,9 +2,9 @@ package monitor
import ( import (
"net" "net"
"net/url"
"time" "time"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health" "github.com/yusing/go-proxy/internal/watcher/health"
) )
@ -15,7 +15,7 @@ type (
} }
) )
func NewRawHealthMonitor(url *types.URL, config *health.HealthCheckConfig) *RawHealthMonitor { func NewRawHealthMonitor(url *url.URL, config *health.HealthCheckConfig) *RawHealthMonitor {
mon := new(RawHealthMonitor) mon := new(RawHealthMonitor)
mon.monitor = newMonitor(url, config, mon.CheckHealth) mon.monitor = newMonitor(url, config, mon.CheckHealth)
mon.dialer = &net.Dialer{ mon.dialer = &net.Dialer{

View file

@ -1,12 +1,12 @@
package health package health
import ( import (
"encoding/json"
"fmt" "fmt"
"net/url"
"time" "time"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils"
) )
type ( type (
@ -24,15 +24,15 @@ type (
task.TaskStarter task.TaskStarter
task.TaskFinisher task.TaskFinisher
fmt.Stringer fmt.Stringer
json.Marshaler utils.MapMarshaller
WithHealthInfo WithHealthInfo
Name() string Name() string
} }
HealthChecker interface { HealthChecker interface {
CheckHealth() (result *HealthCheckResult, err error) CheckHealth() (result *HealthCheckResult, err error)
URL() *types.URL URL() *url.URL
Config() *HealthCheckConfig Config() *HealthCheckConfig
UpdateURL(url *types.URL) UpdateURL(url *url.URL)
} }
HealthMonCheck interface { HealthMonCheck interface {
HealthMonitor HealthMonitor