modules reorganized and code refactor

This commit is contained in:
yusing 2024-11-25 01:40:12 +08:00
parent f3b21e6bd9
commit d723403b6b
46 changed files with 437 additions and 331 deletions

View file

@ -14,12 +14,12 @@ import (
"github.com/yusing/go-proxy/internal/api/v1/query"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/config"
"github.com/yusing/go-proxy/internal/entrypoint"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/metrics"
"github.com/yusing/go-proxy/internal/net/http/middleware"
R "github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/server"
"github.com/yusing/go-proxy/internal/net/http/server"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/pkg"
)
@ -136,7 +136,7 @@ func main() {
CertProvider: autocert,
HTTPAddr: common.ProxyHTTPAddr,
HTTPSAddr: common.ProxyHTTPSAddr,
Handler: http.HandlerFunc(R.ProxyHandler),
Handler: http.HandlerFunc(entrypoint.Handler),
RedirectToHTTPS: config.Value().RedirectToHTTPS,
})
server.StartServer(server.Options{

View file

@ -10,10 +10,10 @@ import (
"github.com/yusing/go-proxy/internal/autocert"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/config/types"
"github.com/yusing/go-proxy/internal/entrypoint"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/notif"
"github.com/yusing/go-proxy/internal/route"
proxy "github.com/yusing/go-proxy/internal/route/provider"
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
@ -183,7 +183,7 @@ func (cfg *Config) load() E.Error {
model.MatchDomains[i] = "." + domain
}
}
route.SetFindMuxDomains(model.MatchDomains)
entrypoint.SetFindRouteDomains(model.MatchDomains)
return errs.Error()
}

View file

@ -6,14 +6,16 @@ import (
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/homepage"
"github.com/yusing/go-proxy/internal/proxy/entry"
"github.com/yusing/go-proxy/internal/route"
route "github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/route/entry"
proxy "github.com/yusing/go-proxy/internal/route/provider"
"github.com/yusing/go-proxy/internal/route/routes"
"github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
func DumpEntries() map[string]*entry.RawEntry {
entries := make(map[string]*entry.RawEntry)
func DumpEntries() map[string]*types.RawEntry {
entries := make(map[string]*types.RawEntry)
instance.providers.RangeAll(func(_ string, p *proxy.Provider) {
p.RangeRoutes(func(alias string, r *route.Route) {
entries[alias] = r.Entry
@ -43,8 +45,8 @@ func HomepageConfig() homepage.Config {
}
hpCfg := homepage.NewHomePageConfig()
route.GetReverseProxies().RangeAll(func(alias string, r *route.HTTPRoute) {
en := r.Raw
routes.GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
en := r.RawEntry()
item := en.Homepage
if item == nil {
item = new(homepage.Item)
@ -113,23 +115,23 @@ func HomepageConfig() homepage.Config {
}
func RoutesByAlias(typeFilter ...route.RouteType) map[string]any {
routes := make(map[string]any)
rts := make(map[string]any)
if len(typeFilter) == 0 || typeFilter[0] == "" {
typeFilter = []route.RouteType{route.RouteTypeReverseProxy, route.RouteTypeStream}
}
for _, t := range typeFilter {
switch t {
case route.RouteTypeReverseProxy:
route.GetReverseProxies().RangeAll(func(alias string, r *route.HTTPRoute) {
routes[alias] = r
routes.GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
rts[alias] = r
})
case route.RouteTypeStream:
route.GetStreamProxies().RangeAll(func(alias string, r *route.StreamRoute) {
routes[alias] = r
routes.GetStreamRoutes().RangeAll(func(alias string, r types.StreamRoute) {
rts[alias] = r
})
}
}
return routes
return rts
}
func Statistics() map[string]any {

View file

@ -6,28 +6,31 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/yusing/go-proxy/internal/common"
. "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
"github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/metrics"
gphttp "github.com/yusing/go-proxy/internal/net/http"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/proxy/entry"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/watcher/health"
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
)
type waker struct {
_ U.NoCopy
type (
Waker = types.Waker
waker struct {
_ U.NoCopy
rp *gphttp.ReverseProxy
stream net.Stream
hc health.HealthChecker
metric *metrics.Gauge
rp *gphttp.ReverseProxy
stream net.Stream
hc health.HealthChecker
metric *metrics.Gauge
ready atomic.Bool
}
ready atomic.Bool
}
)
const (
idleWakerCheckInterval = 100 * time.Millisecond
@ -36,7 +39,7 @@ const (
// TODO: support stream
func newWaker(providerSubTask task.Task, entry entry.Entry, rp *gphttp.ReverseProxy, stream net.Stream) (Waker, E.Error) {
func newWaker(providerSubTask task.Task, entry route.Entry, rp *gphttp.ReverseProxy, stream net.Stream) (Waker, E.Error) {
hcCfg := entry.HealthCheckConfig()
hcCfg.Timeout = idleWakerCheckTimeout
@ -69,11 +72,11 @@ func newWaker(providerSubTask task.Task, entry entry.Entry, rp *gphttp.ReversePr
}
// lifetime should follow route provider.
func NewHTTPWaker(providerSubTask task.Task, entry entry.Entry, rp *gphttp.ReverseProxy) (Waker, E.Error) {
func NewHTTPWaker(providerSubTask task.Task, entry route.Entry, rp *gphttp.ReverseProxy) (Waker, E.Error) {
return newWaker(providerSubTask, entry, rp, nil)
}
func NewStreamWaker(providerSubTask task.Task, entry entry.Entry, stream net.Stream) (Waker, E.Error) {
func NewStreamWaker(providerSubTask task.Task, entry route.Entry, stream net.Stream) (Waker, E.Error) {
return newWaker(providerSubTask, entry, nil, stream)
}

View file

@ -12,7 +12,7 @@ import (
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/proxy/entry"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"
@ -49,7 +49,7 @@ var (
const dockerReqTimeout = 3 * time.Second
func registerWatcher(providerSubtask task.Task, entry entry.Entry, waker *waker) (*Watcher, error) {
func registerWatcher(providerSubtask task.Task, entry route.Entry, waker *waker) (*Watcher, error) {
cfg := entry.IdlewatcherConfig()
if cfg.IdleTimeout == 0 {

View file

@ -0,0 +1,93 @@
package entrypoint
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/net/http/middleware/errorpage"
"github.com/yusing/go-proxy/internal/route/routes"
route "github.com/yusing/go-proxy/internal/route/types"
)
var findRouteFunc = findRouteAnyDomain
func SetFindRouteDomains(domains []string) {
if len(domains) == 0 {
findRouteFunc = findRouteAnyDomain
} else {
findRouteFunc = findRouteByDomains(domains)
}
}
func Handler(w http.ResponseWriter, r *http.Request) {
mux, err := findRouteFunc(r.Host)
if err == nil {
mux.ServeHTTP(w, r)
return
}
// Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway?
// On nginx, when route for domain does not exist, it returns StatusBadGateway.
// Then scraper / scanners will know the subdomain is invalid.
// With StatusNotFound, they won't know whether it's the path, or the subdomain that is invalid.
if !middleware.ServeStaticErrorPageFile(w, r) {
logger.Err(err).Str("method", r.Method).Str("url", r.URL.String()).Msg("request")
errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound)
if ok {
w.WriteHeader(http.StatusNotFound)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if _, err := w.Write(errorPage); err != nil {
logger.Err(err).Msg("failed to write error page")
}
} else {
http.Error(w, err.Error(), http.StatusNotFound)
}
}
}
func findRouteAnyDomain(host string) (route.HTTPRoute, error) {
hostSplit := strings.Split(host, ".")
n := len(hostSplit)
switch {
case n == 3:
host = hostSplit[0]
case n > 3:
var builder strings.Builder
builder.Grow(2*n - 3)
builder.WriteString(hostSplit[0])
for _, part := range hostSplit[:n-2] {
builder.WriteRune('.')
builder.WriteString(part)
}
host = builder.String()
default:
return nil, errors.New("missing subdomain in url")
}
if r, ok := routes.GetHTTPRoute(host); ok {
return r, nil
}
return nil, fmt.Errorf("no such route: %s", host)
}
func findRouteByDomains(domains []string) func(host string) (route.HTTPRoute, error) {
return func(host string) (route.HTTPRoute, error) {
var subdomain string
for _, domain := range domains {
if strings.HasSuffix(host, domain) {
subdomain = strings.TrimSuffix(host, domain)
break
}
}
if subdomain != "" { // matched
if r, ok := routes.GetHTTPRoute(subdomain); ok {
return r, nil
}
return nil, fmt.Errorf("no such route: %s", subdomain)
}
return nil, fmt.Errorf("%s does not match any base domain", host)
}
}

View file

@ -0,0 +1,7 @@
package entrypoint
import (
"github.com/yusing/go-proxy/internal/logging"
)
var logger = logging.With().Str("module", "entrypoint").Logger()

View file

@ -14,7 +14,7 @@ type ipHash struct {
*LoadBalancer
realIP *middleware.Middleware
pool servers
pool Servers
mu sync.Mutex
}
@ -26,7 +26,7 @@ func (lb *LoadBalancer) newIPHash() impl {
var err E.Error
impl.realIP, err = middleware.NewRealIP(lb.Options)
if err != nil {
E.LogError("invalid real_ip options, ignoring", err, &impl.Logger)
E.LogError("invalid real_ip options, ignoring", err, &impl.l)
}
return impl
}
@ -60,7 +60,7 @@ func (impl *ipHash) OnRemoveServer(srv *Server) {
}
}
func (impl *ipHash) ServeHTTP(_ servers, rw http.ResponseWriter, r *http.Request) {
func (impl *ipHash) ServeHTTP(_ Servers, rw http.ResponseWriter, r *http.Request) {
if impl.realIP != nil {
impl.realIP.ModifyRequest(impl.serveHTTP, rw, r)
} else {
@ -72,7 +72,7 @@ func (impl *ipHash) serveHTTP(rw http.ResponseWriter, r *http.Request) {
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(rw, "Internal error", http.StatusInternalServerError)
impl.Err(err).Msg("invalid remote address " + r.RemoteAddr)
impl.l.Err(err).Msg("invalid remote address " + r.RemoteAddr)
return
}
idx := hashIP(ip) % uint32(len(impl.pool))

View file

@ -27,18 +27,18 @@ func (impl *leastConn) OnRemoveServer(srv *Server) {
impl.nConn.Delete(srv)
}
func (impl *leastConn) ServeHTTP(srvs servers, rw http.ResponseWriter, r *http.Request) {
func (impl *leastConn) ServeHTTP(srvs Servers, rw http.ResponseWriter, r *http.Request) {
srv := srvs[0]
minConn, ok := impl.nConn.Load(srv)
if !ok {
impl.Error().Msgf("[BUG] server %s not found", srv.Name)
impl.l.Error().Msgf("[BUG] server %s not found", srv.Name)
http.Error(rw, "Internal error", http.StatusInternalServerError)
}
for i := 1; i < len(srvs); i++ {
nConn, ok := impl.nConn.Load(srvs[i])
if !ok {
impl.Error().Msgf("[BUG] server %s not found", srv.Name)
impl.l.Error().Msgf("[BUG] server %s not found", srv.Name)
http.Error(rw, "Internal error", http.StatusInternalServerError)
}
if nConn.Load() < minConn.Load() {

View file

@ -7,9 +7,8 @@ import (
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/watcher/health"
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
@ -19,19 +18,12 @@ import (
// TODO: support weighted mode.
type (
impl interface {
ServeHTTP(srvs servers, rw http.ResponseWriter, r *http.Request)
ServeHTTP(srvs Servers, rw http.ResponseWriter, r *http.Request)
OnAddServer(srv *Server)
OnRemoveServer(srv *Server)
}
Config struct {
Link string `json:"link" yaml:"link"`
Mode Mode `json:"mode" yaml:"mode"`
Weight weightType `json:"weight" yaml:"weight"`
Options middleware.OptionsRaw `json:"options,omitempty" yaml:"options,omitempty"`
}
LoadBalancer struct {
zerolog.Logger
LoadBalancer struct {
impl
*Config
@ -40,20 +32,20 @@ type (
pool Pool
poolMu sync.Mutex
sumWeight weightType
sumWeight Weight
startTime time.Time
}
weightType uint16
l zerolog.Logger
}
)
const maxWeight weightType = 100
const maxWeight Weight = 100
func New(cfg *Config) *LoadBalancer {
lb := &LoadBalancer{
Logger: logger.With().Str("name", cfg.Link).Logger(),
Config: new(Config),
pool: newPool(),
pool: types.NewServerPool(),
l: logger.With().Str("name", cfg.Link).Logger(),
}
lb.UpdateConfigIfNeeded(cfg)
return lb
@ -81,11 +73,11 @@ func (lb *LoadBalancer) Finish(reason any) {
func (lb *LoadBalancer) updateImpl() {
switch lb.Mode {
case Unset, RoundRobin:
case types.ModeUnset, types.ModeRoundRobin:
lb.impl = lb.newRoundRobin()
case LeastConn:
case types.ModeLeastConn:
lb.impl = lb.newLeastConn()
case IPHash:
case types.ModeIPHash:
lb.impl = lb.newIPHash()
default: // should happen in test only
lb.impl = lb.newRoundRobin()
@ -102,10 +94,10 @@ func (lb *LoadBalancer) UpdateConfigIfNeeded(cfg *Config) {
lb.Link = cfg.Link
if lb.Mode == Unset && cfg.Mode != Unset {
if lb.Mode == types.ModeUnset && cfg.Mode != types.ModeUnset {
lb.Mode = cfg.Mode
if !lb.Mode.ValidateUpdate() {
lb.Error().Msgf("invalid mode %q, fallback to %q", cfg.Mode, lb.Mode)
lb.l.Error().Msgf("invalid mode %q, fallback to %q", cfg.Mode, lb.Mode)
}
lb.updateImpl()
}
@ -135,7 +127,7 @@ func (lb *LoadBalancer) AddServer(srv *Server) {
lb.rebalance()
lb.impl.OnAddServer(srv)
lb.Debug().
lb.l.Debug().
Str("action", "add").
Str("server", srv.Name).
Msgf("%d servers available", lb.pool.Size())
@ -155,7 +147,7 @@ func (lb *LoadBalancer) RemoveServer(srv *Server) {
lb.rebalance()
lb.impl.OnRemoveServer(srv)
lb.Debug().
lb.l.Debug().
Str("action", "remove").
Str("server", srv.Name).
Msgf("%d servers left", lb.pool.Size())
@ -174,8 +166,8 @@ func (lb *LoadBalancer) rebalance() {
return
}
if lb.sumWeight == 0 { // distribute evenly
weightEach := maxWeight / weightType(lb.pool.Size())
remainder := maxWeight % weightType(lb.pool.Size())
weightEach := maxWeight / Weight(lb.pool.Size())
remainder := maxWeight % Weight(lb.pool.Size())
lb.pool.RangeAll(func(_ string, s *Server) {
s.Weight = weightEach
lb.sumWeight += weightEach
@ -192,7 +184,7 @@ func (lb *LoadBalancer) rebalance() {
lb.sumWeight = 0
lb.pool.RangeAll(func(_ string, s *Server) {
s.Weight = weightType(float64(s.Weight) * scaleFactor)
s.Weight = Weight(float64(s.Weight) * scaleFactor)
lb.sumWeight += s.Weight
})
@ -226,13 +218,7 @@ func (lb *LoadBalancer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if r.Header.Get(common.HeaderCheckRedirect) != "" {
// wake all servers
for _, srv := range srvs {
// wake only if server implements Waker
waker, ok := srv.handler.(idlewatcher.Waker)
if ok {
if err := waker.Wake(); err != nil {
lb.Err(err).Msgf("failed to wake server %s", srv.Name)
}
}
srv.TryWake()
}
}
lb.impl.ServeHTTP(srvs, rw, r)
@ -246,7 +232,7 @@ func (lb *LoadBalancer) Uptime() time.Duration {
func (lb *LoadBalancer) MarshalJSON() ([]byte, error) {
extra := make(map[string]any)
lb.pool.RangeAll(func(k string, v *Server) {
extra[v.Name] = v.healthMon
extra[v.Name] = v.HealthMonitor()
})
return (&monitor.JSONRepresentation{

View file

@ -3,13 +3,14 @@ package loadbalancer
import (
"testing"
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
. "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestRebalance(t *testing.T) {
t.Parallel()
t.Run("zero", func(t *testing.T) {
lb := New(new(Config))
lb := New(new(loadbalance.Config))
for range 10 {
lb.AddServer(&Server{})
}
@ -17,25 +18,25 @@ func TestRebalance(t *testing.T) {
ExpectEqual(t, lb.sumWeight, maxWeight)
})
t.Run("less", func(t *testing.T) {
lb := New(new(Config))
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .3)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
lb := New(new(loadbalance.Config))
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .3)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
lb.rebalance()
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
ExpectEqual(t, lb.sumWeight, maxWeight)
})
t.Run("more", func(t *testing.T) {
lb := New(new(Config))
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .3)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .4)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .3)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
lb := New(new(loadbalance.Config))
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .3)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .4)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .3)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
lb.rebalance()
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
ExpectEqual(t, lb.sumWeight, maxWeight)

View file

@ -1,32 +0,0 @@
package loadbalancer
import (
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type Mode string
const (
Unset Mode = ""
RoundRobin Mode = "roundrobin"
LeastConn Mode = "leastconn"
IPHash Mode = "iphash"
)
func (mode *Mode) ValidateUpdate() bool {
switch strutils.ToLowerNoSnake(string(*mode)) {
case "":
return true
case string(RoundRobin):
*mode = RoundRobin
return true
case string(LeastConn):
*mode = LeastConn
return true
case string(IPHash):
*mode = IPHash
return true
}
*mode = RoundRobin
return false
}

View file

@ -13,7 +13,7 @@ func (*LoadBalancer) newRoundRobin() impl { return &roundRobin{} }
func (lb *roundRobin) OnAddServer(srv *Server) {}
func (lb *roundRobin) OnRemoveServer(srv *Server) {}
func (lb *roundRobin) ServeHTTP(srvs servers, rw http.ResponseWriter, r *http.Request) {
func (lb *roundRobin) ServeHTTP(srvs Servers, rw http.ResponseWriter, r *http.Request) {
index := lb.index.Add(1) % uint32(len(srvs))
srvs[index].ServeHTTP(rw, r)
if lb.index.Load() >= 2*uint32(len(srvs)) {

View file

@ -0,0 +1,14 @@
package loadbalancer
import (
"github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
)
type (
Server = types.Server
Servers = types.Servers
Pool = types.Pool
Weight = types.Weight
Config = types.Config
Mode = types.Mode
)

View file

@ -0,0 +1,8 @@
package types
type Config struct {
Link string `json:"link" yaml:"link"`
Mode Mode `json:"mode" yaml:"mode"`
Weight Weight `json:"weight" yaml:"weight"`
Options map[string]any `json:"options,omitempty" yaml:"options,omitempty"`
}

View file

@ -0,0 +1,32 @@
package types
import (
"github.com/yusing/go-proxy/internal/utils/strutils"
)
type Mode string
const (
ModeUnset Mode = ""
ModeRoundRobin Mode = "roundrobin"
ModeLeastConn Mode = "leastconn"
ModeIPHash Mode = "iphash"
)
func (mode *Mode) ValidateUpdate() bool {
switch strutils.ToLowerNoSnake(string(*mode)) {
case "":
return true
case string(ModeRoundRobin):
*mode = ModeRoundRobin
return true
case string(ModeLeastConn):
*mode = ModeLeastConn
return true
case string(ModeIPHash):
*mode = ModeIPHash
return true
}
*mode = ModeRoundRobin
return false
}

View file

@ -1,9 +1,10 @@
package loadbalancer
package types
import (
"net/http"
"time"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
"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"
@ -16,18 +17,18 @@ type (
Name string
URL types.URL
Weight weightType
Weight Weight
handler http.Handler
healthMon health.HealthMonitor
}
servers = []*Server
Servers = []*Server
Pool = F.Map[string, *Server]
)
var newPool = F.NewMap[Pool]
var NewServerPool = F.NewMap[Pool]
func NewServer(name string, url types.URL, weight weightType, handler http.Handler, healthMon health.HealthMonitor) *Server {
func NewServer(name string, url types.URL, weight Weight, handler http.Handler, healthMon health.HealthMonitor) *Server {
srv := &Server{
Name: name,
URL: url,
@ -53,3 +54,17 @@ func (srv *Server) Status() health.Status {
func (srv *Server) Uptime() time.Duration {
return srv.healthMon.Uptime()
}
func (srv *Server) TryWake() error {
waker, ok := srv.handler.(idlewatcher.Waker)
if ok {
if err := waker.Wake(); err != nil {
return err
}
}
return nil
}
func (srv *Server) HealthMonitor() health.HealthMonitor {
return srv.healthMon
}

View file

@ -0,0 +1,3 @@
package types
type Weight uint16

View file

@ -1,25 +1,14 @@
package entry
import (
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
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/watcher/health"
route "github.com/yusing/go-proxy/internal/route/types"
)
type Entry interface {
TargetName() string
TargetURL() net.URL
RawEntry() *RawEntry
LoadBalanceConfig() *loadbalancer.Config
HealthCheckConfig() *health.HealthCheckConfig
IdlewatcherConfig() *idlewatcher.Config
}
type Entry = route.Entry
func ValidateEntry(m *RawEntry) (Entry, E.Error) {
scheme, err := T.NewScheme(m.Scheme)
func ValidateEntry(m *route.RawEntry) (Entry, E.Error) {
scheme, err := route.NewScheme(m.Scheme)
if err != nil {
return nil, E.From(err)
}

View file

@ -7,22 +7,22 @@ import (
"github.com/yusing/go-proxy/internal/docker"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/proxy/fields"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/watcher/health"
)
type ReverseProxyEntry struct { // real model after validation
Raw *RawEntry `json:"raw"`
Raw *route.RawEntry `json:"raw"`
Alias fields.Alias `json:"alias"`
Scheme fields.Scheme `json:"scheme"`
Alias route.Alias `json:"alias"`
Scheme route.Scheme `json:"scheme"`
URL net.URL `json:"url"`
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
PathPatterns fields.PathPatterns `json:"path_patterns,omitempty"`
PathPatterns route.PathPatterns `json:"path_patterns,omitempty"`
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
LoadBalance *loadbalancer.Config `json:"load_balance,omitempty"`
LoadBalance *loadbalance.Config `json:"load_balance,omitempty"`
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty"`
/* Docker only */
@ -37,11 +37,11 @@ func (rp *ReverseProxyEntry) TargetURL() net.URL {
return rp.URL
}
func (rp *ReverseProxyEntry) RawEntry() *RawEntry {
func (rp *ReverseProxyEntry) RawEntry() *route.RawEntry {
return rp.Raw
}
func (rp *ReverseProxyEntry) LoadBalanceConfig() *loadbalancer.Config {
func (rp *ReverseProxyEntry) LoadBalanceConfig() *loadbalance.Config {
return rp.LoadBalance
}
@ -53,7 +53,7 @@ func (rp *ReverseProxyEntry) IdlewatcherConfig() *idlewatcher.Config {
return rp.Idlewatcher
}
func validateRPEntry(m *RawEntry, s fields.Scheme, errs *E.Builder) *ReverseProxyEntry {
func validateRPEntry(m *route.RawEntry, s route.Scheme, errs *E.Builder) *ReverseProxyEntry {
cont := m.Container
if cont == nil {
cont = docker.DummyContainer
@ -64,9 +64,9 @@ func validateRPEntry(m *RawEntry, s fields.Scheme, errs *E.Builder) *ReverseProx
lb = nil
}
host := E.Collect(errs, fields.ValidateHost, m.Host)
port := E.Collect(errs, fields.ValidatePort, m.Port)
pathPats := E.Collect(errs, fields.ValidatePathPatterns, m.PathPatterns)
host := E.Collect(errs, route.ValidateHost, m.Host)
port := E.Collect(errs, route.ValidatePort, m.Port)
pathPats := E.Collect(errs, route.ValidatePathPatterns, m.PathPatterns)
url := E.Collect(errs, url.Parse, fmt.Sprintf("%s://%s:%d", s, host, port))
iwCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
@ -76,7 +76,7 @@ func validateRPEntry(m *RawEntry, s fields.Scheme, errs *E.Builder) *ReverseProx
return &ReverseProxyEntry{
Raw: m,
Alias: fields.Alias(m.Alias),
Alias: route.Alias(m.Alias),
Scheme: s,
URL: net.NewURL(url),
NoTLSVerify: m.NoTLSVerify,

View file

@ -6,20 +6,20 @@ import (
"github.com/yusing/go-proxy/internal/docker"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/proxy/fields"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/watcher/health"
)
type StreamEntry struct {
Raw *RawEntry `json:"raw"`
Raw *route.RawEntry `json:"raw"`
Alias fields.Alias `json:"alias"`
Scheme fields.StreamScheme `json:"scheme"`
Alias route.Alias `json:"alias"`
Scheme route.StreamScheme `json:"scheme"`
URL net.URL `json:"url"`
Host fields.Host `json:"host,omitempty"`
Port fields.StreamPort `json:"port,omitempty"`
Host route.Host `json:"host,omitempty"`
Port route.StreamPort `json:"port,omitempty"`
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
/* Docker only */
@ -34,11 +34,11 @@ func (s *StreamEntry) TargetURL() net.URL {
return s.URL
}
func (s *StreamEntry) RawEntry() *RawEntry {
func (s *StreamEntry) RawEntry() *route.RawEntry {
return s.Raw
}
func (s *StreamEntry) LoadBalanceConfig() *loadbalancer.Config {
func (s *StreamEntry) LoadBalanceConfig() *loadbalance.Config {
// TODO: support stream load balance
return nil
}
@ -51,15 +51,15 @@ func (s *StreamEntry) IdlewatcherConfig() *idlewatcher.Config {
return s.Idlewatcher
}
func validateStreamEntry(m *RawEntry, errs *E.Builder) *StreamEntry {
func validateStreamEntry(m *route.RawEntry, errs *E.Builder) *StreamEntry {
cont := m.Container
if cont == nil {
cont = docker.DummyContainer
}
host := E.Collect(errs, fields.ValidateHost, m.Host)
port := E.Collect(errs, fields.ValidateStreamPort, m.Port)
scheme := E.Collect(errs, fields.ValidateStreamScheme, m.Scheme)
host := E.Collect(errs, route.ValidateHost, m.Host)
port := E.Collect(errs, route.ValidateStreamPort, m.Port)
scheme := E.Collect(errs, route.ValidateStreamScheme, m.Scheme)
url := E.Collect(errs, net.ParseURL, fmt.Sprintf("%s://%s:%d", scheme.ListeningScheme, host, port.ProxyPort))
idleWatcherCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
@ -69,7 +69,7 @@ func validateStreamEntry(m *RawEntry, errs *E.Builder) *StreamEntry {
return &StreamEntry{
Raw: m,
Alias: fields.Alias(m.Alias),
Alias: route.Alias(m.Alias),
Scheme: *scheme,
URL: url,
Host: host,

View file

@ -1,10 +1,7 @@
package route
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
@ -12,12 +9,12 @@ import (
E "github.com/yusing/go-proxy/internal/error"
gphttp "github.com/yusing/go-proxy/internal/net/http"
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/net/http/middleware/errorpage"
"github.com/yusing/go-proxy/internal/proxy/entry"
PT "github.com/yusing/go-proxy/internal/proxy/fields"
"github.com/yusing/go-proxy/internal/route/entry"
"github.com/yusing/go-proxy/internal/route/routes"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
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/monitor"
)
@ -38,27 +35,10 @@ type (
l zerolog.Logger
}
SubdomainKey = PT.Alias
SubdomainKey = route.Alias
)
var (
findMuxFunc = findMuxAnyDomain
httpRoutes = F.NewMapOf[string, *HTTPRoute]()
// globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
)
func GetReverseProxies() F.Map[string, *HTTPRoute] {
return httpRoutes
}
func SetFindMuxDomains(domains []string) {
if len(domains) == 0 {
findMuxFunc = findMuxAnyDomain
} else {
findMuxFunc = findMuxByDomains(domains)
}
}
// var globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) {
var trans *http.Transport
@ -141,9 +121,9 @@ func (r *HTTPRoute) Start(providerSubtask task.Task) E.Error {
if entry.UseLoadBalance(r) {
r.addToLoadBalancer()
} else {
httpRoutes.Store(string(r.Alias), r)
routes.SetHTTPRoute(string(r.Alias), r)
r.task.OnFinished("remove from route table", func() {
httpRoutes.Delete(string(r.Alias))
routes.DeleteHTTPRoute(string(r.Alias))
})
}
@ -164,7 +144,8 @@ func (r *HTTPRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (r *HTTPRoute) addToLoadBalancer() {
var lb *loadbalancer.LoadBalancer
linked, ok := httpRoutes.Load(r.LoadBalance.Link)
l, ok := routes.GetHTTPRoute(r.LoadBalance.Link)
linked := l.(*HTTPRoute)
if ok {
lb = linked.loadBalancer
lb.UpdateConfigIfNeeded(r.LoadBalance)
@ -175,96 +156,26 @@ func (r *HTTPRoute) addToLoadBalancer() {
lb = loadbalancer.New(r.LoadBalance)
lbTask := r.task.Parent().Subtask("loadbalancer " + r.LoadBalance.Link)
lbTask.OnCancel("remove lb from routes", func() {
httpRoutes.Delete(r.LoadBalance.Link)
routes.DeleteHTTPRoute(r.LoadBalance.Link)
})
lb.Start(lbTask)
linked = &HTTPRoute{
ReverseProxyEntry: &entry.ReverseProxyEntry{
Raw: &entry.RawEntry{
Raw: &route.RawEntry{
Homepage: r.Raw.Homepage,
},
Alias: PT.Alias(lb.Link),
Alias: route.Alias(lb.Link),
},
HealthMon: lb,
loadBalancer: lb,
handler: lb,
}
httpRoutes.Store(r.LoadBalance.Link, linked)
routes.SetHTTPRoute(r.LoadBalance.Link, linked)
}
r.loadBalancer = lb
r.server = loadbalancer.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.LoadBalance.Weight, r.handler, r.HealthMon)
lb.AddServer(r.server)
r.task.OnCancel("remove server from lb", func() {
lb.RemoveServer(r.server)
})
}
func ProxyHandler(w http.ResponseWriter, r *http.Request) {
mux, err := findMuxFunc(r.Host)
if err == nil {
mux.ServeHTTP(w, r)
return
}
// Why use StatusNotFound instead of StatusBadRequest or StatusBadGateway?
// On nginx, when route for domain does not exist, it returns StatusBadGateway.
// Then scraper / scanners will know the subdomain is invalid.
// With StatusNotFound, they won't know whether it's the path, or the subdomain that is invalid.
if !middleware.ServeStaticErrorPageFile(w, r) {
logger.Err(err).Str("method", r.Method).Str("url", r.URL.String()).Msg("request")
errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound)
if ok {
w.WriteHeader(http.StatusNotFound)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if _, err := w.Write(errorPage); err != nil {
logger.Err(err).Msg("failed to write error page")
}
} else {
http.Error(w, err.Error(), http.StatusNotFound)
}
}
}
func findMuxAnyDomain(host string) (http.Handler, error) {
hostSplit := strings.Split(host, ".")
n := len(hostSplit)
switch {
case n == 3:
host = hostSplit[0]
case n > 3:
var builder strings.Builder
builder.Grow(2*n - 3)
builder.WriteString(hostSplit[0])
for _, part := range hostSplit[:n-2] {
builder.WriteRune('.')
builder.WriteString(part)
}
host = builder.String()
default:
return nil, errors.New("missing subdomain in url")
}
if r, ok := httpRoutes.Load(host); ok {
return r.handler, nil
}
return nil, fmt.Errorf("no such route: %s", host)
}
func findMuxByDomains(domains []string) func(host string) (http.Handler, error) {
return func(host string) (http.Handler, error) {
var subdomain string
for _, domain := range domains {
if strings.HasSuffix(host, domain) {
subdomain = strings.TrimSuffix(host, domain)
break
}
}
if subdomain != "" { // matched
if r, ok := httpRoutes.Load(subdomain); ok {
return r.handler, nil
}
return nil, fmt.Errorf("no such route: %s", subdomain)
}
return nil, fmt.Errorf("%s does not match any base domain", host)
}
}

View file

@ -9,7 +9,6 @@ import (
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/docker"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/proxy/entry"
"github.com/yusing/go-proxy/internal/route"
U "github.com/yusing/go-proxy/internal/utils"
"github.com/yusing/go-proxy/internal/utils/strutils"
@ -55,7 +54,7 @@ func (p *DockerProvider) NewWatcher() watcher.Watcher {
func (p *DockerProvider) loadRoutesImpl() (route.Routes, E.Error) {
routes := route.NewRoutes()
entries := entry.NewProxyEntries()
entries := route.NewProxyEntries()
containers, err := docker.ListContainers(p.dockerHost)
if err != nil {
@ -78,7 +77,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, E.Error) {
// there may be some valid entries in `en`
dups := entries.MergeFrom(newEntries)
// add the duplicate proxy entries to the error
dups.RangeAll(func(k string, v *entry.RawEntry) {
dups.RangeAll(func(k string, v *route.RawEntry) {
errs.Addf("duplicated alias %s", k)
})
}
@ -98,8 +97,8 @@ func (p *DockerProvider) shouldIgnore(container *docker.Container) bool {
// Returns a list of proxy entries for a container.
// Always non-nil.
func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container) (entries entry.RawEntries, _ E.Error) {
entries = entry.NewProxyEntries()
func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container) (entries route.RawEntries, _ E.Error) {
entries = route.NewProxyEntries()
if p.shouldIgnore(container) {
return
@ -107,7 +106,7 @@ func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container)
// init entries map for all aliases
for _, a := range container.Aliases {
entries.Store(a, &entry.RawEntry{
entries.Store(a, &route.RawEntry{
Alias: a,
Container: container,
})
@ -154,9 +153,9 @@ func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container)
}
// init entry if not exist
var en *entry.RawEntry
var en *route.RawEntry
if en, ok = entries.Load(alias); !ok {
en = &entry.RawEntry{
en = &route.RawEntry{
Alias: alias,
Container: container,
}
@ -172,7 +171,7 @@ func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container)
}
}
if wildcardProps != nil {
entries.RangeAll(func(alias string, re *entry.RawEntry) {
entries.RangeAll(func(alias string, re *route.RawEntry) {
if err := U.Deserialize(wildcardProps, re); err != nil {
errs.Add(err.Subject(alias))
}

View file

@ -10,8 +10,8 @@ import (
"github.com/yusing/go-proxy/internal/common"
D "github.com/yusing/go-proxy/internal/docker"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/proxy/entry"
T "github.com/yusing/go-proxy/internal/proxy/fields"
"github.com/yusing/go-proxy/internal/route/entry"
T "github.com/yusing/go-proxy/internal/route/types"
. "github.com/yusing/go-proxy/internal/utils/testing"
)

View file

@ -3,8 +3,8 @@ package provider
import (
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/proxy/entry"
"github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/route/entry"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/watcher"
)

View file

@ -7,8 +7,7 @@ import (
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/proxy/entry"
R "github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/route"
U "github.com/yusing/go-proxy/internal/utils"
W "github.com/yusing/go-proxy/internal/watcher"
)
@ -44,9 +43,9 @@ func (p *FileProvider) Logger() *zerolog.Logger {
return &p.l
}
func (p *FileProvider) loadRoutesImpl() (R.Routes, E.Error) {
routes := R.NewRoutes()
entries := entry.NewProxyEntries()
func (p *FileProvider) loadRoutesImpl() (route.Routes, E.Error) {
routes := route.NewRoutes()
entries := route.NewProxyEntries()
data, err := os.ReadFile(p.path)
if err != nil {
@ -61,7 +60,7 @@ func (p *FileProvider) loadRoutesImpl() (R.Routes, E.Error) {
E.LogWarn("validation failure", err.Subject(p.fileName))
}
return R.FromEntries(entries)
return route.FromEntries(entries)
}
func (p *FileProvider) NewWatcher() W.Watcher {

View file

@ -4,7 +4,8 @@ import (
"github.com/yusing/go-proxy/internal/docker"
E "github.com/yusing/go-proxy/internal/error"
url "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/proxy/entry"
"github.com/yusing/go-proxy/internal/route/entry"
"github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
U "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"
@ -16,7 +17,7 @@ type (
_ U.NoCopy
impl
Type RouteType
Entry *entry.RawEntry
Entry *RawEntry
}
Routes = F.Map[string, *Route]
@ -27,6 +28,8 @@ type (
String() string
TargetURL() url.URL
}
RawEntry = types.RawEntry
RawEntries = types.RawEntries
)
const (
@ -36,6 +39,7 @@ const (
// function alias.
var NewRoutes = F.NewMap[Routes]
var NewProxyEntries = types.NewProxyEntries
func (rt *Route) Container() *docker.Container {
if rt.Entry.Container == nil {
@ -44,7 +48,7 @@ func (rt *Route) Container() *docker.Container {
return rt.Entry.Container
}
func NewRoute(raw *entry.RawEntry) (*Route, E.Error) {
func NewRoute(raw *RawEntry) (*Route, E.Error) {
raw.Finalize()
en, err := entry.ValidateEntry(raw)
if err != nil {
@ -74,11 +78,11 @@ func NewRoute(raw *entry.RawEntry) (*Route, E.Error) {
}, nil
}
func FromEntries(entries entry.RawEntries) (Routes, E.Error) {
func FromEntries(entries RawEntries) (Routes, E.Error) {
b := E.NewBuilder("errors in routes")
routes := NewRoutes()
entries.RangeAllParallel(func(alias string, en *entry.RawEntry) {
entries.RangeAllParallel(func(alias string, en *RawEntry) {
en.Alias = alias
r, err := NewRoute(en)
switch {

View file

@ -1,4 +1,4 @@
package route
package routes
import "github.com/yusing/go-proxy/internal/metrics"

View file

@ -0,0 +1,43 @@
package routes
import (
"github.com/yusing/go-proxy/internal/route/types"
F "github.com/yusing/go-proxy/internal/utils/functional"
)
var (
httpRoutes = F.NewMapOf[string, types.HTTPRoute]()
streamRoutes = F.NewMapOf[string, types.StreamRoute]()
)
func GetHTTPRoutes() F.Map[string, types.HTTPRoute] {
return httpRoutes
}
func GetStreamRoutes() F.Map[string, types.StreamRoute] {
return streamRoutes
}
func GetHTTPRoute(alias string) (types.HTTPRoute, bool) {
return httpRoutes.Load(alias)
}
func GetStreamRoute(alias string) (types.StreamRoute, bool) {
return streamRoutes.Load(alias)
}
func SetHTTPRoute(alias string, r types.HTTPRoute) {
httpRoutes.Store(alias, r)
}
func SetStreamRoute(alias string, r types.StreamRoute) {
streamRoutes.Store(alias, r)
}
func DeleteHTTPRoute(alias string) {
httpRoutes.Delete(alias)
}
func DeleteStreamRoute(alias string) {
streamRoutes.Delete(alias)
}

View file

@ -9,9 +9,9 @@ import (
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
E "github.com/yusing/go-proxy/internal/error"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/proxy/entry"
"github.com/yusing/go-proxy/internal/route/entry"
"github.com/yusing/go-proxy/internal/route/routes"
"github.com/yusing/go-proxy/internal/task"
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/monitor"
)
@ -19,7 +19,7 @@ import (
type StreamRoute struct {
*entry.StreamEntry
stream net.Stream
net.Stream
HealthMon health.HealthMonitor `json:"health"`
@ -28,12 +28,6 @@ type StreamRoute struct {
l zerolog.Logger
}
var streamRoutes = F.NewMapOf[string, *StreamRoute]()
func GetStreamProxies() F.Map[string, *StreamRoute] {
return streamRoutes
}
func NewStreamRoute(entry *entry.StreamEntry) (impl, E.Error) {
// TODO: support non-coherent scheme
if !entry.Scheme.IsCoherent() {
@ -60,29 +54,29 @@ func (r *StreamRoute) Start(providerSubtask task.Task) E.Error {
}
r.task = providerSubtask
r.stream = NewStream(r)
r.Stream = NewStream(r)
switch {
case entry.UseIdleWatcher(r):
wakerTask := providerSubtask.Parent().Subtask("waker for " + string(r.Alias))
waker, err := idlewatcher.NewStreamWaker(wakerTask, r.StreamEntry, r.stream)
waker, err := idlewatcher.NewStreamWaker(wakerTask, r.StreamEntry, r.Stream)
if err != nil {
r.task.Finish(err)
return err
}
r.stream = waker
r.Stream = waker
r.HealthMon = waker
case entry.UseHealthCheck(r):
r.HealthMon = monitor.NewRawHealthMonitor(r.TargetURL(), r.HealthCheck)
}
if err := r.stream.Setup(); err != nil {
if err := r.Stream.Setup(); err != nil {
r.task.Finish(err)
return E.From(err)
}
r.task.OnFinished("close stream", func() {
if err := r.stream.Close(); err != nil {
if err := r.Stream.Close(); err != nil {
E.LogError("close stream failed", err, &r.l)
}
})
@ -101,9 +95,9 @@ func (r *StreamRoute) Start(providerSubtask task.Task) E.Error {
go r.acceptConnections()
streamRoutes.Store(string(r.Alias), r)
routes.SetStreamRoute(string(r.Alias), r)
r.task.OnFinished("remove from route table", func() {
streamRoutes.Delete(string(r.Alias))
routes.DeleteStreamRoute(string(r.Alias))
})
return nil
}
@ -120,7 +114,7 @@ func (r *StreamRoute) acceptConnections() {
case <-r.task.Context().Done():
return
default:
conn, err := r.stream.Accept()
conn, err := r.Stream.Accept()
if err != nil {
select {
case <-r.task.Context().Done():
@ -135,7 +129,7 @@ func (r *StreamRoute) acceptConnections() {
}
connTask := r.task.Subtask("connection")
go func() {
err := r.stream.Handle(conn)
err := r.Stream.Handle(conn)
if err != nil && !errors.Is(err, context.Canceled) {
E.LogError("handle connection error", err, &r.l)
connTask.Finish(err)

View file

@ -8,7 +8,7 @@ import (
"time"
"github.com/yusing/go-proxy/internal/net/types"
T "github.com/yusing/go-proxy/internal/proxy/fields"
T "github.com/yusing/go-proxy/internal/route/types"
U "github.com/yusing/go-proxy/internal/utils"
)

View file

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

View file

@ -0,0 +1,17 @@
package types
import (
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health"
)
type Entry interface {
TargetName() string
TargetURL() net.URL
RawEntry() *RawEntry
LoadBalanceConfig() *loadbalance.Config
HealthCheckConfig() *health.HealthCheckConfig
IdlewatcherConfig() *idlewatcher.Config
}

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"net/http"

View file

@ -1,4 +1,4 @@
package fields
package types
type (
Host string

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"errors"

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"errors"

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"strconv"

View file

@ -1,4 +1,4 @@
package entry
package types
import (
"strconv"
@ -9,7 +9,7 @@ import (
"github.com/yusing/go-proxy/internal/docker"
"github.com/yusing/go-proxy/internal/homepage"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
U "github.com/yusing/go-proxy/internal/utils"
F "github.com/yusing/go-proxy/internal/utils/functional"
"github.com/yusing/go-proxy/internal/utils/strutils"
@ -29,7 +29,7 @@ type (
NoTLSVerify bool `json:"no_tls_verify,omitempty" yaml:"no_tls_verify"` // https proxy only
PathPatterns []string `json:"path_patterns,omitempty" yaml:"path_patterns"` // http(s) proxy only
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty" yaml:"healthcheck"`
LoadBalance *loadbalancer.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"`
Homepage *homepage.Item `json:"homepage,omitempty" yaml:"homepage"`

View file

@ -0,0 +1,18 @@
package types
import (
"net/http"
net "github.com/yusing/go-proxy/internal/net/types"
)
type (
HTTPRoute interface {
Entry
http.Handler
}
StreamRoute interface {
Entry
net.Stream
}
)

View file

@ -1,4 +1,4 @@
package fields
package types
import (
E "github.com/yusing/go-proxy/internal/error"

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"strings"

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"strconv"

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"fmt"

View file

@ -1,4 +1,4 @@
package fields
package types
import (
"testing"