mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-25 10:18:59 +02:00
merge: main branch
This commit is contained in:
@@ -57,7 +57,7 @@ func NewFileServer(base *Route) (*FileServer, gperr.Error) {
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||
s.task = parent.Subtask("fileserver."+s.TargetName(), false)
|
||||
s.task = parent.Subtask("fileserver."+s.Name(), false)
|
||||
|
||||
pathPatterns := s.PathPatterns
|
||||
switch {
|
||||
@@ -92,7 +92,7 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||
}
|
||||
|
||||
if common.PrometheusEnabled {
|
||||
metricsLogger := metricslogger.NewMetricsLogger(s.TargetName())
|
||||
metricsLogger := metricslogger.NewMetricsLogger(s.Name())
|
||||
s.handler = metricsLogger.GetHandler(s.handler)
|
||||
s.task.OnCancel("reset_metrics", metricsLogger.ResetMetrics)
|
||||
}
|
||||
@@ -104,9 +104,9 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||
}
|
||||
}
|
||||
|
||||
routes.SetHTTPRoute(s.TargetName(), s)
|
||||
routes.HTTP.Add(s)
|
||||
s.task.OnCancel("entrypoint_remove_route", func() {
|
||||
routes.DeleteHTTPRoute(s.TargetName())
|
||||
routes.HTTP.Del(s)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package provider
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -21,11 +21,11 @@ func TestParseDockerLabels(t *testing.T) {
|
||||
ExpectNoError(t, yaml.Unmarshal(testDockerLabelsYAML, &labels))
|
||||
|
||||
routes, err := provider.routesFromContainerLabels(
|
||||
docker.FromDocker(&types.Container{
|
||||
docker.FromDocker(&container.SummaryTrimmed{
|
||||
Names: []string{"container"},
|
||||
Labels: labels,
|
||||
State: "running",
|
||||
Ports: []types.Port{
|
||||
Ports: []container.Port{
|
||||
{Type: "tcp", PrivatePort: 1234, PublicPort: 1234},
|
||||
},
|
||||
}, "/var/run/docker.sock"),
|
||||
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
D "github.com/yusing/go-proxy/internal/docker"
|
||||
"github.com/yusing/go-proxy/internal/route"
|
||||
T "github.com/yusing/go-proxy/internal/route/types"
|
||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
expect "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
)
|
||||
|
||||
var dummyNames = []string{"/a"}
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
testDockerIP = "172.17.0.123"
|
||||
)
|
||||
|
||||
func makeRoutes(cont *types.Container, dockerHostIP ...string) route.Routes {
|
||||
func makeRoutes(cont *container.SummaryTrimmed, dockerHostIP ...string) route.Routes {
|
||||
var p DockerProvider
|
||||
var host string
|
||||
if len(dockerHostIP) > 0 {
|
||||
@@ -64,15 +64,15 @@ func TestApplyLabel(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
entries := makeRoutes(&types.Container{
|
||||
entries := makeRoutes(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
Labels: map[string]string{
|
||||
D.LabelAliases: "a,b",
|
||||
D.LabelIdleTimeout: "",
|
||||
D.LabelStopMethod: common.StopMethodDefault,
|
||||
D.LabelIdleTimeout: "10s",
|
||||
D.LabelStopMethod: "stop",
|
||||
D.LabelStopSignal: "SIGTERM",
|
||||
D.LabelStopTimeout: common.StopTimeoutDefault,
|
||||
D.LabelWakeTimeout: common.WakeTimeoutDefault,
|
||||
D.LabelStopTimeout: "1h",
|
||||
D.LabelWakeTimeout: "10s",
|
||||
"proxy.*.no_tls_verify": "true",
|
||||
"proxy.*.scheme": "https",
|
||||
"proxy.*.host": "app",
|
||||
@@ -88,54 +88,55 @@ func TestApplyLabel(t *testing.T) {
|
||||
})
|
||||
|
||||
a, ok := entries["a"]
|
||||
ExpectTrue(t, ok)
|
||||
expect.True(t, ok)
|
||||
b, ok := entries["b"]
|
||||
ExpectTrue(t, ok)
|
||||
expect.True(t, ok)
|
||||
|
||||
ExpectEqual(t, a.Scheme, "https")
|
||||
ExpectEqual(t, b.Scheme, "https")
|
||||
expect.Equal(t, a.Scheme, "https")
|
||||
expect.Equal(t, b.Scheme, "https")
|
||||
|
||||
ExpectEqual(t, a.Host, "app")
|
||||
ExpectEqual(t, b.Host, "app")
|
||||
expect.Equal(t, a.Host, "app")
|
||||
expect.Equal(t, b.Host, "app")
|
||||
|
||||
ExpectEqual(t, a.Port.Proxy, 4567)
|
||||
ExpectEqual(t, b.Port.Proxy, 4567)
|
||||
expect.Equal(t, a.Port.Proxy, 4567)
|
||||
expect.Equal(t, b.Port.Proxy, 4567)
|
||||
|
||||
ExpectTrue(t, a.NoTLSVerify)
|
||||
ExpectTrue(t, b.NoTLSVerify)
|
||||
expect.True(t, a.NoTLSVerify)
|
||||
expect.True(t, b.NoTLSVerify)
|
||||
|
||||
ExpectEqual(t, a.PathPatterns, pathPatternsExpect)
|
||||
ExpectEqual(t, len(b.PathPatterns), 0)
|
||||
expect.Equal(t, a.PathPatterns, pathPatternsExpect)
|
||||
expect.Equal(t, len(b.PathPatterns), 0)
|
||||
|
||||
ExpectEqual(t, a.Middlewares, middlewaresExpect)
|
||||
ExpectEqual(t, len(b.Middlewares), 0)
|
||||
expect.Equal(t, a.Middlewares, middlewaresExpect)
|
||||
expect.Equal(t, len(b.Middlewares), 0)
|
||||
|
||||
ExpectEqual(t, a.Container.IdleTimeout, "")
|
||||
ExpectEqual(t, b.Container.IdleTimeout, "")
|
||||
expect.NotNil(t, a.Container)
|
||||
expect.NotNil(t, b.Container)
|
||||
expect.NotNil(t, a.Container.IdlewatcherConfig)
|
||||
expect.NotNil(t, b.Container.IdlewatcherConfig)
|
||||
|
||||
ExpectEqual(t, a.Container.StopTimeout, common.StopTimeoutDefault)
|
||||
ExpectEqual(t, b.Container.StopTimeout, common.StopTimeoutDefault)
|
||||
expect.Equal(t, a.Container.IdlewatcherConfig.IdleTimeout, 10*time.Second)
|
||||
expect.Equal(t, b.Container.IdlewatcherConfig.IdleTimeout, 10*time.Second)
|
||||
expect.Equal(t, a.Container.IdlewatcherConfig.StopTimeout, time.Hour)
|
||||
expect.Equal(t, b.Container.IdlewatcherConfig.StopTimeout, time.Hour)
|
||||
expect.Equal(t, a.Container.IdlewatcherConfig.StopMethod, "stop")
|
||||
expect.Equal(t, b.Container.IdlewatcherConfig.StopMethod, "stop")
|
||||
expect.Equal(t, a.Container.IdlewatcherConfig.WakeTimeout, 10*time.Second)
|
||||
expect.Equal(t, b.Container.IdlewatcherConfig.WakeTimeout, 10*time.Second)
|
||||
expect.Equal(t, a.Container.IdlewatcherConfig.StopSignal, "SIGTERM")
|
||||
expect.Equal(t, b.Container.IdlewatcherConfig.StopSignal, "SIGTERM")
|
||||
|
||||
ExpectEqual(t, a.Container.StopMethod, common.StopMethodDefault)
|
||||
ExpectEqual(t, b.Container.StopMethod, common.StopMethodDefault)
|
||||
expect.Equal(t, a.Homepage.Show, true)
|
||||
expect.Equal(t, a.Homepage.Icon.Value, "png/adguard-home.png")
|
||||
expect.Equal(t, a.Homepage.Icon.Extra.FileType, "png")
|
||||
expect.Equal(t, a.Homepage.Icon.Extra.Name, "adguard-home")
|
||||
|
||||
ExpectEqual(t, a.Container.WakeTimeout, common.WakeTimeoutDefault)
|
||||
ExpectEqual(t, b.Container.WakeTimeout, common.WakeTimeoutDefault)
|
||||
|
||||
ExpectEqual(t, a.Container.StopSignal, "SIGTERM")
|
||||
ExpectEqual(t, b.Container.StopSignal, "SIGTERM")
|
||||
|
||||
ExpectEqual(t, a.Homepage.Show, true)
|
||||
ExpectEqual(t, a.Homepage.Icon.Value, "png/adguard-home.png")
|
||||
ExpectEqual(t, a.Homepage.Icon.Extra.FileType, "png")
|
||||
ExpectEqual(t, a.Homepage.Icon.Extra.Name, "adguard-home")
|
||||
|
||||
ExpectEqual(t, a.HealthCheck.Path, "/ping")
|
||||
ExpectEqual(t, a.HealthCheck.Interval, 10*time.Second)
|
||||
expect.Equal(t, a.HealthCheck.Path, "/ping")
|
||||
expect.Equal(t, a.HealthCheck.Interval, 10*time.Second)
|
||||
}
|
||||
|
||||
func TestApplyLabelWithAlias(t *testing.T) {
|
||||
entries := makeRoutes(&types.Container{
|
||||
entries := makeRoutes(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
State: "running",
|
||||
Labels: map[string]string{
|
||||
@@ -162,7 +163,7 @@ func TestApplyLabelWithAlias(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApplyLabelWithRef(t *testing.T) {
|
||||
entries := makeRoutes(&types.Container{
|
||||
entries := makeRoutes(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
State: "running",
|
||||
Labels: map[string]string{
|
||||
@@ -190,7 +191,7 @@ func TestApplyLabelWithRef(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApplyLabelWithRefIndexError(t *testing.T) {
|
||||
c := D.FromDocker(&types.Container{
|
||||
c := D.FromDocker(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
State: "running",
|
||||
Labels: map[string]string{
|
||||
@@ -204,7 +205,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) {
|
||||
_, err := p.routesFromContainerLabels(c)
|
||||
ExpectError(t, ErrAliasRefIndexOutOfRange, err)
|
||||
|
||||
c = D.FromDocker(&types.Container{
|
||||
c = D.FromDocker(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
State: "running",
|
||||
Labels: map[string]string{
|
||||
@@ -217,7 +218,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDynamicAliases(t *testing.T) {
|
||||
c := &types.Container{
|
||||
c := &container.SummaryTrimmed{
|
||||
Names: []string{"app1"},
|
||||
State: "running",
|
||||
Labels: map[string]string{
|
||||
@@ -240,7 +241,7 @@ func TestDynamicAliases(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDisableHealthCheck(t *testing.T) {
|
||||
c := &types.Container{
|
||||
c := &container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
State: "running",
|
||||
Labels: map[string]string{
|
||||
@@ -254,7 +255,7 @@ func TestDisableHealthCheck(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPublicIPLocalhost(t *testing.T) {
|
||||
c := &types.Container{Names: dummyNames, State: "running"}
|
||||
c := &container.SummaryTrimmed{Names: dummyNames, State: "running"}
|
||||
r, ok := makeRoutes(c)["a"]
|
||||
ExpectTrue(t, ok)
|
||||
ExpectEqual(t, r.Container.PublicHostname, "127.0.0.1")
|
||||
@@ -262,7 +263,7 @@ func TestPublicIPLocalhost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPublicIPRemote(t *testing.T) {
|
||||
c := &types.Container{Names: dummyNames, State: "running"}
|
||||
c := &container.SummaryTrimmed{Names: dummyNames, State: "running"}
|
||||
raw, ok := makeRoutes(c, testIP)["a"]
|
||||
ExpectTrue(t, ok)
|
||||
ExpectEqual(t, raw.Container.PublicHostname, testIP)
|
||||
@@ -270,10 +271,10 @@ func TestPublicIPRemote(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrivateIPLocalhost(t *testing.T) {
|
||||
c := &types.Container{
|
||||
c := &container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
NetworkSettings: &types.SummaryNetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
NetworkSettings: &container.NetworkSettingsSummaryTrimmed{
|
||||
Networks: map[string]*struct{ IPAddress string }{
|
||||
"network": {
|
||||
IPAddress: testDockerIP,
|
||||
},
|
||||
@@ -287,11 +288,11 @@ func TestPrivateIPLocalhost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrivateIPRemote(t *testing.T) {
|
||||
c := &types.Container{
|
||||
c := &container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
State: "running",
|
||||
NetworkSettings: &types.SummaryNetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
NetworkSettings: &container.NetworkSettingsSummaryTrimmed{
|
||||
Networks: map[string]*struct{ IPAddress string }{
|
||||
"network": {
|
||||
IPAddress: testDockerIP,
|
||||
},
|
||||
@@ -309,11 +310,11 @@ func TestStreamDefaultValues(t *testing.T) {
|
||||
privPort := uint16(1234)
|
||||
pubPort := uint16(4567)
|
||||
privIP := "172.17.0.123"
|
||||
cont := &types.Container{
|
||||
cont := &container.SummaryTrimmed{
|
||||
Names: []string{"a"},
|
||||
State: "running",
|
||||
NetworkSettings: &types.SummaryNetworkSettings{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
NetworkSettings: &container.NetworkSettingsSummaryTrimmed{
|
||||
Networks: map[string]*struct{ IPAddress string }{
|
||||
"network": {
|
||||
IPAddress: privIP,
|
||||
},
|
||||
@@ -346,7 +347,7 @@ func TestStreamDefaultValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExplicitExclude(t *testing.T) {
|
||||
r, ok := makeRoutes(&types.Container{
|
||||
r, ok := makeRoutes(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
Labels: map[string]string{
|
||||
D.LabelAliases: "a",
|
||||
@@ -360,17 +361,17 @@ func TestExplicitExclude(t *testing.T) {
|
||||
|
||||
func TestImplicitExcludeDatabase(t *testing.T) {
|
||||
t.Run("mount path detection", func(t *testing.T) {
|
||||
r, ok := makeRoutes(&types.Container{
|
||||
r, ok := makeRoutes(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
Mounts: []types.MountPoint{
|
||||
{Source: "/data", Destination: "/var/lib/postgresql/data"},
|
||||
Mounts: []container.MountPointTrimmed{
|
||||
{Destination: "/var/lib/postgresql/data"},
|
||||
},
|
||||
})["a"]
|
||||
ExpectTrue(t, ok)
|
||||
ExpectTrue(t, r.ShouldExclude())
|
||||
})
|
||||
t.Run("exposed port detection", func(t *testing.T) {
|
||||
r, ok := makeRoutes(&types.Container{
|
||||
r, ok := makeRoutes(&container.SummaryTrimmed{
|
||||
Names: dummyNames,
|
||||
Ports: []types.Port{
|
||||
{Type: "tcp", PrivatePort: 5432, PublicPort: 5432},
|
||||
|
||||
@@ -8,10 +8,8 @@ import (
|
||||
"github.com/yusing/go-proxy/agent/pkg/agentproxy"
|
||||
"github.com/yusing/go-proxy/internal/api/v1/favicon"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/idlewatcher"
|
||||
gphttp "github.com/yusing/go-proxy/internal/net/gphttp"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer"
|
||||
@@ -60,7 +58,7 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||
}
|
||||
}
|
||||
|
||||
service := base.TargetName()
|
||||
service := base.Name()
|
||||
rp := reverseproxy.NewReverseProxy(service, proxyURL, trans)
|
||||
|
||||
if len(base.Middlewares) > 0 {
|
||||
@@ -91,38 +89,24 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *ReveseProxyRoute) String() string {
|
||||
return r.TargetName()
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
if existing, ok := routes.GetHTTPRoute(r.TargetName()); ok && !r.UseLoadBalance() {
|
||||
if existing, ok := routes.HTTP.Get(r.Key()); 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.Name(), false)
|
||||
|
||||
switch {
|
||||
case r.UseIdleWatcher():
|
||||
waker, err := idlewatcher.NewHTTPWaker(parent, r, r.rp)
|
||||
waker, err := idlewatcher.NewWatcher(parent, r)
|
||||
if err != nil {
|
||||
r.task.Finish(err)
|
||||
return err
|
||||
return gperr.Wrap(err)
|
||||
}
|
||||
r.handler = waker
|
||||
r.HealthMon = waker
|
||||
case r.UseHealthCheck():
|
||||
if r.IsDocker() {
|
||||
client, err := docker.NewClient(r.Container.DockerHost)
|
||||
if err == nil {
|
||||
fallback := r.newHealthMonitor()
|
||||
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Container.ContainerID, r.TargetName(), r.HealthCheck, fallback)
|
||||
r.task.OnCancel("close_docker_client", client.Close)
|
||||
}
|
||||
}
|
||||
if r.HealthMon == nil {
|
||||
r.HealthMon = r.newHealthMonitor()
|
||||
}
|
||||
r.HealthMon = monitor.NewMonitor(r)
|
||||
}
|
||||
|
||||
if r.UseAccessLog() {
|
||||
@@ -134,32 +118,8 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
}
|
||||
}
|
||||
|
||||
if r.handler == nil {
|
||||
pathPatterns := r.PathPatterns
|
||||
switch {
|
||||
case len(pathPatterns) == 0:
|
||||
r.handler = r.rp
|
||||
case len(pathPatterns) == 1 && pathPatterns[0] == "/":
|
||||
r.handler = r.rp
|
||||
default:
|
||||
logging.Warn().
|
||||
Str("route", r.TargetName()).
|
||||
Msg("`path_patterns` for reverse proxy is deprecated. Use `rules` instead.")
|
||||
mux := gphttp.NewServeMux()
|
||||
patErrs := gperr.NewBuilder("invalid path pattern(s)")
|
||||
for _, p := range pathPatterns {
|
||||
patErrs.Add(mux.HandleFunc(p, r.rp.HandlerFunc))
|
||||
}
|
||||
if err := patErrs.Error(); err != nil {
|
||||
r.task.Finish(err)
|
||||
return err
|
||||
}
|
||||
r.handler = mux
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Rules) > 0 {
|
||||
r.handler = r.Rules.BuildHandler(r.TargetName(), r.handler)
|
||||
r.handler = r.Rules.BuildHandler(r.Name(), r.handler)
|
||||
}
|
||||
|
||||
if r.HealthMon != nil {
|
||||
@@ -169,7 +129,7 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
}
|
||||
|
||||
if common.PrometheusEnabled {
|
||||
metricsLogger := metricslogger.NewMetricsLogger(r.TargetName())
|
||||
metricsLogger := metricslogger.NewMetricsLogger(r.Name())
|
||||
r.handler = metricsLogger.GetHandler(r.handler)
|
||||
r.task.OnCancel("reset_metrics", metricsLogger.ResetMetrics)
|
||||
}
|
||||
@@ -177,9 +137,9 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
if r.UseLoadBalance() {
|
||||
r.addToLoadBalancer(parent)
|
||||
} else {
|
||||
routes.SetHTTPRoute(r.TargetName(), r)
|
||||
r.task.OnCancel("entrypoint_remove_route", func() {
|
||||
routes.DeleteHTTPRoute(r.TargetName())
|
||||
routes.HTTP.Add(r)
|
||||
r.task.OnFinished("entrypoint_remove_route", func() {
|
||||
routes.HTTP.Del(r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -205,21 +165,10 @@ func (r *ReveseProxyRoute) HealthMonitor() health.HealthMonitor {
|
||||
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) {
|
||||
var lb *loadbalancer.LoadBalancer
|
||||
cfg := r.LoadBalance
|
||||
l, ok := routes.GetHTTPRoute(cfg.Link)
|
||||
l, ok := routes.HTTP.Get(cfg.Link)
|
||||
var linked *ReveseProxyRoute
|
||||
if ok {
|
||||
linked = l.(*ReveseProxyRoute)
|
||||
@@ -240,7 +189,10 @@ func (r *ReveseProxyRoute) addToLoadBalancer(parent task.Parent) {
|
||||
loadBalancer: lb,
|
||||
handler: lb,
|
||||
}
|
||||
routes.SetHTTPRoute(cfg.Link, linked)
|
||||
routes.HTTP.Add(linked)
|
||||
r.task.OnFinished("entrypoint_remove_route", func() {
|
||||
routes.HTTP.Del(linked)
|
||||
})
|
||||
}
|
||||
r.loadBalancer = lb
|
||||
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
netutils "github.com/yusing/go-proxy/internal/net"
|
||||
net "github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/proxmox"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
@@ -20,8 +25,9 @@ import (
|
||||
config "github.com/yusing/go-proxy/internal/config/types"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/accesslog"
|
||||
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/route/rules"
|
||||
"github.com/yusing/go-proxy/internal/route/types"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
)
|
||||
|
||||
@@ -30,12 +36,12 @@ type (
|
||||
_ utils.NoCopy
|
||||
|
||||
Alias string `json:"alias"`
|
||||
Scheme types.Scheme `json:"scheme,omitempty"`
|
||||
Scheme route.Scheme `json:"scheme,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Port types.Port `json:"port,omitempty"`
|
||||
Port route.Port `json:"port,omitempty"`
|
||||
Root string `json:"root,omitempty"`
|
||||
|
||||
types.HTTPConfig
|
||||
route.HTTPConfig
|
||||
PathPatterns []string `json:"path_patterns,omitempty"`
|
||||
Rules rules.Rules `json:"rules,omitempty" validate:"omitempty,unique=Name"`
|
||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
||||
@@ -44,6 +50,8 @@ type (
|
||||
Homepage *homepage.ItemConfig `json:"homepage,omitempty"`
|
||||
AccessLog *accesslog.Config `json:"access_log,omitempty"`
|
||||
|
||||
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
|
||||
|
||||
Metadata `deserialize:"-"`
|
||||
}
|
||||
|
||||
@@ -53,17 +61,18 @@ type (
|
||||
Provider string `json:"provider,omitempty"`
|
||||
|
||||
// private fields
|
||||
LisURL *net.URL `json:"lurl,omitempty"`
|
||||
ProxyURL *net.URL `json:"purl,omitempty"`
|
||||
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
|
||||
LisURL *net.URL `json:"lurl,omitempty"`
|
||||
ProxyURL *net.URL `json:"purl,omitempty"`
|
||||
|
||||
impl types.Route
|
||||
impl routes.Route
|
||||
isValidated bool
|
||||
lastError gperr.Error
|
||||
}
|
||||
Routes map[string]*Route
|
||||
)
|
||||
|
||||
const DefaultHost = "localhost"
|
||||
|
||||
func (r Routes) Contains(alias string) bool {
|
||||
_, ok := r[alias]
|
||||
return ok
|
||||
@@ -76,12 +85,79 @@ func (r *Route) Validate() gperr.Error {
|
||||
r.isValidated = true
|
||||
r.Finalize()
|
||||
|
||||
if r.Idlewatcher != nil && r.Idlewatcher.Proxmox != nil {
|
||||
node := r.Idlewatcher.Proxmox.Node
|
||||
vmid := r.Idlewatcher.Proxmox.VMID
|
||||
if node == "" {
|
||||
return gperr.Errorf("node (proxmox node name) is required")
|
||||
}
|
||||
if vmid <= 0 {
|
||||
return gperr.Errorf("vmid (lxc id) is required")
|
||||
}
|
||||
if r.Host == DefaultHost {
|
||||
containerName := r.Idlewatcher.ContainerName()
|
||||
// get ip addresses of the vmid
|
||||
node, ok := proxmox.Nodes.Get(node)
|
||||
if !ok {
|
||||
return gperr.Errorf("proxmox node %s not found in pool", node)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ips, err := node.LXCGetIPs(ctx, vmid)
|
||||
if err != nil {
|
||||
return gperr.Errorf("failed to get ip addresses of vmid %d: %w", vmid, err)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return gperr.Multiline().
|
||||
Addf("no ip addresses found for %s", containerName).
|
||||
Adds("make sure you have set static ip address for container instead of dhcp").
|
||||
Subject(containerName)
|
||||
}
|
||||
|
||||
l := logging.With().Str("container", containerName).Logger()
|
||||
|
||||
l.Info().Msg("checking if container is running")
|
||||
running, err := node.LXCIsRunning(ctx, vmid)
|
||||
if err != nil {
|
||||
return gperr.New("failed to check container state").With(err)
|
||||
}
|
||||
|
||||
if !running {
|
||||
l.Info().Msg("starting container")
|
||||
if err := node.LXCAction(ctx, vmid, proxmox.LXCStart); err != nil {
|
||||
return gperr.New("failed to start container").With(err)
|
||||
}
|
||||
}
|
||||
|
||||
l.Info().Msgf("finding reachable ip addresses")
|
||||
errs := gperr.NewBuilder("failed to find reachable ip addresses")
|
||||
for _, ip := range ips {
|
||||
if err := netutils.PingTCP(ctx, ip, r.Port.Proxy); err != nil {
|
||||
errs.Add(gperr.Unwrap(err).Subjectf("%s:%d", ip, r.Port.Proxy))
|
||||
} else {
|
||||
r.Host = ip.String()
|
||||
l.Info().Msgf("using ip %s", r.Host)
|
||||
break
|
||||
}
|
||||
}
|
||||
if r.Host == DefaultHost {
|
||||
return gperr.Multiline().
|
||||
Addf("no reachable ip addresses found, tried %d IPs", len(ips)).
|
||||
With(errs.Error()).
|
||||
Subject(containerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return error if route is localhost:<godoxy_port>
|
||||
switch r.Host {
|
||||
case "localhost", "127.0.0.1":
|
||||
switch r.Port.Proxy {
|
||||
case common.ProxyHTTPPort, common.ProxyHTTPSPort, common.APIHTTPPort:
|
||||
if r.Scheme.IsReverseProxy() || r.Scheme == types.SchemeTCP {
|
||||
if r.Scheme.IsReverseProxy() || r.Scheme == route.SchemeTCP {
|
||||
return gperr.Errorf("localhost:%d is reserved for godoxy", r.Port.Proxy)
|
||||
}
|
||||
}
|
||||
@@ -89,29 +165,27 @@ func (r *Route) Validate() gperr.Error {
|
||||
|
||||
errs := gperr.NewBuilder("entry validation failed")
|
||||
|
||||
var impl types.Route
|
||||
var impl routes.Route
|
||||
var err gperr.Error
|
||||
|
||||
switch r.Scheme {
|
||||
case types.SchemeFileServer:
|
||||
if r.Scheme == route.SchemeFileServer {
|
||||
r.impl, err = NewFileServer(r)
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
}
|
||||
case types.SchemeHTTP, types.SchemeHTTPS:
|
||||
if r.Port.Listening != 0 {
|
||||
errs.Addf("unexpected listening port for %s scheme", r.Scheme)
|
||||
}
|
||||
fallthrough
|
||||
case types.SchemeTCP, types.SchemeUDP:
|
||||
r.LisURL = gperr.Collect(errs, net.ParseURL, fmt.Sprintf("%s://:%d", r.Scheme, r.Port.Listening))
|
||||
fallthrough
|
||||
default:
|
||||
if r.LoadBalance != nil && r.LoadBalance.Link == "" {
|
||||
r.LoadBalance = nil
|
||||
r.ProxyURL = gperr.Collect(errs, net.ParseURL, "file://"+r.Root)
|
||||
r.Host = ""
|
||||
r.Port.Proxy = 0
|
||||
} else {
|
||||
switch r.Scheme {
|
||||
case route.SchemeHTTP, route.SchemeHTTPS:
|
||||
if r.Port.Listening != 0 {
|
||||
errs.Addf("unexpected listening port for %s scheme", r.Scheme)
|
||||
}
|
||||
case route.SchemeTCP, route.SchemeUDP:
|
||||
r.LisURL = gperr.Collect(errs, net.ParseURL, fmt.Sprintf("%s://:%d", r.Scheme, r.Port.Listening))
|
||||
}
|
||||
r.ProxyURL = gperr.Collect(errs, net.ParseURL, fmt.Sprintf("%s://%s:%d", r.Scheme, r.Host, r.Port.Proxy))
|
||||
r.Idlewatcher = gperr.Collect(errs, idlewatcher.ValidateConfig, r.Container)
|
||||
}
|
||||
|
||||
if !r.UseHealthCheck() && (r.UseLoadBalance() || r.UseIdleWatcher()) {
|
||||
@@ -120,15 +194,15 @@ func (r *Route) Validate() gperr.Error {
|
||||
|
||||
if errs.HasError() {
|
||||
r.lastError = errs.Error()
|
||||
return r.lastError
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
switch r.Scheme {
|
||||
case types.SchemeFileServer:
|
||||
case route.SchemeFileServer:
|
||||
impl, err = NewFileServer(r)
|
||||
case types.SchemeHTTP, types.SchemeHTTPS:
|
||||
case route.SchemeHTTP, route.SchemeHTTPS:
|
||||
impl, err = NewReverseProxyRoute(r)
|
||||
case types.SchemeTCP, types.SchemeUDP:
|
||||
case route.SchemeTCP, route.SchemeUDP:
|
||||
impl, err = NewStreamRoute(r)
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected scheme %s for alias %s", r.Scheme, r.Alias))
|
||||
@@ -167,20 +241,33 @@ func (r *Route) ProviderName() string {
|
||||
return r.Provider
|
||||
}
|
||||
|
||||
func (r *Route) TargetName() string {
|
||||
return r.Alias
|
||||
}
|
||||
|
||||
func (r *Route) TargetURL() *net.URL {
|
||||
return r.ProxyURL
|
||||
}
|
||||
|
||||
func (r *Route) Type() types.RouteType {
|
||||
func (r *Route) Reference() string {
|
||||
if r.Container != nil {
|
||||
return r.Container.Image.Name
|
||||
}
|
||||
return r.Alias
|
||||
}
|
||||
|
||||
// Name implements pool.Object.
|
||||
func (r *Route) Name() string {
|
||||
return r.Alias
|
||||
}
|
||||
|
||||
// Key implements pool.Object.
|
||||
func (r *Route) Key() string {
|
||||
return r.Alias
|
||||
}
|
||||
|
||||
func (r *Route) Type() route.RouteType {
|
||||
switch r.Scheme {
|
||||
case types.SchemeHTTP, types.SchemeHTTPS, types.SchemeFileServer:
|
||||
return types.RouteTypeHTTP
|
||||
case types.SchemeTCP, types.SchemeUDP:
|
||||
return types.RouteTypeStream
|
||||
case route.SchemeHTTP, route.SchemeHTTPS, route.SchemeFileServer:
|
||||
return route.RouteTypeHTTP
|
||||
case route.SchemeTCP, route.SchemeUDP:
|
||||
return route.RouteTypeStream
|
||||
}
|
||||
panic(fmt.Errorf("unexpected scheme %s for alias %s", r.Scheme, r.Alias))
|
||||
}
|
||||
@@ -301,7 +388,7 @@ func (r *Route) Finalize() {
|
||||
scheme, port, ok := getSchemePortByImageName(cont.Image.Name)
|
||||
if ok {
|
||||
if r.Scheme == "" {
|
||||
r.Scheme = types.Scheme(scheme)
|
||||
r.Scheme = route.Scheme(scheme)
|
||||
}
|
||||
if pp == 0 {
|
||||
pp = port
|
||||
@@ -311,7 +398,7 @@ func (r *Route) Finalize() {
|
||||
|
||||
if scheme, port, ok := getSchemePortByAlias(r.Alias); ok {
|
||||
if r.Scheme == "" {
|
||||
r.Scheme = types.Scheme(scheme)
|
||||
r.Scheme = route.Scheme(scheme)
|
||||
}
|
||||
if pp == 0 {
|
||||
pp = port
|
||||
@@ -379,18 +466,6 @@ func (r *Route) Finalize() {
|
||||
r.HealthCheck.Timeout = common.HealthCheckTimeoutDefault
|
||||
}
|
||||
}
|
||||
|
||||
if isDocker && cont.IdleTimeout != "" {
|
||||
if cont.WakeTimeout == "" {
|
||||
cont.WakeTimeout = common.WakeTimeoutDefault
|
||||
}
|
||||
if cont.StopTimeout == "" {
|
||||
cont.StopTimeout = common.StopTimeoutDefault
|
||||
}
|
||||
if cont.StopMethod == "" {
|
||||
cont.StopMethod = common.StopMethodDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Route) FinalizeHomepageConfig() {
|
||||
|
||||
@@ -3,59 +3,45 @@ package route
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||
"github.com/yusing/go-proxy/internal/route/types"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
expect "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
)
|
||||
|
||||
func TestRouteValidate(t *testing.T) {
|
||||
t.Run("AlreadyValidated", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80},
|
||||
Metadata: Metadata{
|
||||
isValidated: true,
|
||||
},
|
||||
}
|
||||
err := r.Validate()
|
||||
require.NoError(t, err, "Validate should return nil for already validated route")
|
||||
})
|
||||
|
||||
t.Run("ReservedPort", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeHTTP,
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "localhost",
|
||||
Port: types.Port{Proxy: common.ProxyHTTPPort},
|
||||
Port: route.Port{Proxy: common.ProxyHTTPPort},
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err, "Validate should return error for localhost with reserved port")
|
||||
require.Contains(t, err.Error(), "reserved for godoxy")
|
||||
expect.HasError(t, err, "Validate should return error for localhost with reserved port")
|
||||
expect.ErrorContains(t, err, "reserved for godoxy")
|
||||
})
|
||||
|
||||
t.Run("ListeningPortWithHTTP", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeHTTP,
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80, Listening: 1234},
|
||||
Port: route.Port{Proxy: 80, Listening: 1234},
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err, "Validate should return error for HTTP scheme with listening port")
|
||||
require.Contains(t, err.Error(), "unexpected listening port")
|
||||
expect.HasError(t, err, "Validate should return error for HTTP scheme with listening port")
|
||||
expect.ErrorContains(t, err, "unexpected listening port")
|
||||
})
|
||||
|
||||
t.Run("DisabledHealthCheckWithLoadBalancer", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeHTTP,
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80},
|
||||
Port: route.Port{Proxy: 80},
|
||||
HealthCheck: &health.HealthCheckConfig{
|
||||
Disable: true,
|
||||
},
|
||||
@@ -64,53 +50,53 @@ func TestRouteValidate(t *testing.T) {
|
||||
}, // Minimal LoadBalance config with non-empty Link will be checked by UseLoadBalance
|
||||
}
|
||||
err := r.Validate()
|
||||
require.Error(t, err, "Validate should return error for disabled healthcheck with loadbalancer")
|
||||
require.Contains(t, err.Error(), "cannot disable healthcheck")
|
||||
expect.HasError(t, err, "Validate should return error for disabled healthcheck with loadbalancer")
|
||||
expect.ErrorContains(t, err, "cannot disable healthcheck")
|
||||
})
|
||||
|
||||
t.Run("FileServerScheme", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeFileServer,
|
||||
Scheme: route.SchemeFileServer,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80},
|
||||
Port: route.Port{Proxy: 80},
|
||||
Root: "/tmp", // Root is required for file server
|
||||
}
|
||||
err := r.Validate()
|
||||
require.NoError(t, err, "Validate should not return error for valid file server route")
|
||||
require.NotNil(t, r.impl, "Impl should be initialized")
|
||||
expect.NoError(t, err, "Validate should not return error for valid file server route")
|
||||
expect.NotNil(t, r.impl, "Impl should be initialized")
|
||||
})
|
||||
|
||||
t.Run("HTTPScheme", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeHTTP,
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80},
|
||||
Port: route.Port{Proxy: 80},
|
||||
}
|
||||
err := r.Validate()
|
||||
require.NoError(t, err, "Validate should not return error for valid HTTP route")
|
||||
require.NotNil(t, r.impl, "Impl should be initialized")
|
||||
expect.NoError(t, err, "Validate should not return error for valid HTTP route")
|
||||
expect.NotNil(t, r.impl, "Impl should be initialized")
|
||||
})
|
||||
|
||||
t.Run("TCPScheme", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeTCP,
|
||||
Scheme: route.SchemeTCP,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80, Listening: 8080},
|
||||
Port: route.Port{Proxy: 80, Listening: 8080},
|
||||
}
|
||||
err := r.Validate()
|
||||
require.NoError(t, err, "Validate should not return error for valid TCP route")
|
||||
require.NotNil(t, r.impl, "Impl should be initialized")
|
||||
expect.NoError(t, err, "Validate should not return error for valid TCP route")
|
||||
expect.NotNil(t, r.impl, "Impl should be initialized")
|
||||
})
|
||||
|
||||
t.Run("DockerContainer", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeHTTP,
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80},
|
||||
Port: route.Port{Proxy: 80},
|
||||
Metadata: Metadata{
|
||||
Container: &docker.Container{
|
||||
ContainerID: "test-id",
|
||||
@@ -121,8 +107,8 @@ func TestRouteValidate(t *testing.T) {
|
||||
},
|
||||
}
|
||||
err := r.Validate()
|
||||
require.NoError(t, err, "Validate should not return error for valid docker container route")
|
||||
require.NotNil(t, r.ProxyURL, "ProxyURL should be set")
|
||||
expect.NoError(t, err, "Validate should not return error for valid docker container route")
|
||||
expect.NotNil(t, r.ProxyURL, "ProxyURL should be set")
|
||||
})
|
||||
|
||||
t.Run("InvalidScheme", func(t *testing.T) {
|
||||
@@ -130,9 +116,9 @@ func TestRouteValidate(t *testing.T) {
|
||||
Alias: "test",
|
||||
Scheme: "invalid",
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80},
|
||||
Port: route.Port{Proxy: 80},
|
||||
}
|
||||
require.Panics(t, func() {
|
||||
expect.Panics(t, func() {
|
||||
_ = r.Validate()
|
||||
}, "Validate should panic for invalid scheme")
|
||||
})
|
||||
@@ -140,14 +126,13 @@ func TestRouteValidate(t *testing.T) {
|
||||
t.Run("ModifiedFields", func(t *testing.T) {
|
||||
r := &Route{
|
||||
Alias: "test",
|
||||
Scheme: types.SchemeHTTP,
|
||||
Scheme: route.SchemeHTTP,
|
||||
Host: "example.com",
|
||||
Port: types.Port{Proxy: 80},
|
||||
Port: route.Port{Proxy: 80},
|
||||
}
|
||||
err := r.Validate()
|
||||
require.NoError(t, err)
|
||||
require.True(t, r.isValidated)
|
||||
require.NotNil(t, r.ProxyURL)
|
||||
require.NotNil(t, r.HealthCheck)
|
||||
expect.NoError(t, err)
|
||||
expect.NotNil(t, r.ProxyURL)
|
||||
expect.NotNil(t, r.HealthCheck)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package routequery
|
||||
package routes
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
)
|
||||
|
||||
func getHealthInfo(r route.Route) map[string]string {
|
||||
func getHealthInfo(r Route) map[string]string {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return map[string]string{
|
||||
@@ -26,11 +25,11 @@ func getHealthInfo(r route.Route) map[string]string {
|
||||
}
|
||||
|
||||
type HealthInfoRaw struct {
|
||||
Status health.Status
|
||||
Latency time.Duration
|
||||
Status health.Status `json:"status,string"`
|
||||
Latency time.Duration `json:"latency"`
|
||||
}
|
||||
|
||||
func getHealthInfoRaw(r route.Route) *HealthInfoRaw {
|
||||
func getHealthInfoRaw(r Route) *HealthInfoRaw {
|
||||
mon := r.HealthMonitor()
|
||||
if mon == nil {
|
||||
return &HealthInfoRaw{
|
||||
@@ -45,69 +44,69 @@ func getHealthInfoRaw(r route.Route) *HealthInfoRaw {
|
||||
}
|
||||
|
||||
func HealthMap() map[string]map[string]string {
|
||||
healthMap := make(map[string]map[string]string, routes.NumRoutes())
|
||||
routes.RangeRoutes(func(alias string, r route.Route) {
|
||||
healthMap := make(map[string]map[string]string, NumRoutes())
|
||||
for alias, r := range Iter {
|
||||
healthMap[alias] = getHealthInfo(r)
|
||||
})
|
||||
}
|
||||
return healthMap
|
||||
}
|
||||
|
||||
func HealthInfo() map[string]*HealthInfoRaw {
|
||||
healthMap := make(map[string]*HealthInfoRaw, routes.NumRoutes())
|
||||
routes.RangeRoutes(func(alias string, r route.Route) {
|
||||
healthMap := make(map[string]*HealthInfoRaw, NumRoutes())
|
||||
for alias, r := range Iter {
|
||||
healthMap[alias] = getHealthInfoRaw(r)
|
||||
})
|
||||
}
|
||||
return healthMap
|
||||
}
|
||||
|
||||
func HomepageCategories() []string {
|
||||
check := make(map[string]struct{})
|
||||
categories := make([]string, 0)
|
||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
||||
for _, r := range HTTP.Iter {
|
||||
item := r.HomepageConfig()
|
||||
if item == nil || item.Category == "" {
|
||||
return
|
||||
continue
|
||||
}
|
||||
if _, ok := check[item.Category]; ok {
|
||||
return
|
||||
continue
|
||||
}
|
||||
check[item.Category] = struct{}{}
|
||||
categories = append(categories, item.Category)
|
||||
})
|
||||
}
|
||||
return categories
|
||||
}
|
||||
|
||||
func HomepageConfig(categoryFilter, providerFilter string) homepage.Homepage {
|
||||
hp := make(homepage.Homepage)
|
||||
|
||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
||||
for _, r := range HTTP.Iter {
|
||||
if providerFilter != "" && r.ProviderName() != providerFilter {
|
||||
return
|
||||
continue
|
||||
}
|
||||
item := r.HomepageItem()
|
||||
if categoryFilter != "" && item.Category != categoryFilter {
|
||||
return
|
||||
continue
|
||||
}
|
||||
hp.Add(item)
|
||||
})
|
||||
}
|
||||
return hp
|
||||
}
|
||||
|
||||
func RoutesByAlias(typeFilter ...route.RouteType) map[string]route.Route {
|
||||
rts := make(map[string]route.Route)
|
||||
func ByAlias(typeFilter ...route.RouteType) map[string]Route {
|
||||
rts := make(map[string]Route)
|
||||
if len(typeFilter) == 0 || typeFilter[0] == "" {
|
||||
typeFilter = []route.RouteType{route.RouteTypeHTTP, route.RouteTypeStream}
|
||||
}
|
||||
for _, t := range typeFilter {
|
||||
switch t {
|
||||
case route.RouteTypeHTTP:
|
||||
routes.GetHTTPRoutes().RangeAll(func(alias string, r route.HTTPRoute) {
|
||||
for alias, r := range HTTP.Iter {
|
||||
rts[alias] = r
|
||||
})
|
||||
}
|
||||
case route.RouteTypeStream:
|
||||
routes.GetStreamRoutes().RangeAll(func(alias string, r route.StreamRoute) {
|
||||
for alias, r := range Stream.Iter {
|
||||
rts[alias] = r
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return rts
|
||||
@@ -1,17 +1,19 @@
|
||||
package types
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
|
||||
"github.com/yusing/go-proxy/internal/homepage"
|
||||
idlewatcher "github.com/yusing/go-proxy/internal/idlewatcher/types"
|
||||
net "github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/utils/pool"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
|
||||
loadbalance "github.com/yusing/go-proxy/internal/net/gphttp/loadbalancer/types"
|
||||
"github.com/yusing/go-proxy/internal/net/gphttp/reverseproxy"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -19,10 +21,11 @@ type (
|
||||
Route interface {
|
||||
task.TaskStarter
|
||||
task.TaskFinisher
|
||||
pool.Object
|
||||
ProviderName() string
|
||||
TargetName() string
|
||||
TargetURL() *net.URL
|
||||
HealthMonitor() health.HealthMonitor
|
||||
Reference() string
|
||||
|
||||
Started() bool
|
||||
|
||||
@@ -46,6 +49,10 @@ type (
|
||||
Route
|
||||
http.Handler
|
||||
}
|
||||
ReverseProxyRoute interface {
|
||||
HTTPRoute
|
||||
ReverseProxy() *reverseproxy.ReverseProxy
|
||||
}
|
||||
StreamRoute interface {
|
||||
Route
|
||||
net.Stream
|
||||
@@ -1,78 +1,49 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/yusing/go-proxy/internal/route/types"
|
||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||
"github.com/yusing/go-proxy/internal/utils/pool"
|
||||
)
|
||||
|
||||
var (
|
||||
httpRoutes = F.NewMapOf[string, types.HTTPRoute]()
|
||||
streamRoutes = F.NewMapOf[string, types.StreamRoute]()
|
||||
HTTP = pool.New[HTTPRoute]("http_routes")
|
||||
Stream = pool.New[StreamRoute]("stream_routes")
|
||||
)
|
||||
|
||||
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 Iter(yield func(alias string, r Route) bool) {
|
||||
for k, r := range HTTP.Iter {
|
||||
if !yield(k, r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for k, r := range Stream.Iter {
|
||||
if !yield(k, r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NumRoutes() int {
|
||||
return httpRoutes.Size() + streamRoutes.Size()
|
||||
return HTTP.Size() + Stream.Size()
|
||||
}
|
||||
|
||||
func GetHTTPRoutes() F.Map[string, types.HTTPRoute] {
|
||||
return httpRoutes
|
||||
func Clear() {
|
||||
HTTP.Clear()
|
||||
Stream.Clear()
|
||||
}
|
||||
|
||||
func GetStreamRoutes() F.Map[string, types.StreamRoute] {
|
||||
return streamRoutes
|
||||
}
|
||||
|
||||
func GetHTTPRouteOrExact(alias, host string) (types.HTTPRoute, bool) {
|
||||
r, ok := httpRoutes.Load(alias)
|
||||
func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
||||
r, ok := HTTP.Get(alias)
|
||||
if ok {
|
||||
return r, true
|
||||
}
|
||||
// try find with exact match
|
||||
return httpRoutes.Load(host)
|
||||
return HTTP.Get(host)
|
||||
}
|
||||
|
||||
func GetHTTPRoute(alias string) (types.HTTPRoute, bool) {
|
||||
return httpRoutes.Load(alias)
|
||||
}
|
||||
|
||||
func GetStreamRoute(alias string) (types.StreamRoute, bool) {
|
||||
return streamRoutes.Load(alias)
|
||||
}
|
||||
|
||||
func GetRoute(alias string) (types.Route, bool) {
|
||||
r, ok := httpRoutes.Load(alias)
|
||||
func Get(alias string) (Route, bool) {
|
||||
r, ok := HTTP.Get(alias)
|
||||
if ok {
|
||||
return r, true
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func TestClear() {
|
||||
httpRoutes = F.NewMapOf[string, types.HTTPRoute]()
|
||||
streamRoutes = F.NewMapOf[string, types.StreamRoute]()
|
||||
return Stream.Get(alias)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/yusing/go-proxy/internal/docker"
|
||||
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/idlewatcher"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
net "github.com/yusing/go-proxy/internal/net/types"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
route "github.com/yusing/go-proxy/internal/route/types"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health"
|
||||
"github.com/yusing/go-proxy/internal/watcher/health/monitor"
|
||||
@@ -30,53 +28,36 @@ type StreamRoute struct {
|
||||
l zerolog.Logger
|
||||
}
|
||||
|
||||
func NewStreamRoute(base *Route) (route.Route, gperr.Error) {
|
||||
func NewStreamRoute(base *Route) (routes.Route, gperr.Error) {
|
||||
// TODO: support non-coherent scheme
|
||||
return &StreamRoute{
|
||||
Route: base,
|
||||
l: logging.With().
|
||||
Str("type", string(base.Scheme)).
|
||||
Str("name", base.TargetName()).
|
||||
Str("name", base.Name()).
|
||||
Logger(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *StreamRoute) String() string {
|
||||
return "stream " + r.TargetName()
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||
if existing, ok := routes.GetStreamRoute(r.TargetName()); ok {
|
||||
if existing, ok := routes.Stream.Get(r.Key()); 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.Name())
|
||||
r.Stream = NewStream(r)
|
||||
parent.OnCancel("finish", func() {
|
||||
r.task.Finish(nil)
|
||||
})
|
||||
|
||||
switch {
|
||||
case r.UseIdleWatcher():
|
||||
waker, err := idlewatcher.NewStreamWaker(parent, r, r.Stream)
|
||||
waker, err := idlewatcher.NewWatcher(parent, r)
|
||||
if err != nil {
|
||||
r.task.Finish(err)
|
||||
return err
|
||||
return gperr.Wrap(err, "idlewatcher error")
|
||||
}
|
||||
r.Stream = waker
|
||||
r.HealthMon = waker
|
||||
case r.UseHealthCheck():
|
||||
if r.IsDocker() {
|
||||
client, err := docker.NewClient(r.Container.DockerHost)
|
||||
if err == nil {
|
||||
fallback := monitor.NewRawHealthChecker(r.TargetURL(), r.HealthCheck)
|
||||
r.HealthMon = monitor.NewDockerHealthMonitor(client, r.Container.ContainerID, r.TargetName(), r.HealthCheck, fallback)
|
||||
r.task.OnCancel("close_docker_client", client.Close)
|
||||
}
|
||||
}
|
||||
if r.HealthMon == nil {
|
||||
r.HealthMon = monitor.NewRawHealthMonitor(r.TargetURL(), r.HealthCheck)
|
||||
}
|
||||
r.HealthMon = monitor.NewMonitor(r)
|
||||
}
|
||||
|
||||
if err := r.Stream.Setup(); err != nil {
|
||||
@@ -94,9 +75,9 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||
|
||||
go r.acceptConnections()
|
||||
|
||||
routes.SetStreamRoute(r.TargetName(), r)
|
||||
r.task.OnCancel("entrypoint_remove_route", func() {
|
||||
routes.DeleteStreamRoute(r.TargetName())
|
||||
routes.Stream.Add(r)
|
||||
r.task.OnFinished("entrypoint_remove_route", func() {
|
||||
routes.Stream.Del(r)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package types
|
||||
package route
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package types_test
|
||||
package route_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/yusing/go-proxy/internal/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"
|
||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
expect "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
)
|
||||
|
||||
func TestHTTPConfigDeserialize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input map[string]any
|
||||
expected types.HTTPConfig
|
||||
expected route.HTTPConfig
|
||||
}{
|
||||
{
|
||||
name: "no_tls_verify",
|
||||
input: map[string]any{
|
||||
"no_tls_verify": "true",
|
||||
},
|
||||
expected: types.HTTPConfig{
|
||||
expected: route.HTTPConfig{
|
||||
NoTLSVerify: true,
|
||||
},
|
||||
},
|
||||
@@ -30,7 +30,7 @@ func TestHTTPConfigDeserialize(t *testing.T) {
|
||||
input: map[string]any{
|
||||
"response_header_timeout": "1s",
|
||||
},
|
||||
expected: types.HTTPConfig{
|
||||
expected: route.HTTPConfig{
|
||||
ResponseHeaderTimeout: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
@@ -39,11 +39,12 @@ func TestHTTPConfigDeserialize(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := Route{}
|
||||
tt.input["host"] = "internal"
|
||||
err := utils.Deserialize(tt.input, &cfg)
|
||||
if err != nil {
|
||||
ExpectNoError(t, err)
|
||||
expect.NoError(t, err)
|
||||
}
|
||||
ExpectEqual(t, cfg.HTTPConfig, tt.expected)
|
||||
expect.Equal(t, cfg.HTTPConfig, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package types
|
||||
package route
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package types
|
||||
package route
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package types
|
||||
package route
|
||||
|
||||
type RouteType string
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package types
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
|
||||
Reference in New Issue
Block a user