mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-11 08:34:04 +02:00
feat: enhance route handling with agent support and refactor port selection mapping
This commit is contained in:
parent
fbb07011f1
commit
7ef8354eb0
7 changed files with 336 additions and 162 deletions
79
internal/route/port_selection.go
Normal file
79
internal/route/port_selection.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package route
|
||||||
|
|
||||||
|
var (
|
||||||
|
ImageNamePortMapTCP = map[string]int{
|
||||||
|
"mssql": 1433,
|
||||||
|
"mysql": 3306,
|
||||||
|
"mariadb": 3306,
|
||||||
|
"postgres": 5432,
|
||||||
|
"rabbitmq": 5672,
|
||||||
|
"redis": 6379,
|
||||||
|
"memcached": 11211,
|
||||||
|
"mongo": 27017,
|
||||||
|
"minecraft-server": 25565,
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageNamePortMapHTTP = map[string]int{
|
||||||
|
"adguardhome": 3000,
|
||||||
|
"bazarr": 6767,
|
||||||
|
"calibre-web": 8083,
|
||||||
|
"changedetection.io": 3000,
|
||||||
|
"dockge": 5001,
|
||||||
|
"gitea": 3000,
|
||||||
|
"gogs": 3000,
|
||||||
|
"grafana": 3000,
|
||||||
|
"home-assistant": 8123,
|
||||||
|
"homebridge": 8581,
|
||||||
|
"httpd": 80,
|
||||||
|
"immich": 3001,
|
||||||
|
"jellyfin": 8096,
|
||||||
|
"lidarr": 8686,
|
||||||
|
"microbin": 8080,
|
||||||
|
"nginx": 80,
|
||||||
|
"nginx-proxy-manager": 81,
|
||||||
|
"open-webui": 8080,
|
||||||
|
"plex": 32400,
|
||||||
|
"prometheus": 9090,
|
||||||
|
"prowlarr": 9696,
|
||||||
|
"radarr": 7878,
|
||||||
|
"radarr-sma": 7878,
|
||||||
|
"rsshub": 1200,
|
||||||
|
"rss-bridge": 80,
|
||||||
|
"sonarr": 8989,
|
||||||
|
"sonarr-sma": 8989,
|
||||||
|
"uptime-kuma": 3001,
|
||||||
|
"whisparr": 6969,
|
||||||
|
}
|
||||||
|
ImageNamePortMapHTTPS = map[string]int{
|
||||||
|
"portainer-be": 9443,
|
||||||
|
"portainer-ce": 9443,
|
||||||
|
}
|
||||||
|
AliasPortMapHTTP = map[string]int{}
|
||||||
|
AliasPortMapHTTPS = map[string]int{
|
||||||
|
"portainer": 9443,
|
||||||
|
"crafty": 8080,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSchemePortByImageName(imageName string) (scheme string, port int, ok bool) {
|
||||||
|
if port, ok := ImageNamePortMapHTTP[imageName]; ok {
|
||||||
|
return "http", port, true
|
||||||
|
}
|
||||||
|
if port, ok := ImageNamePortMapHTTPS[imageName]; ok {
|
||||||
|
return "https", port, true
|
||||||
|
}
|
||||||
|
if port, ok := ImageNamePortMapTCP[imageName]; ok {
|
||||||
|
return "tcp", port, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSchemePortByAlias(alias string) (scheme string, port int, ok bool) {
|
||||||
|
if port, ok := AliasPortMapHTTP[alias]; ok {
|
||||||
|
return "http", port, true
|
||||||
|
}
|
||||||
|
if port, ok := AliasPortMapHTTPS[alias]; ok {
|
||||||
|
return "https", port, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -1,21 +1,24 @@
|
||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/go-proxy/agent/pkg/agentproxy"
|
||||||
"github.com/yusing/go-proxy/internal/api/v1/favicon"
|
"github.com/yusing/go-proxy/internal/api/v1/favicon"
|
||||||
"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"
|
||||||
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
||||||
"github.com/yusing/go-proxy/internal/gperr"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
gphttp "github.com/yusing/go-proxy/internal/net/http"
|
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/loadbalancer"
|
"github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer"
|
||||||
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/middleware"
|
"github.com/yusing/go-proxy/internal/net/gphttp/middleware"
|
||||||
metricslogger "github.com/yusing/go-proxy/internal/net/http/middleware/metrics_logger"
|
metricslogger "github.com/yusing/go-proxy/internal/net/gphttp/middleware/metrics_logger"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/reverseproxy"
|
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
"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"
|
||||||
|
@ -40,17 +43,25 @@ type (
|
||||||
|
|
||||||
func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||||
httpConfig := base.HTTPConfig
|
httpConfig := base.HTTPConfig
|
||||||
|
proxyURL := base.ProxyURL
|
||||||
|
|
||||||
|
var trans *http.Transport
|
||||||
|
a := base.Agent()
|
||||||
|
if a != nil {
|
||||||
|
trans = a.Transport()
|
||||||
|
proxyURL = agent.HTTPProxyURL
|
||||||
|
} else {
|
||||||
|
trans = gphttp.NewTransport()
|
||||||
if httpConfig.NoTLSVerify {
|
if httpConfig.NoTLSVerify {
|
||||||
trans = gphttp.DefaultTransportNoTLS
|
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
}
|
}
|
||||||
if httpConfig.ResponseHeaderTimeout > 0 {
|
if httpConfig.ResponseHeaderTimeout > 0 {
|
||||||
trans = trans.Clone()
|
|
||||||
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
|
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
service := base.TargetName()
|
service := base.TargetName()
|
||||||
rp := reverseproxy.NewReverseProxy(service, base.ProxyURL, trans)
|
rp := reverseproxy.NewReverseProxy(service, proxyURL, trans)
|
||||||
|
|
||||||
if len(base.Middlewares) > 0 {
|
if len(base.Middlewares) > 0 {
|
||||||
err := middleware.PatchReverseProxy(rp, base.Middlewares)
|
err := middleware.PatchReverseProxy(rp, base.Middlewares)
|
||||||
|
@ -59,6 +70,20 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a != nil {
|
||||||
|
headers := &agentproxy.AgentProxyHeaders{
|
||||||
|
Host: base.ProxyURL.Host,
|
||||||
|
IsHTTPS: base.ProxyURL.Scheme == "https",
|
||||||
|
SkipTLSVerify: httpConfig.NoTLSVerify,
|
||||||
|
ResponseHeaderTimeout: int(httpConfig.ResponseHeaderTimeout.Seconds()),
|
||||||
|
}
|
||||||
|
ori := rp.HandlerFunc
|
||||||
|
rp.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
agentproxy.SetAgentProxyHeaders(r, headers)
|
||||||
|
ori(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r := &ReveseProxyRoute{
|
r := &ReveseProxyRoute{
|
||||||
Route: base,
|
Route: base,
|
||||||
rp: rp,
|
rp: rp,
|
||||||
|
@ -72,6 +97,9 @@ func (r *ReveseProxyRoute) String() string {
|
||||||
|
|
||||||
// Start implements task.TaskStarter.
|
// Start implements task.TaskStarter.
|
||||||
func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||||
|
if existing, ok := routes.GetHTTPRoute(r.TargetName()); ok && !r.UseLoadBalance() {
|
||||||
|
return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
||||||
|
}
|
||||||
r.task = parent.Subtask("http."+r.TargetName(), false)
|
r.task = parent.Subtask("http."+r.TargetName(), false)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -85,15 +113,15 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||||
r.HealthMon = waker
|
r.HealthMon = waker
|
||||||
case r.UseHealthCheck():
|
case r.UseHealthCheck():
|
||||||
if r.IsDocker() {
|
if r.IsDocker() {
|
||||||
client, err := docker.ConnectClient(r.Idlewatcher.DockerHost)
|
client, err := docker.NewClient(r.Container.DockerHost)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fallback := monitor.NewHTTPHealthChecker(r.rp.TargetURL, r.HealthCheck)
|
fallback := r.newHealthMonitor()
|
||||||
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Idlewatcher.ContainerID, r.TargetName(), r.HealthCheck, fallback)
|
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Container.ContainerID, r.TargetName(), r.HealthCheck, fallback)
|
||||||
r.task.OnCancel("close_docker_client", client.Close)
|
r.task.OnCancel("close_docker_client", client.Close)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.HealthMon == nil {
|
if r.HealthMon == nil {
|
||||||
r.HealthMon = monitor.NewHTTPHealthMonitor(r.rp.TargetURL, r.HealthCheck)
|
r.HealthMon = r.newHealthMonitor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +205,17 @@ func (r *ReveseProxyRoute) HealthMonitor() health.HealthMonitor {
|
||||||
return r.HealthMon
|
return r.HealthMon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ReveseProxyRoute) newHealthMonitor() interface {
|
||||||
|
health.HealthMonitor
|
||||||
|
health.HealthChecker
|
||||||
|
} {
|
||||||
|
if a := r.Agent(); a != nil {
|
||||||
|
target := monitor.AgentTargetFromURL(r.ProxyURL)
|
||||||
|
return monitor.NewAgentProxiedMonitor(a, r.HealthCheck, target)
|
||||||
|
}
|
||||||
|
return monitor.NewHTTPHealthMonitor(r.ProxyURL, r.HealthCheck)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
|
func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
|
||||||
var lb *loadbalancer.LoadBalancer
|
var lb *loadbalancer.LoadBalancer
|
||||||
cfg := r.LoadBalance
|
cfg := r.LoadBalance
|
||||||
|
@ -186,7 +225,7 @@ func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
|
||||||
linked = l.(*ReveseProxyRoute)
|
linked = l.(*ReveseProxyRoute)
|
||||||
lb = linked.loadBalancer
|
lb = linked.loadBalancer
|
||||||
lb.UpdateConfigIfNeeded(cfg)
|
lb.UpdateConfigIfNeeded(cfg)
|
||||||
if linked.Homepage.IsEmpty() && !r.Homepage.IsEmpty() {
|
if linked.Homepage == nil {
|
||||||
linked.Homepage = r.Homepage
|
linked.Homepage = r.Homepage
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,7 +244,7 @@ func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
|
||||||
}
|
}
|
||||||
r.loadBalancer = lb
|
r.loadBalancer = lb
|
||||||
|
|
||||||
server := loadbalance.NewServer(r.task.Name(), r.rp.TargetURL, r.LoadBalance.Weight, r.handler, r.HealthMon)
|
server := loadbalance.NewServer(r.task.Name(), r.ProxyURL, r.LoadBalance.Weight, r.handler, r.HealthMon)
|
||||||
lb.AddServer(server)
|
lb.AddServer(server)
|
||||||
r.task.OnCancel("lb_remove_server", func() {
|
r.task.OnCancel("lb_remove_server", func() {
|
||||||
lb.RemoveServer(server)
|
lb.RemoveServer(server)
|
||||||
|
|
|
@ -2,21 +2,24 @@ package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
|
"github.com/yusing/go-proxy/internal"
|
||||||
"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"
|
||||||
"github.com/yusing/go-proxy/internal/gperr"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
net "github.com/yusing/go-proxy/internal/net/types"
|
net "github.com/yusing/go-proxy/internal/net/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
"github.com/yusing/go-proxy/internal/net/http/accesslog"
|
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||||
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||||
"github.com/yusing/go-proxy/internal/route/rules"
|
"github.com/yusing/go-proxy/internal/route/rules"
|
||||||
"github.com/yusing/go-proxy/internal/route/types"
|
"github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
|
@ -38,7 +41,7 @@ type (
|
||||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
||||||
LoadBalance *loadbalance.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"`
|
||||||
Homepage *homepage.Item `json:"homepage,omitempty"`
|
Homepage *homepage.ItemConfig `json:"homepage,omitempty"`
|
||||||
AccessLog *accesslog.Config `json:"access_log,omitempty"`
|
AccessLog *accesslog.Config `json:"access_log,omitempty"`
|
||||||
|
|
||||||
Metadata `deserialize:"-"`
|
Metadata `deserialize:"-"`
|
||||||
|
@ -160,6 +163,17 @@ func (r *Route) Type() types.RouteType {
|
||||||
panic(fmt.Errorf("unexpected scheme %s for alias %s", r.Scheme, r.Alias))
|
panic(fmt.Errorf("unexpected scheme %s for alias %s", r.Scheme, r.Alias))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Route) Agent() *agent.AgentConfig {
|
||||||
|
if r.Container == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.Container.Agent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) IsAgent() bool {
|
||||||
|
return r.Container != nil && r.Container.Agent != nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Route) HealthMonitor() health.HealthMonitor {
|
func (r *Route) HealthMonitor() health.HealthMonitor {
|
||||||
return r.impl.HealthMonitor()
|
return r.impl.HealthMonitor()
|
||||||
}
|
}
|
||||||
|
@ -176,8 +190,16 @@ func (r *Route) LoadBalanceConfig() *loadbalance.Config {
|
||||||
return r.LoadBalance
|
return r.LoadBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Route) HomepageConfig() *homepage.Item {
|
func (r *Route) HomepageConfig() *homepage.ItemConfig {
|
||||||
return r.Homepage
|
return r.Homepage.GetOverride(r.Alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) HomepageItem() *homepage.Item {
|
||||||
|
return &homepage.Item{
|
||||||
|
Alias: r.Alias,
|
||||||
|
Provider: r.Provider,
|
||||||
|
ItemConfig: r.HomepageConfig(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Route) ContainerInfo() *docker.Container {
|
func (r *Route) ContainerInfo() *docker.Container {
|
||||||
|
@ -202,7 +224,7 @@ func (r *Route) ShouldExclude() bool {
|
||||||
return true
|
return true
|
||||||
case r.IsZeroPort() && !r.UseIdleWatcher():
|
case r.IsZeroPort() && !r.UseIdleWatcher():
|
||||||
return true
|
return true
|
||||||
case r.Container.IsDatabase && !r.Container.IsExplicit:
|
case !r.Container.IsExplicit && r.Container.IsBlacklisted():
|
||||||
return true
|
return true
|
||||||
case strings.HasPrefix(r.Container.ContainerName, "buildx_"):
|
case strings.HasPrefix(r.Container.ContainerName, "buildx_"):
|
||||||
return true
|
return true
|
||||||
|
@ -244,76 +266,77 @@ func (r *Route) Finalize() {
|
||||||
switch {
|
switch {
|
||||||
case !isDocker:
|
case !isDocker:
|
||||||
r.Host = "localhost"
|
r.Host = "localhost"
|
||||||
case cont.PrivateIP != "":
|
case cont.PrivateHostname != "":
|
||||||
r.Host = cont.PrivateIP
|
r.Host = cont.PrivateHostname
|
||||||
case cont.PublicIP != "":
|
case cont.PublicHostname != "":
|
||||||
r.Host = cont.PublicIP
|
r.Host = cont.PublicHostname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lp, pp := r.Port.Listening, r.Port.Proxy
|
lp, pp := r.Port.Listening, r.Port.Proxy
|
||||||
|
|
||||||
if isDocker {
|
if isDocker {
|
||||||
if port, ok := common.ServiceNamePortMapTCP[cont.ImageName]; ok {
|
scheme, port, ok := getSchemePortByImageName(cont.Image.Name)
|
||||||
|
if ok {
|
||||||
|
if r.Scheme == "" {
|
||||||
|
r.Scheme = types.Scheme(scheme)
|
||||||
|
}
|
||||||
if pp == 0 {
|
if pp == 0 {
|
||||||
pp = port
|
pp = port
|
||||||
}
|
}
|
||||||
if r.Scheme == "" {
|
|
||||||
r.Scheme = "tcp"
|
|
||||||
}
|
}
|
||||||
} else if port, ok := common.ImageNamePortMap[cont.ImageName]; ok {
|
}
|
||||||
|
|
||||||
|
if scheme, port, ok := getSchemePortByAlias(r.Alias); ok {
|
||||||
|
if r.Scheme == "" {
|
||||||
|
r.Scheme = types.Scheme(scheme)
|
||||||
|
}
|
||||||
if pp == 0 {
|
if pp == 0 {
|
||||||
pp = port
|
pp = port
|
||||||
}
|
}
|
||||||
if r.Scheme == "" {
|
|
||||||
r.Scheme = "http"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pp == 0 {
|
if pp == 0 {
|
||||||
switch {
|
switch {
|
||||||
case r.Scheme == "https":
|
case isDocker:
|
||||||
pp = 443
|
|
||||||
case !isDocker:
|
|
||||||
pp = 80
|
|
||||||
default:
|
|
||||||
pp = lowestPort(cont.PrivatePortMapping)
|
pp = lowestPort(cont.PrivatePortMapping)
|
||||||
if pp == 0 {
|
if pp == 0 {
|
||||||
pp = lowestPort(cont.PublicPortMapping)
|
pp = lowestPort(cont.PublicPortMapping)
|
||||||
}
|
}
|
||||||
|
case r.Scheme == "https":
|
||||||
|
pp = 443
|
||||||
|
default:
|
||||||
|
pp = 80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isDocker {
|
if isDocker {
|
||||||
|
if r.Scheme == "" {
|
||||||
|
for _, p := range cont.PublicPortMapping {
|
||||||
|
if p.PrivatePort == uint16(pp) && p.Type == "udp" {
|
||||||
|
r.Scheme = "udp"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// replace private port with public port if using public IP.
|
// replace private port with public port if using public IP.
|
||||||
if r.Host == cont.PublicIP {
|
if r.Host == cont.PublicHostname {
|
||||||
if p, ok := cont.PrivatePortMapping[pp]; ok {
|
if p, ok := cont.PrivatePortMapping[pp]; ok {
|
||||||
pp = int(p.PublicPort)
|
pp = int(p.PublicPort)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
// replace public port with private port if using private IP.
|
// replace public port with private port if using private IP.
|
||||||
if r.Host == cont.PrivateIP {
|
|
||||||
if p, ok := cont.PublicPortMapping[pp]; ok {
|
if p, ok := cont.PublicPortMapping[pp]; ok {
|
||||||
pp = int(p.PrivatePort)
|
pp = int(p.PrivatePort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Scheme == "" {
|
|
||||||
switch {
|
|
||||||
case r.Host == cont.PublicIP && cont.PublicPortMapping[pp].Type == "udp":
|
|
||||||
r.Scheme = "udp"
|
|
||||||
case r.Host == cont.PrivateIP && cont.PrivatePortMapping[pp].Type == "udp":
|
|
||||||
r.Scheme = "udp"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Scheme == "" {
|
if r.Scheme == "" {
|
||||||
switch {
|
switch {
|
||||||
case lp != 0:
|
case lp != 0:
|
||||||
r.Scheme = "tcp"
|
r.Scheme = "tcp"
|
||||||
case strings.HasSuffix(strconv.Itoa(pp), "443"):
|
case pp%1000 == 443:
|
||||||
r.Scheme = "https"
|
r.Scheme = "https"
|
||||||
default: // assume its http
|
default: // assume its http
|
||||||
r.Scheme = "http"
|
r.Scheme = "http"
|
||||||
|
@ -323,7 +346,7 @@ func (r *Route) Finalize() {
|
||||||
r.Port.Listening, r.Port.Proxy = lp, pp
|
r.Port.Listening, r.Port.Proxy = lp, pp
|
||||||
|
|
||||||
if r.HealthCheck == nil {
|
if r.HealthCheck == nil {
|
||||||
r.HealthCheck = health.DefaultHealthConfig
|
r.HealthCheck = health.DefaultHealthConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.HealthCheck.Disable {
|
if !r.HealthCheck.Disable {
|
||||||
|
@ -346,13 +369,68 @@ func (r *Route) Finalize() {
|
||||||
cont.StopMethod = common.StopMethodDefault
|
cont.StopMethod = common.StopMethodDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if r.Homepage.IsEmpty() {
|
func (r *Route) FinalizeHomepageConfig() {
|
||||||
r.Homepage = homepage.NewItem(r.Alias)
|
if r.Alias == "" {
|
||||||
|
panic("alias is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
isDocker := r.Container != nil
|
||||||
|
|
||||||
|
if r.Homepage == nil {
|
||||||
|
r.Homepage = &homepage.ItemConfig{Show: true}
|
||||||
|
}
|
||||||
|
r.Homepage = r.Homepage.GetOverride(r.Alias)
|
||||||
|
|
||||||
|
hp := r.Homepage
|
||||||
|
|
||||||
|
var key string
|
||||||
|
if hp.Name == "" {
|
||||||
|
if r.Container != nil {
|
||||||
|
key = r.Container.Image.Name
|
||||||
|
} else {
|
||||||
|
key = r.Alias
|
||||||
|
}
|
||||||
|
displayName, ok := internal.GetDisplayName(key)
|
||||||
|
if ok {
|
||||||
|
hp.Name = displayName
|
||||||
|
} else {
|
||||||
|
hp.Name = strutils.Title(
|
||||||
|
strings.ReplaceAll(
|
||||||
|
strings.ReplaceAll(key, "-", " "),
|
||||||
|
"_", " ",
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lowestPort(ports map[int]dockertypes.Port) (res int) {
|
if hp.Category == "" {
|
||||||
|
if config.GetInstance().Value().Homepage.UseDefaultCategories {
|
||||||
|
if isDocker {
|
||||||
|
key = r.Container.Image.Name
|
||||||
|
} else {
|
||||||
|
key = strings.ToLower(r.Alias)
|
||||||
|
}
|
||||||
|
if category, ok := homepage.PredefinedCategories[key]; ok {
|
||||||
|
hp.Category = category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hp.Category == "" {
|
||||||
|
switch {
|
||||||
|
case r.UseLoadBalance():
|
||||||
|
hp.Category = "Load-balanced"
|
||||||
|
case isDocker:
|
||||||
|
hp.Category = "Docker"
|
||||||
|
default:
|
||||||
|
hp.Category = "Others"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowestPort(ports map[int]container.Port) (res int) {
|
||||||
cmp := (uint16)(65535)
|
cmp := (uint16)(65535)
|
||||||
for port, v := range ports {
|
for port, v := range ports {
|
||||||
if v.PrivatePort < cmp {
|
if v.PrivatePort < cmp {
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package routequery
|
package routequery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal"
|
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
provider "github.com/yusing/go-proxy/internal/route/provider/types"
|
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
route "github.com/yusing/go-proxy/internal/route/types"
|
route "github.com/yusing/go-proxy/internal/route/types"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHealthInfo(r route.Route) map[string]string {
|
func getHealthInfo(r route.Route) map[string]string {
|
||||||
|
@ -28,13 +25,37 @@ func getHealthInfo(r route.Route) map[string]string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HealthInfoRaw struct {
|
||||||
|
Status health.Status
|
||||||
|
Latency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHealthInfoRaw(r route.Route) *HealthInfoRaw {
|
||||||
|
mon := r.HealthMonitor()
|
||||||
|
if mon == nil {
|
||||||
|
return &HealthInfoRaw{
|
||||||
|
Status: health.StatusUnknown,
|
||||||
|
Latency: time.Duration(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &HealthInfoRaw{
|
||||||
|
Status: mon.Status(),
|
||||||
|
Latency: mon.Latency(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func HealthMap() map[string]map[string]string {
|
func HealthMap() map[string]map[string]string {
|
||||||
healthMap := make(map[string]map[string]string)
|
healthMap := make(map[string]map[string]string, routes.NumRoutes())
|
||||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
routes.RangeRoutes(func(alias string, r route.Route) {
|
||||||
healthMap[alias] = getHealthInfo(r)
|
healthMap[alias] = getHealthInfo(r)
|
||||||
})
|
})
|
||||||
routes.GetStreamRoutes().RangeAll(func(alias string, r route.StreamRoute) {
|
return healthMap
|
||||||
healthMap[alias] = getHealthInfo(r)
|
}
|
||||||
|
|
||||||
|
func HealthInfo() map[string]*HealthInfoRaw {
|
||||||
|
healthMap := make(map[string]*HealthInfoRaw, routes.NumRoutes())
|
||||||
|
routes.RangeRoutes(func(alias string, r route.Route) {
|
||||||
|
healthMap[alias] = getHealthInfoRaw(r)
|
||||||
})
|
})
|
||||||
return healthMap
|
return healthMap
|
||||||
}
|
}
|
||||||
|
@ -43,105 +64,33 @@ func HomepageCategories() []string {
|
||||||
check := make(map[string]struct{})
|
check := make(map[string]struct{})
|
||||||
categories := make([]string, 0)
|
categories := make([]string, 0)
|
||||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
||||||
homepage := r.HomepageConfig()
|
item := r.HomepageConfig()
|
||||||
if homepage.IsEmpty() || homepage.Category == "" {
|
if item == nil || item.Category == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := check[homepage.Category]; ok {
|
if _, ok := check[item.Category]; ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
check[homepage.Category] = struct{}{}
|
check[item.Category] = struct{}{}
|
||||||
categories = append(categories, homepage.Category)
|
categories = append(categories, item.Category)
|
||||||
})
|
})
|
||||||
return categories
|
return categories
|
||||||
}
|
}
|
||||||
|
|
||||||
func HomepageConfig(useDefaultCategories bool, categoryFilter, providerFilter string) homepage.Categories {
|
func HomepageConfig(categoryFilter, providerFilter string) homepage.Homepage {
|
||||||
hpCfg := homepage.NewHomePageConfig()
|
hp := make(homepage.Homepage)
|
||||||
|
|
||||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
||||||
item := r.HomepageConfig()
|
if providerFilter != "" && r.ProviderName() != providerFilter {
|
||||||
|
|
||||||
if item.IsEmpty() {
|
|
||||||
item = homepage.NewItem(alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
if override := item.GetOverride(); override != item {
|
|
||||||
if providerFilter != "" && override.Provider != providerFilter ||
|
|
||||||
categoryFilter != "" && override.Category != categoryFilter {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hpCfg.Add(override)
|
item := r.HomepageItem()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item.Alias = alias
|
|
||||||
item.Provider = r.ProviderName()
|
|
||||||
|
|
||||||
if providerFilter != "" && item.Provider != providerFilter {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.Name == "" {
|
|
||||||
reference := r.TargetName()
|
|
||||||
cont := r.ContainerInfo()
|
|
||||||
if cont != nil {
|
|
||||||
reference = cont.ImageName
|
|
||||||
}
|
|
||||||
name, ok := internal.GetDisplayName(reference)
|
|
||||||
if ok {
|
|
||||||
item.Name = name
|
|
||||||
} else {
|
|
||||||
item.Name = strutils.Title(
|
|
||||||
strings.ReplaceAll(
|
|
||||||
strings.ReplaceAll(alias, "-", " "),
|
|
||||||
"_", " ",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if useDefaultCategories {
|
|
||||||
container := r.ContainerInfo()
|
|
||||||
if container != nil && item.Category == "" {
|
|
||||||
if category, ok := homepage.PredefinedCategories[container.ImageName]; ok {
|
|
||||||
item.Category = category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.Category == "" {
|
|
||||||
if category, ok := homepage.PredefinedCategories[strings.ToLower(alias)]; ok {
|
|
||||||
item.Category = category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if categoryFilter != "" && item.Category != categoryFilter {
|
if categoryFilter != "" && item.Category != categoryFilter {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
hp.Add(item)
|
||||||
switch {
|
|
||||||
case r.IsDocker():
|
|
||||||
if item.Category == "" {
|
|
||||||
item.Category = "Docker"
|
|
||||||
}
|
|
||||||
item.SourceType = string(provider.ProviderTypeDocker)
|
|
||||||
case r.UseLoadBalance():
|
|
||||||
if item.Category == "" {
|
|
||||||
item.Category = "Load-balanced"
|
|
||||||
}
|
|
||||||
item.SourceType = "loadbalancer"
|
|
||||||
default:
|
|
||||||
if item.Category == "" {
|
|
||||||
item.Category = "Others"
|
|
||||||
}
|
|
||||||
item.SourceType = string(provider.ProviderTypeFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
item.AltURL = r.TargetURL().String()
|
|
||||||
hpCfg.Add(item)
|
|
||||||
})
|
})
|
||||||
return hpCfg
|
return hp
|
||||||
}
|
}
|
||||||
|
|
||||||
func RoutesByAlias(typeFilter ...route.RouteType) map[string]route.Route {
|
func RoutesByAlias(typeFilter ...route.RouteType) map[string]route.Route {
|
||||||
|
|
|
@ -10,6 +10,19 @@ var (
|
||||||
streamRoutes = F.NewMapOf[string, types.StreamRoute]()
|
streamRoutes = F.NewMapOf[string, types.StreamRoute]()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func RangeRoutes(callback func(alias string, r types.Route)) {
|
||||||
|
httpRoutes.RangeAll(func(alias string, r types.HTTPRoute) {
|
||||||
|
callback(alias, r)
|
||||||
|
})
|
||||||
|
streamRoutes.RangeAll(func(alias string, r types.StreamRoute) {
|
||||||
|
callback(alias, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NumRoutes() int {
|
||||||
|
return httpRoutes.Size() + streamRoutes.Size()
|
||||||
|
}
|
||||||
|
|
||||||
func GetHTTPRoutes() F.Map[string, types.HTTPRoute] {
|
func GetHTTPRoutes() F.Map[string, types.HTTPRoute] {
|
||||||
return httpRoutes
|
return httpRoutes
|
||||||
}
|
}
|
||||||
|
@ -35,6 +48,14 @@ func GetStreamRoute(alias string) (types.StreamRoute, bool) {
|
||||||
return streamRoutes.Load(alias)
|
return streamRoutes.Load(alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetRoute(alias string) (types.Route, bool) {
|
||||||
|
r, ok := httpRoutes.Load(alias)
|
||||||
|
if ok {
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
return streamRoutes.Load(alias)
|
||||||
|
}
|
||||||
|
|
||||||
func SetHTTPRoute(alias string, r types.HTTPRoute) {
|
func SetHTTPRoute(alias string, r types.HTTPRoute) {
|
||||||
httpRoutes.Store(alias, r)
|
httpRoutes.Store(alias, r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yusing/go-proxy/internal/docker"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
net "github.com/yusing/go-proxy/internal/net/types"
|
net "github.com/yusing/go-proxy/internal/net/types"
|
||||||
"github.com/yusing/go-proxy/internal/route/routes"
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
|
@ -47,6 +47,9 @@ func (r *StreamRoute) String() string {
|
||||||
|
|
||||||
// Start implements task.TaskStarter.
|
// Start implements task.TaskStarter.
|
||||||
func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||||
|
if existing, ok := routes.GetStreamRoute(r.TargetName()); ok {
|
||||||
|
return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
||||||
|
}
|
||||||
r.task = parent.Subtask("stream." + r.TargetName())
|
r.task = parent.Subtask("stream." + r.TargetName())
|
||||||
r.Stream = NewStream(r)
|
r.Stream = NewStream(r)
|
||||||
parent.OnCancel("finish", func() {
|
parent.OnCancel("finish", func() {
|
||||||
|
@ -64,10 +67,10 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||||
r.HealthMon = waker
|
r.HealthMon = waker
|
||||||
case r.UseHealthCheck():
|
case r.UseHealthCheck():
|
||||||
if r.IsDocker() {
|
if r.IsDocker() {
|
||||||
client, err := docker.ConnectClient(r.IdlewatcherConfig().DockerHost)
|
client, err := docker.NewClient(r.Container.DockerHost)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fallback := monitor.NewRawHealthChecker(r.TargetURL(), r.HealthCheck)
|
fallback := monitor.NewRawHealthChecker(r.TargetURL(), r.HealthCheck)
|
||||||
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.IdlewatcherConfig().ContainerID, r.TargetName(), r.HealthCheck, fallback)
|
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Container.ContainerID, r.TargetName(), r.HealthCheck, fallback)
|
||||||
r.task.OnCancel("close_docker_client", client.Close)
|
r.task.OnCancel("close_docker_client", client.Close)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package types
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
"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"
|
||||||
"github.com/yusing/go-proxy/internal/homepage"
|
"github.com/yusing/go-proxy/internal/homepage"
|
||||||
|
@ -10,7 +11,7 @@ import (
|
||||||
"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"
|
||||||
|
|
||||||
loadbalance "github.com/yusing/go-proxy/internal/net/http/loadbalancer/types"
|
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -28,10 +29,14 @@ type (
|
||||||
IdlewatcherConfig() *idlewatcher.Config
|
IdlewatcherConfig() *idlewatcher.Config
|
||||||
HealthCheckConfig() *health.HealthCheckConfig
|
HealthCheckConfig() *health.HealthCheckConfig
|
||||||
LoadBalanceConfig() *loadbalance.Config
|
LoadBalanceConfig() *loadbalance.Config
|
||||||
HomepageConfig() *homepage.Item
|
HomepageConfig() *homepage.ItemConfig
|
||||||
|
HomepageItem() *homepage.Item
|
||||||
ContainerInfo() *docker.Container
|
ContainerInfo() *docker.Container
|
||||||
|
|
||||||
|
Agent() *agent.AgentConfig
|
||||||
|
|
||||||
IsDocker() bool
|
IsDocker() bool
|
||||||
|
IsAgent() bool
|
||||||
UseLoadBalance() bool
|
UseLoadBalance() bool
|
||||||
UseIdleWatcher() bool
|
UseIdleWatcher() bool
|
||||||
UseHealthCheck() bool
|
UseHealthCheck() bool
|
||||||
|
|
Loading…
Add table
Reference in a new issue