diff --git a/agent/pkg/handler/check_health.go b/agent/pkg/handler/check_health.go index 5f9c954e..ceddc992 100644 --- a/agent/pkg/handler/check_health.go +++ b/agent/pkg/handler/check_health.go @@ -8,7 +8,6 @@ import ( "strings" "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/monitor" ) @@ -44,11 +43,11 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - result, err = monitor.NewHTTPHealthMonitor(types.NewURL(&url.URL{ + result, err = monitor.NewHTTPHealthMonitor(&url.URL{ Scheme: scheme, Host: host, Path: path, - }), defaultHealthConfig).CheckHealth() + }, defaultHealthConfig).CheckHealth() case "tcp", "udp": host := query.Get("host") if host == "" { @@ -63,10 +62,10 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - result, err = monitor.NewRawHealthMonitor(types.NewURL(&url.URL{ + result, err = monitor.NewRawHealthMonitor(&url.URL{ Scheme: scheme, Host: host, - }), defaultHealthConfig).CheckHealth() + }, defaultHealthConfig).CheckHealth() } if err != nil { diff --git a/agent/pkg/handler/docker_socket.go b/agent/pkg/handler/docker_socket.go index 27bedf29..3d309105 100644 --- a/agent/pkg/handler/docker_socket.go +++ b/agent/pkg/handler/docker_socket.go @@ -9,7 +9,6 @@ import ( "github.com/yusing/go-proxy/internal/docker" "github.com/yusing/go-proxy/internal/logging" "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) { @@ -22,10 +21,10 @@ func DockerSocketHandler() http.HandlerFunc { logging.Warn().Err(err).Msg("failed to connect to docker client") return serviceUnavailable } - rp := reverseproxy.NewReverseProxy("docker", types.NewURL(&url.URL{ + rp := reverseproxy.NewReverseProxy("docker", &url.URL{ Scheme: "http", Host: client.DummyHost, - }), dockerClient.HTTPClient().Transport) + }, dockerClient.HTTPClient().Transport) return rp.ServeHTTP } diff --git a/agent/pkg/handler/proxy_http.go b/agent/pkg/handler/proxy_http.go index 712f2618..eeeb3685 100644 --- a/agent/pkg/handler/proxy_http.go +++ b/agent/pkg/handler/proxy_http.go @@ -12,7 +12,6 @@ import ( "github.com/yusing/go-proxy/internal/logging" "github.com/yusing/go-proxy/internal/net/gphttp" "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" ) @@ -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()) - rp := reverseproxy.NewReverseProxy("agent", types.NewURL(&url.URL{ + rp := reverseproxy.NewReverseProxy("agent", &url.URL{ Scheme: scheme, Host: host, - }), transport) + }, transport) rp.ServeHTTP(w, r) } diff --git a/internal/homepage/route.go b/internal/homepage/route.go index d48cf712..a91c5aef 100644 --- a/internal/homepage/route.go +++ b/internal/homepage/route.go @@ -2,15 +2,14 @@ package homepage import ( "net/http" - - net "github.com/yusing/go-proxy/internal/net/types" + "net/url" ) type route interface { TargetName() string ProviderName() string Reference() string - TargetURL() *net.URL + TargetURL() *url.URL } type httpRoute interface { diff --git a/internal/net/gphttp/accesslog/filter.go b/internal/net/gphttp/accesslog/filter.go index c0c3e29d..a4911138 100644 --- a/internal/net/gphttp/accesslog/filter.go +++ b/internal/net/gphttp/accesslog/filter.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/yusing/go-proxy/internal/gperr" - "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/utils/strutils" ) @@ -24,7 +23,7 @@ type ( Key, Value string } Host string - CIDR struct{ types.CIDR } + CIDR struct{ net.IPNet } ) var ErrInvalidHTTPHeaderFilter = gperr.New("invalid http header filter") diff --git a/internal/net/gphttp/accesslog/filter_test.go b/internal/net/gphttp/accesslog/filter_test.go index a934a7b6..306f4981 100644 --- a/internal/net/gphttp/accesslog/filter_test.go +++ b/internal/net/gphttp/accesslog/filter_test.go @@ -1,6 +1,7 @@ package accesslog_test import ( + "net" "net/http" "testing" @@ -155,8 +156,11 @@ func TestHeaderFilter(t *testing.T) { } func TestCIDRFilter(t *testing.T) { - cidr := []*CIDR{ - strutils.MustParse[*CIDR]("192.168.10.0/24"), + cidr := []*CIDR{{ + net.IPNet{ + IP: net.ParseIP("192.168.10.0"), + Mask: net.CIDRMask(24, 32), + }}, } ExpectEqual(t, cidr[0].String(), "192.168.10.0/24") inCIDR := &http.Request{ diff --git a/internal/net/gphttp/loadbalancer/loadbalancer.go b/internal/net/gphttp/loadbalancer/loadbalancer.go index 3d474cf6..85d3a952 100644 --- a/internal/net/gphttp/loadbalancer/loadbalancer.go +++ b/internal/net/gphttp/loadbalancer/loadbalancer.go @@ -13,7 +13,6 @@ import ( "github.com/yusing/go-proxy/internal/route/routes" "github.com/yusing/go-proxy/internal/task" "github.com/yusing/go-proxy/internal/watcher/health" - "github.com/yusing/go-proxy/internal/watcher/health/monitor" ) // 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) } -// MarshalJSON implements health.HealthMonitor. -func (lb *LoadBalancer) MarshalJSON() ([]byte, error) { +// MarshalMap implements health.HealthMonitor. +func (lb *LoadBalancer) MarshalMap() map[string]any { extra := make(map[string]any) lb.pool.RangeAll(func(k string, v Server) { extra[v.Key()] = v }) - return (&monitor.JSONRepresentation{ + return (&health.JSONRepresentation{ Name: lb.Name(), Status: lb.Status(), Started: lb.startTime, @@ -256,7 +255,7 @@ func (lb *LoadBalancer) MarshalJSON() ([]byte, error) { "config": lb.Config, "pool": extra, }, - }).MarshalJSON() + }).MarshalMap() } // Name implements health.HealthMonitor. diff --git a/internal/net/gphttp/loadbalancer/types/server.go b/internal/net/gphttp/loadbalancer/types/server.go index 59dee05c..37eda424 100644 --- a/internal/net/gphttp/loadbalancer/types/server.go +++ b/internal/net/gphttp/loadbalancer/types/server.go @@ -2,9 +2,9 @@ package types import ( "net/http" + "net/url" 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" F "github.com/yusing/go-proxy/internal/utils/functional" "github.com/yusing/go-proxy/internal/watcher/health" @@ -15,7 +15,7 @@ type ( _ U.NoCopy name string - url *net.URL + url *url.URL weight Weight http.Handler `json:"-"` @@ -27,7 +27,7 @@ type ( health.HealthMonitor Name() string Key() string - URL() *net.URL + URL() *url.URL Weight() Weight SetWeight(weight Weight) TryWake() error @@ -38,7 +38,7 @@ type ( 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{ name: name, 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 { srv := &server{ weight: Weight(weight), - url: net.MustParseURL("http://localhost"), + url: &url.URL{Scheme: "http", Host: "localhost"}, } return srv } @@ -61,7 +61,7 @@ func (srv *server) Name() string { return srv.name } -func (srv *server) URL() *net.URL { +func (srv *server) URL() *url.URL { return srv.url } diff --git a/internal/net/gphttp/middleware/cidr_whitelist.go b/internal/net/gphttp/middleware/cidr_whitelist.go index 6b9271f0..a9d1dc60 100644 --- a/internal/net/gphttp/middleware/cidr_whitelist.go +++ b/internal/net/gphttp/middleware/cidr_whitelist.go @@ -6,7 +6,6 @@ import ( "github.com/go-playground/validator/v10" gphttp "github.com/yusing/go-proxy/internal/net/gphttp" - "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/utils" F "github.com/yusing/go-proxy/internal/utils/functional" ) @@ -18,8 +17,8 @@ type ( cachedAddr F.Map[string, bool] // cache for trusted IPs } CIDRWhitelistOpts struct { - Allow []*types.CIDR `validate:"min=1"` - StatusCode int `json:"status_code" aliases:"status" validate:"omitempty,status_code"` + Allow []*net.IPNet `validate:"min=1"` + StatusCode int `json:"status_code" aliases:"status" validate:"omitempty,status_code"` Message string } ) @@ -27,7 +26,7 @@ type ( var ( CIDRWhiteList = NewMiddleware[cidrWhitelist]() cidrWhitelistDefaults = CIDRWhitelistOpts{ - Allow: []*types.CIDR{}, + Allow: []*net.IPNet{}, StatusCode: http.StatusForbidden, Message: "IP not allowed", } diff --git a/internal/net/gphttp/middleware/cloudflare_real_ip.go b/internal/net/gphttp/middleware/cloudflare_real_ip.go index 4fdcf2eb..fc299d30 100644 --- a/internal/net/gphttp/middleware/cloudflare_real_ip.go +++ b/internal/net/gphttp/middleware/cloudflare_real_ip.go @@ -11,7 +11,6 @@ import ( "github.com/yusing/go-proxy/internal/common" "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/strutils" ) @@ -33,7 +32,7 @@ var ( cfCIDRsMu sync.Mutex // 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(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 @@ -68,7 +67,7 @@ func (cri *cloudflareRealIP) getTracer() *Tracer { return cri.realIP.getTracer() } -func tryFetchCFCIDR() (cfCIDRs []*types.CIDR) { +func tryFetchCFCIDR() (cfCIDRs []*net.IPNet) { if time.Since(cfCIDRsLastUpdate.Load()) < cfCIDRsUpdateInterval { return } @@ -83,7 +82,7 @@ func tryFetchCFCIDR() (cfCIDRs []*types.CIDR) { if common.IsTest { cfCIDRs = localCIDRs } else { - cfCIDRs = make([]*types.CIDR, 0, 30) + cfCIDRs = make([]*net.IPNet, 0, 30) err := errors.Join( fetchUpdateCFIPRange(cfIPv4CIDRsEndpoint, &cfCIDRs), fetchUpdateCFIPRange(cfIPv6CIDRsEndpoint, &cfCIDRs), @@ -103,7 +102,7 @@ func tryFetchCFCIDR() (cfCIDRs []*types.CIDR) { return } -func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*types.CIDR) error { +func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*net.IPNet) error { resp, err := http.Get(endpoint) if err != nil { return err @@ -124,7 +123,7 @@ func fetchUpdateCFIPRange(endpoint string, cfCIDRs *[]*types.CIDR) error { 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...) return nil diff --git a/internal/net/gphttp/middleware/modify_request_test.go b/internal/net/gphttp/middleware/modify_request_test.go index 8e694225..aa67d6d5 100644 --- a/internal/net/gphttp/middleware/modify_request_test.go +++ b/internal/net/gphttp/middleware/modify_request_test.go @@ -4,10 +4,10 @@ import ( "bytes" "net" "net/http" + "net/url" "slices" "testing" - "github.com/yusing/go-proxy/internal/net/types" . "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) { - reqURL := types.MustParseURL("https://my.app/?arg_1=b") - upstreamURL := types.MustParseURL("http://test.example.com") + reqURL := Must(url.Parse("https://my.app/?arg_1=b")) + upstreamURL := Must(url.Parse("http://test.example.com")) result, err := newMiddlewareTest(ModifyRequest, &testArgs{ middlewareOpt: opts, reqURL: reqURL, @@ -128,8 +128,8 @@ func TestModifyRequest(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - reqURL := types.MustParseURL("https://my.app" + tt.path) - upstreamURL := types.MustParseURL(tt.upstreamURL) + reqURL := Must(url.Parse("https://my.app" + tt.path)) + upstreamURL := Must(url.Parse(tt.upstreamURL)) opts["add_prefix"] = tt.addPrefix result, err := newMiddlewareTest(ModifyRequest, &testArgs{ diff --git a/internal/net/gphttp/middleware/modify_response_test.go b/internal/net/gphttp/middleware/modify_response_test.go index 60922a1e..9f426b2a 100644 --- a/internal/net/gphttp/middleware/modify_response_test.go +++ b/internal/net/gphttp/middleware/modify_response_test.go @@ -4,10 +4,10 @@ import ( "bytes" "net" "net/http" + "net/url" "slices" "testing" - "github.com/yusing/go-proxy/internal/net/types" . "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) { - reqURL := types.MustParseURL("https://my.app/?arg_1=b") - upstreamURL := types.MustParseURL("http://test.example.com") + reqURL := Must(url.Parse("https://my.app/?arg_1=b")) + upstreamURL := Must(url.Parse("http://test.example.com")) result, err := newMiddlewareTest(ModifyResponse, &testArgs{ middlewareOpt: opts, reqURL: reqURL, diff --git a/internal/net/gphttp/middleware/real_ip.go b/internal/net/gphttp/middleware/real_ip.go index ed11d126..558ad5a7 100644 --- a/internal/net/gphttp/middleware/real_ip.go +++ b/internal/net/gphttp/middleware/real_ip.go @@ -5,7 +5,6 @@ import ( "net/http" "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 @@ -19,7 +18,7 @@ type ( // Header is the name of the header to use for the real client IP Header string `validate:"required"` // 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, the original client address that matches one of the trusted addresses is replaced by @@ -36,7 +35,7 @@ var ( RealIP = NewMiddleware[realIP]() realIPOptsDefault = RealIPOpts{ Header: "X-Real-IP", - From: []*types.CIDR{}, + From: []*net.IPNet{}, } ) diff --git a/internal/net/gphttp/middleware/real_ip_test.go b/internal/net/gphttp/middleware/real_ip_test.go index 372862d1..a70a182e 100644 --- a/internal/net/gphttp/middleware/real_ip_test.go +++ b/internal/net/gphttp/middleware/real_ip_test.go @@ -7,7 +7,6 @@ import ( "testing" "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" ) @@ -23,7 +22,7 @@ func TestSetRealIPOpts(t *testing.T) { } optExpected := &RealIPOpts{ Header: httpheaders.HeaderXRealIP, - From: []*types.CIDR{ + From: []*net.IPNet{ { IP: net.ParseIP("127.0.0.0"), Mask: net.IPv4Mask(255, 0, 0, 0), diff --git a/internal/net/gphttp/middleware/redirect_http_test.go b/internal/net/gphttp/middleware/redirect_http_test.go index eccd33cd..82dfb7cd 100644 --- a/internal/net/gphttp/middleware/redirect_http_test.go +++ b/internal/net/gphttp/middleware/redirect_http_test.go @@ -2,15 +2,15 @@ package middleware import ( "net/http" + "net/url" "testing" - "github.com/yusing/go-proxy/internal/net/types" . "github.com/yusing/go-proxy/internal/utils/testing" ) func TestRedirectToHTTPs(t *testing.T) { result, err := newMiddlewareTest(RedirectHTTP, &testArgs{ - reqURL: types.MustParseURL("http://example.com"), + reqURL: Must(url.Parse("http://example.com")), }) ExpectNoError(t, err) ExpectEqual(t, result.ResponseStatus, http.StatusPermanentRedirect) @@ -19,7 +19,7 @@ func TestRedirectToHTTPs(t *testing.T) { func TestNoRedirect(t *testing.T) { result, err := newMiddlewareTest(RedirectHTTP, &testArgs{ - reqURL: types.MustParseURL("https://example.com"), + reqURL: Must(url.Parse("https://example.com")), }) ExpectNoError(t, err) ExpectEqual(t, result.ResponseStatus, http.StatusOK) diff --git a/internal/net/gphttp/middleware/test_utils.go b/internal/net/gphttp/middleware/test_utils.go index 2bd208b2..edb5e24a 100644 --- a/internal/net/gphttp/middleware/test_utils.go +++ b/internal/net/gphttp/middleware/test_utils.go @@ -7,11 +7,11 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "github.com/yusing/go-proxy/internal/common" "github.com/yusing/go-proxy/internal/gperr" "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" ) @@ -80,11 +80,11 @@ type TestResult struct { type testArgs struct { middlewareOpt OptionsRaw - upstreamURL *types.URL + upstreamURL *url.URL realRoundTrip bool - reqURL *types.URL + reqURL *url.URL reqMethod string headers http.Header body []byte @@ -96,13 +96,13 @@ type testArgs struct { func (args *testArgs) setDefaults() { if args.reqURL == nil { - args.reqURL = Must(types.ParseURL("https://example.com")) + args.reqURL = Must(url.Parse("https://example.com")) } if args.reqMethod == "" { args.reqMethod = http.MethodGet } 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 { args.respHeaders = http.Header{} diff --git a/internal/net/gphttp/reverseproxy/reverse_proxy_mod.go b/internal/net/gphttp/reverseproxy/reverse_proxy_mod.go index 49988f0a..0530a97c 100644 --- a/internal/net/gphttp/reverseproxy/reverse_proxy_mod.go +++ b/internal/net/gphttp/reverseproxy/reverse_proxy_mod.go @@ -28,7 +28,6 @@ import ( "github.com/yusing/go-proxy/internal/logging" "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/types" U "github.com/yusing/go-proxy/internal/utils" "golang.org/x/net/http/httpguts" ) @@ -93,7 +92,7 @@ type ReverseProxy struct { HandlerFunc http.HandlerFunc TargetName string - TargetURL *types.URL + TargetURL *url.URL } 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 // target's path is "/base" and the incoming request was for "/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 { panic("nil transport") } @@ -151,7 +150,7 @@ func (p *ReverseProxy) rewriteRequestURL(req *http.Request) { targetQuery := p.TargetURL.RawQuery req.URL.Scheme = p.TargetURL.Scheme 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 == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery } else { diff --git a/internal/net/types/cidr.go b/internal/net/types/cidr.go deleted file mode 100644 index 67ca2976..00000000 --- a/internal/net/types/cidr.go +++ /dev/null @@ -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 -} diff --git a/internal/net/types/url.go b/internal/net/types/url.go deleted file mode 100644 index a704813a..00000000 --- a/internal/net/types/url.go +++ /dev/null @@ -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() -} diff --git a/internal/route/provider/docker_test.go b/internal/route/provider/docker_test.go index 47a9090c..5a45a0bc 100644 --- a/internal/route/provider/docker_test.go +++ b/internal/route/provider/docker_test.go @@ -4,7 +4,7 @@ import ( "testing" "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/client" "github.com/yusing/go-proxy/internal/common" @@ -21,7 +21,7 @@ const ( 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 host string if len(dockerHostIP) > 0 { @@ -64,7 +64,7 @@ func TestApplyLabel(t *testing.T) { }, }, } - entries := makeRoutes(&types.Container{ + entries := makeRoutes(&container.Summary{ Names: dummyNames, Labels: map[string]string{ D.LabelAliases: "a,b", @@ -135,7 +135,7 @@ func TestApplyLabel(t *testing.T) { } func TestApplyLabelWithAlias(t *testing.T) { - entries := makeRoutes(&types.Container{ + entries := makeRoutes(&container.Summary{ Names: dummyNames, State: "running", Labels: map[string]string{ @@ -162,7 +162,7 @@ func TestApplyLabelWithAlias(t *testing.T) { } func TestApplyLabelWithRef(t *testing.T) { - entries := makeRoutes(&types.Container{ + entries := makeRoutes(&container.Summary{ Names: dummyNames, State: "running", Labels: map[string]string{ @@ -190,7 +190,7 @@ func TestApplyLabelWithRef(t *testing.T) { } func TestApplyLabelWithRefIndexError(t *testing.T) { - c := D.FromDocker(&types.Container{ + c := D.FromDocker(&container.Summary{ Names: dummyNames, State: "running", Labels: map[string]string{ @@ -204,7 +204,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) { _, err := p.routesFromContainerLabels(c) ExpectError(t, ErrAliasRefIndexOutOfRange, err) - c = D.FromDocker(&types.Container{ + c = D.FromDocker(&container.Summary{ Names: dummyNames, State: "running", Labels: map[string]string{ @@ -217,7 +217,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) { } func TestDynamicAliases(t *testing.T) { - c := &types.Container{ + c := &container.Summary{ Names: []string{"app1"}, State: "running", Labels: map[string]string{ @@ -240,7 +240,7 @@ func TestDynamicAliases(t *testing.T) { } func TestDisableHealthCheck(t *testing.T) { - c := &types.Container{ + c := &container.Summary{ Names: dummyNames, State: "running", Labels: map[string]string{ @@ -254,7 +254,7 @@ func TestDisableHealthCheck(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"] ExpectTrue(t, ok) ExpectEqual(t, r.Container.PublicHostname, "127.0.0.1") @@ -262,7 +262,7 @@ func TestPublicIPLocalhost(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"] ExpectTrue(t, ok) ExpectEqual(t, raw.Container.PublicHostname, testIP) @@ -270,9 +270,9 @@ func TestPublicIPRemote(t *testing.T) { } func TestPrivateIPLocalhost(t *testing.T) { - c := &types.Container{ + c := &container.Summary{ Names: dummyNames, - NetworkSettings: &types.SummaryNetworkSettings{ + NetworkSettings: &container.NetworkSettingsSummary{ Networks: map[string]*network.EndpointSettings{ "network": { IPAddress: testDockerIP, @@ -287,10 +287,10 @@ func TestPrivateIPLocalhost(t *testing.T) { } func TestPrivateIPRemote(t *testing.T) { - c := &types.Container{ + c := &container.Summary{ Names: dummyNames, State: "running", - NetworkSettings: &types.SummaryNetworkSettings{ + NetworkSettings: &container.NetworkSettingsSummary{ Networks: map[string]*network.EndpointSettings{ "network": { IPAddress: testDockerIP, @@ -309,17 +309,17 @@ func TestStreamDefaultValues(t *testing.T) { privPort := uint16(1234) pubPort := uint16(4567) privIP := "172.17.0.123" - cont := &types.Container{ + cont := &container.Summary{ Names: []string{"a"}, State: "running", - NetworkSettings: &types.SummaryNetworkSettings{ + NetworkSettings: &container.NetworkSettingsSummary{ Networks: map[string]*network.EndpointSettings{ "network": { IPAddress: privIP, }, }, }, - Ports: []types.Port{ + Ports: []container.Port{ {Type: "udp", PrivatePort: privPort, PublicPort: pubPort}, }, } @@ -346,7 +346,7 @@ func TestStreamDefaultValues(t *testing.T) { } func TestExplicitExclude(t *testing.T) { - r, ok := makeRoutes(&types.Container{ + r, ok := makeRoutes(&container.Summary{ Names: dummyNames, Labels: map[string]string{ D.LabelAliases: "a", @@ -360,9 +360,9 @@ func TestExplicitExclude(t *testing.T) { func TestImplicitExcludeDatabase(t *testing.T) { t.Run("mount path detection", func(t *testing.T) { - r, ok := makeRoutes(&types.Container{ + r, ok := makeRoutes(&container.Summary{ Names: dummyNames, - Mounts: []types.MountPoint{ + Mounts: []container.MountPoint{ {Source: "/data", Destination: "/var/lib/postgresql/data"}, }, })["a"] @@ -370,9 +370,9 @@ func TestImplicitExcludeDatabase(t *testing.T) { ExpectTrue(t, r.ShouldExclude()) }) t.Run("exposed port detection", func(t *testing.T) { - r, ok := makeRoutes(&types.Container{ + r, ok := makeRoutes(&container.Summary{ Names: dummyNames, - Ports: []types.Port{ + Ports: []container.Port{ {Type: "tcp", PrivatePort: 5432, PublicPort: 5432}, }, })["a"] diff --git a/internal/route/provider/provider.go b/internal/route/provider/provider.go index 1313bc7f..14e222c2 100644 --- a/internal/route/provider/provider.go +++ b/internal/route/provider/provider.go @@ -128,7 +128,7 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) { if err != nil && len(routes) == 0 { return route.Routes{}, err } - errs := gperr.NewBuilder("routes error") + errs := gperr.NewBuilder() errs.Add(err) // check for exclusion // set alias and provider, then validate diff --git a/internal/route/reverse_proxy.go b/internal/route/reverse_proxy.go index bd03fc14..a4180d61 100755 --- a/internal/route/reverse_proxy.go +++ b/internal/route/reverse_proxy.go @@ -6,10 +6,9 @@ import ( "github.com/yusing/go-proxy/agent/pkg/agent" "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/docker" "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/logging" gphttp "github.com/yusing/go-proxy/internal/net/gphttp" @@ -104,10 +103,10 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error { switch { case r.UseIdleWatcher(): - waker, err := idlewatcher.NewHTTPWaker(parent, r, r.rp) + waker, err := idlewatcher.NewWatcher(parent, r) if err != nil { r.task.Finish(err) - return err + return gperr.Wrap(err, "idlewatcher error") } r.handler = waker r.HealthMon = waker @@ -191,6 +190,10 @@ func (r *ReveseProxyRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) { r.handler.ServeHTTP(w, req) } +func (r *ReveseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy { + return r.rp +} + func (r *ReveseProxyRoute) HealthMonitor() health.HealthMonitor { return r.HealthMon } diff --git a/internal/route/route.go b/internal/route/route.go index d79033e5..f32e5e31 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -10,7 +10,6 @@ import ( "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/homepage" 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/utils/strutils" "github.com/yusing/go-proxy/internal/watcher/health" @@ -52,8 +51,8 @@ type ( Provider string `json:"provider,omitempty"` // private fields - LisURL *net.URL `json:"lurl,omitempty"` - ProxyURL *net.URL `json:"purl,omitempty"` + LisURL *url.URL `json:"lurl,omitempty"` + ProxyURL *url.URL `json:"purl,omitempty"` Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"` @@ -88,7 +87,7 @@ func (r *Route) Validate() (err gperr.Error) { if err != nil { 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.Port.Proxy = 0 } else { @@ -98,9 +97,9 @@ func (r *Route) Validate() (err gperr.Error) { errs.Addf("unexpected listening port for %s scheme", r.Scheme) } 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()) { @@ -160,7 +159,7 @@ func (r *Route) TargetName() string { return r.Alias } -func (r *Route) TargetURL() *net.URL { +func (r *Route) TargetURL() *url.URL { return r.ProxyURL } diff --git a/internal/route/rules/do.go b/internal/route/rules/do.go index f00c20d3..f113a4c0 100644 --- a/internal/route/rules/do.go +++ b/internal/route/rules/do.go @@ -2,6 +2,7 @@ package rules import ( "net/http" + "net/url" "path" "strconv" "strings" @@ -9,7 +10,6 @@ import ( "github.com/yusing/go-proxy/internal/gperr" 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/types" "github.com/yusing/go-proxy/internal/utils/strutils" ) @@ -95,7 +95,7 @@ var commands = map[string]struct { }, validate: validateURL, build: func(args any) CommandHandler { - target := args.(*types.URL).String() + target := args.(*url.URL).String() return ReturningCommand(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, target, http.StatusTemporaryRedirect) }) @@ -160,7 +160,7 @@ var commands = map[string]struct { }, validate: validateAbsoluteURL, build: func(args any) CommandHandler { - target := args.(*types.URL) + target := args.(*url.URL) if target.Scheme == "" { target.Scheme = "http" } diff --git a/internal/route/rules/on.go b/internal/route/rules/on.go index 8c9e29f7..f69e7a8e 100644 --- a/internal/route/rules/on.go +++ b/internal/route/rules/on.go @@ -1,10 +1,10 @@ package rules import ( + "net" "net/http" "github.com/yusing/go-proxy/internal/gperr" - "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/utils/strutils" ) @@ -205,7 +205,7 @@ var checkers = map[string]struct { }, validate: validateCIDR, builder: func(args any) CheckFunc { - cidr := args.(types.CIDR) + cidr := args.(*net.IPNet) return func(cached Cache, r *http.Request) bool { ip := cached.GetRemoteIP(r) if ip == nil { diff --git a/internal/route/rules/validate.go b/internal/route/rules/validate.go index 58a7dd89..09d3a664 100644 --- a/internal/route/rules/validate.go +++ b/internal/route/rules/validate.go @@ -2,13 +2,14 @@ package rules import ( "fmt" + "net" + "net/url" "os" "path" "strings" "github.com/yusing/go-proxy/internal/gperr" gphttp "github.com/yusing/go-proxy/internal/net/gphttp" - "github.com/yusing/go-proxy/internal/net/types" ) 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) { if len(args) != 1 { return nil, ErrExpectOneArg } - u, err := types.ParseURL(args[0]) + u, err := url.Parse(args[0]) if err != nil { return nil, ErrInvalidArguments.With(err) } 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) { if len(args) != 1 { return nil, ErrExpectOneArg } - u, err := types.ParseURL(args[0]) + u, err := url.Parse(args[0]) if err != nil { return nil, ErrInvalidArguments.With(err) } @@ -86,7 +87,7 @@ func validateCIDR(args []string) (any, gperr.Error) { if !strings.Contains(args[0], "/") { args[0] += "/32" } - cidr, err := types.ParseCIDR(args[0]) + _, cidr, err := net.ParseCIDR(args[0]) if err != nil { return nil, ErrInvalidArguments.With(err) } diff --git a/internal/route/stream.go b/internal/route/stream.go index dd09df7b..11f8efe7 100755 --- a/internal/route/stream.go +++ b/internal/route/stream.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/rs/zerolog" - "github.com/yusing/go-proxy/internal/docker" "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/idlewatcher" "github.com/yusing/go-proxy/internal/logging" @@ -58,10 +57,10 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error { switch { case r.UseIdleWatcher(): - waker, err := idlewatcher.NewStreamWaker(parent, r, r.Stream) + waker, err := idlewatcher.NewWatcher(parent, r) if err != nil { r.task.Finish(err) - return err + return gperr.Wrap(err, "idlewatcher error") } r.Stream = waker r.HealthMon = waker diff --git a/internal/route/types/route.go b/internal/route/types/route.go index d9be0ff6..9be415a7 100644 --- a/internal/route/types/route.go +++ b/internal/route/types/route.go @@ -2,6 +2,7 @@ package route import ( "net/http" + "net/url" "github.com/yusing/go-proxy/agent/pkg/agent" "github.com/yusing/go-proxy/internal/docker" @@ -22,7 +23,7 @@ type ( task.TaskFinisher ProviderName() string TargetName() string - TargetURL() *net.URL + TargetURL() *url.URL HealthMonitor() health.HealthMonitor Reference() string diff --git a/internal/utils/serialization.go b/internal/utils/serialization.go index ab5381f0..59d7293b 100644 --- a/internal/utils/serialization.go +++ b/internal/utils/serialization.go @@ -1,8 +1,11 @@ package utils import ( + "bytes" "encoding/json" "errors" + "net" + "net/url" "os" "reflect" "strconv" @@ -42,7 +45,14 @@ var ( 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]() @@ -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) } - if dstT.Implements(mapUnmarshalerType) { + if dstT.Implements(typeMapUnmarshaler) { dstV, _, err = dive(dstV) if err != nil { return err @@ -370,7 +380,7 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error { } obj, ok := src.Interface().(SerializedObject) if !ok { - return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String()) + return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT) } return MapUnmarshalValidate(obj, dst.Addr().Interface()) case srcKind == reflect.Slice: @@ -378,7 +388,7 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error { return nil } 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") 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) } +func nilPointer[T any]() reflect.Value { + return reflect.ValueOf((*T)(nil)) +} + func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) { convertible = true dstT := dst.Type() @@ -417,10 +431,10 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe return } switch dstT { - case reflect.TypeFor[time.Duration](): + case typeDuration: if src == "" { dst.Set(reflect.Zero(dstT)) - return + return false, nil } d, err := time.ParseDuration(src) if err != nil { @@ -431,7 +445,31 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe } dst.Set(reflect.ValueOf(d)) 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) { var i any diff --git a/internal/utils/serialization_test.go b/internal/utils/serialization_test.go index ce5669a7..86a8dc60 100644 --- a/internal/utils/serialization_test.go +++ b/internal/utils/serialization_test.go @@ -1,10 +1,13 @@ package utils import ( + "net/url" "reflect" "strconv" "testing" + "time" + "github.com/yusing/go-proxy/internal/utils/strutils" . "github.com/yusing/go-proxy/internal/utils/testing" "gopkg.in/yaml.v3" ) @@ -42,7 +45,7 @@ func TestDeserialize(t *testing.T) { var s2 S err := MapUnmarshalValidate(testStructSerialized, &s2) 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) err := MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s) ExpectNoError(t, err) - ExpectEqual(t, s.A, 1) - ExpectEqual(t, s.B, 2) - ExpectEqual(t, s.C, 3) + ExpectEqualValues(t, s.A, 1) + ExpectEqualValues(t, s.B, 2) + ExpectEqualValues(t, s.C, 3) err = MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s2) ExpectNoError(t, err) - ExpectEqual(t, s2.A, 1) - ExpectEqual(t, s2.B, 2) - ExpectEqual(t, s2.C, 3) + ExpectEqualValues(t, s2.A, 1) + ExpectEqualValues(t, s2.B, 2) + ExpectEqualValues(t, s2.C, 3) } func TestStringIntConvert(t *testing.T) { @@ -91,42 +94,42 @@ func TestStringIntConvert(t *testing.T) { ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.i8, int8(127)) + ExpectEqualValues(t, test.i8, int8(127)) ok, err = ConvertString(s, reflect.ValueOf(&test.i16)) ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.i16, int16(127)) + ExpectEqualValues(t, test.i16, int16(127)) ok, err = ConvertString(s, reflect.ValueOf(&test.i32)) ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.i32, int32(127)) + ExpectEqualValues(t, test.i32, int32(127)) ok, err = ConvertString(s, reflect.ValueOf(&test.i64)) ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.i64, int64(127)) + ExpectEqualValues(t, test.i64, int64(127)) ok, err = ConvertString(s, reflect.ValueOf(&test.u8)) ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.u8, uint8(127)) + ExpectEqualValues(t, test.u8, uint8(127)) ok, err = ConvertString(s, reflect.ValueOf(&test.u16)) ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.u16, uint16(127)) + ExpectEqualValues(t, test.u16, uint16(127)) ok, err = ConvertString(s, reflect.ValueOf(&test.u32)) ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.u32, uint32(127)) + ExpectEqualValues(t, test.u32, uint32(127)) ok, err = ConvertString(s, reflect.ValueOf(&test.u64)) ExpectTrue(t, ok) ExpectNoError(t, err) - ExpectEqual(t, test.u64, uint64(127)) + ExpectEqualValues(t, test.u64, uint64(127)) } type testModel struct { @@ -150,19 +153,19 @@ func TestConvertor(t *testing.T) { m := new(testModel) ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m)) - ExpectEqual(t, m.Test.foo, 123) - ExpectEqual(t, m.Test.bar, "123") + ExpectEqualValues(t, m.Test.foo, 123) + ExpectEqualValues(t, m.Test.bar, "123") }) t.Run("int_to_string", func(t *testing.T) { m := new(testModel) ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m)) - ExpectEqual(t, m.Test.foo, 123) - ExpectEqual(t, m.Test.bar, "123") + ExpectEqualValues(t, m.Test.foo, 123) + ExpectEqualValues(t, m.Test.bar, "123") 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) { @@ -177,21 +180,21 @@ func TestStringToSlice(t *testing.T) { convertible, err := ConvertString("a,b,c", reflect.ValueOf(&dst)) ExpectTrue(t, convertible) 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) { dst := make([]string, 0) convertible, err := ConvertString("- a\n- b\n- c", reflect.ValueOf(&dst)) ExpectTrue(t, convertible) 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) { dst := make([]string, 0) convertible, err := ConvertString("- a", reflect.ValueOf(&dst)) ExpectTrue(t, convertible) 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)) ExpectTrue(t, convertible) 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)) ExpectTrue(t, convertible) ExpectNoError(t, err) - ExpectEqual(t, dst, struct { + ExpectEqualValues(t, dst, struct { A string B int }{"a", 123}) diff --git a/internal/utils/testing/testing.go b/internal/utils/testing/testing.go index 11c6d25d..7187e15f 100644 --- a/internal/utils/testing/testing.go +++ b/internal/utils/testing/testing.go @@ -21,50 +21,55 @@ func Must[Result any](r Result, err error) Result { return r } -func ExpectNoError(t *testing.T, err error) { +func ExpectNoError(t *testing.T, err error, msgAndArgs ...any) { 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() - 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() - 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() 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() - 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() - 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() - 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() - 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() _, ok := got.(T) - require.True(t, ok) + require.True(t, ok, msgAndArgs...) return got.(T) } diff --git a/internal/watcher/health/monitor/json.go b/internal/watcher/health/json.go similarity index 74% rename from internal/watcher/health/monitor/json.go rename to internal/watcher/health/json.go index 1ebdca57..47a56aba 100644 --- a/internal/watcher/health/monitor/json.go +++ b/internal/watcher/health/json.go @@ -1,34 +1,32 @@ -package monitor +package health import ( - "encoding/json" + "net/url" "strconv" "time" - net "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/utils/strutils" - "github.com/yusing/go-proxy/internal/watcher/health" ) type JSONRepresentation struct { Name string - Config *health.HealthCheckConfig - Status health.Status + Config *HealthCheckConfig + Status Status Started time.Time Uptime time.Duration Latency time.Duration LastSeen time.Time Detail string - URL *net.URL + URL *url.URL Extra map[string]any } -func (jsonRepr *JSONRepresentation) MarshalJSON() ([]byte, error) { +func (jsonRepr *JSONRepresentation) MarshalMap() map[string]any { url := jsonRepr.URL.String() if url == "http://:0" { url = "" } - return json.Marshal(map[string]any{ + return map[string]any{ "name": jsonRepr.Name, "config": jsonRepr.Config, "started": jsonRepr.Started.Unix(), @@ -43,5 +41,5 @@ func (jsonRepr *JSONRepresentation) MarshalJSON() ([]byte, error) { "detail": jsonRepr.Detail, "url": url, "extra": jsonRepr.Extra, - }) + } } diff --git a/internal/watcher/health/monitor/agent_proxied.go b/internal/watcher/health/monitor/agent_proxied.go index 82ca0aa3..67db85c9 100644 --- a/internal/watcher/health/monitor/agent_proxied.go +++ b/internal/watcher/health/monitor/agent_proxied.go @@ -7,7 +7,6 @@ import ( "net/url" 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" ) @@ -24,7 +23,7 @@ type ( } ) -func AgentTargetFromURL(url *types.URL) *AgentCheckHealthTarget { +func AgentTargetFromURL(url *url.URL) *AgentCheckHealthTarget { return &AgentCheckHealthTarget{ Scheme: url.Scheme, Host: url.Host, @@ -40,12 +39,12 @@ func (target *AgentCheckHealthTarget) buildQuery() string { return query.Encode() } -func (target *AgentCheckHealthTarget) displayURL() *types.URL { - return types.NewURL(&url.URL{ +func (target *AgentCheckHealthTarget) displayURL() *url.URL { + return &url.URL{ Scheme: target.Scheme, Host: target.Host, Path: target.Path, - }) + } } func NewAgentProxiedMonitor(agent *agentPkg.AgentConfig, config *health.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor { diff --git a/internal/watcher/health/monitor/http.go b/internal/watcher/health/monitor/http.go index 9dec9f64..ce95b23c 100644 --- a/internal/watcher/health/monitor/http.go +++ b/internal/watcher/health/monitor/http.go @@ -4,9 +4,9 @@ import ( "crypto/tls" "errors" "net/http" + "net/url" "time" - "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/watcher/health" "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.monitor = newMonitor(url, config, mon.CheckHealth) if config.UseGet { diff --git a/internal/watcher/health/monitor/monitor.go b/internal/watcher/health/monitor/monitor.go index 05197f5e..84d2bdc9 100644 --- a/internal/watcher/health/monitor/monitor.go +++ b/internal/watcher/health/monitor/monitor.go @@ -4,12 +4,12 @@ import ( "context" "errors" "fmt" + "net/url" "time" "github.com/yusing/go-proxy/internal/docker" "github.com/yusing/go-proxy/internal/gperr" "github.com/yusing/go-proxy/internal/logging" - "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/notif" route "github.com/yusing/go-proxy/internal/route/types" "github.com/yusing/go-proxy/internal/task" @@ -23,7 +23,7 @@ type ( monitor struct { service string config *health.HealthCheckConfig - url atomic.Value[*types.URL] + url atomic.Value[*url.URL] status atomic.Value[health.Status] lastResult atomic.Value[*health.HealthCheckResult] @@ -63,7 +63,7 @@ func NewMonitor(r route.Route) health.HealthMonCheck { 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{ config: config, checkHealth: healthCheckFunc, @@ -135,12 +135,12 @@ func (mon *monitor) Finish(reason any) { } // UpdateURL implements HealthChecker. -func (mon *monitor) UpdateURL(url *types.URL) { +func (mon *monitor) UpdateURL(url *url.URL) { mon.url.Store(url) } // URL implements HealthChecker. -func (mon *monitor) URL() *types.URL { +func (mon *monitor) URL() *url.URL { return mon.url.Load() } @@ -179,8 +179,8 @@ func (mon *monitor) String() string { return mon.Name() } -// MarshalJSON implements json.Marshaler of HealthMonitor. -func (mon *monitor) MarshalJSON() ([]byte, error) { +// MarshalMap implements health.HealthMonitor. +func (mon *monitor) MarshalMap() map[string]any { res := mon.lastResult.Load() if res == nil { res = &health.HealthCheckResult{ @@ -188,7 +188,7 @@ func (mon *monitor) MarshalJSON() ([]byte, error) { } } - return (&JSONRepresentation{ + return (&health.JSONRepresentation{ Name: mon.service, Config: mon.config, Status: mon.status.Load(), @@ -198,7 +198,7 @@ func (mon *monitor) MarshalJSON() ([]byte, error) { LastSeen: GetLastSeen(mon.service), Detail: res.Detail, URL: mon.url.Load(), - }).MarshalJSON() + }).MarshalMap() } func (mon *monitor) checkUpdateHealth() error { diff --git a/internal/watcher/health/monitor/raw.go b/internal/watcher/health/monitor/raw.go index e6358fd7..eef60040 100644 --- a/internal/watcher/health/monitor/raw.go +++ b/internal/watcher/health/monitor/raw.go @@ -2,9 +2,9 @@ package monitor import ( "net" + "net/url" "time" - "github.com/yusing/go-proxy/internal/net/types" "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.monitor = newMonitor(url, config, mon.CheckHealth) mon.dialer = &net.Dialer{ diff --git a/internal/watcher/health/types.go b/internal/watcher/health/types.go index de8a07fe..de7ab119 100644 --- a/internal/watcher/health/types.go +++ b/internal/watcher/health/types.go @@ -1,12 +1,12 @@ package health import ( - "encoding/json" "fmt" + "net/url" "time" - "github.com/yusing/go-proxy/internal/net/types" "github.com/yusing/go-proxy/internal/task" + "github.com/yusing/go-proxy/internal/utils" ) type ( @@ -24,15 +24,15 @@ type ( task.TaskStarter task.TaskFinisher fmt.Stringer - json.Marshaler + utils.MapMarshaller WithHealthInfo Name() string } HealthChecker interface { CheckHealth() (result *HealthCheckResult, err error) - URL() *types.URL + URL() *url.URL Config() *HealthCheckConfig - UpdateURL(url *types.URL) + UpdateURL(url *url.URL) } HealthMonCheck interface { HealthMonitor