proxy_parser.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. package upstream
  2. import (
  3. "net/url"
  4. "regexp"
  5. "strings"
  6. "github.com/0xJacky/Nginx-UI/internal/nginx"
  7. )
  8. // ProxyTarget represents a proxy destination
  9. type ProxyTarget struct {
  10. Host string `json:"host"`
  11. Port string `json:"port"`
  12. Type string `json:"type"` // "proxy_pass" or "upstream"
  13. }
  14. // ParseProxyTargets extracts proxy targets from nginx configuration
  15. func ParseProxyTargets(config *nginx.NgxConfig) []ProxyTarget {
  16. var targets []ProxyTarget
  17. if config == nil {
  18. return targets
  19. }
  20. // Parse upstream servers
  21. for _, upstream := range config.Upstreams {
  22. upstreamTargets := parseUpstreamServers(upstream)
  23. targets = append(targets, upstreamTargets...)
  24. }
  25. // Parse proxy_pass directives in servers
  26. for _, server := range config.Servers {
  27. proxyTargets := parseServerProxyPass(server)
  28. targets = append(targets, proxyTargets...)
  29. }
  30. return deduplicateTargets(targets)
  31. }
  32. // ParseProxyTargetsFromRawContent parses proxy targets from raw nginx configuration content
  33. func ParseProxyTargetsFromRawContent(content string) []ProxyTarget {
  34. var targets []ProxyTarget
  35. // First, collect all upstream names
  36. upstreamNames := make(map[string]bool)
  37. upstreamRegex := regexp.MustCompile(`(?s)upstream\s+([^\s]+)\s*\{([^}]+)\}`)
  38. upstreamMatches := upstreamRegex.FindAllStringSubmatch(content, -1)
  39. // Parse upstream blocks and collect upstream names
  40. for _, match := range upstreamMatches {
  41. if len(match) >= 3 {
  42. upstreamName := match[1]
  43. upstreamNames[upstreamName] = true
  44. upstreamContent := match[2]
  45. serverRegex := regexp.MustCompile(`(?m)^\s*server\s+([^;]+);`)
  46. serverMatches := serverRegex.FindAllStringSubmatch(upstreamContent, -1)
  47. for _, serverMatch := range serverMatches {
  48. if len(serverMatch) >= 2 {
  49. target := parseServerAddress(strings.TrimSpace(serverMatch[1]), "upstream")
  50. if target.Host != "" {
  51. targets = append(targets, target)
  52. }
  53. }
  54. }
  55. }
  56. }
  57. // Parse proxy_pass directives, but skip upstream references
  58. proxyPassRegex := regexp.MustCompile(`(?m)^\s*proxy_pass\s+([^;]+);`)
  59. proxyMatches := proxyPassRegex.FindAllStringSubmatch(content, -1)
  60. for _, match := range proxyMatches {
  61. if len(match) >= 2 {
  62. proxyPassURL := strings.TrimSpace(match[1])
  63. // Skip if this proxy_pass references an upstream
  64. if !isUpstreamReference(proxyPassURL, upstreamNames) {
  65. target := parseProxyPassURL(proxyPassURL)
  66. if target.Host != "" {
  67. targets = append(targets, target)
  68. }
  69. }
  70. }
  71. }
  72. return deduplicateTargets(targets)
  73. }
  74. // parseUpstreamServers extracts server addresses from upstream blocks
  75. func parseUpstreamServers(upstream *nginx.NgxUpstream) []ProxyTarget {
  76. var targets []ProxyTarget
  77. for _, directive := range upstream.Directives {
  78. if directive.Directive == "server" {
  79. target := parseServerAddress(directive.Params, "upstream")
  80. if target.Host != "" {
  81. targets = append(targets, target)
  82. }
  83. }
  84. }
  85. return targets
  86. }
  87. // parseServerProxyPass extracts proxy_pass targets from server blocks
  88. func parseServerProxyPass(server *nginx.NgxServer) []ProxyTarget {
  89. var targets []ProxyTarget
  90. // Check directives in server block
  91. for _, directive := range server.Directives {
  92. if directive.Directive == "proxy_pass" {
  93. target := parseProxyPassURL(directive.Params)
  94. if target.Host != "" {
  95. targets = append(targets, target)
  96. }
  97. }
  98. }
  99. // Check directives in location blocks
  100. for _, location := range server.Locations {
  101. locationTargets := parseLocationProxyPass(location.Content)
  102. targets = append(targets, locationTargets...)
  103. }
  104. return targets
  105. }
  106. // parseLocationProxyPass extracts proxy_pass from location content
  107. func parseLocationProxyPass(content string) []ProxyTarget {
  108. var targets []ProxyTarget
  109. // Use regex to find proxy_pass directives
  110. proxyPassRegex := regexp.MustCompile(`(?m)^\s*proxy_pass\s+([^;]+);`)
  111. matches := proxyPassRegex.FindAllStringSubmatch(content, -1)
  112. for _, match := range matches {
  113. if len(match) >= 2 {
  114. target := parseProxyPassURL(strings.TrimSpace(match[1]))
  115. if target.Host != "" {
  116. targets = append(targets, target)
  117. }
  118. }
  119. }
  120. return targets
  121. }
  122. // parseProxyPassURL parses a proxy_pass URL and extracts host and port
  123. func parseProxyPassURL(proxyPass string) ProxyTarget {
  124. proxyPass = strings.TrimSpace(proxyPass)
  125. // Handle HTTP/HTTPS URLs (e.g., "http://backend")
  126. if strings.HasPrefix(proxyPass, "http://") || strings.HasPrefix(proxyPass, "https://") {
  127. if parsedURL, err := url.Parse(proxyPass); err == nil {
  128. host := parsedURL.Hostname()
  129. port := parsedURL.Port()
  130. // Set default ports if not specified
  131. if port == "" {
  132. if parsedURL.Scheme == "https" {
  133. port = "443"
  134. } else {
  135. port = "80"
  136. }
  137. }
  138. return ProxyTarget{
  139. Host: host,
  140. Port: port,
  141. Type: "proxy_pass",
  142. }
  143. }
  144. }
  145. // Handle direct address format for stream module (e.g., "127.0.0.1:8080", "backend.example.com:12345")
  146. // This is used in stream configurations where proxy_pass doesn't require a protocol
  147. if !strings.Contains(proxyPass, "://") {
  148. return parseServerAddress(proxyPass, "proxy_pass")
  149. }
  150. return ProxyTarget{}
  151. }
  152. // parseServerAddress parses upstream server address
  153. func parseServerAddress(serverAddr string, targetType string) ProxyTarget {
  154. serverAddr = strings.TrimSpace(serverAddr)
  155. // Remove additional parameters (weight, max_fails, etc.)
  156. parts := strings.Fields(serverAddr)
  157. if len(parts) == 0 {
  158. return ProxyTarget{}
  159. }
  160. addr := parts[0]
  161. // Handle IPv6 addresses
  162. if strings.HasPrefix(addr, "[") {
  163. // IPv6 format: [::1]:8080
  164. if idx := strings.LastIndex(addr, "]:"); idx != -1 {
  165. host := addr[1:idx]
  166. port := addr[idx+2:]
  167. return ProxyTarget{
  168. Host: host,
  169. Port: port,
  170. Type: targetType,
  171. }
  172. }
  173. // IPv6 without port: [::1]
  174. host := strings.Trim(addr, "[]")
  175. return ProxyTarget{
  176. Host: host,
  177. Port: "80",
  178. Type: targetType,
  179. }
  180. }
  181. // Handle IPv4 addresses and hostnames
  182. if strings.Contains(addr, ":") {
  183. parts := strings.Split(addr, ":")
  184. if len(parts) == 2 {
  185. return ProxyTarget{
  186. Host: parts[0],
  187. Port: parts[1],
  188. Type: targetType,
  189. }
  190. }
  191. }
  192. // No port specified, use default
  193. return ProxyTarget{
  194. Host: addr,
  195. Port: "80",
  196. Type: targetType,
  197. }
  198. }
  199. // deduplicateTargets removes duplicate proxy targets
  200. func deduplicateTargets(targets []ProxyTarget) []ProxyTarget {
  201. seen := make(map[string]bool)
  202. var result []ProxyTarget
  203. for _, target := range targets {
  204. key := target.Host + ":" + target.Port + ":" + target.Type
  205. if !seen[key] {
  206. seen[key] = true
  207. result = append(result, target)
  208. }
  209. }
  210. return result
  211. }
  212. // isUpstreamReference checks if a proxy_pass URL references an upstream block
  213. func isUpstreamReference(proxyPass string, upstreamNames map[string]bool) bool {
  214. proxyPass = strings.TrimSpace(proxyPass)
  215. // For HTTP/HTTPS URLs, parse the URL to extract the hostname
  216. if strings.HasPrefix(proxyPass, "http://") || strings.HasPrefix(proxyPass, "https://") {
  217. if parsedURL, err := url.Parse(proxyPass); err == nil {
  218. hostname := parsedURL.Hostname()
  219. // Check if the hostname matches any upstream name
  220. return upstreamNames[hostname]
  221. }
  222. }
  223. // For stream module, proxy_pass can directly reference upstream name without protocol
  224. // Check if the proxy_pass value directly matches an upstream name
  225. if !strings.Contains(proxyPass, "://") && !strings.Contains(proxyPass, ":") {
  226. return upstreamNames[proxyPass]
  227. }
  228. return false
  229. }