mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-16 10:14:04 +02:00
modules reorganized and code refactor
This commit is contained in:
parent
f3b21e6bd9
commit
d723403b6b
46 changed files with 437 additions and 331 deletions
|
@ -14,12 +14,12 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/query"
|
"github.com/yusing/go-proxy/internal/api/v1/query"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/config"
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
|
"github.com/yusing/go-proxy/internal/entrypoint"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/metrics"
|
"github.com/yusing/go-proxy/internal/metrics"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
||||||
R "github.com/yusing/go-proxy/internal/route"
|
"github.com/yusing/go-proxy/internal/net/http/server"
|
||||||
"github.com/yusing/go-proxy/internal/server"
|
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/pkg"
|
"github.com/yusing/go-proxy/pkg"
|
||||||
)
|
)
|
||||||
|
@ -136,7 +136,7 @@ func main() {
|
||||||
CertProvider: autocert,
|
CertProvider: autocert,
|
||||||
HTTPAddr: common.ProxyHTTPAddr,
|
HTTPAddr: common.ProxyHTTPAddr,
|
||||||
HTTPSAddr: common.ProxyHTTPSAddr,
|
HTTPSAddr: common.ProxyHTTPSAddr,
|
||||||
Handler: http.HandlerFunc(R.ProxyHandler),
|
Handler: http.HandlerFunc(entrypoint.Handler),
|
||||||
RedirectToHTTPS: config.Value().RedirectToHTTPS,
|
RedirectToHTTPS: config.Value().RedirectToHTTPS,
|
||||||
})
|
})
|
||||||
server.StartServer(server.Options{
|
server.StartServer(server.Options{
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/autocert"
|
"github.com/yusing/go-proxy/internal/autocert"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/config/types"
|
"github.com/yusing/go-proxy/internal/config/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/entrypoint"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/notif"
|
"github.com/yusing/go-proxy/internal/notif"
|
||||||
"github.com/yusing/go-proxy/internal/route"
|
|
||||||
proxy "github.com/yusing/go-proxy/internal/route/provider"
|
proxy "github.com/yusing/go-proxy/internal/route/provider"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
|
@ -183,7 +183,7 @@ func (cfg *Config) load() E.Error {
|
||||||
model.MatchDomains[i] = "." + domain
|
model.MatchDomains[i] = "." + domain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
route.SetFindMuxDomains(model.MatchDomains)
|
entrypoint.SetFindRouteDomains(model.MatchDomains)
|
||||||
return errs.Error()
|
return errs.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,16 @@ import (
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
"github.com/yusing/go-proxy/internal/proxy/entry"
|
route "github.com/yusing/go-proxy/internal/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"
|
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"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DumpEntries() map[string]*entry.RawEntry {
|
func DumpEntries() map[string]*types.RawEntry {
|
||||||
entries := make(map[string]*entry.RawEntry)
|
entries := make(map[string]*types.RawEntry)
|
||||||
instance.providers.RangeAll(func(_ string, p *proxy.Provider) {
|
instance.providers.RangeAll(func(_ string, p *proxy.Provider) {
|
||||||
p.RangeRoutes(func(alias string, r *route.Route) {
|
p.RangeRoutes(func(alias string, r *route.Route) {
|
||||||
entries[alias] = r.Entry
|
entries[alias] = r.Entry
|
||||||
|
@ -43,8 +45,8 @@ func HomepageConfig() homepage.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
hpCfg := homepage.NewHomePageConfig()
|
hpCfg := homepage.NewHomePageConfig()
|
||||||
route.GetReverseProxies().RangeAll(func(alias string, r *route.HTTPRoute) {
|
routes.GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
|
||||||
en := r.Raw
|
en := r.RawEntry()
|
||||||
item := en.Homepage
|
item := en.Homepage
|
||||||
if item == nil {
|
if item == nil {
|
||||||
item = new(homepage.Item)
|
item = new(homepage.Item)
|
||||||
|
@ -113,23 +115,23 @@ func HomepageConfig() homepage.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RoutesByAlias(typeFilter ...route.RouteType) map[string]any {
|
func RoutesByAlias(typeFilter ...route.RouteType) map[string]any {
|
||||||
routes := make(map[string]any)
|
rts := make(map[string]any)
|
||||||
if len(typeFilter) == 0 || typeFilter[0] == "" {
|
if len(typeFilter) == 0 || typeFilter[0] == "" {
|
||||||
typeFilter = []route.RouteType{route.RouteTypeReverseProxy, route.RouteTypeStream}
|
typeFilter = []route.RouteType{route.RouteTypeReverseProxy, route.RouteTypeStream}
|
||||||
}
|
}
|
||||||
for _, t := range typeFilter {
|
for _, t := range typeFilter {
|
||||||
switch t {
|
switch t {
|
||||||
case route.RouteTypeReverseProxy:
|
case route.RouteTypeReverseProxy:
|
||||||
route.GetReverseProxies().RangeAll(func(alias string, r *route.HTTPRoute) {
|
routes.GetHTTPRoutes().RangeAll(func(alias string, r types.HTTPRoute) {
|
||||||
routes[alias] = r
|
rts[alias] = r
|
||||||
})
|
})
|
||||||
case route.RouteTypeStream:
|
case route.RouteTypeStream:
|
||||||
route.GetStreamProxies().RangeAll(func(alias string, r *route.StreamRoute) {
|
routes.GetStreamRoutes().RangeAll(func(alias string, r types.StreamRoute) {
|
||||||
routes[alias] = r
|
rts[alias] = r
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return routes
|
return rts
|
||||||
}
|
}
|
||||||
|
|
||||||
func Statistics() map[string]any {
|
func Statistics() map[string]any {
|
||||||
|
|
|
@ -6,19 +6,21 @@ import (
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"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"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/metrics"
|
"github.com/yusing/go-proxy/internal/metrics"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
||||||
net "github.com/yusing/go-proxy/internal/net/types"
|
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"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||||
)
|
)
|
||||||
|
|
||||||
type waker struct {
|
type (
|
||||||
|
Waker = types.Waker
|
||||||
|
waker struct {
|
||||||
_ U.NoCopy
|
_ U.NoCopy
|
||||||
|
|
||||||
rp *gphttp.ReverseProxy
|
rp *gphttp.ReverseProxy
|
||||||
|
@ -27,7 +29,8 @@ type waker struct {
|
||||||
metric *metrics.Gauge
|
metric *metrics.Gauge
|
||||||
|
|
||||||
ready atomic.Bool
|
ready atomic.Bool
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
idleWakerCheckInterval = 100 * time.Millisecond
|
idleWakerCheckInterval = 100 * time.Millisecond
|
||||||
|
@ -36,7 +39,7 @@ const (
|
||||||
|
|
||||||
// TODO: support stream
|
// 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 := entry.HealthCheckConfig()
|
||||||
hcCfg.Timeout = idleWakerCheckTimeout
|
hcCfg.Timeout = idleWakerCheckTimeout
|
||||||
|
|
||||||
|
@ -69,11 +72,11 @@ func newWaker(providerSubTask task.Task, entry entry.Entry, rp *gphttp.ReversePr
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifetime should follow route provider.
|
// 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)
|
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)
|
return newWaker(providerSubTask, entry, nil, stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"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"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
|
@ -49,7 +49,7 @@ var (
|
||||||
|
|
||||||
const dockerReqTimeout = 3 * time.Second
|
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()
|
cfg := entry.IdlewatcherConfig()
|
||||||
|
|
||||||
if cfg.IdleTimeout == 0 {
|
if cfg.IdleTimeout == 0 {
|
||||||
|
|
93
internal/entrypoint/entrypoint.go
Normal file
93
internal/entrypoint/entrypoint.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
7
internal/entrypoint/logger.go
Normal file
7
internal/entrypoint/logger.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package entrypoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = logging.With().Str("module", "entrypoint").Logger()
|
|
@ -14,7 +14,7 @@ type ipHash struct {
|
||||||
*LoadBalancer
|
*LoadBalancer
|
||||||
|
|
||||||
realIP *middleware.Middleware
|
realIP *middleware.Middleware
|
||||||
pool servers
|
pool Servers
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ func (lb *LoadBalancer) newIPHash() impl {
|
||||||
var err E.Error
|
var err E.Error
|
||||||
impl.realIP, err = middleware.NewRealIP(lb.Options)
|
impl.realIP, err = middleware.NewRealIP(lb.Options)
|
||||||
if err != nil {
|
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
|
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 {
|
if impl.realIP != nil {
|
||||||
impl.realIP.ModifyRequest(impl.serveHTTP, rw, r)
|
impl.realIP.ModifyRequest(impl.serveHTTP, rw, r)
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,7 +72,7 @@ func (impl *ipHash) serveHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, "Internal error", http.StatusInternalServerError)
|
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
|
return
|
||||||
}
|
}
|
||||||
idx := hashIP(ip) % uint32(len(impl.pool))
|
idx := hashIP(ip) % uint32(len(impl.pool))
|
||||||
|
|
|
@ -27,18 +27,18 @@ func (impl *leastConn) OnRemoveServer(srv *Server) {
|
||||||
impl.nConn.Delete(srv)
|
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]
|
srv := srvs[0]
|
||||||
minConn, ok := impl.nConn.Load(srv)
|
minConn, ok := impl.nConn.Load(srv)
|
||||||
if !ok {
|
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)
|
http.Error(rw, "Internal error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 1; i < len(srvs); i++ {
|
for i := 1; i < len(srvs); i++ {
|
||||||
nConn, ok := impl.nConn.Load(srvs[i])
|
nConn, ok := impl.nConn.Load(srvs[i])
|
||||||
if !ok {
|
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)
|
http.Error(rw, "Internal error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
if nConn.Load() < minConn.Load() {
|
if nConn.Load() < minConn.Load() {
|
||||||
|
|
|
@ -7,9 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"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"
|
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/task"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||||
|
@ -19,19 +18,12 @@ import (
|
||||||
// TODO: support weighted mode.
|
// TODO: support weighted mode.
|
||||||
type (
|
type (
|
||||||
impl interface {
|
impl interface {
|
||||||
ServeHTTP(srvs servers, rw http.ResponseWriter, r *http.Request)
|
ServeHTTP(srvs Servers, rw http.ResponseWriter, r *http.Request)
|
||||||
OnAddServer(srv *Server)
|
OnAddServer(srv *Server)
|
||||||
OnRemoveServer(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
|
impl
|
||||||
*Config
|
*Config
|
||||||
|
|
||||||
|
@ -40,20 +32,20 @@ type (
|
||||||
pool Pool
|
pool Pool
|
||||||
poolMu sync.Mutex
|
poolMu sync.Mutex
|
||||||
|
|
||||||
sumWeight weightType
|
sumWeight Weight
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
}
|
|
||||||
|
|
||||||
weightType uint16
|
l zerolog.Logger
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxWeight weightType = 100
|
const maxWeight Weight = 100
|
||||||
|
|
||||||
func New(cfg *Config) *LoadBalancer {
|
func New(cfg *Config) *LoadBalancer {
|
||||||
lb := &LoadBalancer{
|
lb := &LoadBalancer{
|
||||||
Logger: logger.With().Str("name", cfg.Link).Logger(),
|
|
||||||
Config: new(Config),
|
Config: new(Config),
|
||||||
pool: newPool(),
|
pool: types.NewServerPool(),
|
||||||
|
l: logger.With().Str("name", cfg.Link).Logger(),
|
||||||
}
|
}
|
||||||
lb.UpdateConfigIfNeeded(cfg)
|
lb.UpdateConfigIfNeeded(cfg)
|
||||||
return lb
|
return lb
|
||||||
|
@ -81,11 +73,11 @@ func (lb *LoadBalancer) Finish(reason any) {
|
||||||
|
|
||||||
func (lb *LoadBalancer) updateImpl() {
|
func (lb *LoadBalancer) updateImpl() {
|
||||||
switch lb.Mode {
|
switch lb.Mode {
|
||||||
case Unset, RoundRobin:
|
case types.ModeUnset, types.ModeRoundRobin:
|
||||||
lb.impl = lb.newRoundRobin()
|
lb.impl = lb.newRoundRobin()
|
||||||
case LeastConn:
|
case types.ModeLeastConn:
|
||||||
lb.impl = lb.newLeastConn()
|
lb.impl = lb.newLeastConn()
|
||||||
case IPHash:
|
case types.ModeIPHash:
|
||||||
lb.impl = lb.newIPHash()
|
lb.impl = lb.newIPHash()
|
||||||
default: // should happen in test only
|
default: // should happen in test only
|
||||||
lb.impl = lb.newRoundRobin()
|
lb.impl = lb.newRoundRobin()
|
||||||
|
@ -102,10 +94,10 @@ func (lb *LoadBalancer) UpdateConfigIfNeeded(cfg *Config) {
|
||||||
|
|
||||||
lb.Link = cfg.Link
|
lb.Link = cfg.Link
|
||||||
|
|
||||||
if lb.Mode == Unset && cfg.Mode != Unset {
|
if lb.Mode == types.ModeUnset && cfg.Mode != types.ModeUnset {
|
||||||
lb.Mode = cfg.Mode
|
lb.Mode = cfg.Mode
|
||||||
if !lb.Mode.ValidateUpdate() {
|
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()
|
lb.updateImpl()
|
||||||
}
|
}
|
||||||
|
@ -135,7 +127,7 @@ func (lb *LoadBalancer) AddServer(srv *Server) {
|
||||||
lb.rebalance()
|
lb.rebalance()
|
||||||
lb.impl.OnAddServer(srv)
|
lb.impl.OnAddServer(srv)
|
||||||
|
|
||||||
lb.Debug().
|
lb.l.Debug().
|
||||||
Str("action", "add").
|
Str("action", "add").
|
||||||
Str("server", srv.Name).
|
Str("server", srv.Name).
|
||||||
Msgf("%d servers available", lb.pool.Size())
|
Msgf("%d servers available", lb.pool.Size())
|
||||||
|
@ -155,7 +147,7 @@ func (lb *LoadBalancer) RemoveServer(srv *Server) {
|
||||||
lb.rebalance()
|
lb.rebalance()
|
||||||
lb.impl.OnRemoveServer(srv)
|
lb.impl.OnRemoveServer(srv)
|
||||||
|
|
||||||
lb.Debug().
|
lb.l.Debug().
|
||||||
Str("action", "remove").
|
Str("action", "remove").
|
||||||
Str("server", srv.Name).
|
Str("server", srv.Name).
|
||||||
Msgf("%d servers left", lb.pool.Size())
|
Msgf("%d servers left", lb.pool.Size())
|
||||||
|
@ -174,8 +166,8 @@ func (lb *LoadBalancer) rebalance() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if lb.sumWeight == 0 { // distribute evenly
|
if lb.sumWeight == 0 { // distribute evenly
|
||||||
weightEach := maxWeight / weightType(lb.pool.Size())
|
weightEach := maxWeight / Weight(lb.pool.Size())
|
||||||
remainder := maxWeight % weightType(lb.pool.Size())
|
remainder := maxWeight % Weight(lb.pool.Size())
|
||||||
lb.pool.RangeAll(func(_ string, s *Server) {
|
lb.pool.RangeAll(func(_ string, s *Server) {
|
||||||
s.Weight = weightEach
|
s.Weight = weightEach
|
||||||
lb.sumWeight += weightEach
|
lb.sumWeight += weightEach
|
||||||
|
@ -192,7 +184,7 @@ func (lb *LoadBalancer) rebalance() {
|
||||||
lb.sumWeight = 0
|
lb.sumWeight = 0
|
||||||
|
|
||||||
lb.pool.RangeAll(func(_ string, s *Server) {
|
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
|
lb.sumWeight += s.Weight
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -226,13 +218,7 @@ func (lb *LoadBalancer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get(common.HeaderCheckRedirect) != "" {
|
if r.Header.Get(common.HeaderCheckRedirect) != "" {
|
||||||
// wake all servers
|
// wake all servers
|
||||||
for _, srv := range srvs {
|
for _, srv := range srvs {
|
||||||
// wake only if server implements Waker
|
srv.TryWake()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lb.impl.ServeHTTP(srvs, rw, r)
|
lb.impl.ServeHTTP(srvs, rw, r)
|
||||||
|
@ -246,7 +232,7 @@ func (lb *LoadBalancer) Uptime() time.Duration {
|
||||||
func (lb *LoadBalancer) MarshalJSON() ([]byte, error) {
|
func (lb *LoadBalancer) MarshalJSON() ([]byte, error) {
|
||||||
extra := make(map[string]any)
|
extra := make(map[string]any)
|
||||||
lb.pool.RangeAll(func(k string, v *Server) {
|
lb.pool.RangeAll(func(k string, v *Server) {
|
||||||
extra[v.Name] = v.healthMon
|
extra[v.Name] = v.HealthMonitor()
|
||||||
})
|
})
|
||||||
|
|
||||||
return (&monitor.JSONRepresentation{
|
return (&monitor.JSONRepresentation{
|
||||||
|
|
|
@ -3,13 +3,14 @@ package loadbalancer
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRebalance(t *testing.T) {
|
func TestRebalance(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("zero", func(t *testing.T) {
|
t.Run("zero", func(t *testing.T) {
|
||||||
lb := New(new(Config))
|
lb := New(new(loadbalance.Config))
|
||||||
for range 10 {
|
for range 10 {
|
||||||
lb.AddServer(&Server{})
|
lb.AddServer(&Server{})
|
||||||
}
|
}
|
||||||
|
@ -17,25 +18,25 @@ func TestRebalance(t *testing.T) {
|
||||||
ExpectEqual(t, lb.sumWeight, maxWeight)
|
ExpectEqual(t, lb.sumWeight, maxWeight)
|
||||||
})
|
})
|
||||||
t.Run("less", func(t *testing.T) {
|
t.Run("less", func(t *testing.T) {
|
||||||
lb := New(new(Config))
|
lb := New(new(loadbalance.Config))
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .3)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .3)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
|
||||||
lb.rebalance()
|
lb.rebalance()
|
||||||
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
|
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
|
||||||
ExpectEqual(t, lb.sumWeight, maxWeight)
|
ExpectEqual(t, lb.sumWeight, maxWeight)
|
||||||
})
|
})
|
||||||
t.Run("more", func(t *testing.T) {
|
t.Run("more", func(t *testing.T) {
|
||||||
lb := New(new(Config))
|
lb := New(new(loadbalance.Config))
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .3)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .3)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .4)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .4)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .3)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .3)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .2)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .2)})
|
||||||
lb.AddServer(&Server{Weight: weightType(float64(maxWeight) * .1)})
|
lb.AddServer(&Server{Weight: loadbalance.Weight(float64(maxWeight) * .1)})
|
||||||
lb.rebalance()
|
lb.rebalance()
|
||||||
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
|
// t.Logf("%s", U.Must(json.MarshalIndent(lb.pool, "", " ")))
|
||||||
ExpectEqual(t, lb.sumWeight, maxWeight)
|
ExpectEqual(t, lb.sumWeight, maxWeight)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -13,7 +13,7 @@ func (*LoadBalancer) newRoundRobin() impl { return &roundRobin{} }
|
||||||
func (lb *roundRobin) OnAddServer(srv *Server) {}
|
func (lb *roundRobin) OnAddServer(srv *Server) {}
|
||||||
func (lb *roundRobin) OnRemoveServer(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))
|
index := lb.index.Add(1) % uint32(len(srvs))
|
||||||
srvs[index].ServeHTTP(rw, r)
|
srvs[index].ServeHTTP(rw, r)
|
||||||
if lb.index.Load() >= 2*uint32(len(srvs)) {
|
if lb.index.Load() >= 2*uint32(len(srvs)) {
|
||||||
|
|
14
internal/net/http/loadbalancer/types.go
Normal file
14
internal/net/http/loadbalancer/types.go
Normal 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
|
||||||
|
)
|
8
internal/net/http/loadbalancer/types/config.go
Normal file
8
internal/net/http/loadbalancer/types/config.go
Normal 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"`
|
||||||
|
}
|
32
internal/net/http/loadbalancer/types/mode.go
Normal file
32
internal/net/http/loadbalancer/types/mode.go
Normal 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
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package loadbalancer
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"github.com/yusing/go-proxy/internal/net/types"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
|
@ -16,18 +17,18 @@ type (
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
URL types.URL
|
URL types.URL
|
||||||
Weight weightType
|
Weight Weight
|
||||||
|
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
healthMon health.HealthMonitor
|
healthMon health.HealthMonitor
|
||||||
}
|
}
|
||||||
servers = []*Server
|
Servers = []*Server
|
||||||
Pool = F.Map[string, *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{
|
srv := &Server{
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: url,
|
URL: url,
|
||||||
|
@ -53,3 +54,17 @@ func (srv *Server) Status() health.Status {
|
||||||
func (srv *Server) Uptime() time.Duration {
|
func (srv *Server) Uptime() time.Duration {
|
||||||
return srv.healthMon.Uptime()
|
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
|
||||||
|
}
|
3
internal/net/http/loadbalancer/types/weight.go
Normal file
3
internal/net/http/loadbalancer/types/weight.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type Weight uint16
|
|
@ -1,25 +1,14 @@
|
||||||
package entry
|
package entry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Entry interface {
|
type Entry = route.Entry
|
||||||
TargetName() string
|
|
||||||
TargetURL() net.URL
|
|
||||||
RawEntry() *RawEntry
|
|
||||||
LoadBalanceConfig() *loadbalancer.Config
|
|
||||||
HealthCheckConfig() *health.HealthCheckConfig
|
|
||||||
IdlewatcherConfig() *idlewatcher.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateEntry(m *RawEntry) (Entry, E.Error) {
|
func ValidateEntry(m *route.RawEntry) (Entry, E.Error) {
|
||||||
scheme, err := T.NewScheme(m.Scheme)
|
scheme, err := route.NewScheme(m.Scheme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.From(err)
|
return nil, E.From(err)
|
||||||
}
|
}
|
|
@ -7,22 +7,22 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
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"
|
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"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReverseProxyEntry struct { // real model after validation
|
type ReverseProxyEntry struct { // real model after validation
|
||||||
Raw *RawEntry `json:"raw"`
|
Raw *route.RawEntry `json:"raw"`
|
||||||
|
|
||||||
Alias fields.Alias `json:"alias"`
|
Alias route.Alias `json:"alias"`
|
||||||
Scheme fields.Scheme `json:"scheme"`
|
Scheme route.Scheme `json:"scheme"`
|
||||||
URL net.URL `json:"url"`
|
URL net.URL `json:"url"`
|
||||||
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
|
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"`
|
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"`
|
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty"`
|
||||||
|
|
||||||
/* Docker only */
|
/* Docker only */
|
||||||
|
@ -37,11 +37,11 @@ func (rp *ReverseProxyEntry) TargetURL() net.URL {
|
||||||
return rp.URL
|
return rp.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *ReverseProxyEntry) RawEntry() *RawEntry {
|
func (rp *ReverseProxyEntry) RawEntry() *route.RawEntry {
|
||||||
return rp.Raw
|
return rp.Raw
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *ReverseProxyEntry) LoadBalanceConfig() *loadbalancer.Config {
|
func (rp *ReverseProxyEntry) LoadBalanceConfig() *loadbalance.Config {
|
||||||
return rp.LoadBalance
|
return rp.LoadBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ func (rp *ReverseProxyEntry) IdlewatcherConfig() *idlewatcher.Config {
|
||||||
return rp.Idlewatcher
|
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
|
cont := m.Container
|
||||||
if cont == nil {
|
if cont == nil {
|
||||||
cont = docker.DummyContainer
|
cont = docker.DummyContainer
|
||||||
|
@ -64,9 +64,9 @@ func validateRPEntry(m *RawEntry, s fields.Scheme, errs *E.Builder) *ReverseProx
|
||||||
lb = nil
|
lb = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
host := E.Collect(errs, fields.ValidateHost, m.Host)
|
host := E.Collect(errs, route.ValidateHost, m.Host)
|
||||||
port := E.Collect(errs, fields.ValidatePort, m.Port)
|
port := E.Collect(errs, route.ValidatePort, m.Port)
|
||||||
pathPats := E.Collect(errs, fields.ValidatePathPatterns, m.PathPatterns)
|
pathPats := E.Collect(errs, route.ValidatePathPatterns, m.PathPatterns)
|
||||||
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, host, port))
|
||||||
iwCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
|
iwCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ func validateRPEntry(m *RawEntry, s fields.Scheme, errs *E.Builder) *ReverseProx
|
||||||
|
|
||||||
return &ReverseProxyEntry{
|
return &ReverseProxyEntry{
|
||||||
Raw: m,
|
Raw: m,
|
||||||
Alias: fields.Alias(m.Alias),
|
Alias: route.Alias(m.Alias),
|
||||||
Scheme: s,
|
Scheme: s,
|
||||||
URL: net.NewURL(url),
|
URL: net.NewURL(url),
|
||||||
NoTLSVerify: m.NoTLSVerify,
|
NoTLSVerify: m.NoTLSVerify,
|
|
@ -6,20 +6,20 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
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"
|
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"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StreamEntry struct {
|
type StreamEntry struct {
|
||||||
Raw *RawEntry `json:"raw"`
|
Raw *route.RawEntry `json:"raw"`
|
||||||
|
|
||||||
Alias fields.Alias `json:"alias"`
|
Alias route.Alias `json:"alias"`
|
||||||
Scheme fields.StreamScheme `json:"scheme"`
|
Scheme route.StreamScheme `json:"scheme"`
|
||||||
URL net.URL `json:"url"`
|
URL net.URL `json:"url"`
|
||||||
Host fields.Host `json:"host,omitempty"`
|
Host route.Host `json:"host,omitempty"`
|
||||||
Port fields.StreamPort `json:"port,omitempty"`
|
Port route.StreamPort `json:"port,omitempty"`
|
||||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
||||||
|
|
||||||
/* Docker only */
|
/* Docker only */
|
||||||
|
@ -34,11 +34,11 @@ func (s *StreamEntry) TargetURL() net.URL {
|
||||||
return s.URL
|
return s.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StreamEntry) RawEntry() *RawEntry {
|
func (s *StreamEntry) RawEntry() *route.RawEntry {
|
||||||
return s.Raw
|
return s.Raw
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StreamEntry) LoadBalanceConfig() *loadbalancer.Config {
|
func (s *StreamEntry) LoadBalanceConfig() *loadbalance.Config {
|
||||||
// TODO: support stream load balance
|
// TODO: support stream load balance
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -51,15 +51,15 @@ func (s *StreamEntry) IdlewatcherConfig() *idlewatcher.Config {
|
||||||
return s.Idlewatcher
|
return s.Idlewatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateStreamEntry(m *RawEntry, errs *E.Builder) *StreamEntry {
|
func validateStreamEntry(m *route.RawEntry, errs *E.Builder) *StreamEntry {
|
||||||
cont := m.Container
|
cont := m.Container
|
||||||
if cont == nil {
|
if cont == nil {
|
||||||
cont = docker.DummyContainer
|
cont = docker.DummyContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
host := E.Collect(errs, fields.ValidateHost, m.Host)
|
host := E.Collect(errs, route.ValidateHost, m.Host)
|
||||||
port := E.Collect(errs, fields.ValidateStreamPort, m.Port)
|
port := E.Collect(errs, route.ValidateStreamPort, m.Port)
|
||||||
scheme := E.Collect(errs, fields.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.ListeningScheme, host, port.ProxyPort))
|
||||||
idleWatcherCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
|
idleWatcherCfg := E.Collect(errs, idlewatcher.ValidateConfig, cont)
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ func validateStreamEntry(m *RawEntry, errs *E.Builder) *StreamEntry {
|
||||||
|
|
||||||
return &StreamEntry{
|
return &StreamEntry{
|
||||||
Raw: m,
|
Raw: m,
|
||||||
Alias: fields.Alias(m.Alias),
|
Alias: route.Alias(m.Alias),
|
||||||
Scheme: *scheme,
|
Scheme: *scheme,
|
||||||
URL: url,
|
URL: url,
|
||||||
Host: host,
|
Host: host,
|
|
@ -1,10 +1,7 @@
|
||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
|
@ -12,12 +9,12 @@ import (
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
|
"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"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware/errorpage"
|
"github.com/yusing/go-proxy/internal/route/entry"
|
||||||
"github.com/yusing/go-proxy/internal/proxy/entry"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
PT "github.com/yusing/go-proxy/internal/proxy/fields"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"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"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||||
)
|
)
|
||||||
|
@ -38,27 +35,10 @@ type (
|
||||||
l zerolog.Logger
|
l zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdomainKey = PT.Alias
|
SubdomainKey = route.Alias
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// var globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) {
|
func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) {
|
||||||
var trans *http.Transport
|
var trans *http.Transport
|
||||||
|
@ -141,9 +121,9 @@ func (r *HTTPRoute) Start(providerSubtask task.Task) E.Error {
|
||||||
if entry.UseLoadBalance(r) {
|
if entry.UseLoadBalance(r) {
|
||||||
r.addToLoadBalancer()
|
r.addToLoadBalancer()
|
||||||
} else {
|
} else {
|
||||||
httpRoutes.Store(string(r.Alias), r)
|
routes.SetHTTPRoute(string(r.Alias), r)
|
||||||
r.task.OnFinished("remove from route table", func() {
|
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() {
|
func (r *HTTPRoute) addToLoadBalancer() {
|
||||||
var lb *loadbalancer.LoadBalancer
|
var lb *loadbalancer.LoadBalancer
|
||||||
linked, ok := httpRoutes.Load(r.LoadBalance.Link)
|
l, ok := routes.GetHTTPRoute(r.LoadBalance.Link)
|
||||||
|
linked := l.(*HTTPRoute)
|
||||||
if ok {
|
if ok {
|
||||||
lb = linked.loadBalancer
|
lb = linked.loadBalancer
|
||||||
lb.UpdateConfigIfNeeded(r.LoadBalance)
|
lb.UpdateConfigIfNeeded(r.LoadBalance)
|
||||||
|
@ -175,96 +156,26 @@ func (r *HTTPRoute) addToLoadBalancer() {
|
||||||
lb = loadbalancer.New(r.LoadBalance)
|
lb = loadbalancer.New(r.LoadBalance)
|
||||||
lbTask := r.task.Parent().Subtask("loadbalancer " + r.LoadBalance.Link)
|
lbTask := r.task.Parent().Subtask("loadbalancer " + r.LoadBalance.Link)
|
||||||
lbTask.OnCancel("remove lb from routes", func() {
|
lbTask.OnCancel("remove lb from routes", func() {
|
||||||
httpRoutes.Delete(r.LoadBalance.Link)
|
routes.DeleteHTTPRoute(r.LoadBalance.Link)
|
||||||
})
|
})
|
||||||
lb.Start(lbTask)
|
lb.Start(lbTask)
|
||||||
linked = &HTTPRoute{
|
linked = &HTTPRoute{
|
||||||
ReverseProxyEntry: &entry.ReverseProxyEntry{
|
ReverseProxyEntry: &entry.ReverseProxyEntry{
|
||||||
Raw: &entry.RawEntry{
|
Raw: &route.RawEntry{
|
||||||
Homepage: r.Raw.Homepage,
|
Homepage: r.Raw.Homepage,
|
||||||
},
|
},
|
||||||
Alias: PT.Alias(lb.Link),
|
Alias: route.Alias(lb.Link),
|
||||||
},
|
},
|
||||||
HealthMon: lb,
|
HealthMon: lb,
|
||||||
loadBalancer: lb,
|
loadBalancer: lb,
|
||||||
handler: lb,
|
handler: lb,
|
||||||
}
|
}
|
||||||
httpRoutes.Store(r.LoadBalance.Link, linked)
|
routes.SetHTTPRoute(r.LoadBalance.Link, linked)
|
||||||
}
|
}
|
||||||
r.loadBalancer = lb
|
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)
|
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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
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"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"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) {
|
func (p *DockerProvider) loadRoutesImpl() (route.Routes, E.Error) {
|
||||||
routes := route.NewRoutes()
|
routes := route.NewRoutes()
|
||||||
entries := entry.NewProxyEntries()
|
entries := route.NewProxyEntries()
|
||||||
|
|
||||||
containers, err := docker.ListContainers(p.dockerHost)
|
containers, err := docker.ListContainers(p.dockerHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -78,7 +77,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, E.Error) {
|
||||||
// there may be some valid entries in `en`
|
// there may be some valid entries in `en`
|
||||||
dups := entries.MergeFrom(newEntries)
|
dups := entries.MergeFrom(newEntries)
|
||||||
// add the duplicate proxy entries to the error
|
// 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)
|
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.
|
// Returns a list of proxy entries for a container.
|
||||||
// Always non-nil.
|
// Always non-nil.
|
||||||
func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container) (entries entry.RawEntries, _ E.Error) {
|
func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container) (entries route.RawEntries, _ E.Error) {
|
||||||
entries = entry.NewProxyEntries()
|
entries = route.NewProxyEntries()
|
||||||
|
|
||||||
if p.shouldIgnore(container) {
|
if p.shouldIgnore(container) {
|
||||||
return
|
return
|
||||||
|
@ -107,7 +106,7 @@ func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container)
|
||||||
|
|
||||||
// init entries map for all aliases
|
// init entries map for all aliases
|
||||||
for _, a := range container.Aliases {
|
for _, a := range container.Aliases {
|
||||||
entries.Store(a, &entry.RawEntry{
|
entries.Store(a, &route.RawEntry{
|
||||||
Alias: a,
|
Alias: a,
|
||||||
Container: container,
|
Container: container,
|
||||||
})
|
})
|
||||||
|
@ -154,9 +153,9 @@ func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container)
|
||||||
}
|
}
|
||||||
|
|
||||||
// init entry if not exist
|
// init entry if not exist
|
||||||
var en *entry.RawEntry
|
var en *route.RawEntry
|
||||||
if en, ok = entries.Load(alias); !ok {
|
if en, ok = entries.Load(alias); !ok {
|
||||||
en = &entry.RawEntry{
|
en = &route.RawEntry{
|
||||||
Alias: alias,
|
Alias: alias,
|
||||||
Container: container,
|
Container: container,
|
||||||
}
|
}
|
||||||
|
@ -172,7 +171,7 @@ func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if wildcardProps != nil {
|
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 {
|
if err := U.Deserialize(wildcardProps, re); err != nil {
|
||||||
errs.Add(err.Subject(alias))
|
errs.Add(err.Subject(alias))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/proxy/entry"
|
"github.com/yusing/go-proxy/internal/route/entry"
|
||||||
T "github.com/yusing/go-proxy/internal/proxy/fields"
|
T "github.com/yusing/go-proxy/internal/route/types"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ package provider
|
||||||
import (
|
import (
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
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"
|
||||||
|
"github.com/yusing/go-proxy/internal/route/entry"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/internal/watcher"
|
"github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,8 +7,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/proxy/entry"
|
"github.com/yusing/go-proxy/internal/route"
|
||||||
R "github.com/yusing/go-proxy/internal/route"
|
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
W "github.com/yusing/go-proxy/internal/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
@ -44,9 +43,9 @@ func (p *FileProvider) Logger() *zerolog.Logger {
|
||||||
return &p.l
|
return &p.l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FileProvider) loadRoutesImpl() (R.Routes, E.Error) {
|
func (p *FileProvider) loadRoutesImpl() (route.Routes, E.Error) {
|
||||||
routes := R.NewRoutes()
|
routes := route.NewRoutes()
|
||||||
entries := entry.NewProxyEntries()
|
entries := route.NewProxyEntries()
|
||||||
|
|
||||||
data, err := os.ReadFile(p.path)
|
data, err := os.ReadFile(p.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -61,7 +60,7 @@ func (p *FileProvider) loadRoutesImpl() (R.Routes, E.Error) {
|
||||||
E.LogWarn("validation failure", err.Subject(p.fileName))
|
E.LogWarn("validation failure", err.Subject(p.fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
return R.FromEntries(entries)
|
return route.FromEntries(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FileProvider) NewWatcher() W.Watcher {
|
func (p *FileProvider) NewWatcher() W.Watcher {
|
||||||
|
|
|
@ -4,7 +4,8 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
url "github.com/yusing/go-proxy/internal/net/types"
|
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"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
|
@ -16,7 +17,7 @@ type (
|
||||||
_ U.NoCopy
|
_ U.NoCopy
|
||||||
impl
|
impl
|
||||||
Type RouteType
|
Type RouteType
|
||||||
Entry *entry.RawEntry
|
Entry *RawEntry
|
||||||
}
|
}
|
||||||
Routes = F.Map[string, *Route]
|
Routes = F.Map[string, *Route]
|
||||||
|
|
||||||
|
@ -27,6 +28,8 @@ type (
|
||||||
String() string
|
String() string
|
||||||
TargetURL() url.URL
|
TargetURL() url.URL
|
||||||
}
|
}
|
||||||
|
RawEntry = types.RawEntry
|
||||||
|
RawEntries = types.RawEntries
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -36,6 +39,7 @@ const (
|
||||||
|
|
||||||
// function alias.
|
// function alias.
|
||||||
var NewRoutes = F.NewMap[Routes]
|
var NewRoutes = F.NewMap[Routes]
|
||||||
|
var NewProxyEntries = types.NewProxyEntries
|
||||||
|
|
||||||
func (rt *Route) Container() *docker.Container {
|
func (rt *Route) Container() *docker.Container {
|
||||||
if rt.Entry.Container == nil {
|
if rt.Entry.Container == nil {
|
||||||
|
@ -44,7 +48,7 @@ func (rt *Route) Container() *docker.Container {
|
||||||
return rt.Entry.Container
|
return rt.Entry.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoute(raw *entry.RawEntry) (*Route, E.Error) {
|
func NewRoute(raw *RawEntry) (*Route, E.Error) {
|
||||||
raw.Finalize()
|
raw.Finalize()
|
||||||
en, err := entry.ValidateEntry(raw)
|
en, err := entry.ValidateEntry(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,11 +78,11 @@ func NewRoute(raw *entry.RawEntry) (*Route, E.Error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromEntries(entries entry.RawEntries) (Routes, E.Error) {
|
func FromEntries(entries RawEntries) (Routes, E.Error) {
|
||||||
b := E.NewBuilder("errors in routes")
|
b := E.NewBuilder("errors in routes")
|
||||||
|
|
||||||
routes := NewRoutes()
|
routes := NewRoutes()
|
||||||
entries.RangeAllParallel(func(alias string, en *entry.RawEntry) {
|
entries.RangeAllParallel(func(alias string, en *RawEntry) {
|
||||||
en.Alias = alias
|
en.Alias = alias
|
||||||
r, err := NewRoute(en)
|
r, err := NewRoute(en)
|
||||||
switch {
|
switch {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package route
|
package routes
|
||||||
|
|
||||||
import "github.com/yusing/go-proxy/internal/metrics"
|
import "github.com/yusing/go-proxy/internal/metrics"
|
||||||
|
|
43
internal/route/routes/routes.go
Normal file
43
internal/route/routes/routes.go
Normal 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)
|
||||||
|
}
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
net "github.com/yusing/go-proxy/internal/net/types"
|
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"
|
"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"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||||
)
|
)
|
||||||
|
@ -19,7 +19,7 @@ import (
|
||||||
type StreamRoute struct {
|
type StreamRoute struct {
|
||||||
*entry.StreamEntry
|
*entry.StreamEntry
|
||||||
|
|
||||||
stream net.Stream
|
net.Stream
|
||||||
|
|
||||||
HealthMon health.HealthMonitor `json:"health"`
|
HealthMon health.HealthMonitor `json:"health"`
|
||||||
|
|
||||||
|
@ -28,12 +28,6 @@ type StreamRoute struct {
|
||||||
l zerolog.Logger
|
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) {
|
func NewStreamRoute(entry *entry.StreamEntry) (impl, E.Error) {
|
||||||
// TODO: support non-coherent scheme
|
// TODO: support non-coherent scheme
|
||||||
if !entry.Scheme.IsCoherent() {
|
if !entry.Scheme.IsCoherent() {
|
||||||
|
@ -60,29 +54,29 @@ func (r *StreamRoute) Start(providerSubtask task.Task) E.Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
r.task = providerSubtask
|
r.task = providerSubtask
|
||||||
r.stream = NewStream(r)
|
r.Stream = NewStream(r)
|
||||||
|
|
||||||
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 " + string(r.Alias))
|
||||||
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)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.stream = waker
|
r.Stream = waker
|
||||||
r.HealthMon = waker
|
r.HealthMon = waker
|
||||||
case entry.UseHealthCheck(r):
|
case entry.UseHealthCheck(r):
|
||||||
r.HealthMon = monitor.NewRawHealthMonitor(r.TargetURL(), r.HealthCheck)
|
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)
|
r.task.Finish(err)
|
||||||
return E.From(err)
|
return E.From(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.task.OnFinished("close stream", func() {
|
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)
|
E.LogError("close stream failed", err, &r.l)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -101,9 +95,9 @@ func (r *StreamRoute) Start(providerSubtask task.Task) E.Error {
|
||||||
|
|
||||||
go r.acceptConnections()
|
go r.acceptConnections()
|
||||||
|
|
||||||
streamRoutes.Store(string(r.Alias), r)
|
routes.SetStreamRoute(string(r.Alias), r)
|
||||||
r.task.OnFinished("remove from route table", func() {
|
r.task.OnFinished("remove from route table", func() {
|
||||||
streamRoutes.Delete(string(r.Alias))
|
routes.DeleteStreamRoute(string(r.Alias))
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -120,7 +114,7 @@ func (r *StreamRoute) acceptConnections() {
|
||||||
case <-r.task.Context().Done():
|
case <-r.task.Context().Done():
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
conn, err := r.stream.Accept()
|
conn, err := r.Stream.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
select {
|
select {
|
||||||
case <-r.task.Context().Done():
|
case <-r.task.Context().Done():
|
||||||
|
@ -135,7 +129,7 @@ func (r *StreamRoute) acceptConnections() {
|
||||||
}
|
}
|
||||||
connTask := r.task.Subtask("connection")
|
connTask := r.task.Subtask("connection")
|
||||||
go func() {
|
go func() {
|
||||||
err := r.stream.Handle(conn)
|
err := r.Stream.Handle(conn)
|
||||||
if err != nil && !errors.Is(err, context.Canceled) {
|
if err != nil && !errors.Is(err, context.Canceled) {
|
||||||
E.LogError("handle connection error", err, &r.l)
|
E.LogError("handle connection error", err, &r.l)
|
||||||
connTask.Finish(err)
|
connTask.Finish(err)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/net/types"
|
"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"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
type Alias string
|
type Alias string
|
17
internal/route/types/entry.go
Normal file
17
internal/route/types/entry.go
Normal 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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Host string
|
Host string
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
|
@ -1,4 +1,4 @@
|
||||||
package entry
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"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"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"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
|
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
|
PathPatterns []string `json:"path_patterns,omitempty" yaml:"path_patterns"` // http(s) proxy only
|
||||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty" yaml:"healthcheck"`
|
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"`
|
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"`
|
||||||
|
|
18
internal/route/types/route.go
Normal file
18
internal/route/types/route.go
Normal 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
|
||||||
|
}
|
||||||
|
)
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,4 +1,4 @@
|
||||||
package fields
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
Loading…
Add table
Reference in a new issue