@@ -18,6 +18,26 @@ const (
18
18
rootPath = "/"
19
19
)
20
20
21
+ // baseHeaders contains the constant headers set in each server block
22
+ var baseHeaders = []http.Header {
23
+ {
24
+ Name : "Host" ,
25
+ Value : "$gw_api_compliant_host" ,
26
+ },
27
+ {
28
+ Name : "X-Forwarded-For" ,
29
+ Value : "$proxy_add_x_forwarded_for" ,
30
+ },
31
+ {
32
+ Name : "Upgrade" ,
33
+ Value : "$http_upgrade" ,
34
+ },
35
+ {
36
+ Name : "Connection" ,
37
+ Value : "$connection_upgrade" ,
38
+ },
39
+ }
40
+
21
41
func executeServers (conf dataplane.Configuration ) []byte {
22
42
servers := createServers (conf .HTTPServers , conf .SSLServers )
23
43
@@ -72,6 +92,15 @@ func createServer(virtualServer dataplane.VirtualServer) http.Server {
72
92
}
73
93
}
74
94
95
+ // rewriteConfig contains the configuration for a location to rewrite paths,
96
+ // as specified in a URLRewrite filter
97
+ type rewriteConfig struct {
98
+ // InternalRewrite rewrites an internal URI to the original URI (ex: /coffee_prefix_route0 -> /coffee)
99
+ InternalRewrite string
100
+ // MainRewrite rewrites the original URI to the new URI (ex: /coffee -> /beans)
101
+ MainRewrite string
102
+ }
103
+
75
104
func createLocations (pathRules []dataplane.PathRule , listenerPort int32 ) []http.Location {
76
105
maxLocs , pathsAndTypes := getMaxLocationCountAndPathMap (pathRules )
77
106
locs := make ([]http.Location , 0 , maxLocs )
@@ -94,42 +123,7 @@ func createLocations(pathRules []dataplane.PathRule, listenerPort int32) []http.
94
123
matches = append (matches , match )
95
124
}
96
125
97
- if r .Filters .InvalidFilter != nil {
98
- for i := range buildLocations {
99
- buildLocations [i ].Return = & http.Return {Code : http .StatusInternalServerError }
100
- }
101
- locs = append (locs , buildLocations ... )
102
- continue
103
- }
104
-
105
- // There could be a case when the filter has the type set but not the corresponding field.
106
- // For example, type is v1.HTTPRouteFilterRequestRedirect, but RequestRedirect field is nil.
107
- // The imported Webhook validation webhook catches that.
108
-
109
- // FIXME(pleshakov): Ensure dataplane.Configuration -related types don't include v1 types, so that
110
- // we don't need to make any assumptions like above here. After fixing this, ensure that there is a test
111
- // for checking the imported Webhook validation catches the case above.
112
- // https://github.com/nginxinc/nginx-gateway-fabric/issues/660
113
-
114
- // RequestRedirect and proxying are mutually exclusive.
115
- if r .Filters .RequestRedirect != nil {
116
- ret := createReturnValForRedirectFilter (r .Filters .RequestRedirect , listenerPort )
117
- for i := range buildLocations {
118
- buildLocations [i ].Return = ret
119
- }
120
- locs = append (locs , buildLocations ... )
121
- continue
122
- }
123
-
124
- proxySetHeaders := generateProxySetHeaders (r .Filters .RequestHeaderModifiers )
125
- for i := range buildLocations {
126
- buildLocations [i ].ProxySetHeaders = proxySetHeaders
127
- }
128
-
129
- proxyPass := createProxyPass (r .BackendGroup )
130
- for i := range buildLocations {
131
- buildLocations [i ].ProxyPass = proxyPass
132
- }
126
+ buildLocations = updateLocationsForFilters (r .Filters , buildLocations , r , listenerPort , rule .Path )
133
127
locs = append (locs , buildLocations ... )
134
128
}
135
129
@@ -230,6 +224,48 @@ func initializeInternalLocation(
230
224
return createMatchLocation (path ), createHTTPMatch (match , path )
231
225
}
232
226
227
+ // updateLocationsForFilters updates the existing locations with any relevant filters.
228
+ func updateLocationsForFilters (
229
+ filters dataplane.HTTPFilters ,
230
+ buildLocations []http.Location ,
231
+ matchRule dataplane.MatchRule ,
232
+ listenerPort int32 ,
233
+ path string ,
234
+ ) []http.Location {
235
+ if filters .InvalidFilter != nil {
236
+ for i := range buildLocations {
237
+ buildLocations [i ].Return = & http.Return {Code : http .StatusInternalServerError }
238
+ }
239
+ return buildLocations
240
+ }
241
+
242
+ if filters .RequestRedirect != nil {
243
+ ret := createReturnValForRedirectFilter (filters .RequestRedirect , listenerPort )
244
+ for i := range buildLocations {
245
+ buildLocations [i ].Return = ret
246
+ }
247
+ return buildLocations
248
+ }
249
+
250
+ rewrites := createRewritesValForRewriteFilter (filters .RequestURLRewrite , path )
251
+ proxySetHeaders := generateProxySetHeaders (& matchRule .Filters )
252
+ proxyPass := createProxyPass (matchRule .BackendGroup , matchRule .Filters .RequestURLRewrite )
253
+ for i := range buildLocations {
254
+ if rewrites != nil {
255
+ if buildLocations [i ].Internal && rewrites .InternalRewrite != "" {
256
+ buildLocations [i ].Rewrites = append (buildLocations [i ].Rewrites , rewrites .InternalRewrite )
257
+ }
258
+ if rewrites .MainRewrite != "" {
259
+ buildLocations [i ].Rewrites = append (buildLocations [i ].Rewrites , rewrites .MainRewrite )
260
+ }
261
+ }
262
+ buildLocations [i ].ProxySetHeaders = proxySetHeaders
263
+ buildLocations [i ].ProxyPass = proxyPass
264
+ }
265
+
266
+ return buildLocations
267
+ }
268
+
233
269
func createReturnValForRedirectFilter (filter * dataplane.HTTPRequestRedirectFilter , listenerPort int32 ) * http.Return {
234
270
if filter == nil {
235
271
return nil
@@ -275,6 +311,49 @@ func createReturnValForRedirectFilter(filter *dataplane.HTTPRequestRedirectFilte
275
311
}
276
312
}
277
313
314
+ func createRewritesValForRewriteFilter (filter * dataplane.HTTPURLRewriteFilter , path string ) * rewriteConfig {
315
+ if filter == nil {
316
+ return nil
317
+ }
318
+
319
+ rewrites := & rewriteConfig {}
320
+
321
+ if filter .Path != nil {
322
+ rewrites .InternalRewrite = "^ $request_uri"
323
+ switch filter .Path .Type {
324
+ case dataplane .ReplaceFullPath :
325
+ rewrites .MainRewrite = fmt .Sprintf ("^ %s break" , filter .Path .Replacement )
326
+ case dataplane .ReplacePrefixMatch :
327
+ filterPrefix := filter .Path .Replacement
328
+ if filterPrefix == "" {
329
+ filterPrefix = "/"
330
+ }
331
+
332
+ // capture everything after the configured prefix
333
+ regex := fmt .Sprintf ("^%s(.*)$" , path )
334
+ // replace the configured prefix with the filter prefix and append what was captured
335
+ replacement := fmt .Sprintf ("%s$1" , filterPrefix )
336
+
337
+ // if configured prefix does not end in /, but replacement prefix does end in /,
338
+ // then make sure that we *require* but *don't capture* a trailing slash in the request,
339
+ // otherwise we'll get duplicate slashes in the full replacement
340
+ if strings .HasSuffix (filterPrefix , "/" ) && ! strings .HasSuffix (path , "/" ) {
341
+ regex = fmt .Sprintf ("^%s(?:/(.*))?$" , path )
342
+ }
343
+
344
+ // if configured prefix ends in / we won't capture it for a request (since it's not in the regex),
345
+ // so append it to the replacement prefix if the replacement prefix doesn't already end in /
346
+ if strings .HasSuffix (path , "/" ) && ! strings .HasSuffix (filterPrefix , "/" ) {
347
+ replacement = fmt .Sprintf ("%s/$1" , filterPrefix )
348
+ }
349
+
350
+ rewrites .MainRewrite = fmt .Sprintf ("%s %s break" , regex , replacement )
351
+ }
352
+ }
353
+
354
+ return rewrites
355
+ }
356
+
278
357
// httpMatch is an internal representation of an HTTPRouteMatch.
279
358
// This struct is marshaled into a string and stored as a variable in the nginx location block for the route's path.
280
359
// The NJS httpmatches module will look up this variable on the request object and compare the request against the
@@ -354,13 +433,18 @@ func isPathOnlyMatch(match dataplane.Match) bool {
354
433
return match .Method == nil && len (match .Headers ) == 0 && len (match .QueryParams ) == 0
355
434
}
356
435
357
- func createProxyPass (backendGroup dataplane.BackendGroup ) string {
436
+ func createProxyPass (backendGroup dataplane.BackendGroup , filter * dataplane.HTTPURLRewriteFilter ) string {
437
+ var requestURI string
438
+ if filter == nil || filter .Path == nil {
439
+ requestURI = "$request_uri"
440
+ }
441
+
358
442
backendName := backendGroupName (backendGroup )
359
443
if backendGroupNeedsSplit (backendGroup ) {
360
- return "http://$" + convertStringToSafeVariableName (backendName )
444
+ return "http://$" + convertStringToSafeVariableName (backendName ) + requestURI
361
445
}
362
446
363
- return "http://" + backendName
447
+ return "http://" + backendName + requestURI
364
448
}
365
449
366
450
func createMatchLocation (path string ) http.Location {
@@ -370,27 +454,44 @@ func createMatchLocation(path string) http.Location {
370
454
}
371
455
}
372
456
373
- func generateProxySetHeaders (filters * dataplane.HTTPHeaderFilter ) []http.Header {
374
- if filters == nil {
375
- return nil
457
+ func generateProxySetHeaders (filters * dataplane.HTTPFilters ) []http.Header {
458
+ headers := make ([]http.Header , len (baseHeaders ))
459
+ copy (headers , baseHeaders )
460
+
461
+ if filters != nil && filters .RequestURLRewrite != nil && filters .RequestURLRewrite .Hostname != nil {
462
+ for i , header := range headers {
463
+ if header .Name == "Host" {
464
+ headers [i ].Value = * filters .RequestURLRewrite .Hostname
465
+ break
466
+ }
467
+ }
468
+ }
469
+
470
+ if filters == nil || filters .RequestHeaderModifiers == nil {
471
+ return headers
376
472
}
377
- proxySetHeaders := make ([]http.Header , 0 , len (filters .Add )+ len (filters .Set )+ len (filters .Remove ))
378
- if len (filters .Add ) > 0 {
379
- addHeaders := convertAddHeaders (filters .Add )
473
+
474
+ headerFilter := filters .RequestHeaderModifiers
475
+
476
+ headerLen := len (headerFilter .Add ) + len (headerFilter .Set ) + len (headerFilter .Remove ) + len (headers )
477
+ proxySetHeaders := make ([]http.Header , 0 , headerLen )
478
+ if len (headerFilter .Add ) > 0 {
479
+ addHeaders := convertAddHeaders (headerFilter .Add )
380
480
proxySetHeaders = append (proxySetHeaders , addHeaders ... )
381
481
}
382
- if len (filters .Set ) > 0 {
383
- setHeaders := convertSetHeaders (filters .Set )
482
+ if len (headerFilter .Set ) > 0 {
483
+ setHeaders := convertSetHeaders (headerFilter .Set )
384
484
proxySetHeaders = append (proxySetHeaders , setHeaders ... )
385
485
}
386
486
// If the value of a header field is an empty string then this field will not be passed to a proxied server
387
- for _ , h := range filters .Remove {
487
+ for _ , h := range headerFilter .Remove {
388
488
proxySetHeaders = append (proxySetHeaders , http.Header {
389
489
Name : h ,
390
490
Value : "" ,
391
491
})
392
492
}
393
- return proxySetHeaders
493
+
494
+ return append (proxySetHeaders , headers ... )
394
495
}
395
496
396
497
func convertAddHeaders (headers []dataplane.HTTPHeader ) []http.Header {
0 commit comments