No-SilverBullet commented on code in PR #3047:
URL: https://github.com/apache/dubbo-go/pull/3047#discussion_r2418941754


##########
server/health.go:
##########
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package server
+
+import (
+       "context"
+       "encoding/json"
+       "net/http"
+       "sync"
+       "time"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/common/extension"
+)
+
+// HealthCheckServer HTTP health check server for Kubernetes integration
+type HealthCheckServer struct {
+       url     *common.URL
+       server  *http.Server
+       checker HealthChecker
+       handler HealthCheckHandler
+       mu      sync.RWMutex
+}
+
+// HealthResponse represents the HTTP response for health check endpoints
+type HealthResponse struct {
+       Status    string            `json:"status"`            // UP or DOWN
+       Timestamp int64             `json:"timestamp"`         // Unix 
timestamp in milliseconds
+       Message   string            `json:"message,omitempty"` // Optional 
descriptive message
+       Details   map[string]string `json:"details,omitempty"` // Detailed 
check results
+}
+
+// DefaultHealthCheckHandler provides standard JSON response handling
+type DefaultHealthCheckHandler struct{}
+
+func (d *DefaultHealthCheckHandler) HandleLivenessCheck(w http.ResponseWriter, 
r *http.Request, result *HealthCheckResult) {
+       status := "UP"
+       httpStatus := http.StatusOK
+       if !result.Healthy {
+               status = "DOWN"
+               httpStatus = http.StatusServiceUnavailable
+       }
+
+       response := HealthResponse{
+               Status:    status,
+               Timestamp: time.Now().UnixMilli(),
+               Message:   result.Message,
+               Details:   result.Details,
+       }
+
+       if response.Details == nil {
+               response.Details = make(map[string]string)
+       }
+       response.Details["check"] = "liveness"
+
+       writeJSONResponse(w, response, httpStatus)
+}
+
+func (d *DefaultHealthCheckHandler) HandleReadinessCheck(w 
http.ResponseWriter, r *http.Request, result *HealthCheckResult) {
+       status := "UP"
+       httpStatus := http.StatusOK
+       if !result.Healthy {
+               status = "DOWN"
+               httpStatus = http.StatusServiceUnavailable
+       }
+
+       response := HealthResponse{
+               Status:    status,
+               Timestamp: time.Now().UnixMilli(),
+               Message:   result.Message,
+               Details:   result.Details,
+       }
+
+       if response.Details == nil {
+               response.Details = make(map[string]string)
+       }
+       response.Details["check"] = "readiness"
+
+       writeJSONResponse(w, response, httpStatus)
+}
+
+// NewHealthCheckServer creates a new health check server
+func NewHealthCheckServer(url *common.URL) *HealthCheckServer {
+       return &HealthCheckServer{
+               url:     url,
+               checker: GetHealthChecker(),
+               handler: GetHealthCheckHandler(),
+       }
+}
+
+// SetHealthChecker sets the health checker for this server instance
+func (h *HealthCheckServer) SetHealthChecker(checker HealthChecker) {
+       h.mu.Lock()
+       defer h.mu.Unlock()
+       h.checker = checker
+}
+
+// SetHealthCheckHandler sets the health check handler for this server instance
+func (h *HealthCheckServer) SetHealthCheckHandler(handler HealthCheckHandler) {
+       h.mu.Lock()
+       defer h.mu.Unlock()
+       h.handler = handler
+}
+
+// Start starts the health check server if enabled
+func (h *HealthCheckServer) Start() {
+       if !h.url.GetParamBool(constant.HealthCheckEnabledKey, false) {
+               return
+       }
+
+       mux := http.NewServeMux()
+       port := h.url.GetParam(constant.HealthCheckPortKey, 
constant.HealthCheckDefaultPort)
+       livePath := h.url.GetParam(constant.HealthCheckLivePathKey, 
constant.HealthCheckDefaultLivePath)
+       readyPath := h.url.GetParam(constant.HealthCheckReadyPathKey, 
constant.HealthCheckDefaultReadyPath)
+
+       mux.HandleFunc(livePath, h.livenessHandler)
+       mux.HandleFunc(readyPath, h.readinessHandler)
+
+       h.server = &http.Server{Addr: ":" + port, Handler: mux}
+
+       // Register graceful shutdown callback
+       extension.AddCustomShutdownCallback(func() {
+               timeoutStr := h.url.GetParam(constant.HealthCheckTimeoutKey, 
constant.HealthCheckDefaultTimeout)
+               timeout, err := time.ParseDuration(timeoutStr)
+               if err != nil {
+                       timeout = 10 * time.Second
+               }
+               ctx, cancel := context.WithTimeout(context.Background(), 
timeout)
+               defer cancel()
+               if err := h.server.Shutdown(ctx); err != nil {
+                       logger.Errorf("health check server shutdown failed, 
err: %v", err)
+               } else {
+                       logger.Info("health check server gracefully shutdown 
success")
+               }
+       })
+
+       logger.Infof("health check endpoints started - liveness: :%s%s, 
readiness: :%s%s",
+               port, livePath, port, readyPath)
+
+       go func() {
+               if err := h.server.ListenAndServe(); err != nil && err != 
http.ErrServerClosed {
+                       logger.Errorf("health check server error: %v", err)
+               }
+       }()
+}
+
+// livenessHandler handles Kubernetes liveness probe requests
+func (h *HealthCheckServer) livenessHandler(w http.ResponseWriter, r 
*http.Request) {
+       if r.Method != http.MethodGet {
+               http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+               return
+       }
+
+       h.mu.RLock()
+       checker := h.checker
+       handler := h.handler
+       h.mu.RUnlock()
+
+       ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)

Review Comment:
   why hard-code 5 seconds



##########
server/health.go:
##########
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package server
+
+import (
+       "context"
+       "encoding/json"
+       "net/http"
+       "sync"
+       "time"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/common/extension"
+)
+
+// HealthCheckServer HTTP health check server for Kubernetes integration
+type HealthCheckServer struct {
+       url     *common.URL
+       server  *http.Server
+       checker HealthChecker
+       handler HealthCheckHandler
+       mu      sync.RWMutex
+}
+
+// HealthResponse represents the HTTP response for health check endpoints
+type HealthResponse struct {
+       Status    string            `json:"status"`            // UP or DOWN
+       Timestamp int64             `json:"timestamp"`         // Unix 
timestamp in milliseconds
+       Message   string            `json:"message,omitempty"` // Optional 
descriptive message
+       Details   map[string]string `json:"details,omitempty"` // Detailed 
check results
+}
+
+// DefaultHealthCheckHandler provides standard JSON response handling
+type DefaultHealthCheckHandler struct{}
+
+func (d *DefaultHealthCheckHandler) HandleLivenessCheck(w http.ResponseWriter, 
r *http.Request, result *HealthCheckResult) {
+       status := "UP"

Review Comment:
   change to const, same with 'DOWN'



##########
server/health.go:
##########
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package server
+
+import (
+       "context"
+       "encoding/json"
+       "net/http"
+       "sync"
+       "time"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/common"
+       "dubbo.apache.org/dubbo-go/v3/common/constant"
+       "dubbo.apache.org/dubbo-go/v3/common/extension"
+)
+
+// HealthCheckServer HTTP health check server for Kubernetes integration
+type HealthCheckServer struct {
+       url     *common.URL
+       server  *http.Server
+       checker HealthChecker
+       handler HealthCheckHandler
+       mu      sync.RWMutex
+}
+
+// HealthResponse represents the HTTP response for health check endpoints
+type HealthResponse struct {
+       Status    string            `json:"status"`            // UP or DOWN
+       Timestamp int64             `json:"timestamp"`         // Unix 
timestamp in milliseconds
+       Message   string            `json:"message,omitempty"` // Optional 
descriptive message
+       Details   map[string]string `json:"details,omitempty"` // Detailed 
check results
+}
+
+// DefaultHealthCheckHandler provides standard JSON response handling
+type DefaultHealthCheckHandler struct{}
+
+func (d *DefaultHealthCheckHandler) HandleLivenessCheck(w http.ResponseWriter, 
r *http.Request, result *HealthCheckResult) {
+       status := "UP"
+       httpStatus := http.StatusOK
+       if !result.Healthy {
+               status = "DOWN"
+               httpStatus = http.StatusServiceUnavailable
+       }
+
+       response := HealthResponse{
+               Status:    status,
+               Timestamp: time.Now().UnixMilli(),
+               Message:   result.Message,
+               Details:   result.Details,
+       }
+
+       if response.Details == nil {
+               response.Details = make(map[string]string)
+       }
+       response.Details["check"] = "liveness"
+
+       writeJSONResponse(w, response, httpStatus)
+}
+
+func (d *DefaultHealthCheckHandler) HandleReadinessCheck(w 
http.ResponseWriter, r *http.Request, result *HealthCheckResult) {
+       status := "UP"
+       httpStatus := http.StatusOK
+       if !result.Healthy {
+               status = "DOWN"
+               httpStatus = http.StatusServiceUnavailable
+       }
+
+       response := HealthResponse{
+               Status:    status,
+               Timestamp: time.Now().UnixMilli(),
+               Message:   result.Message,
+               Details:   result.Details,
+       }
+
+       if response.Details == nil {
+               response.Details = make(map[string]string)
+       }
+       response.Details["check"] = "readiness"
+
+       writeJSONResponse(w, response, httpStatus)
+}
+
+// NewHealthCheckServer creates a new health check server
+func NewHealthCheckServer(url *common.URL) *HealthCheckServer {
+       return &HealthCheckServer{
+               url:     url,
+               checker: GetHealthChecker(),
+               handler: GetHealthCheckHandler(),
+       }
+}
+
+// SetHealthChecker sets the health checker for this server instance
+func (h *HealthCheckServer) SetHealthChecker(checker HealthChecker) {
+       h.mu.Lock()
+       defer h.mu.Unlock()
+       h.checker = checker
+}
+
+// SetHealthCheckHandler sets the health check handler for this server instance
+func (h *HealthCheckServer) SetHealthCheckHandler(handler HealthCheckHandler) {
+       h.mu.Lock()
+       defer h.mu.Unlock()
+       h.handler = handler
+}
+
+// Start starts the health check server if enabled
+func (h *HealthCheckServer) Start() {
+       if !h.url.GetParamBool(constant.HealthCheckEnabledKey, false) {
+               return
+       }
+
+       mux := http.NewServeMux()
+       port := h.url.GetParam(constant.HealthCheckPortKey, 
constant.HealthCheckDefaultPort)
+       livePath := h.url.GetParam(constant.HealthCheckLivePathKey, 
constant.HealthCheckDefaultLivePath)
+       readyPath := h.url.GetParam(constant.HealthCheckReadyPathKey, 
constant.HealthCheckDefaultReadyPath)
+
+       mux.HandleFunc(livePath, h.livenessHandler)
+       mux.HandleFunc(readyPath, h.readinessHandler)
+
+       h.server = &http.Server{Addr: ":" + port, Handler: mux}
+
+       // Register graceful shutdown callback
+       extension.AddCustomShutdownCallback(func() {
+               timeoutStr := h.url.GetParam(constant.HealthCheckTimeoutKey, 
constant.HealthCheckDefaultTimeout)
+               timeout, err := time.ParseDuration(timeoutStr)
+               if err != nil {
+                       timeout = 10 * time.Second

Review Comment:
   I think it is better to return an error directly so that users can know that 
their configuration is wrong



##########
server/health_checker.go:
##########
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package server
+
+import (
+       "context"
+       "net/http"
+       "sync"
+)
+
+// HealthChecker defines the interface for customizable health check logic
+// Users can implement this interface to define their own health criteria
+type HealthChecker interface {
+       // CheckLiveness checks if the application is alive
+       // Return false only for serious internal errors that require container 
restart
+       CheckLiveness(ctx context.Context) bool
+
+       // CheckReadiness checks if the application is ready to serve traffic
+       // Return false if external dependencies are unavailable
+       CheckReadiness(ctx context.Context) bool
+
+       // Name returns the identifier of this health checker
+       Name() string
+}
+
+// HealthCheckResult provides detailed health check information
+type HealthCheckResult struct {
+       Healthy bool              // Overall health status
+       Message string            // Optional descriptive message
+       Details map[string]string // Detailed check results
+}
+
+// DetailedHealthChecker extends HealthChecker with detailed result information
+type DetailedHealthChecker interface {
+       HealthChecker
+
+       // CheckLivenessDetailed returns detailed liveness check result
+       CheckLivenessDetailed(ctx context.Context) *HealthCheckResult
+
+       // CheckReadinessDetailed returns detailed readiness check result
+       CheckReadinessDetailed(ctx context.Context) *HealthCheckResult
+}
+
+// HealthCheckHandler allows customizing HTTP response handling
+type HealthCheckHandler interface {
+       // HandleLivenessCheck processes liveness probe HTTP request
+       HandleLivenessCheck(w http.ResponseWriter, r *http.Request, result 
*HealthCheckResult)
+
+       // HandleReadinessCheck processes readiness probe HTTP request
+       HandleReadinessCheck(w http.ResponseWriter, r *http.Request, result 
*HealthCheckResult)
+}
+
+// CompositeHealthChecker combines multiple health checkers
+// All checkers must pass for the composite to be healthy
+type CompositeHealthChecker struct {
+       checkers []HealthChecker
+}
+
+// NewCompositeHealthChecker creates a composite health checker
+func NewCompositeHealthChecker(checkers ...HealthChecker) 
*CompositeHealthChecker {
+       return &CompositeHealthChecker{checkers: checkers}
+}
+
+func (c *CompositeHealthChecker) CheckLiveness(ctx context.Context) bool {
+       for _, checker := range c.checkers {
+               if !checker.CheckLiveness(ctx) {
+                       return false
+               }
+       }
+       return len(c.checkers) > 0
+}
+
+func (c *CompositeHealthChecker) CheckReadiness(ctx context.Context) bool {
+       for _, checker := range c.checkers {
+               if !checker.CheckReadiness(ctx) {
+                       return false
+               }
+       }
+       return len(c.checkers) > 0
+}
+
+func (c *CompositeHealthChecker) CheckLivenessDetailed(ctx context.Context) 
*HealthCheckResult {
+       result := &HealthCheckResult{
+               Healthy: true,
+               Details: make(map[string]string),
+       }
+
+       for _, checker := range c.checkers {
+               if detailedChecker, ok := checker.(DetailedHealthChecker); ok {
+                       subResult := detailedChecker.CheckLivenessDetailed(ctx)
+                       if !subResult.Healthy {
+                               result.Healthy = false
+                               result.Message = subResult.Message
+                       }
+                       // Merge details
+                       for k, v := range subResult.Details {
+                               result.Details[checker.Name()+"_"+k] = v
+                       }
+               } else {
+                       healthy := checker.CheckLiveness(ctx)
+                       result.Details[checker.Name()] = map[bool]string{true: 
"UP", false: "DOWN"}[healthy]
+                       if !healthy {
+                               result.Healthy = false
+                               result.Message = checker.Name() + " liveness 
check failed"
+                       }
+               }
+       }
+
+       return result
+}
+
+func (c *CompositeHealthChecker) CheckReadinessDetailed(ctx context.Context) 
*HealthCheckResult {
+       result := &HealthCheckResult{
+               Healthy: true,
+               Details: make(map[string]string),
+       }
+
+       for _, checker := range c.checkers {
+               if detailedChecker, ok := checker.(DetailedHealthChecker); ok {
+                       subResult := detailedChecker.CheckReadinessDetailed(ctx)
+                       if !subResult.Healthy {
+                               result.Healthy = false
+                               result.Message = subResult.Message
+                       }
+                       // Merge details
+                       for k, v := range subResult.Details {
+                               result.Details[checker.Name()+"_"+k] = v
+                       }
+               } else {
+                       healthy := checker.CheckReadiness(ctx)
+                       result.Details[checker.Name()] = map[bool]string{true: 
"UP", false: "DOWN"}[healthy]
+                       if !healthy {
+                               result.Healthy = false
+                               result.Message = checker.Name() + " readiness 
check failed"
+                       }
+               }
+       }
+
+       return result
+}
+
+func (c *CompositeHealthChecker) Name() string {
+       return "CompositeHealthChecker"
+}
+
+// DefaultHealthChecker provides basic implementation
+type DefaultHealthChecker struct{}
+
+func (d *DefaultHealthChecker) CheckLiveness(ctx context.Context) bool {
+       // Basic liveness: process is running
+       return true
+}
+
+func (d *DefaultHealthChecker) CheckReadiness(ctx context.Context) bool {
+       // Basic readiness: assume ready
+       return true
+}

Review Comment:
   The default checker's survival check returns true by default, which is fine. 
The readiness check can do a little more, such as checking the current service 
registration status. you can refer 
https://www.bookstack.cn/read/dubbo-3.1-zh/3b6bb5d2b90f4b85.md



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to