mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(host): complete launcher and gateway multi-host binding support
- add shared netbind planning for strict tcp4/tcp6 bind semantics - support launcher/gateway host env overrides and launcher-to-gateway forwarding - cover host binding and forwarding with network and subprocess env tests
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/health"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/netbind"
|
||||
ppid "github.com/sipeed/picoclaw/pkg/pid"
|
||||
"github.com/sipeed/picoclaw/web/backend/utils"
|
||||
)
|
||||
@@ -119,6 +120,7 @@ var (
|
||||
gatewayRestartGracePeriod = 5 * time.Second
|
||||
gatewayRestartForceKillWindow = 3 * time.Second
|
||||
gatewayRestartPollInterval = 100 * time.Millisecond
|
||||
gatewayExecCommand = exec.Command
|
||||
)
|
||||
|
||||
var gatewayHealthGet = func(url string, timeout time.Duration) (*http.Response, error) {
|
||||
@@ -262,7 +264,7 @@ func (h *Handler) getGatewayHealthForPidData(
|
||||
host = gatewayProbeHost(h.effectiveGatewayBindHost(cfg))
|
||||
}
|
||||
if host == "" {
|
||||
host = resolveDefaultLoopbackHost()
|
||||
host = netbind.ResolveAdaptiveLoopbackHost()
|
||||
}
|
||||
|
||||
url := "http://" + net.JoinHostPort(host, strconv.Itoa(port)) + "/health"
|
||||
@@ -723,7 +725,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int
|
||||
execPath := utils.FindPicoclawBinary()
|
||||
logger.InfoC("gateway", fmt.Sprintf("Starting gateway process (%s)", execPath))
|
||||
|
||||
cmd = exec.Command(execPath, h.gatewayCommandArgs()...)
|
||||
cmd = gatewayExecCommand(execPath, h.gatewayCommandArgs()...)
|
||||
cmd.Env = os.Environ()
|
||||
// Forward the launcher's config path via the environment variable that
|
||||
// GetConfigPath() already reads, so the gateway sub-process uses the same
|
||||
@@ -731,17 +733,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int
|
||||
if h.configPath != "" {
|
||||
cmd.Env = append(cmd.Env, config.EnvConfig+"="+h.configPath)
|
||||
}
|
||||
gatewayHostOverride := h.gatewayHostOverrideForConfig(cfg)
|
||||
if h.serverHostExplicit && gatewayHostOverride == "" {
|
||||
logger.WarnC(
|
||||
"gateway",
|
||||
fmt.Sprintf(
|
||||
"Explicit launcher host %q was not forwarded to gateway because configured gateway host is %q; gateway keeps original bind host",
|
||||
strings.TrimSpace(h.serverHost),
|
||||
strings.TrimSpace(cfg.Gateway.Host),
|
||||
),
|
||||
)
|
||||
}
|
||||
gatewayHostOverride := h.gatewayHostOverride()
|
||||
if gatewayHostOverride != "" {
|
||||
cmd.Env = append(cmd.Env, config.EnvGatewayHost+"="+gatewayHostOverride)
|
||||
}
|
||||
|
||||
@@ -8,38 +8,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/web/backend/utils"
|
||||
"github.com/sipeed/picoclaw/pkg/netbind"
|
||||
)
|
||||
|
||||
func selectAdaptiveLoopbackHost(hasIPv4, hasIPv6 bool) string {
|
||||
return utils.SelectAdaptiveLoopbackHost(hasIPv4, hasIPv6)
|
||||
}
|
||||
|
||||
func selectAdaptiveAnyHost(hasIPv4, hasIPv6 bool) string {
|
||||
return utils.SelectAdaptiveAnyHost(hasIPv4, hasIPv6)
|
||||
}
|
||||
|
||||
func isLoopbackEquivalentHost(host string) bool {
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return true
|
||||
}
|
||||
trimmed := strings.Trim(host, "[]")
|
||||
ip := net.ParseIP(trimmed)
|
||||
return ip != nil && ip.IsLoopback()
|
||||
}
|
||||
|
||||
func resolveDefaultLoopbackHost() string {
|
||||
return utils.ResolveAdaptiveLoopbackHost()
|
||||
}
|
||||
|
||||
func resolveDefaultAnyHost() string {
|
||||
return utils.ResolveAdaptiveAnyHost()
|
||||
}
|
||||
|
||||
func (h *Handler) effectiveLauncherPublic() bool {
|
||||
if h.serverHostExplicit {
|
||||
// -host takes precedence over -public and launcher-config public setting.
|
||||
@@ -58,64 +29,18 @@ func (h *Handler) effectiveLauncherPublic() bool {
|
||||
return h.serverPublic
|
||||
}
|
||||
|
||||
func canonicalLauncherBindHost(host string) string {
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
return resolveDefaultLoopbackHost()
|
||||
}
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return resolveDefaultLoopbackHost()
|
||||
}
|
||||
trimmed := strings.Trim(host, "[]")
|
||||
if ip := net.ParseIP(trimmed); ip != nil && ip.IsUnspecified() {
|
||||
return resolveDefaultAnyHost()
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func (h *Handler) launcherAndGatewayBindHostsAligned(cfg *config.Config) bool {
|
||||
if cfg == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// With -host specified, -public is ignored, so launcher baseline bind host is loopback.
|
||||
launcherHost := canonicalLauncherBindHost("")
|
||||
gatewayHost := canonicalLauncherBindHost(cfg.Gateway.Host)
|
||||
if isLoopbackEquivalentHost(launcherHost) && isLoopbackEquivalentHost(gatewayHost) {
|
||||
return true
|
||||
}
|
||||
|
||||
return launcherHost == gatewayHost
|
||||
}
|
||||
|
||||
func (h *Handler) gatewayHostOverrideForConfig(cfg *config.Config) string {
|
||||
func (h *Handler) gatewayHostOverride() string {
|
||||
if h.serverHostExplicit {
|
||||
if h.launcherAndGatewayBindHostsAligned(cfg) {
|
||||
return strings.TrimSpace(h.serverHost)
|
||||
}
|
||||
return ""
|
||||
return strings.TrimSpace(h.serverHostInput)
|
||||
}
|
||||
|
||||
if h.effectiveLauncherPublic() {
|
||||
return resolveDefaultAnyHost()
|
||||
return "*"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *Handler) gatewayHostOverride() string {
|
||||
if !h.serverHostExplicit {
|
||||
return h.gatewayHostOverrideForConfig(nil)
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig(h.configPath)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return h.gatewayHostOverrideForConfig(cfg)
|
||||
}
|
||||
|
||||
func (h *Handler) effectiveGatewayBindHost(cfg *config.Config) string {
|
||||
if override := h.gatewayHostOverrideForConfig(cfg); override != "" {
|
||||
if override := h.gatewayHostOverride(); override != "" {
|
||||
return override
|
||||
}
|
||||
if cfg == nil {
|
||||
@@ -125,19 +50,11 @@ func (h *Handler) effectiveGatewayBindHost(cfg *config.Config) string {
|
||||
}
|
||||
|
||||
func gatewayProbeHost(bindHost string) string {
|
||||
bindHost = strings.TrimSpace(bindHost)
|
||||
if bindHost == "" {
|
||||
return resolveDefaultLoopbackHost()
|
||||
plan, err := netbind.BuildPlan(bindHost, netbind.DefaultLoopback)
|
||||
if err != nil || strings.TrimSpace(plan.ProbeHost) == "" {
|
||||
return netbind.ResolveAdaptiveLoopbackHost()
|
||||
}
|
||||
if strings.EqualFold(bindHost, "localhost") {
|
||||
return resolveDefaultLoopbackHost()
|
||||
}
|
||||
|
||||
trimmed := strings.Trim(bindHost, "[]")
|
||||
if ip := net.ParseIP(trimmed); ip != nil && ip.IsUnspecified() {
|
||||
return resolveDefaultLoopbackHost()
|
||||
}
|
||||
return bindHost
|
||||
return plan.ProbeHost
|
||||
}
|
||||
|
||||
func (h *Handler) gatewayProxyURL() *url.URL {
|
||||
@@ -165,7 +82,7 @@ func requestHostName(r *http.Request) string {
|
||||
if strings.TrimSpace(r.Host) != "" {
|
||||
return r.Host
|
||||
}
|
||||
return resolveDefaultLoopbackHost()
|
||||
return netbind.ResolveAdaptiveLoopbackHost()
|
||||
}
|
||||
|
||||
func requestWSScheme(r *http.Request) string {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/netbind"
|
||||
"github.com/sipeed/picoclaw/web/backend/launcherconfig"
|
||||
)
|
||||
|
||||
@@ -27,8 +28,8 @@ func TestGatewayHostOverrideUsesExplicitRuntimePublic(t *testing.T) {
|
||||
h := NewHandler(configPath)
|
||||
h.SetServerOptions(18800, true, true, nil)
|
||||
|
||||
if got := h.gatewayHostOverride(); got != resolveDefaultAnyHost() {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, resolveDefaultAnyHost())
|
||||
if got := h.gatewayHostOverride(); got != "*" {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, "*")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,78 +65,40 @@ func TestBuildWsURLUsesRequestHostWhenLauncherPublicSaved(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectAdaptiveLoopbackHost(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hasIPv4 bool
|
||||
hasIPv6 bool
|
||||
want string
|
||||
}{
|
||||
{name: "dual stack prefers localhost", hasIPv4: true, hasIPv6: true, want: "localhost"},
|
||||
{name: "ipv6 only", hasIPv4: false, hasIPv6: true, want: "::1"},
|
||||
{name: "ipv4 only", hasIPv4: true, hasIPv6: false, want: "127.0.0.1"},
|
||||
{name: "fallback", hasIPv4: false, hasIPv6: false, want: "localhost"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := selectAdaptiveLoopbackHost(tt.hasIPv4, tt.hasIPv6); got != tt.want {
|
||||
t.Fatalf("selectAdaptiveLoopbackHost(%t, %t) = %q, want %q", tt.hasIPv4, tt.hasIPv6, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectAdaptiveAnyHost(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hasIPv4 bool
|
||||
hasIPv6 bool
|
||||
want string
|
||||
}{
|
||||
{name: "dual stack prefers ipv6 wildcard", hasIPv4: true, hasIPv6: true, want: "::"},
|
||||
{name: "ipv6 only", hasIPv4: false, hasIPv6: true, want: "::"},
|
||||
{name: "ipv4 only", hasIPv4: true, hasIPv6: false, want: "0.0.0.0"},
|
||||
{name: "fallback", hasIPv4: false, hasIPv6: false, want: "::"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := selectAdaptiveAnyHost(tt.hasIPv4, tt.hasIPv6); got != tt.want {
|
||||
t.Fatalf("selectAdaptiveAnyHost(%t, %t) = %q, want %q", tt.hasIPv4, tt.hasIPv6, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProbeHostUsesLoopbackForWildcardBind(t *testing.T) {
|
||||
want := resolveDefaultLoopbackHost()
|
||||
want := "127.0.0.1"
|
||||
if got := gatewayProbeHost("0.0.0.0"); got != want {
|
||||
t.Fatalf("gatewayProbeHost() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProbeHostUsesPreferredLoopbackForEmptyBind(t *testing.T) {
|
||||
want := resolveDefaultLoopbackHost()
|
||||
want := netbind.ResolveAdaptiveLoopbackHost()
|
||||
if got := gatewayProbeHost(""); got != want {
|
||||
t.Fatalf("gatewayProbeHost(empty) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProbeHostUsesPreferredLoopbackForLocalhostBind(t *testing.T) {
|
||||
want := resolveDefaultLoopbackHost()
|
||||
want := netbind.ResolveAdaptiveLoopbackHost()
|
||||
if got := gatewayProbeHost("localhost"); got != want {
|
||||
t.Fatalf("gatewayProbeHost(localhost) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProbeHostUsesLoopbackForIPv6WildcardBind(t *testing.T) {
|
||||
want := resolveDefaultLoopbackHost()
|
||||
want := "::1"
|
||||
if got := gatewayProbeHost("::"); got != want {
|
||||
t.Fatalf("gatewayProbeHost(::) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProbeHostUsesFirstConcreteHostForMultiHostBind(t *testing.T) {
|
||||
if got := gatewayProbeHost("127.0.0.1,::1"); got != "127.0.0.1" {
|
||||
t.Fatalf("gatewayProbeHost(multi) = %q, want %q", got, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProxyURLUsesConfiguredHost(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
@@ -204,7 +167,7 @@ func TestGetGatewayHealthUsesProbeHostForPublicLauncher(t *testing.T) {
|
||||
_ = statusCode
|
||||
_ = err
|
||||
|
||||
want := "http://" + net.JoinHostPort(resolveDefaultLoopbackHost(), "18791") + "/health"
|
||||
want := "http://" + net.JoinHostPort(netbind.ResolveAdaptiveLoopbackHost(), "18791") + "/health"
|
||||
if requestedURL != want {
|
||||
t.Fatalf("health url = %q, want %q", requestedURL, want)
|
||||
}
|
||||
@@ -310,23 +273,17 @@ func TestBuildWsURLUsesRequestHostNotGatewayBindLoopback(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGatewayHostOverrideWithExplicitHostAndAlignedGatewayHost(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
writeGatewayHostConfig(t, configPath, "127.0.0.1")
|
||||
|
||||
h := NewHandler(configPath)
|
||||
h := NewHandler(filepath.Join(t.TempDir(), "config.json"))
|
||||
h.SetServerOptions(18800, false, false, nil)
|
||||
h.SetServerBindHost("0.0.0.0", true)
|
||||
|
||||
if got := h.gatewayHostOverride(); got != resolveDefaultAnyHost() {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, resolveDefaultAnyHost())
|
||||
if got := h.gatewayHostOverride(); got != "0.0.0.0" {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, "0.0.0.0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayHostOverrideWithExplicitHostAndLocalhostGatewayHost(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
writeGatewayHostConfig(t, configPath, "localhost")
|
||||
|
||||
h := NewHandler(configPath)
|
||||
h := NewHandler(filepath.Join(t.TempDir(), "config.json"))
|
||||
h.SetServerOptions(18800, false, false, nil)
|
||||
h.SetServerBindHost("::", true)
|
||||
|
||||
@@ -335,24 +292,18 @@ func TestGatewayHostOverrideWithExplicitHostAndLocalhostGatewayHost(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayHostOverrideWithExplicitHostAndMismatchedGatewayHost(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
writeGatewayHostConfig(t, configPath, "0.0.0.0")
|
||||
|
||||
h := NewHandler(configPath)
|
||||
func TestGatewayHostOverrideWithExplicitMultiHost(t *testing.T) {
|
||||
h := NewHandler(filepath.Join(t.TempDir(), "config.json"))
|
||||
h.SetServerOptions(18800, false, false, nil)
|
||||
h.SetServerBindHost("192.168.1.10", true)
|
||||
h.SetServerBindHost("127.0.0.1,::1", true)
|
||||
|
||||
if got := h.gatewayHostOverride(); got != "" {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want empty", got)
|
||||
if got := h.gatewayHostOverride(); got != "127.0.0.1,::1" {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, "127.0.0.1,::1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayHostExplicitIgnoresPublicFlag(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
writeGatewayHostConfig(t, configPath, "127.0.0.1")
|
||||
|
||||
h := NewHandler(configPath)
|
||||
h := NewHandler(filepath.Join(t.TempDir(), "config.json"))
|
||||
h.SetServerOptions(18800, true, true, nil)
|
||||
h.SetServerBindHost("127.0.0.1", true)
|
||||
|
||||
@@ -360,13 +311,3 @@ func TestGatewayHostExplicitIgnoresPublicFlag(t *testing.T) {
|
||||
t.Fatalf("effectiveLauncherPublic() = %t, want false when explicit host is set", got)
|
||||
}
|
||||
}
|
||||
|
||||
func writeGatewayHostConfig(t *testing.T, configPath, host string) {
|
||||
t.Helper()
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Gateway.Host = host
|
||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||
t.Fatalf("SaveConfig() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ func resetGatewayTestState(t *testing.T) {
|
||||
|
||||
originalHealthGet := gatewayHealthGet
|
||||
originalProcessMatcher := gatewayProcessMatcher
|
||||
originalExecCommand := gatewayExecCommand
|
||||
originalRestartGracePeriod := gatewayRestartGracePeriod
|
||||
originalRestartForceKillWindow := gatewayRestartForceKillWindow
|
||||
originalRestartPollInterval := gatewayRestartPollInterval
|
||||
@@ -104,6 +105,7 @@ func resetGatewayTestState(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
gatewayHealthGet = originalHealthGet
|
||||
gatewayProcessMatcher = originalProcessMatcher
|
||||
gatewayExecCommand = originalExecCommand
|
||||
gatewayRestartGracePeriod = originalRestartGracePeriod
|
||||
gatewayRestartForceKillWindow = originalRestartForceKillWindow
|
||||
gatewayRestartPollInterval = originalRestartPollInterval
|
||||
@@ -119,6 +121,158 @@ func resetGatewayTestState(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
type gatewayStartEnvSnapshot struct {
|
||||
GatewayHost string `json:"gateway_host"`
|
||||
GatewayHostSet bool `json:"gateway_host_set"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
}
|
||||
|
||||
func TestGatewayStartHelperProcess(t *testing.T) {
|
||||
var envPath string
|
||||
for i, arg := range os.Args {
|
||||
if arg == "--" && i+2 < len(os.Args) && os.Args[i+1] == "gateway-env-helper" {
|
||||
envPath = os.Args[i+2]
|
||||
break
|
||||
}
|
||||
}
|
||||
if envPath == "" {
|
||||
t.Skip("helper process")
|
||||
}
|
||||
|
||||
host, ok := os.LookupEnv(config.EnvGatewayHost)
|
||||
raw, err := json.Marshal(gatewayStartEnvSnapshot{
|
||||
GatewayHost: host,
|
||||
GatewayHostSet: ok,
|
||||
ConfigPath: os.Getenv(config.EnvConfig),
|
||||
})
|
||||
if err != nil {
|
||||
_, _ = io.WriteString(os.Stderr, err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
if err := os.WriteFile(envPath, raw, 0o600); err != nil {
|
||||
_, _ = io.WriteString(os.Stderr, err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func unsetGatewayStartEnvForTest(t *testing.T, key string) {
|
||||
t.Helper()
|
||||
|
||||
prev, hadPrev := os.LookupEnv(key)
|
||||
if err := os.Unsetenv(key); err != nil {
|
||||
t.Fatalf("Unsetenv(%q) error = %v", key, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if hadPrev {
|
||||
_ = os.Setenv(key, prev)
|
||||
return
|
||||
}
|
||||
_ = os.Unsetenv(key)
|
||||
})
|
||||
}
|
||||
|
||||
func newGatewayStartTestHandler(t *testing.T) *Handler {
|
||||
t.Helper()
|
||||
resetGatewayTestState(t)
|
||||
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
cfg := config.DefaultConfig()
|
||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||
t.Fatalf("SaveConfig() error = %v", err)
|
||||
}
|
||||
|
||||
h := NewHandler(configPath)
|
||||
h.SetServerOptions(18800, false, false, nil)
|
||||
return h
|
||||
}
|
||||
|
||||
func startGatewayAndCaptureEnv(t *testing.T, h *Handler) gatewayStartEnvSnapshot {
|
||||
t.Helper()
|
||||
|
||||
unsetGatewayStartEnvForTest(t, config.EnvGatewayHost)
|
||||
|
||||
envPath := filepath.Join(t.TempDir(), "gateway-child-env.json")
|
||||
gatewayExecCommand = func(_ string, _ ...string) *exec.Cmd {
|
||||
return exec.Command(
|
||||
os.Args[0],
|
||||
"-test.run=TestGatewayStartHelperProcess",
|
||||
"--",
|
||||
"gateway-env-helper",
|
||||
envPath,
|
||||
)
|
||||
}
|
||||
|
||||
pid, err := h.startGatewayLocked("starting", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("startGatewayLocked() error = %v", err)
|
||||
}
|
||||
if pid <= 0 {
|
||||
t.Fatalf("startGatewayLocked() pid = %d, want > 0", pid)
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for {
|
||||
raw, err := os.ReadFile(envPath)
|
||||
if err == nil {
|
||||
var snapshot gatewayStartEnvSnapshot
|
||||
if err := json.Unmarshal(raw, &snapshot); err != nil {
|
||||
t.Fatalf("Unmarshal(child env) error = %v", err)
|
||||
}
|
||||
return snapshot
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
t.Fatalf("ReadFile(%q) error = %v", envPath, err)
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("timed out waiting for gateway child env snapshot %q", envPath)
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartGatewayLocked_ForwardsLauncherHostOverrideToGatewayEnv(t *testing.T) {
|
||||
h := newGatewayStartTestHandler(t)
|
||||
h.SetServerBindHost("127.0.0.1,::1", true)
|
||||
|
||||
snapshot := startGatewayAndCaptureEnv(t, h)
|
||||
if !snapshot.GatewayHostSet {
|
||||
t.Fatal("gateway host env was not set")
|
||||
}
|
||||
if snapshot.GatewayHost != "127.0.0.1,::1" {
|
||||
t.Fatalf("gateway host env = %q, want %q", snapshot.GatewayHost, "127.0.0.1,::1")
|
||||
}
|
||||
if snapshot.ConfigPath != h.configPath {
|
||||
t.Fatalf("config env = %q, want %q", snapshot.ConfigPath, h.configPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartGatewayLocked_ForwardsLauncherHostFromEnvironmentToGatewayEnv(t *testing.T) {
|
||||
h := newGatewayStartTestHandler(t)
|
||||
h.SetServerBindHost("::", true)
|
||||
|
||||
snapshot := startGatewayAndCaptureEnv(t, h)
|
||||
if !snapshot.GatewayHostSet {
|
||||
t.Fatal("gateway host env was not set")
|
||||
}
|
||||
if snapshot.GatewayHost != "::" {
|
||||
t.Fatalf("gateway host env = %q, want %q", snapshot.GatewayHost, "::")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartGatewayLocked_ForwardsWildcardHostForPublicLauncher(t *testing.T) {
|
||||
h := newGatewayStartTestHandler(t)
|
||||
h.SetServerOptions(18800, true, true, nil)
|
||||
|
||||
snapshot := startGatewayAndCaptureEnv(t, h)
|
||||
if !snapshot.GatewayHostSet {
|
||||
t.Fatal("gateway host env was not set")
|
||||
}
|
||||
if snapshot.GatewayHost != "*" {
|
||||
t.Fatalf("gateway host env = %q, want %q", snapshot.GatewayHost, "*")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayStartReady_NoDefaultModel(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
|
||||
@@ -14,7 +14,7 @@ type Handler struct {
|
||||
serverPort int
|
||||
serverPublic bool
|
||||
serverPublicExplicit bool
|
||||
serverHost string
|
||||
serverHostInput string
|
||||
serverHostExplicit bool
|
||||
serverCIDRs []string
|
||||
debug bool
|
||||
@@ -32,7 +32,6 @@ func NewHandler(configPath string) *Handler {
|
||||
return &Handler{
|
||||
configPath: configPath,
|
||||
serverPort: launcherconfig.DefaultPort,
|
||||
serverHost: resolveDefaultLoopbackHost(),
|
||||
oauthFlows: make(map[string]*oauthFlow),
|
||||
oauthState: make(map[string]string),
|
||||
weixinFlows: make(map[string]*weixinFlow),
|
||||
@@ -45,28 +44,18 @@ func (h *Handler) SetServerOptions(port int, public bool, publicExplicit bool, a
|
||||
h.serverPort = port
|
||||
h.serverPublic = public
|
||||
h.serverPublicExplicit = publicExplicit
|
||||
h.serverHost = resolveDefaultLoopbackHost()
|
||||
if public {
|
||||
h.serverHost = resolveDefaultAnyHost()
|
||||
}
|
||||
h.serverHostInput = ""
|
||||
h.serverHostExplicit = false
|
||||
h.serverCIDRs = append([]string(nil), allowedCIDRs...)
|
||||
}
|
||||
|
||||
// SetServerBindHost stores the launcher's effective bind host.
|
||||
// When explicit is true, the value came from the -host flag.
|
||||
func (h *Handler) SetServerBindHost(host string, explicit bool) {
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
host = resolveDefaultLoopbackHost()
|
||||
if h.serverPublic {
|
||||
host = resolveDefaultAnyHost()
|
||||
}
|
||||
explicit = false
|
||||
// When explicit is true, hostInput is the normalized -host / PICOCLAW_LAUNCHER_HOST value.
|
||||
func (h *Handler) SetServerBindHost(hostInput string, explicit bool) {
|
||||
h.serverHostInput = strings.TrimSpace(hostInput)
|
||||
if !explicit {
|
||||
h.serverHostInput = ""
|
||||
}
|
||||
host = canonicalLauncherBindHost(host)
|
||||
|
||||
h.serverHost = host
|
||||
h.serverHostExplicit = explicit
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user