package proxy import ( "fmt" "net/url" "time" D "github.com/yusing/go-proxy/internal/docker" E "github.com/yusing/go-proxy/internal/error" "github.com/yusing/go-proxy/internal/net/http/loadbalancer" net "github.com/yusing/go-proxy/internal/net/types" T "github.com/yusing/go-proxy/internal/proxy/fields" "github.com/yusing/go-proxy/internal/types" "github.com/yusing/go-proxy/internal/watcher/health" ) type ( ReverseProxyEntry struct { // real model after validation Alias T.Alias `json:"alias"` Scheme T.Scheme `json:"scheme"` URL net.URL `json:"url"` NoTLSVerify bool `json:"no_tls_verify,omitempty"` PathPatterns T.PathPatterns `json:"path_patterns"` HealthCheck *health.HealthCheckConfig `json:"healthcheck"` LoadBalance *loadbalancer.Config `json:"load_balance,omitempty"` Middlewares D.NestedLabelMap `json:"middlewares,omitempty"` /* Docker only */ IdleTimeout time.Duration `json:"idle_timeout"` WakeTimeout time.Duration `json:"wake_timeout"` StopMethod T.StopMethod `json:"stop_method"` StopTimeout int `json:"stop_timeout"` StopSignal T.Signal `json:"stop_signal,omitempty"` DockerHost string `json:"docker_host"` ContainerName string `json:"container_name"` ContainerID string `json:"container_id"` ContainerRunning bool `json:"container_running"` } StreamEntry struct { Alias T.Alias `json:"alias"` Scheme T.StreamScheme `json:"scheme"` Host T.Host `json:"host"` Port T.StreamPort `json:"port"` Healthcheck *health.HealthCheckConfig `json:"healthcheck"` } ) func (rp *ReverseProxyEntry) UseIdleWatcher() bool { return rp.IdleTimeout > 0 && rp.IsDocker() } func (rp *ReverseProxyEntry) UseLoadBalance() bool { return rp.LoadBalance.Link != "" } func (rp *ReverseProxyEntry) IsDocker() bool { return rp.DockerHost != "" } func (rp *ReverseProxyEntry) IsZeroPort() bool { return rp.URL.Port() == "0" } func (rp *ReverseProxyEntry) ShouldNotServe() bool { return rp.IsZeroPort() && !rp.UseIdleWatcher() } func ValidateEntry(m *types.RawEntry) (any, E.NestedError) { m.FillMissingFields() scheme, err := T.NewScheme(m.Scheme) if err != nil { return nil, err } var entry any e := E.NewBuilder("error validating entry") if scheme.IsStream() { entry = validateStreamEntry(m, e) } else { entry = validateRPEntry(m, scheme, e) } if err := e.Build(); err != nil { return nil, err } return entry, nil } func validateRPEntry(m *types.RawEntry, s T.Scheme, b E.Builder) *ReverseProxyEntry { var stopTimeOut time.Duration host, err := T.ValidateHost(m.Host) b.Add(err) port, err := T.ValidatePort(m.Port) b.Add(err) pathPatterns, err := T.ValidatePathPatterns(m.PathPatterns) b.Add(err) url, err := E.Check(url.Parse(fmt.Sprintf("%s://%s:%d", s, host, port))) b.Add(err) idleTimeout, err := T.ValidateDurationPostitive(m.IdleTimeout) b.Add(err) wakeTimeout, err := T.ValidateDurationPostitive(m.WakeTimeout) b.Add(err) stopMethod, err := T.ValidateStopMethod(m.StopMethod) b.Add(err) if stopMethod == T.StopMethodStop { stopTimeOut, err = T.ValidateDurationPostitive(m.StopTimeout) b.Add(err) } stopSignal, err := T.ValidateSignal(m.StopSignal) b.Add(err) if err != nil { return nil } return &ReverseProxyEntry{ Alias: T.NewAlias(m.Alias), Scheme: s, URL: net.NewURL(url), NoTLSVerify: m.NoTLSVerify, PathPatterns: pathPatterns, HealthCheck: &m.HealthCheck, LoadBalance: &m.LoadBalance, Middlewares: m.Middlewares, IdleTimeout: idleTimeout, WakeTimeout: wakeTimeout, StopMethod: stopMethod, StopTimeout: int(stopTimeOut.Seconds()), // docker api takes integer seconds for timeout argument StopSignal: stopSignal, DockerHost: m.DockerHost, ContainerName: m.ContainerName, ContainerID: m.ContainerID, ContainerRunning: m.Running, } } func validateStreamEntry(m *types.RawEntry, b E.Builder) *StreamEntry { host, err := T.ValidateHost(m.Host) b.Add(err) port, err := T.ValidateStreamPort(m.Port) b.Add(err) scheme, err := T.ValidateStreamScheme(m.Scheme) b.Add(err) if b.HasError() { return nil } return &StreamEntry{ Alias: T.NewAlias(m.Alias), Scheme: *scheme, Host: host, Port: port, Healthcheck: &m.HealthCheck, } }