package nginx import ( "fmt" "io" "net/http" "os" "regexp" "strconv" "strings" "time" "github.com/pkg/errors" "github.com/uozi-tech/cosy/logger" ) // StubStatusInfo Store the stub_status module status type StubStatusInfo struct { Enabled bool `json:"stub_status_enabled"` // stub_status module is enabled URL string `json:"stub_status_url"` // stub_status access address } type StubStatusData struct { Active int `json:"active"` Accepts int `json:"accepts"` Handled int `json:"handled"` Requests int `json:"requests"` Reading int `json:"reading"` Writing int `json:"writing"` Waiting int `json:"waiting"` } const ( StubStatusPort = 51828 StubStatusPath = "/stub_status" StubStatusHost = "127.0.0.1" StubStatusProtocol = "http" StubStatusAllow = "127.0.0.1" StubStatusDeny = "all" StubStatusConfigName = "stub_status_nginx-ui.conf" ) // GetStubStatusData Get the stub_status module data func GetStubStatusData() (bool, *StubStatusData, error) { result := &StubStatusData{ Active: 0, Accepts: 0, Handled: 0, Requests: 0, Reading: 0, Writing: 0, Waiting: 0, } // Get the stub_status status information enabled, statusURL := IsStubStatusEnabled() logger.Info("GetStubStatusData", "enabled", enabled, "statusURL", statusURL) if !enabled { return false, result, fmt.Errorf("stub_status is not enabled") } // Create an HTTP client client := &http.Client{ Timeout: 5 * time.Second, } // Send a request to get the stub_status data resp, err := client.Get(statusURL) if err != nil { return enabled, result, fmt.Errorf("failed to get stub status: %v", err) } defer resp.Body.Close() // Read the response content body, err := io.ReadAll(resp.Body) if err != nil { return enabled, result, fmt.Errorf("failed to read response body: %v", err) } // Parse the response content statusContent := string(body) // Match the active connection number activeRe := regexp.MustCompile(`Active connections:\s+(\d+)`) if matches := activeRe.FindStringSubmatch(statusContent); len(matches) > 1 { result.Active, _ = strconv.Atoi(matches[1]) } // Match the request statistics information serverRe := regexp.MustCompile(`(\d+)\s+(\d+)\s+(\d+)`) if matches := serverRe.FindStringSubmatch(statusContent); len(matches) > 3 { result.Accepts, _ = strconv.Atoi(matches[1]) result.Handled, _ = strconv.Atoi(matches[2]) result.Requests, _ = strconv.Atoi(matches[3]) } // Match the read and write waiting numbers connRe := regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`) if matches := connRe.FindStringSubmatch(statusContent); len(matches) > 3 { result.Reading, _ = strconv.Atoi(matches[1]) result.Writing, _ = strconv.Atoi(matches[2]) result.Waiting, _ = strconv.Atoi(matches[3]) } return enabled, result, nil } // GetStubStatus Get the stub_status module status func GetStubStatus() *StubStatusInfo { enabled, statusURL := IsStubStatusEnabled() return &StubStatusInfo{ Enabled: enabled, URL: statusURL, } } // IsStubStatusEnabled Check if the stub_status module is enabled and return the access address // Only check the stub_status_nginx-ui.conf configuration file func IsStubStatusEnabled() (bool, string) { stubStatusConfPath := GetConfPath("conf.d", StubStatusConfigName) if _, err := os.Stat(stubStatusConfPath); os.IsNotExist(err) { return false, "" } ngxConfig, err := ParseNgxConfig(stubStatusConfPath) if err != nil { return false, "" } // Find the stub_status configuration for _, server := range ngxConfig.Servers { protocol := StubStatusProtocol host := StubStatusHost port := strconv.Itoa(StubStatusPort) for _, location := range server.Locations { // Check if the location content contains stub_status if strings.Contains(location.Content, "stub_status") { stubStatusURL := fmt.Sprintf("%s://%s:%s%s", protocol, host, port, StubStatusPath) return true, stubStatusURL } } } return false, "" } // EnableStubStatus Enable stub_status module func EnableStubStatus() error { enabled, _ := IsStubStatusEnabled() if enabled { return nil } return CreateStubStatusConfig() } // DisableStubStatus Disable stub_status module func DisableStubStatus() error { stubStatusConfPath := GetConfPath("conf.d", StubStatusConfigName) if _, err := os.Stat(stubStatusConfPath); os.IsNotExist(err) { return nil } return os.Remove(stubStatusConfPath) } // CreateStubStatusConfig Create a new stub_status configuration file func CreateStubStatusConfig() error { httpConfPath := GetConfPath("conf.d", StubStatusConfigName) stubStatusConfig := ` # DO NOT EDIT THIS FILE, IT IS AUTO GENERATED BY NGINX-UI # Nginx stub_status configuration for Nginx-UI # Modified at ` + time.Now().Format("2006-01-02 15:04:05") + ` server { listen 51828; # Use non-standard port to avoid conflicts server_name localhost; # Status monitoring interface location /stub_status { stub_status; allow 127.0.0.1; # Only allow local access deny all; } } ` ngxConfig, err := ParseNgxConfigByContent(stubStatusConfig) if err != nil { return errors.Wrap(err, "failed to parse new nginx config") } ngxConfig.FileName = httpConfPath configText, err := ngxConfig.BuildConfig() if err != nil { return errors.Wrap(err, "failed to build nginx config") } return os.WriteFile(httpConfPath, []byte(configText), 0644) }