@@ -5,15 +5,14 @@ import (
5
5
"regexp"
6
6
"strings"
7
7
8
- "github.com/0xJacky/Nginx-UI/internal/nginx"
9
8
"github.com/0xJacky/Nginx-UI/settings"
10
9
)
11
10
12
11
// ProxyTarget represents a proxy destination
13
12
type ProxyTarget struct {
14
13
Host string `json:"host"`
15
14
Port string `json:"port"`
16
- Type string `json:"type"` // "proxy_pass" or "upstream"
15
+ Type string `json:"type"` // "proxy_pass", "grpc_pass" or "upstream"
17
16
Resolver string `json:"resolver"` // DNS resolver address (e.g., "127.0.0.1:8600")
18
17
IsConsul bool `json:"is_consul"` // Whether this is a consul service discovery target
19
18
ServiceURL 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 {
82
81
proxyPassURL := strings .TrimSpace (match [1 ])
83
82
// Skip if this proxy_pass references an upstream
84
83
if ! isUpstreamReference (proxyPassURL , upstreamNames ) {
85
- target := parseProxyPassURL (proxyPassURL )
84
+ target := parseProxyPassURL (proxyPassURL , "proxy_pass" )
86
85
if target .Host != "" {
87
86
targets = append (targets , target )
88
87
}
89
88
}
90
89
}
91
90
}
92
91
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 )
134
95
135
- for _ , match := range matches {
96
+ for _ , match := range grpcMatches {
136
97
if 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
+ }
140
105
}
141
106
}
142
107
}
143
108
144
- return targets
109
+ return deduplicateTargets ( targets )
145
110
}
146
111
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 )
150
115
151
116
// Skip URLs that contain Nginx variables
152
- if strings .Contains (proxyPass , "$" ) {
117
+ if strings .Contains (passURL , "$" ) {
153
118
return ProxyTarget {}
154
119
}
155
120
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 {
159
124
host := parsedURL .Hostname ()
160
125
port := parsedURL .Port ()
161
126
162
127
// Set default ports if not specified
163
128
if port == "" {
164
- if parsedURL .Scheme == "https" {
129
+ switch parsedURL .Scheme {
130
+ case "https" :
131
+ port = "443"
132
+ case "grpcs" :
165
133
port = "443"
166
- } else {
134
+ case "grpc" :
135
+ port = "80"
136
+ default : // http
167
137
port = "80"
168
138
}
169
139
}
@@ -176,15 +146,15 @@ func parseProxyPassURL(proxyPass string) ProxyTarget {
176
146
return ProxyTarget {
177
147
Host : host ,
178
148
Port : port ,
179
- Type : "proxy_pass" ,
149
+ Type : passType ,
180
150
}
181
151
}
182
152
}
183
153
184
154
// 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
188
158
189
159
// Skip if this is the HTTP challenge port used by Let's Encrypt
190
160
if target .Host == "127.0.0.1" && target .Port == settings .CertSettings .HTTPChallengePort {
@@ -262,7 +232,7 @@ func isConsulServiceDiscovery(serverAddr string) bool {
262
232
if strings .Contains (serverAddr , "service=" ) && strings .Contains (serverAddr , "resolve" ) {
263
233
return true
264
234
}
265
- // Legacy consul format: "service.consul service=name resolve"
235
+ // Legacy consul format: "service.consul service=name resolve"
266
236
return strings .Contains (serverAddr , "service.consul" ) &&
267
237
(strings .Contains (serverAddr , "service=" ) || strings .Contains (serverAddr , "resolve" ))
268
238
}
@@ -327,17 +297,17 @@ func deduplicateTargets(targets []ProxyTarget) []ProxyTarget {
327
297
return result
328
298
}
329
299
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 )
333
303
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 ://" ) {
336
306
// Handle URLs with nginx variables (e.g., "https://myUpStr$request_uri")
337
307
// 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 ]
341
311
}
342
312
343
313
// Try to parse the URL, if it fails, try manual extraction
@@ -348,11 +318,15 @@ func isUpstreamReference(proxyPass string, upstreamNames map[string]bool) bool {
348
318
} else {
349
319
// Fallback: manually extract hostname for URLs with variables
350
320
// 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://" )
356
330
}
357
331
358
332
// Extract hostname before any path, port, or variable
@@ -371,10 +345,10 @@ func isUpstreamReference(proxyPass string, upstreamNames map[string]bool) bool {
371
345
}
372
346
}
373
347
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 ]
378
352
}
379
353
380
354
return false
0 commit comments