remove unnecessary encapsulation, setup branch updated to v0.8

This commit is contained in:
yusing 2024-12-18 00:33:48 +08:00
parent 2baeb6a572
commit 276684f076
10 changed files with 60 additions and 94 deletions

View file

@ -15,22 +15,14 @@ import (
type ReverseProxyEntry struct { // real model after validation type ReverseProxyEntry struct { // real model after validation
Raw *route.RawEntry `json:"raw"` Raw *route.RawEntry `json:"raw"`
Alias route.Alias `json:"alias"`
Scheme route.Scheme `json:"scheme"`
URL net.URL `json:"url"` URL net.URL `json:"url"`
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
PathPatterns []string `json:"path_patterns,omitempty"`
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
LoadBalance *loadbalance.Config `json:"load_balance,omitempty"`
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty"`
/* Docker only */ /* Docker only */
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"` Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
} }
func (rp *ReverseProxyEntry) TargetName() string { func (rp *ReverseProxyEntry) TargetName() string {
return string(rp.Alias) return rp.Raw.Alias
} }
func (rp *ReverseProxyEntry) TargetURL() net.URL { func (rp *ReverseProxyEntry) TargetURL() net.URL {
@ -42,11 +34,11 @@ func (rp *ReverseProxyEntry) RawEntry() *route.RawEntry {
} }
func (rp *ReverseProxyEntry) LoadBalanceConfig() *loadbalance.Config { func (rp *ReverseProxyEntry) LoadBalanceConfig() *loadbalance.Config {
return rp.LoadBalance return rp.Raw.LoadBalance
} }
func (rp *ReverseProxyEntry) HealthCheckConfig() *health.HealthCheckConfig { func (rp *ReverseProxyEntry) HealthCheckConfig() *health.HealthCheckConfig {
return rp.HealthCheck return rp.Raw.HealthCheck
} }
func (rp *ReverseProxyEntry) IdlewatcherConfig() *idlewatcher.Config { func (rp *ReverseProxyEntry) IdlewatcherConfig() *idlewatcher.Config {
@ -59,14 +51,12 @@ func validateRPEntry(m *route.RawEntry, s route.Scheme, errs *E.Builder) *Revers
cont = docker.DummyContainer cont = docker.DummyContainer
} }
lb := m.LoadBalance if m.LoadBalance != nil && m.LoadBalance.Link == "" {
if lb != nil && lb.Link == "" { m.LoadBalance = nil
lb = nil
} }
host := E.Collect(errs, route.ValidateHost, m.Host)
port := E.Collect(errs, route.ValidatePort, m.Port) port := E.Collect(errs, route.ValidatePort, m.Port)
url := E.Collect(errs, url.Parse, fmt.Sprintf("%s://%s:%d", s, host, port)) url := E.Collect(errs, url.Parse, fmt.Sprintf("%s://%s:%d", s, m.Host, port))
iwCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont) iwCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
if errs.HasError() { if errs.HasError() {
@ -75,14 +65,7 @@ func validateRPEntry(m *route.RawEntry, s route.Scheme, errs *E.Builder) *Revers
return &ReverseProxyEntry{ return &ReverseProxyEntry{
Raw: m, Raw: m,
Alias: route.Alias(m.Alias),
Scheme: s,
URL: net.NewURL(url), URL: net.NewURL(url),
NoTLSVerify: m.NoTLSVerify,
PathPatterns: m.PathPatterns,
HealthCheck: m.HealthCheck,
LoadBalance: lb,
Middlewares: m.Middlewares,
Idlewatcher: iwCfg, Idlewatcher: iwCfg,
} }
} }

View file

@ -15,19 +15,17 @@ import (
type StreamEntry struct { type StreamEntry struct {
Raw *route.RawEntry `json:"raw"` Raw *route.RawEntry `json:"raw"`
Alias route.Alias `json:"alias"`
Scheme route.StreamScheme `json:"scheme"` Scheme route.StreamScheme `json:"scheme"`
URL net.URL `json:"url"` URL net.URL `json:"url"`
Host route.Host `json:"host,omitempty"` ListenURL net.URL `json:"listening_url"`
Port route.StreamPort `json:"port,omitempty"` Port route.StreamPort `json:"port,omitempty"`
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
/* Docker only */ /* Docker only */
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"` Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
} }
func (s *StreamEntry) TargetName() string { func (s *StreamEntry) TargetName() string {
return string(s.Alias) return s.Raw.Alias
} }
func (s *StreamEntry) TargetURL() net.URL { func (s *StreamEntry) TargetURL() net.URL {
@ -44,7 +42,7 @@ func (s *StreamEntry) LoadBalanceConfig() *loadbalance.Config {
} }
func (s *StreamEntry) HealthCheckConfig() *health.HealthCheckConfig { func (s *StreamEntry) HealthCheckConfig() *health.HealthCheckConfig {
return s.HealthCheck return s.Raw.HealthCheck
} }
func (s *StreamEntry) IdlewatcherConfig() *idlewatcher.Config { func (s *StreamEntry) IdlewatcherConfig() *idlewatcher.Config {
@ -57,10 +55,10 @@ func validateStreamEntry(m *route.RawEntry, errs *E.Builder) *StreamEntry {
cont = docker.DummyContainer cont = docker.DummyContainer
} }
host := E.Collect(errs, route.ValidateHost, m.Host)
port := E.Collect(errs, route.ValidateStreamPort, m.Port) port := E.Collect(errs, route.ValidateStreamPort, m.Port)
scheme := E.Collect(errs, route.ValidateStreamScheme, m.Scheme) scheme := E.Collect(errs, route.ValidateStreamScheme, m.Scheme)
url := E.Collect(errs, net.ParseURL, fmt.Sprintf("%s://%s:%d", scheme.ListeningScheme, host, port.ProxyPort)) url := E.Collect(errs, net.ParseURL, fmt.Sprintf("%s://%s:%d", scheme.ProxyScheme, m.Host, port.ProxyPort))
listenURL := E.Collect(errs, net.ParseURL, fmt.Sprintf("%s://:%d", scheme.ListeningScheme, port.ListeningPort))
idleWatcherCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont) idleWatcherCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
if errs.HasError() { if errs.HasError() {
@ -69,12 +67,10 @@ func validateStreamEntry(m *route.RawEntry, errs *E.Builder) *StreamEntry {
return &StreamEntry{ return &StreamEntry{
Raw: m, Raw: m,
Alias: route.Alias(m.Alias),
Scheme: *scheme, Scheme: *scheme,
URL: url, URL: url,
Host: host, ListenURL: listenURL,
Port: port, Port: port,
HealthCheck: m.HealthCheck,
Idlewatcher: idleWatcherCfg, Idlewatcher: idleWatcherCfg,
} }
} }

View file

@ -35,25 +35,23 @@ type (
l zerolog.Logger l zerolog.Logger
} }
SubdomainKey = route.Alias
) )
// var globalMux = http.NewServeMux() // TODO: support regex subdomain matching. // var globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) { func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) {
var trans *http.Transport var trans *http.Transport
if entry.NoTLSVerify { if entry.Raw.NoTLSVerify {
trans = gphttp.DefaultTransportNoTLS trans = gphttp.DefaultTransportNoTLS
} else { } else {
trans = gphttp.DefaultTransport trans = gphttp.DefaultTransport
} }
service := string(entry.Alias) service := entry.TargetName()
rp := gphttp.NewReverseProxy(service, entry.URL, trans) rp := gphttp.NewReverseProxy(service, entry.URL, trans)
if len(entry.Middlewares) > 0 { if len(entry.Raw.Middlewares) > 0 {
err := middleware.PatchReverseProxy(rp, entry.Middlewares) err := middleware.PatchReverseProxy(rp, entry.Raw.Middlewares)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -63,15 +61,15 @@ func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) {
ReverseProxyEntry: entry, ReverseProxyEntry: entry,
rp: rp, rp: rp,
l: logger.With(). l: logger.With().
Str("type", string(entry.Scheme)). Str("type", entry.URL.Scheme).
Str("name", string(entry.Alias)). Str("name", service).
Logger(), Logger(),
} }
return r, nil return r, nil
} }
func (r *HTTPRoute) String() string { func (r *HTTPRoute) String() string {
return string(r.Alias) return r.TargetName()
} }
// Start implements*task.TaskStarter. // Start implements*task.TaskStarter.
@ -85,7 +83,7 @@ func (r *HTTPRoute) Start(providerSubtask *task.Task) E.Error {
switch { switch {
case entry.UseIdleWatcher(r): case entry.UseIdleWatcher(r):
wakerTask := providerSubtask.Parent().Subtask("waker for " + string(r.Alias)) wakerTask := providerSubtask.Parent().Subtask("waker for " + r.TargetName())
waker, err := idlewatcher.NewHTTPWaker(wakerTask, r.ReverseProxyEntry, r.rp) waker, err := idlewatcher.NewHTTPWaker(wakerTask, r.ReverseProxyEntry, r.rp)
if err != nil { if err != nil {
return err return err
@ -96,26 +94,27 @@ func (r *HTTPRoute) Start(providerSubtask *task.Task) E.Error {
if entry.IsDocker(r) { if entry.IsDocker(r) {
client, err := docker.ConnectClient(r.Idlewatcher.DockerHost) client, err := docker.ConnectClient(r.Idlewatcher.DockerHost)
if err == nil { if err == nil {
fallback := monitor.NewHTTPHealthChecker(r.rp.TargetURL, r.HealthCheck) fallback := monitor.NewHTTPHealthChecker(r.rp.TargetURL, r.Raw.HealthCheck)
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Idlewatcher.ContainerID, r.HealthCheck, fallback) r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Idlewatcher.ContainerID, r.Raw.HealthCheck, fallback)
r.task.OnCancel("close docker client", client.Close) r.task.OnCancel("close docker client", client.Close)
} }
} }
if r.HealthMon == nil { if r.HealthMon == nil {
r.HealthMon = monitor.NewHTTPHealthMonitor(r.rp.TargetURL, r.HealthCheck) r.HealthMon = monitor.NewHTTPHealthMonitor(r.rp.TargetURL, r.Raw.HealthCheck)
} }
} }
if r.handler == nil { if r.handler == nil {
pathPatterns := r.Raw.PathPatterns
switch { switch {
case len(r.PathPatterns) == 0: case len(pathPatterns) == 0:
r.handler = r.rp r.handler = r.rp
case len(r.PathPatterns) == 1 && r.PathPatterns[0] == "/": case len(pathPatterns) == 1 && pathPatterns[0] == "/":
r.handler = r.rp r.handler = r.rp
default: default:
mux := gphttp.NewServeMux() mux := gphttp.NewServeMux()
patErrs := E.NewBuilder("invalid path pattern(s)") patErrs := E.NewBuilder("invalid path pattern(s)")
for _, p := range r.PathPatterns { for _, p := range pathPatterns {
patErrs.Add(mux.HandleFunc(p, r.rp.HandlerFunc)) patErrs.Add(mux.HandleFunc(p, r.rp.HandlerFunc))
} }
if err := patErrs.Error(); err != nil { if err := patErrs.Error(); err != nil {
@ -136,9 +135,9 @@ func (r *HTTPRoute) Start(providerSubtask *task.Task) E.Error {
if entry.UseLoadBalance(r) { if entry.UseLoadBalance(r) {
r.addToLoadBalancer() r.addToLoadBalancer()
} else { } else {
routes.SetHTTPRoute(string(r.Alias), r) routes.SetHTTPRoute(r.TargetName(), r)
r.task.OnFinished("remove from route table", func() { r.task.OnFinished("remove from route table", func() {
routes.DeleteHTTPRoute(string(r.Alias)) routes.DeleteHTTPRoute(r.TargetName())
}) })
} }
@ -159,20 +158,21 @@ func (r *HTTPRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (r *HTTPRoute) addToLoadBalancer() { func (r *HTTPRoute) addToLoadBalancer() {
var lb *loadbalancer.LoadBalancer var lb *loadbalancer.LoadBalancer
l, ok := routes.GetHTTPRoute(r.LoadBalance.Link) cfg := r.Raw.LoadBalance
l, ok := routes.GetHTTPRoute(cfg.Link)
var linked *HTTPRoute var linked *HTTPRoute
if ok { if ok {
linked = l.(*HTTPRoute) linked = l.(*HTTPRoute)
lb = linked.loadBalancer lb = linked.loadBalancer
lb.UpdateConfigIfNeeded(r.LoadBalance) lb.UpdateConfigIfNeeded(cfg)
if linked.Raw.Homepage == nil && r.Raw.Homepage != nil { if linked.Raw.Homepage == nil && r.Raw.Homepage != nil {
linked.Raw.Homepage = r.Raw.Homepage linked.Raw.Homepage = r.Raw.Homepage
} }
} else { } else {
lb = loadbalancer.New(r.LoadBalance) lb = loadbalancer.New(cfg)
lbTask := r.task.Parent().Subtask("loadbalancer " + r.LoadBalance.Link) lbTask := r.task.Parent().Subtask("loadbalancer " + cfg.Link)
lbTask.OnCancel("remove lb from routes", func() { lbTask.OnCancel("remove lb from routes", func() {
routes.DeleteHTTPRoute(r.LoadBalance.Link) routes.DeleteHTTPRoute(cfg.Link)
}) })
if err := lb.Start(lbTask); err != nil { if err := lb.Start(lbTask); err != nil {
panic(err) // should always return nil panic(err) // should always return nil
@ -180,18 +180,18 @@ func (r *HTTPRoute) addToLoadBalancer() {
linked = &HTTPRoute{ linked = &HTTPRoute{
ReverseProxyEntry: &entry.ReverseProxyEntry{ ReverseProxyEntry: &entry.ReverseProxyEntry{
Raw: &route.RawEntry{ Raw: &route.RawEntry{
Alias: cfg.Link,
Homepage: r.Raw.Homepage, Homepage: r.Raw.Homepage,
}, },
Alias: route.Alias(lb.Link),
}, },
HealthMon: lb, HealthMon: lb,
loadBalancer: lb, loadBalancer: lb,
handler: lb, handler: lb,
} }
routes.SetHTTPRoute(r.LoadBalance.Link, linked) routes.SetHTTPRoute(cfg.Link, linked)
} }
r.loadBalancer = lb r.loadBalancer = lb
r.server = loadbalance.NewServer(r.task.String(), r.rp.TargetURL, r.LoadBalance.Weight, r.handler, r.HealthMon) r.server = loadbalance.NewServer(r.task.String(), r.rp.TargetURL, r.Raw.LoadBalance.Weight, r.handler, r.HealthMon)
lb.AddServer(r.server) lb.AddServer(r.server)
r.task.OnCancel("remove server from lb", func() { r.task.OnCancel("remove server from lb", func() {
lb.RemoveServer(r.server) lb.RemoveServer(r.server)

View file

@ -325,7 +325,7 @@ func TestStreamDefaultValues(t *testing.T) {
a := ExpectType[*entry.StreamEntry](t, en) a := ExpectType[*entry.StreamEntry](t, en)
ExpectEqual(t, a.Scheme.ListeningScheme, T.Scheme("udp")) ExpectEqual(t, a.Scheme.ListeningScheme, T.Scheme("udp"))
ExpectEqual(t, a.Scheme.ProxyScheme, T.Scheme("udp")) ExpectEqual(t, a.Scheme.ProxyScheme, T.Scheme("udp"))
ExpectEqual(t, a.Host, T.Host(privIP)) ExpectEqual(t, a.URL.Hostname(), privIP)
ExpectEqual(t, a.Port.ListeningPort, 0) ExpectEqual(t, a.Port.ListeningPort, 0)
ExpectEqual(t, a.Port.ProxyPort, T.Port(privPort)) ExpectEqual(t, a.Port.ProxyPort, T.Port(privPort))
}) })
@ -337,7 +337,7 @@ func TestStreamDefaultValues(t *testing.T) {
a := ExpectType[*entry.StreamEntry](t, en) a := ExpectType[*entry.StreamEntry](t, en)
ExpectEqual(t, a.Scheme.ListeningScheme, T.Scheme("udp")) ExpectEqual(t, a.Scheme.ListeningScheme, T.Scheme("udp"))
ExpectEqual(t, a.Scheme.ProxyScheme, T.Scheme("udp")) ExpectEqual(t, a.Scheme.ProxyScheme, T.Scheme("udp"))
ExpectEqual(t, a.Host, testIP) ExpectEqual(t, a.URL.Hostname(), testIP)
ExpectEqual(t, a.Port.ListeningPort, 0) ExpectEqual(t, a.Port.ListeningPort, 0)
ExpectEqual(t, a.Port.ProxyPort, T.Port(pubPort)) ExpectEqual(t, a.Port.ProxyPort, T.Port(pubPort))
}) })

View file

@ -3,7 +3,6 @@ package route
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/docker" "github.com/yusing/go-proxy/internal/docker"
@ -44,7 +43,7 @@ func NewStreamRoute(entry *entry.StreamEntry) (impl, E.Error) {
} }
func (r *StreamRoute) String() string { func (r *StreamRoute) String() string {
return fmt.Sprintf("stream %s", r.Alias) return "stream " + r.TargetName()
} }
// Start implements*task.TaskStarter. // Start implements*task.TaskStarter.
@ -59,7 +58,7 @@ func (r *StreamRoute) Start(providerSubtask *task.Task) E.Error {
switch { switch {
case entry.UseIdleWatcher(r): case entry.UseIdleWatcher(r):
wakerTask := providerSubtask.Parent().Subtask("waker for " + string(r.Alias)) wakerTask := providerSubtask.Parent().Subtask("waker for " + r.TargetName())
waker, err := idlewatcher.NewStreamWaker(wakerTask, r.StreamEntry, r.Stream) waker, err := idlewatcher.NewStreamWaker(wakerTask, r.StreamEntry, r.Stream)
if err != nil { if err != nil {
r.task.Finish(err) r.task.Finish(err)
@ -71,13 +70,13 @@ func (r *StreamRoute) Start(providerSubtask *task.Task) E.Error {
if entry.IsDocker(r) { if entry.IsDocker(r) {
client, err := docker.ConnectClient(r.Idlewatcher.DockerHost) client, err := docker.ConnectClient(r.Idlewatcher.DockerHost)
if err == nil { if err == nil {
fallback := monitor.NewRawHealthChecker(r.TargetURL(), r.HealthCheck) fallback := monitor.NewRawHealthChecker(r.TargetURL(), r.Raw.HealthCheck)
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Idlewatcher.ContainerID, r.HealthCheck, fallback) r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Idlewatcher.ContainerID, r.Raw.HealthCheck, fallback)
r.task.OnCancel("close docker client", client.Close) r.task.OnCancel("close docker client", client.Close)
} }
} }
if r.HealthMon == nil { if r.HealthMon == nil {
r.HealthMon = monitor.NewRawHealthMonitor(r.TargetURL(), r.HealthCheck) r.HealthMon = monitor.NewRawHealthMonitor(r.TargetURL(), r.Raw.HealthCheck)
} }
} }
@ -106,9 +105,9 @@ func (r *StreamRoute) Start(providerSubtask *task.Task) E.Error {
go r.acceptConnections() go r.acceptConnections()
routes.SetStreamRoute(string(r.Alias), r) routes.SetStreamRoute(r.TargetName(), r)
r.task.OnFinished("remove from route table", func() { r.task.OnFinished("remove from route table", func() {
routes.DeleteStreamRoute(string(r.Alias)) routes.DeleteStreamRoute(r.TargetName())
}) })
return nil return nil
} }

View file

@ -45,22 +45,22 @@ func (stream *Stream) Setup() error {
switch stream.Scheme.ListeningScheme { switch stream.Scheme.ListeningScheme {
case "tcp": case "tcp":
stream.targetAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%v", stream.Host, stream.Port.ProxyPort)) stream.targetAddr, err = net.ResolveTCPAddr("tcp", stream.URL.Host)
if err != nil { if err != nil {
return err return err
} }
tcpListener, err := lcfg.Listen(stream.task.Context(), "tcp", fmt.Sprintf(":%v", stream.Port.ListeningPort)) tcpListener, err := lcfg.Listen(stream.task.Context(), "tcp", stream.ListenURL.Host)
if err != nil { if err != nil {
return err return err
} }
stream.Port.ListeningPort = T.Port(tcpListener.Addr().(*net.TCPAddr).Port) stream.Port.ListeningPort = T.Port(tcpListener.Addr().(*net.TCPAddr).Port)
stream.listener = types.NetListener(tcpListener) stream.listener = types.NetListener(tcpListener)
case "udp": case "udp":
stream.targetAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%v", stream.Host, stream.Port.ProxyPort)) stream.targetAddr, err = net.ResolveUDPAddr("udp", stream.URL.Host)
if err != nil { if err != nil {
return err return err
} }
udpListener, err := lcfg.ListenPacket(stream.task.Context(), "udp", fmt.Sprintf(":%v", stream.Port.ListeningPort)) udpListener, err := lcfg.ListenPacket(stream.task.Context(), "udp", stream.ListenURL.Host)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,3 +0,0 @@
package types
type Alias string

View file

@ -1,10 +0,0 @@
package types
type (
Host string
Subdomain = Alias
)
func ValidateHost[String ~string](s String) (Host, error) {
return Host(s), nil
}

View file

@ -33,6 +33,7 @@ type (
LoadBalance *loadbalance.Config `json:"load_balance,omitempty" yaml:"load_balance"` LoadBalance *loadbalance.Config `json:"load_balance,omitempty" yaml:"load_balance"`
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty" yaml:"middlewares"` Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty" yaml:"middlewares"`
Homepage *homepage.Item `json:"homepage,omitempty" yaml:"homepage"` Homepage *homepage.Item `json:"homepage,omitempty" yaml:"homepage"`
// AccessLog *accesslog.Config `json:"access_log,omitempty" yaml:"access_log"`
/* Docker only */ /* Docker only */
Container *docker.Container `json:"container,omitempty" yaml:"-"` Container *docker.Container `json:"container,omitempty" yaml:"-"`

View file

@ -12,7 +12,7 @@ import (
) )
var ( var (
branch = common.GetEnvString("BRANCH", "v0.7") branch = common.GetEnvString("BRANCH", "v0.8")
baseURL = "https://github.com/yusing/go-proxy/raw/" + branch baseURL = "https://github.com/yusing/go-proxy/raw/" + branch
requiredConfigs = []Config{ requiredConfigs = []Config{
{common.ConfigBasePath, true, false, ""}, {common.ConfigBasePath, true, false, ""},