nginx-ui/internal/nginx/stub_status.go

234 lines
6.1 KiB
Go

package nginx
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"text/template"
"time"
"github.com/0xJacky/Nginx-UI/settings"
"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 (
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.Debug("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 := settings.NginxSettings.GetStubStatusPort()
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:%d%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)
const stubStatusTemplate = `
# DO NOT EDIT THIS FILE, IT IS AUTO GENERATED BY NGINX-UI
# Nginx stub_status configuration for Nginx-UI
# Modified at {{.ModifiedTime}}
server {
listen {{.Port}}; # Use non-standard port to avoid conflicts
server_name {{.ServerName}};
# Status monitoring interface
location {{.StatusPath}} {
stub_status;
allow {{.AllowIP}}; # Only allow local access
deny {{.DenyAccess}};
}
}
`
type StubStatusTemplateData struct {
ModifiedTime string
Port uint
ServerName string
StatusPath string
AllowIP string
DenyAccess string
}
data := StubStatusTemplateData{
ModifiedTime: time.Now().Format(time.DateTime),
Port: settings.NginxSettings.GetStubStatusPort(),
ServerName: "localhost",
StatusPath: StubStatusPath,
AllowIP: StubStatusAllow,
DenyAccess: StubStatusDeny,
}
tmpl, err := template.New("stub_status").Parse(stubStatusTemplate)
if err != nil {
return errors.Wrap(err, "failed to parse template")
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
return errors.Wrap(err, "failed to execute template")
}
stubStatusConfig := buf.String()
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)
}