mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(host): modernize default host selection order
This commit is contained in:
@@ -262,7 +262,7 @@ func (h *Handler) getGatewayHealthForPidData(
|
||||
host = gatewayProbeHost(h.effectiveGatewayBindHost(cfg))
|
||||
}
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
host = resolveDefaultLoopbackHost()
|
||||
}
|
||||
|
||||
url := "http://" + net.JoinHostPort(host, strconv.Itoa(port)) + "/health"
|
||||
|
||||
@@ -12,8 +12,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
adaptiveLoopbackHostOnce sync.Once
|
||||
adaptiveLoopbackHost string
|
||||
adaptiveIPFamiliesOnce sync.Once
|
||||
adaptiveHasIPv4 bool
|
||||
adaptiveHasIPv6 bool
|
||||
lookupLocalhostIPs = func() ([]net.IP, error) { return net.LookupIP("localhost") }
|
||||
listInterfaceAddrs = net.InterfaceAddrs
|
||||
)
|
||||
|
||||
func selectAdaptiveLoopbackHost(hasIPv4, hasIPv6 bool) string {
|
||||
@@ -25,7 +28,20 @@ func selectAdaptiveLoopbackHost(hasIPv4, hasIPv6 bool) string {
|
||||
case hasIPv4:
|
||||
return "127.0.0.1"
|
||||
default:
|
||||
return "127.0.0.1"
|
||||
return "localhost"
|
||||
}
|
||||
}
|
||||
|
||||
func selectAdaptiveAnyHost(hasIPv4, hasIPv6 bool) string {
|
||||
switch {
|
||||
case hasIPv4 && hasIPv6:
|
||||
return "::"
|
||||
case hasIPv6:
|
||||
return "::"
|
||||
case hasIPv4:
|
||||
return "0.0.0.0"
|
||||
default:
|
||||
return "::"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,36 +58,61 @@ func isLoopbackEquivalentHost(host string) bool {
|
||||
return ip != nil && ip.IsLoopback()
|
||||
}
|
||||
|
||||
func resolveAdaptiveLoopbackHost() string {
|
||||
adaptiveLoopbackHostOnce.Do(func() {
|
||||
ips, err := net.LookupIP("localhost")
|
||||
if err != nil {
|
||||
adaptiveLoopbackHost = selectAdaptiveLoopbackHost(false, false)
|
||||
func detectAdaptiveIPFamilies() (bool, bool) {
|
||||
adaptiveIPFamiliesOnce.Do(func() {
|
||||
if ips, err := lookupLocalhostIPs(); err == nil {
|
||||
for _, ip := range ips {
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
adaptiveHasIPv4 = true
|
||||
continue
|
||||
}
|
||||
adaptiveHasIPv6 = true
|
||||
}
|
||||
}
|
||||
|
||||
if adaptiveHasIPv4 && adaptiveHasIPv6 {
|
||||
return
|
||||
}
|
||||
|
||||
hasIPv4 := false
|
||||
hasIPv6 := false
|
||||
for _, ip := range ips {
|
||||
if ip == nil {
|
||||
continue
|
||||
if addrs, err := listInterfaceAddrs(); err == nil {
|
||||
for _, addr := range addrs {
|
||||
ipnet, ok := addr.(*net.IPNet)
|
||||
if !ok || ipnet.IP == nil {
|
||||
continue
|
||||
}
|
||||
if ipnet.IP.To4() != nil {
|
||||
adaptiveHasIPv4 = true
|
||||
continue
|
||||
}
|
||||
adaptiveHasIPv6 = true
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
hasIPv4 = true
|
||||
continue
|
||||
}
|
||||
hasIPv6 = true
|
||||
}
|
||||
|
||||
adaptiveLoopbackHost = selectAdaptiveLoopbackHost(hasIPv4, hasIPv6)
|
||||
})
|
||||
return adaptiveLoopbackHost
|
||||
|
||||
return adaptiveHasIPv4, adaptiveHasIPv6
|
||||
}
|
||||
|
||||
func resolveAdaptiveLoopbackHost() string {
|
||||
hasIPv4, hasIPv6 := detectAdaptiveIPFamilies()
|
||||
return selectAdaptiveLoopbackHost(hasIPv4, hasIPv6)
|
||||
}
|
||||
|
||||
func resolveAdaptiveAnyHost() string {
|
||||
hasIPv4, hasIPv6 := detectAdaptiveIPFamilies()
|
||||
return selectAdaptiveAnyHost(hasIPv4, hasIPv6)
|
||||
}
|
||||
|
||||
func resolveDefaultLoopbackHost() string {
|
||||
return resolveAdaptiveLoopbackHost()
|
||||
}
|
||||
|
||||
func resolveDefaultAnyHost() string {
|
||||
return resolveAdaptiveAnyHost()
|
||||
}
|
||||
|
||||
func resolveLocalhostLoopbackHost() string {
|
||||
return resolveAdaptiveLoopbackHost()
|
||||
}
|
||||
@@ -102,6 +143,10 @@ func canonicalLauncherBindHost(host string) string {
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return resolveLocalhostLoopbackHost()
|
||||
}
|
||||
trimmed := strings.Trim(host, "[]")
|
||||
if ip := net.ParseIP(trimmed); ip != nil && ip.IsUnspecified() {
|
||||
return resolveDefaultAnyHost()
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
@@ -110,8 +155,8 @@ func (h *Handler) launcherAndGatewayBindHostsAligned(cfg *config.Config) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// With -host specified, -public is ignored, so launcher's legacy bind host is loopback.
|
||||
launcherHost := canonicalLauncherBindHost("127.0.0.1")
|
||||
// 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
|
||||
@@ -129,7 +174,7 @@ func (h *Handler) gatewayHostOverrideForConfig(cfg *config.Config) string {
|
||||
}
|
||||
|
||||
if h.effectiveLauncherPublic() {
|
||||
return "0.0.0.0"
|
||||
return resolveDefaultAnyHost()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -167,10 +212,7 @@ func gatewayProbeHost(bindHost string) string {
|
||||
|
||||
trimmed := strings.Trim(bindHost, "[]")
|
||||
if ip := net.ParseIP(trimmed); ip != nil && ip.IsUnspecified() {
|
||||
if ip.To4() == nil {
|
||||
return "::1"
|
||||
}
|
||||
return "127.0.0.1"
|
||||
return resolveDefaultLoopbackHost()
|
||||
}
|
||||
return bindHost
|
||||
}
|
||||
@@ -200,7 +242,7 @@ func requestHostName(r *http.Request) string {
|
||||
if strings.TrimSpace(r.Host) != "" {
|
||||
return r.Host
|
||||
}
|
||||
return "127.0.0.1"
|
||||
return resolveDefaultLoopbackHost()
|
||||
}
|
||||
|
||||
func requestWSScheme(r *http.Request) string {
|
||||
|
||||
@@ -3,9 +3,11 @@ package api
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,6 +15,12 @@ import (
|
||||
"github.com/sipeed/picoclaw/web/backend/launcherconfig"
|
||||
)
|
||||
|
||||
func resetAdaptiveIPFamiliesForTest() {
|
||||
adaptiveIPFamiliesOnce = sync.Once{}
|
||||
adaptiveHasIPv4 = false
|
||||
adaptiveHasIPv6 = false
|
||||
}
|
||||
|
||||
func TestGatewayHostOverrideUsesExplicitRuntimePublic(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
launcherPath := launcherconfig.PathForAppConfig(configPath)
|
||||
@@ -26,8 +34,8 @@ func TestGatewayHostOverrideUsesExplicitRuntimePublic(t *testing.T) {
|
||||
h := NewHandler(configPath)
|
||||
h.SetServerOptions(18800, true, true, nil)
|
||||
|
||||
if got := h.gatewayHostOverride(); got != "0.0.0.0" {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, "0.0.0.0")
|
||||
if got := h.gatewayHostOverride(); got != resolveDefaultAnyHost() {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, resolveDefaultAnyHost())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +81,7 @@ func TestSelectAdaptiveLoopbackHost(t *testing.T) {
|
||||
{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: "127.0.0.1"},
|
||||
{name: "fallback", hasIPv4: false, hasIPv6: false, want: "localhost"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -85,9 +93,60 @@ func TestSelectAdaptiveLoopbackHost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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 TestAdaptiveHostSelectionFallsBackToInterfaceAddrs(t *testing.T) {
|
||||
oldLookup := lookupLocalhostIPs
|
||||
oldList := listInterfaceAddrs
|
||||
lookupLocalhostIPs = func() ([]net.IP, error) {
|
||||
return nil, errors.New("lookup failed")
|
||||
}
|
||||
_, v4Net, err := net.ParseCIDR("192.0.2.10/24")
|
||||
if err != nil {
|
||||
t.Fatalf("ParseCIDR() error = %v", err)
|
||||
}
|
||||
listInterfaceAddrs = func() ([]net.Addr, error) {
|
||||
return []net.Addr{v4Net}, nil
|
||||
}
|
||||
resetAdaptiveIPFamiliesForTest()
|
||||
t.Cleanup(func() {
|
||||
lookupLocalhostIPs = oldLookup
|
||||
listInterfaceAddrs = oldList
|
||||
resetAdaptiveIPFamiliesForTest()
|
||||
})
|
||||
|
||||
if got := resolveDefaultAnyHost(); got != "0.0.0.0" {
|
||||
t.Fatalf("resolveDefaultAnyHost() = %q, want %q", got, "0.0.0.0")
|
||||
}
|
||||
if got := resolveDefaultLoopbackHost(); got != "127.0.0.1" {
|
||||
t.Fatalf("resolveDefaultLoopbackHost() = %q, want %q", got, "127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProbeHostUsesLoopbackForWildcardBind(t *testing.T) {
|
||||
if got := gatewayProbeHost("0.0.0.0"); got != "127.0.0.1" {
|
||||
t.Fatalf("gatewayProbeHost() = %q, want %q", got, "127.0.0.1")
|
||||
want := resolveDefaultLoopbackHost()
|
||||
if got := gatewayProbeHost("0.0.0.0"); got != want {
|
||||
t.Fatalf("gatewayProbeHost() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +165,9 @@ func TestGatewayProbeHostUsesPreferredLoopbackForLocalhostBind(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGatewayProbeHostUsesLoopbackForIPv6WildcardBind(t *testing.T) {
|
||||
if got := gatewayProbeHost("::"); got != "::1" {
|
||||
t.Fatalf("gatewayProbeHost(::) = %q, want %q", got, "::1")
|
||||
want := resolveDefaultLoopbackHost()
|
||||
if got := gatewayProbeHost("::"); got != want {
|
||||
t.Fatalf("gatewayProbeHost(::) = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,8 +239,9 @@ func TestGetGatewayHealthUsesProbeHostForPublicLauncher(t *testing.T) {
|
||||
_ = statusCode
|
||||
_ = err
|
||||
|
||||
if requestedURL != "http://127.0.0.1:18791/health" {
|
||||
t.Fatalf("health url = %q, want %q", requestedURL, "http://127.0.0.1:18791/health")
|
||||
want := "http://" + net.JoinHostPort(resolveDefaultLoopbackHost(), "18791") + "/health"
|
||||
if requestedURL != want {
|
||||
t.Fatalf("health url = %q, want %q", requestedURL, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,8 +352,8 @@ func TestGatewayHostOverrideWithExplicitHostAndAlignedGatewayHost(t *testing.T)
|
||||
h.SetServerOptions(18800, false, false, nil)
|
||||
h.SetServerBindHost("0.0.0.0", true)
|
||||
|
||||
if got := h.gatewayHostOverride(); got != "0.0.0.0" {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, "0.0.0.0")
|
||||
if got := h.gatewayHostOverride(); got != resolveDefaultAnyHost() {
|
||||
t.Fatalf("gatewayHostOverride() = %q, want %q", got, resolveDefaultAnyHost())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func NewHandler(configPath string) *Handler {
|
||||
return &Handler{
|
||||
configPath: configPath,
|
||||
serverPort: launcherconfig.DefaultPort,
|
||||
serverHost: "127.0.0.1",
|
||||
serverHost: resolveDefaultLoopbackHost(),
|
||||
oauthFlows: make(map[string]*oauthFlow),
|
||||
oauthState: make(map[string]string),
|
||||
weixinFlows: make(map[string]*weixinFlow),
|
||||
@@ -45,9 +45,9 @@ func (h *Handler) SetServerOptions(port int, public bool, publicExplicit bool, a
|
||||
h.serverPort = port
|
||||
h.serverPublic = public
|
||||
h.serverPublicExplicit = publicExplicit
|
||||
h.serverHost = "127.0.0.1"
|
||||
h.serverHost = resolveDefaultLoopbackHost()
|
||||
if public {
|
||||
h.serverHost = "0.0.0.0"
|
||||
h.serverHost = resolveDefaultAnyHost()
|
||||
}
|
||||
h.serverHostExplicit = false
|
||||
h.serverCIDRs = append([]string(nil), allowedCIDRs...)
|
||||
@@ -58,12 +58,13 @@ func (h *Handler) SetServerOptions(port int, public bool, publicExplicit bool, a
|
||||
func (h *Handler) SetServerBindHost(host string, explicit bool) {
|
||||
host = strings.TrimSpace(host)
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
host = resolveDefaultLoopbackHost()
|
||||
if h.serverPublic {
|
||||
host = "0.0.0.0"
|
||||
host = resolveDefaultAnyHost()
|
||||
}
|
||||
explicit = false
|
||||
}
|
||||
host = canonicalLauncherBindHost(host)
|
||||
|
||||
h.serverHost = host
|
||||
h.serverHostExplicit = explicit
|
||||
|
||||
Reference in New Issue
Block a user