From b2c3ae1e20700a8710d719b492b6db9e60fc4800 Mon Sep 17 00:00:00 2001 From: tiezhu <1130315273@qq.com> Date: Wed, 5 Jun 2024 10:46:54 +0800 Subject: [PATCH] Add command line arguments --bind and --port to set host and port, default is localhost and 7070. --- cmd/go-tool-debug-compile/main.go | 2 +- cmd/xgo/runtime_gen/core/version.go | 4 +- cmd/xgo/test-explorer/debug.go | 2 +- cmd/xgo/test-explorer/main.go | 26 ++++++++- cmd/xgo/trace/main.go | 42 +++++++------ cmd/xgo/version.go | 4 +- runtime/core/version.go | 4 +- support/netutil/netutil.go | 91 +++++++++++++++++++++++++++-- support/netutil/netutil_test.go | 73 +++++++++++++++++++++++ 9 files changed, 214 insertions(+), 34 deletions(-) create mode 100644 support/netutil/netutil_test.go diff --git a/cmd/go-tool-debug-compile/main.go b/cmd/go-tool-debug-compile/main.go index cb37bcc1..31a33909 100644 --- a/cmd/go-tool-debug-compile/main.go +++ b/cmd/go-tool-debug-compile/main.go @@ -154,7 +154,7 @@ func dlvExecListen(dir string, env []string, compilerBinary string, args []strin } break } - return netutil.ServePort(2345, true, 500*time.Millisecond, func(port int) { + return netutil.ServePort("localhost", 2345, true, 500*time.Millisecond, func(port int) { prompt := debug.FormatDlvPromptOptions(port, &debug.FormatDlvOptions{ VscodeExtra: vscodeExtra, }) diff --git a/cmd/xgo/runtime_gen/core/version.go b/cmd/xgo/runtime_gen/core/version.go index ade54178..ed75ad68 100755 --- a/cmd/xgo/runtime_gen/core/version.go +++ b/cmd/xgo/runtime_gen/core/version.go @@ -7,8 +7,8 @@ import ( ) const VERSION = "1.0.39" -const REVISION = "e6ba142b8a4c344bfd9e009090d1666635e1c9e5+1" -const NUMBER = 261 +const REVISION = "7fa581a5041d839180502f1d2377ea043803bde7+1" +const NUMBER = 262 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/cmd/xgo/test-explorer/debug.go b/cmd/xgo/test-explorer/debug.go index 7fcfdee4..11d2a379 100644 --- a/cmd/xgo/test-explorer/debug.go +++ b/cmd/xgo/test-explorer/debug.go @@ -73,7 +73,7 @@ func debug(ctx *RunContext) error { if err != nil { return err } - err = netutil.ServePort(2345, true, 500*time.Millisecond, func(port int) { + err = netutil.ServePort("localhost", 2345, true, 500*time.Millisecond, func(port int) { fmt.Fprintln(stderr, debug_util.FormatDlvPrompt(port)) }, func(port int) error { // dlv exec --api-version=2 --listen=localhost:2345 --accept-multiclient --headless ./debug.bin diff --git a/cmd/xgo/test-explorer/main.go b/cmd/xgo/test-explorer/main.go index 1756fe71..1675557f 100644 --- a/cmd/xgo/test-explorer/main.go +++ b/cmd/xgo/test-explorer/main.go @@ -33,6 +33,8 @@ type Options struct { Flags []string Config string + Port string + Bind string } func Main(args []string, opts *Options) error { @@ -93,6 +95,23 @@ func Main(args []string, opts *Options) error { if ok { continue } + if arg == "--port" { + if i+1 >= n { + return fmt.Errorf("%s requires value", arg) + } + opts.Port = args[i+1] + i++ + continue + } + if arg == "--bind" { + if i+1 >= n { + return fmt.Errorf("%s requires value", arg) + } + opts.Bind = args[i+1] + i++ + continue + } + if !strings.HasPrefix(arg, "-") { remainArgs = append(remainArgs, arg) continue @@ -281,9 +300,10 @@ func handle(opts *Options) error { setupTestHandler(server, opts.ProjectDir, getTestConfig) setupOpenHandler(server) - return netutil.ServePortHTTP(server, 7070, true, 500*time.Millisecond, func(port int) { - url = fmt.Sprintf("http://localhost:%d", port) - fmt.Printf("Server listen at %s\n", url) + host, port := netutil.GetHostAndIP(opts.Bind, opts.Port) + autoIncrPort := true + return netutil.ServePortHTTP(server, host, port, autoIncrPort, 500*time.Millisecond, func(port int) { + url := netutil.BuildAndDisplayURL(host, port) openURL(url) }) } diff --git a/cmd/xgo/trace/main.go b/cmd/xgo/trace/main.go index 67a10a33..9dbc0623 100644 --- a/cmd/xgo/trace/main.go +++ b/cmd/xgo/trace/main.go @@ -11,7 +11,6 @@ import ( "os/exec" "runtime" "runtime/debug" - "strconv" "strings" "time" @@ -36,6 +35,7 @@ See https://github.com/xhd2015/xgo for documentation. func Main(args []string) { var files []string var port string + var bind string n := len(args) var showHelp bool @@ -61,6 +61,20 @@ func Main(args []string) { port = strings.TrimPrefix(arg, "--port=") continue } + // add --bind + if arg == "--bind" { + if i+1 >= n { + fmt.Fprintf(os.Stderr, "--bind requires arg\n") + os.Exit(1) + } + bind = args[i+1] + i++ + continue + } else if strings.HasPrefix(arg, "--bind=") { + bind = strings.TrimPrefix(arg, "--bind=") + continue + } + if !strings.HasPrefix(arg, "-") { files = append(files, arg) continue @@ -84,7 +98,10 @@ func Main(args []string) { if port == "" { port = os.Getenv("PORT") } - err := serveFile(port, file) + if bind == "" { + bind = "localhost" + } + err := serveFile(bind, port, file) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) @@ -92,7 +109,7 @@ func Main(args []string) { } -func serveFile(portStr string, file string) error { +func serveFile(bindStr string, portStr string, file string) error { stat, err := os.Stat(file) if err != nil { return err @@ -149,22 +166,11 @@ func serveFile(portStr string, file string) error { } w.Write(output) }) - var autoIncrPort bool - var port int - if portStr == "" { - port = 7070 - autoIncrPort = true - } else { - parsePort, err := strconv.ParseInt(portStr, 10, 64) - if err != nil { - return fmt.Errorf("bad port: %s", portStr) - } - port = int(parsePort) - } - err = netutil.ServePortHTTP(server, port, autoIncrPort, 500*time.Millisecond, func(port int) { - url := fmt.Sprintf("http://localhost:%d", port) - fmt.Printf("Server listen at %s\n", url) + host, port := netutil.GetHostAndIP(bindStr, portStr) + autoIncrPort := true + err = netutil.ServePortHTTP(server, host, port, autoIncrPort, 500*time.Millisecond, func(port int) { + url := netutil.BuildAndDisplayURL(host, port) openURL(url) }) if err != nil { diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 805aeb89..4a2745be 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -3,8 +3,8 @@ package main import "fmt" const VERSION = "1.0.39" -const REVISION = "e6ba142b8a4c344bfd9e009090d1666635e1c9e5+1" -const NUMBER = 261 +const REVISION = "7fa581a5041d839180502f1d2377ea043803bde7+1" +const NUMBER = 262 func getRevision() string { revSuffix := "" diff --git a/runtime/core/version.go b/runtime/core/version.go index ade54178..ed75ad68 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -7,8 +7,8 @@ import ( ) const VERSION = "1.0.39" -const REVISION = "e6ba142b8a4c344bfd9e009090d1666635e1c9e5+1" -const NUMBER = 261 +const REVISION = "7fa581a5041d839180502f1d2377ea043803bde7+1" +const NUMBER = 262 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/support/netutil/netutil.go b/support/netutil/netutil.go index 5f78a840..cac5bace 100644 --- a/support/netutil/netutil.go +++ b/support/netutil/netutil.go @@ -19,16 +19,16 @@ func IsTCPAddrServing(url string, timeout time.Duration) (bool, error) { return true, nil } -func ServePortHTTP(server *http.ServeMux, port int, autoIncrPort bool, watchTimeout time.Duration, watch func(port int)) error { - return ServePort(port, autoIncrPort, watchTimeout, watch, func(port int) error { - return http.ListenAndServe(fmt.Sprintf("localhost:%d", port), server) +func ServePortHTTP(server *http.ServeMux, host string, port int, autoIncrPort bool, watchTimeout time.Duration, watch func(port int)) error { + return ServePort(host, port, autoIncrPort, watchTimeout, watch, func(port int) error { + return http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), server) }) } // suggested watch timeout: 500ms -func ServePort(port int, autoIncrPort bool, watchTimeout time.Duration, watch func(port int), doWithPort func(port int) error) error { +func ServePort(host string, port int, autoIncrPort bool, watchTimeout time.Duration, watch func(port int), doWithPort func(port int) error) error { for { - addr := net.JoinHostPort("localhost", strconv.Itoa(port)) + addr := net.JoinHostPort(host, strconv.Itoa(port)) serving, err := IsTCPAddrServing(addr, 20*time.Millisecond) if err != nil { return err @@ -72,3 +72,84 @@ func watchSignalWithinTimeout(timeout time.Duration, errSignal chan struct{}, ac } action() } + +// get the local area network IP address by ipType +func GetLocalHostByIPType(ipType string) []string { + addresses, err := net.InterfaceAddrs() + if err != nil { + fmt.Printf("\nnet.InterfaceAddrs failed, err: %v\n", err.Error()) + return nil + } + // ipv4 + ipv6 + var ips []string + for _, address := range addresses { + if ipNet, ok := address.(*net.IPNet); ok { + // IPv4 + if ipNet.IP.To4() != nil && (ipType == "all" || ipType == "ipv4") { + ips = append(ips, ipNet.IP.String()) + } + // IPv6 + if ipNet.IP.To16() != nil && ipNet.IP.To4() == nil && (ipType == "all" || ipType == "ipv6") { + ips = append(ips, ipNet.IP.String()) + } + } + } + if len(ips) == 0 { + fmt.Println("no network interface found") + return nil + } + return ips +} + +// provide default values for host and port +func GetHostAndIP(bindStr string, portStr string) (host string, port int) { + // default host=localhost, ipv4 127.0.0.1 or ipv6 ::1 + host = "localhost" + port = 7070 + + if portStr != "" { + parsePort, err := strconv.ParseInt(portStr, 10, 64) + if err == nil { + port = int(parsePort) + } + } + + localIPs := GetLocalHostByIPType("all") + if localIPs == nil { + return + } + // add default router + localIPs = append(localIPs, []string{"0.0.0.0", "::"}...) + for _, localIP := range localIPs { + if localIP == bindStr { + host = bindStr + break + } + } + // parse ipv6 + ip := net.ParseIP(host) + if ip != nil { + // IP is valid, check if it is IPv4 or IPv6 + if ip.To4() == nil { + // ip is not a v4 addr, must be v6 + host = fmt.Sprintf("[%s]", host) + } + } + return +} + +// build URL based on host and port +func BuildAndDisplayURL(host string, port int) string { + url := fmt.Sprintf("http://%s:%d", host, port) + fmt.Println("Server listen at:") + fmt.Printf("- Open: %s \r\n", url) + fmt.Printf("- Local: http://localhost:%d \r\n", port) + // only print ipv4 + if host == "0.0.0.0" { + ipv4IPs := GetLocalHostByIPType("ipv4") + for _, ipv4IP := range ipv4IPs { + fmt.Printf("- Network: http://%s:%d \r\n", ipv4IP, port) + } + } + return url +} diff --git a/support/netutil/netutil_test.go b/support/netutil/netutil_test.go new file mode 100644 index 00000000..a2ed98aa --- /dev/null +++ b/support/netutil/netutil_test.go @@ -0,0 +1,73 @@ +package netutil + +import ( + "fmt" + "testing" +) + +func TestGetLocalHostByIPType(t *testing.T) { + + tests := []struct { + name string + ipType string + want bool + }{ + {name: "Test with all IPs", ipType: "all", want: true}, + {name: "Test with IPv4", ipType: "ipv4", want: true}, + {name: "Test unknow ipType", ipType: "test", want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ips := GetLocalHostByIPType(tt.ipType) + if got := len(ips); got > 0 != tt.want { + t.Errorf("GetLocalHostByIPType(%v) = %v; want %v", tt.ipType, got, tt.want) + } + }) + } +} + +func TestGetHostAndIP(t *testing.T) { + ipv4IPs := GetLocalHostByIPType("ipv4") + var tests = []struct { + bindStr string + portStr string + wantHost string + wantPort int + }{ + {"", "", "localhost", 7070}, // default host and port + {ipv4IPs[0], "", ipv4IPs[0], 7070}, // provide host + {"", "8080", "localhost", 8080}, // provide port + {ipv4IPs[0], "8080", ipv4IPs[0], 8080}, // provide host and port + } + + for _, tt := range tests { + host, port := GetHostAndIP(tt.bindStr, tt.portStr) + if host != tt.wantHost || port != tt.wantPort { + t.Errorf("GetHostAndIP(%v, %v) => (%v, %v), want (%v, %v)", tt.bindStr, tt.portStr, host, port, tt.wantHost, tt.wantPort) + } + } +} + +func TestBuildAndDisplayURL(t *testing.T) { + ipv4IPs := GetLocalHostByIPType("ipv4") + url := fmt.Sprintf("http://%s:7070", ipv4IPs[0]) + var tests = []struct { + host string + port int + wantURL string + }{ + {"localhost", 7070, "http://localhost:7070"}, + {"127.0.0.1", 7070, "http://127.0.0.1:7070"}, + {"0.0.0.0", 7070, "http://0.0.0.0:7070"}, + {ipv4IPs[0], 7070, url}, + {"::1", 7070, "http://::1:7070"}, + } + + for _, tt := range tests { + gotURL := BuildAndDisplayURL(tt.host, tt.port) + if gotURL != tt.wantURL { + t.Errorf("BuildAndDisplayURL(%v, %v) => %v, want %v", tt.host, tt.port, gotURL, tt.wantURL) + } + } +}