From 8d1433b07dfa3f691d6db70e988218c582ff5f73 Mon Sep 17 00:00:00 2001
From: Yuxing Deng <jxfa0043379@hotmail.com>
Date: Wed, 9 Oct 2024 15:45:48 +0800
Subject: [PATCH] feat: Support to serve http via socket

---
 go.mod                              |  3 +-
 go.sum                              |  2 ++
 internal/config/flags.go            |  6 ++++
 internal/server/listener.go         | 52 +++++++++++++++++++++++++++++
 internal/server/listener_unix.go    | 33 ++++++++++++++++++
 internal/server/listener_windows.go | 34 +++++++++++++++++++
 main.go                             |  2 +-
 7 files changed, 130 insertions(+), 2 deletions(-)
 create mode 100644 internal/server/listener.go
 create mode 100644 internal/server/listener_unix.go
 create mode 100644 internal/server/listener_windows.go

diff --git a/go.mod b/go.mod
index 07736dc..990f6d5 100644
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,10 @@ go 1.22.0
 replace k8s.io/client-go => k8s.io/client-go v0.30.1
 
 require (
+	github.com/Microsoft/go-winio v0.6.2
 	github.com/gorilla/mux v1.8.1
 	github.com/rancher/apiserver v0.0.0-20240708202538-39a6f2535146
+	github.com/rancher/dynamiclistener v0.6.0-rc2
 	github.com/rancher/steve v0.0.0-20240911190153-79304d93b49b
 	github.com/rancher/wrangler/v3 v3.0.0
 	github.com/sirupsen/logrus v1.9.3
@@ -62,7 +64,6 @@ require (
 	github.com/prometheus/client_model v0.4.0 // indirect
 	github.com/prometheus/common v0.44.0 // indirect
 	github.com/prometheus/procfs v0.10.1 // indirect
-	github.com/rancher/dynamiclistener v0.6.0-rc2 // indirect
 	github.com/rancher/kubernetes-provider-detector v0.1.5 // indirect
 	github.com/rancher/lasso v0.0.0-20240705194423-b2a060d103c1 // indirect
 	github.com/rancher/norman v0.0.0-20240708202514-a0127673d1b9 // indirect
diff --git a/go.sum b/go.sum
index 86810c6..4786243 100644
--- a/go.sum
+++ b/go.sum
@@ -601,6 +601,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
diff --git a/internal/config/flags.go b/internal/config/flags.go
index 82c1e97..d581285 100644
--- a/internal/config/flags.go
+++ b/internal/config/flags.go
@@ -8,6 +8,7 @@ var InsecureSkipTLSVerify bool
 var SystemDefaultRegistry string
 var APIUIVersion = "1.1.11"
 var ShellPodImage string
+var BindAddress string
 
 func Flags() []cli.Flag {
 	return []cli.Flag{
@@ -30,5 +31,10 @@ func Flags() []cli.Flag {
 			Destination: &APIUIVersion,
 			Value:       APIUIVersion,
 		},
+		cli.StringFlag{
+			Name:        "bind-address",
+			Destination: &BindAddress,
+			Usage:       `Bind address with url format. The supported schemes are unix, tcp and namedpipe, e.g. unix:///path/to/kube-explorer.sock or namedpipe:/\.\pipe\kube-explorer`,
+		},
 	}
 }
diff --git a/internal/server/listener.go b/internal/server/listener.go
new file mode 100644
index 0000000..3d6d61c
--- /dev/null
+++ b/internal/server/listener.go
@@ -0,0 +1,52 @@
+package server
+
+import (
+	"context"
+	"log"
+	"net"
+	"net/http"
+
+	"github.com/cnrancher/kube-explorer/internal/config"
+	dynamicserver "github.com/rancher/dynamiclistener/server"
+	"github.com/rancher/steve/pkg/server"
+	"github.com/sirupsen/logrus"
+)
+
+func Serve(ctx context.Context, server *server.Server) error {
+	listener, ipOrPath, err := ensureListener()
+	if err != nil {
+		return err
+	}
+	if listener != nil {
+		return serveSocket(ctx, ipOrPath, listener, server)
+	}
+	return server.ListenAndServe(ctx, config.Steve.HTTPSListenPort, config.Steve.HTTPListenPort, &dynamicserver.ListenOpts{
+		BindHost: ipOrPath,
+	})
+}
+
+func serveSocket(ctx context.Context, socketPath string, listener net.Listener, handler http.Handler) error {
+	logger := logrus.StandardLogger()
+	errorLog := log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags)
+	socketServer := &http.Server{
+		Handler:  handler,
+		ErrorLog: errorLog,
+		BaseContext: func(_ net.Listener) context.Context {
+			return ctx
+		},
+	}
+	go func() {
+		logrus.Infof("Listening on %s", socketPath)
+		err := socketServer.Serve(listener)
+		if err != http.ErrServerClosed && err != nil {
+			logrus.Fatalf("https server failed: %v", err)
+		}
+	}()
+	go func() {
+		<-ctx.Done()
+		_ = socketServer.Shutdown(context.Background())
+		_ = listener.Close()
+	}()
+	<-ctx.Done()
+	return ctx.Err()
+}
diff --git a/internal/server/listener_unix.go b/internal/server/listener_unix.go
new file mode 100644
index 0000000..d1df629
--- /dev/null
+++ b/internal/server/listener_unix.go
@@ -0,0 +1,33 @@
+//go:build unix
+// +build unix
+
+package server
+
+import (
+	"fmt"
+	"net"
+	"net/url"
+
+	"github.com/cnrancher/kube-explorer/internal/config"
+)
+
+func ensureListener() (net.Listener, string, error) {
+	if config.BindAddress == "" {
+		return nil, "", nil
+	}
+	u, err := url.Parse(config.BindAddress)
+	if err != nil {
+		return nil, "", err
+	}
+	switch u.Scheme {
+	case "":
+		return nil, config.BindAddress, nil
+	case "tcp":
+		return nil, u.Host, nil
+	case "unix":
+		listener, err := net.Listen("unix", u.Path)
+		return listener, u.Path, err
+	default:
+		return nil, "", fmt.Errorf("Unsupported scheme %s, only tcp and unix are supported in UNIX OS", u.Scheme)
+	}
+}
diff --git a/internal/server/listener_windows.go b/internal/server/listener_windows.go
new file mode 100644
index 0000000..6da3637
--- /dev/null
+++ b/internal/server/listener_windows.go
@@ -0,0 +1,34 @@
+//go:build windows
+// +build windows
+
+package server
+
+import (
+	"fmt"
+	"net"
+	"net/url"
+
+	"github.com/Microsoft/go-winio"
+	"github.com/cnrancher/kube-explorer/internal/config"
+)
+
+func ensureListener() (net.Listener, string, error) {
+	if config.BindAddress == "" {
+		return nil, "", nil
+	}
+	u, err := url.Parse(config.BindAddress)
+	if err != nil {
+		return nil, "", err
+	}
+	switch u.Scheme {
+	case "":
+		return nil, config.BindAddress, nil
+	case "tcp":
+		return nil, u.Host, nil
+	case "namedpipe":
+		listener, err := winio.ListenPipe(u.Path, nil)
+		return listener, u.Path, err
+	default:
+		return nil, "", fmt.Errorf("Unsupported scheme %s, only tcp and namedpipe are supported in windows", u.Scheme)
+	}
+}
diff --git a/main.go b/main.go
index 8e4c71c..726e010 100644
--- a/main.go
+++ b/main.go
@@ -38,7 +38,7 @@ func run(_ *cli.Context) error {
 	if err != nil {
 		return err
 	}
-	return s.ListenAndServe(ctx, keconfig.Steve.HTTPSListenPort, keconfig.Steve.HTTPListenPort, nil)
+	return server.Serve(ctx, s)
 }
 
 func joinFlags(flags ...[]cli.Flag) []cli.Flag {