mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-25 17:12:50 +00:00
117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
package manager
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/emicklei/go-restful/v3"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/klog/v2"
|
|
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/kubesphere/kubekey/v4/pkg/web"
|
|
)
|
|
|
|
// webManager handles the web server functionality for the application
|
|
type webManager struct {
|
|
port int
|
|
workdir string
|
|
|
|
ctrlclient.Client
|
|
*rest.Config
|
|
}
|
|
|
|
// Run starts the web server and handles incoming requests
|
|
func (m webManager) Run(ctx context.Context) error {
|
|
container := restful.DefaultContainer
|
|
container.Filter(logRequestAndResponse)
|
|
container.RecoverHandler(func(panicReason any, httpWriter http.ResponseWriter) {
|
|
logStackOnRecover(panicReason, httpWriter)
|
|
})
|
|
container.Add(web.NewWebService(ctx, m.workdir, m.Client, m.Config)).
|
|
// openapi
|
|
Add(web.NewSwaggerUIService()).
|
|
Add(web.NewAPIService(container.RegisteredWebServices()))
|
|
|
|
server := &http.Server{
|
|
Addr: fmt.Sprintf(":%d", m.port),
|
|
Handler: container,
|
|
ReadHeaderTimeout: 10 * time.Second, // Prevent Slowloris attack by timing out slow headers
|
|
}
|
|
|
|
shutdownCtx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
_ = server.Shutdown(shutdownCtx)
|
|
}()
|
|
|
|
return server.ListenAndServe()
|
|
}
|
|
|
|
// logStackOnRecover handles panic recovery and logs the stack trace
|
|
func logStackOnRecover(panicReason any, w http.ResponseWriter) {
|
|
var buf bytes.Buffer
|
|
buf.WriteString(fmt.Sprintf("recover from panic: %v\n", panicReason))
|
|
for i := 2; ; i++ {
|
|
_, file, line, ok := runtime.Caller(i)
|
|
if !ok {
|
|
break
|
|
}
|
|
buf.WriteString(fmt.Sprintf(" %s:%d\n", file, line))
|
|
}
|
|
klog.Errorln(buf.String())
|
|
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write([]byte("Internal Server Error"))
|
|
}
|
|
|
|
// logRequestAndResponse logs HTTP request and response details
|
|
func logRequestAndResponse(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
|
|
start := time.Now()
|
|
chain.ProcessFilter(req, resp)
|
|
|
|
// Always log error response
|
|
logWithVerbose := klog.V(4)
|
|
if resp.StatusCode() > http.StatusBadRequest {
|
|
logWithVerbose = klog.V(0)
|
|
}
|
|
|
|
logWithVerbose.Infof("%s - \"%s %s %s\" %d %d %dms",
|
|
remoteIP(req.Request),
|
|
req.Request.Method,
|
|
req.Request.URL,
|
|
req.Request.Proto,
|
|
resp.StatusCode(),
|
|
resp.ContentLength(),
|
|
time.Since(start)/time.Millisecond,
|
|
)
|
|
}
|
|
|
|
// remoteIP extracts the client IP address from the request, handling various proxy headers
|
|
func remoteIP(req *http.Request) string {
|
|
remoteAddr := req.RemoteAddr
|
|
if ip := req.Header.Get("X-Client-Ip"); ip != "" {
|
|
remoteAddr = ip
|
|
} else if ip := req.Header.Get("X-Real-IP"); ip != "" {
|
|
remoteAddr = ip
|
|
} else if ip = req.Header.Get("X-Forwarded-For"); ip != "" {
|
|
remoteAddr = ip
|
|
} else {
|
|
remoteAddr, _, _ = net.SplitHostPort(remoteAddr)
|
|
}
|
|
|
|
if remoteAddr == "::1" {
|
|
remoteAddr = "127.0.0.1"
|
|
}
|
|
|
|
return remoteAddr
|
|
}
|