@@ -5,15 +5,14 @@ import (
55"regexp"
66"strings"
77
8- "github.com/0xJacky/Nginx-UI/internal/nginx"
98"github.com/0xJacky/Nginx-UI/settings"
109)
1110
1211// ProxyTarget represents a proxy destination
1312type ProxyTarget struct {
1413Host string `json:"host"`
1514Port string `json:"port"`
16- Type string `json:"type"` // "proxy_pass" or "upstream"
15+ Type string `json:"type"` // "proxy_pass", "grpc_pass" or "upstream"
1716Resolver string `json:"resolver"` // DNS resolver address (e.g., "127.0.0.1:8600")
1817IsConsul bool `json:"is_consul"` // Whether this is a consul service discovery target
1918ServiceURL string `json:"service_url"` // Full service URL for consul (e.g., "service.consul service=redacted-net resolve")
@@ -82,88 +81,59 @@ func ParseProxyTargetsFromRawContent(content string) []ProxyTarget {
8281proxyPassURL := strings .TrimSpace (match [1 ])
8382// Skip if this proxy_pass references an upstream
8483if ! isUpstreamReference (proxyPassURL , upstreamNames ) {
85- target := parseProxyPassURL (proxyPassURL )
84+ target := parseProxyPassURL (proxyPassURL , "proxy_pass" )
8685if target .Host != "" {
8786targets = append (targets , target )
8887}
8988}
9089}
9190}
9291
93- return deduplicateTargets (targets )
94- }
95-
96- // parseUpstreamServers extracts server addresses from upstream blocks
97- func parseUpstreamServers (upstream * nginx.NgxUpstream ) []ProxyTarget {
98- var targets []ProxyTarget
99-
100- // Create upstream context for this upstream block
101- ctx := & UpstreamContext {
102- Name : upstream .Name ,
103- }
104-
105- // Extract resolver from upstream directives
106- for _ , directive := range upstream .Directives {
107- if directive .Directive == "resolver" {
108- resolverParts := strings .Fields (directive .Params )
109- if len (resolverParts ) > 0 {
110- ctx .Resolver = resolverParts [0 ]
111- }
112- }
113- }
114-
115- for _ , directive := range upstream .Directives {
116- if directive .Directive == "server" {
117- target := parseServerAddress (directive .Params , "upstream" , ctx )
118- if target .Host != "" {
119- targets = append (targets , target )
120- }
121- }
122- }
123-
124- return targets
125- }
126-
127- // parseLocationProxyPass extracts proxy_pass from location content
128- func parseLocationProxyPass (content string ) []ProxyTarget {
129- var targets []ProxyTarget
130-
131- // Use regex to find proxy_pass directives
132- proxyPassRegex := regexp .MustCompile (`(?m)^\s*proxy_pass\s+([^;]+);` )
133- matches := proxyPassRegex .FindAllStringSubmatch (content , - 1 )
92+ // Parse grpc_pass directives, but skip upstream references
93+ grpcPassRegex := regexp .MustCompile (`(?m)^\s*grpc_pass\s+([^;]+);` )
94+ grpcMatches := grpcPassRegex .FindAllStringSubmatch (content , - 1 )
13495
135- for _ , match := range matches {
96+ for _ , match := range grpcMatches {
13697if len (match ) >= 2 {
137- target := parseProxyPassURL (strings .TrimSpace (match [1 ]))
138- if target .Host != "" {
139- targets = append (targets , target )
98+ grpcPassURL := strings .TrimSpace (match [1 ])
99+ // Skip if this grpc_pass references an upstream
100+ if ! isUpstreamReference (grpcPassURL , upstreamNames ) {
101+ target := parseProxyPassURL (grpcPassURL , "grpc_pass" )
102+ if target .Host != "" {
103+ targets = append (targets , target )
104+ }
140105}
141106}
142107}
143108
144- return targets
109+ return deduplicateTargets ( targets )
145110}
146111
147- // parseProxyPassURL parses a proxy_pass URL and extracts host and port
148- func parseProxyPassURL (proxyPass string ) ProxyTarget {
149- proxyPass = strings .TrimSpace (proxyPass )
112+ // parseProxyPassURL parses a proxy_pass or grpc_pass URL and extracts host and port
113+ func parseProxyPassURL (passURL , passType string ) ProxyTarget {
114+ passURL = strings .TrimSpace (passURL )
150115
151116// Skip URLs that contain Nginx variables
152- if strings .Contains (proxyPass , "$" ) {
117+ if strings .Contains (passURL , "$" ) {
153118return ProxyTarget {}
154119}
155120
156- // Handle HTTP/HTTPS URLs (e.g., "http://backend")
157- if strings .HasPrefix (proxyPass , "http://" ) || strings .HasPrefix (proxyPass , "https://" ) {
158- if parsedURL , err := url .Parse (proxyPass ); err == nil {
121+ // Handle HTTP/HTTPS/gRPC URLs (e.g., "http://backend", "grpc ://backend")
122+ if strings .HasPrefix (passURL , "http://" ) || strings .HasPrefix (passURL , "https://" ) || strings . HasPrefix ( passURL , "grpc://" ) || strings . HasPrefix ( passURL , "grpcs ://" ) {
123+ if parsedURL , err := url .Parse (passURL ); err == nil {
159124host := parsedURL .Hostname ()
160125port := parsedURL .Port ()
161126
162127// Set default ports if not specified
163128if port == "" {
164- if parsedURL .Scheme == "https" {
129+ switch parsedURL .Scheme {
130+ case "https" :
131+ port = "443"
132+ case "grpcs" :
165133port = "443"
166- } else {
134+ case "grpc" :
135+ port = "80"
136+ default : // http
167137port = "80"
168138}
169139}
@@ -176,15 +146,15 @@ func parseProxyPassURL(proxyPass string) ProxyTarget {
176146return ProxyTarget {
177147Host : host ,
178148Port : port ,
179- Type : "proxy_pass" ,
149+ Type : passType ,
180150}
181151}
182152}
183153
184154// Handle direct address format for stream module (e.g., "127.0.0.1:8080", "backend.example.com:12345")
185- // This is used in stream configurations where proxy_pass doesn't require a protocol
186- if ! strings .Contains (proxyPass , "://" ) {
187- target := parseServerAddress (proxyPass , "proxy_pass" , nil ) // No upstream context for this function
155+ // This is used in stream configurations where proxy_pass/grpc_pass doesn't require a protocol
156+ if ! strings .Contains (passURL , "://" ) {
157+ target := parseServerAddress (passURL , passType , nil ) // No upstream context for this function
188158
189159// Skip if this is the HTTP challenge port used by Let's Encrypt
190160if target .Host == "127.0.0.1" && target .Port == settings .CertSettings .HTTPChallengePort {
@@ -262,7 +232,7 @@ func isConsulServiceDiscovery(serverAddr string) bool {
262232if strings .Contains (serverAddr , "service=" ) && strings .Contains (serverAddr , "resolve" ) {
263233return true
264234}
265- // Legacy consul format: "service.consul service=name resolve"
235+ // Legacy consul format: "service.consul service=name resolve"
266236return strings .Contains (serverAddr , "service.consul" ) &&
267237(strings .Contains (serverAddr , "service=" ) || strings .Contains (serverAddr , "resolve" ))
268238}
@@ -327,17 +297,17 @@ func deduplicateTargets(targets []ProxyTarget) []ProxyTarget {
327297return result
328298}
329299
330- // isUpstreamReference checks if a proxy_pass URL references an upstream block
331- func isUpstreamReference (proxyPass string , upstreamNames map [string ]bool ) bool {
332- proxyPass = strings .TrimSpace (proxyPass )
300+ // isUpstreamReference checks if a proxy_pass or grpc_pass URL references an upstream block
301+ func isUpstreamReference (passURL string , upstreamNames map [string ]bool ) bool {
302+ passURL = strings .TrimSpace (passURL )
333303
334- // For HTTP/HTTPS URLs, parse the URL to extract the hostname
335- if strings .HasPrefix (proxyPass , "http://" ) || strings .HasPrefix (proxyPass , "https://" ) {
304+ // For HTTP/HTTPS/gRPC URLs, parse the URL to extract the hostname
305+ if strings .HasPrefix (passURL , "http://" ) || strings .HasPrefix (passURL , "https://" ) || strings . HasPrefix ( passURL , "grpc://" ) || strings . HasPrefix ( passURL , "grpcs ://" ) {
336306// Handle URLs with nginx variables (e.g., "https://myUpStr$request_uri")
337307// Extract the scheme and hostname part before any nginx variables
338- schemeAndHost := proxyPass
339- if dollarIndex := strings .Index (proxyPass , "$" ); dollarIndex != - 1 {
340- schemeAndHost = proxyPass [:dollarIndex ]
308+ schemeAndHost := passURL
309+ if dollarIndex := strings .Index (passURL , "$" ); dollarIndex != - 1 {
310+ schemeAndHost = passURL [:dollarIndex ]
341311}
342312
343313// Try to parse the URL, if it fails, try manual extraction
@@ -348,11 +318,15 @@ func isUpstreamReference(proxyPass string, upstreamNames map[string]bool) bool {
348318} else {
349319// Fallback: manually extract hostname for URLs with variables
350320// Remove scheme prefix
351- withoutScheme := proxyPass
352- if strings .HasPrefix (proxyPass , "https://" ) {
353- withoutScheme = strings .TrimPrefix (proxyPass , "https://" )
354- } else if strings .HasPrefix (proxyPass , "http://" ) {
355- withoutScheme = strings .TrimPrefix (proxyPass , "http://" )
321+ withoutScheme := passURL
322+ if strings .HasPrefix (passURL , "https://" ) {
323+ withoutScheme = strings .TrimPrefix (passURL , "https://" )
324+ } else if strings .HasPrefix (passURL , "http://" ) {
325+ withoutScheme = strings .TrimPrefix (passURL , "http://" )
326+ } else if strings .HasPrefix (passURL , "grpc://" ) {
327+ withoutScheme = strings .TrimPrefix (passURL , "grpc://" )
328+ } else if strings .HasPrefix (passURL , "grpcs://" ) {
329+ withoutScheme = strings .TrimPrefix (passURL , "grpcs://" )
356330}
357331
358332// Extract hostname before any path, port, or variable
@@ -371,10 +345,10 @@ func isUpstreamReference(proxyPass string, upstreamNames map[string]bool) bool {
371345}
372346}
373347
374- // For stream module, proxy_pass can directly reference upstream name without protocol
375- // Check if the proxy_pass value directly matches an upstream name
376- if ! strings .Contains (proxyPass , "://" ) && ! strings .Contains (proxyPass , ":" ) {
377- return upstreamNames [proxyPass ]
348+ // For stream module, proxy_pass/grpc_pass can directly reference upstream name without protocol
349+ // Check if the pass value directly matches an upstream name
350+ if ! strings .Contains (passURL , "://" ) && ! strings .Contains (passURL , ":" ) {
351+ return upstreamNames [passURL ]
378352}
379353
380354return false
0 commit comments