@@ -75,24 +75,9 @@ func createServer(virtualServer dataplane.VirtualServer) http.Server {
75
75
}
76
76
77
77
func createLocations (pathRules []dataplane.PathRule , listenerPort int32 ) []http.Location {
78
- lenPathRules := len (pathRules )
79
-
80
- if lenPathRules == 0 {
81
- return []http.Location {createDefaultRootLocation ()}
82
- }
83
-
84
- // To calculate the maximum number of locations, we need to take into account the following:
85
- // 1. Each match rule for a path rule will have one location.
86
- // 2. Each path rule may have an additional location if it contains non-path-only matches.
87
- // 3. There may be an additional location for the default root path.
88
- maxLocs := 1
89
- for _ , rules := range pathRules {
90
- maxLocs += len (rules .MatchRules ) + 1
91
- }
92
-
78
+ maxLocs , pathsAndTypes := getMaxLocationCountAndPathMap (pathRules )
93
79
locs := make ([]http.Location , 0 , maxLocs )
94
-
95
- rootPathExists := false
80
+ var rootPathExists bool
96
81
97
82
for _ , rule := range pathRules {
98
83
matches := make ([]httpMatch , 0 , len (rule .MatchRules ))
@@ -101,27 +86,23 @@ func createLocations(pathRules []dataplane.PathRule, listenerPort int32) []http.
101
86
rootPathExists = true
102
87
}
103
88
89
+ extLocations := initializeExternalLocations (rule , pathsAndTypes )
90
+
104
91
for matchRuleIdx , r := range rule .MatchRules {
105
92
m := r .GetMatch ()
106
93
107
- var loc http.Location
108
-
109
- // handle case where the only route is a path-only match
110
- // generate a standard location block without http_matches.
111
- if len (rule .MatchRules ) == 1 && isPathOnlyMatch (m ) {
112
- loc = http.Location {
113
- Path : rule .Path ,
114
- Exact : rule .PathType == dataplane .PathTypeExact ,
115
- }
116
- } else {
117
- path := createPathForMatch (rule .Path , rule .PathType , matchRuleIdx )
118
- loc = createMatchLocation (path )
119
- matches = append (matches , createHTTPMatch (m , path ))
94
+ buildLocations := extLocations
95
+ if len (rule .MatchRules ) != 1 || ! isPathOnlyMatch (m ) {
96
+ intLocation , match := initializeInternalLocation (rule , matchRuleIdx , m )
97
+ buildLocations = []http.Location {intLocation }
98
+ matches = append (matches , match )
120
99
}
121
100
122
101
if r .Filters .InvalidFilter != nil {
123
- loc .Return = & http.Return {Code : http .StatusInternalServerError }
124
- locs = append (locs , loc )
102
+ for i := range buildLocations {
103
+ buildLocations [i ].Return = & http.Return {Code : http .StatusInternalServerError }
104
+ }
105
+ locs = append (locs , buildLocations ... )
125
106
continue
126
107
}
127
108
@@ -136,40 +117,32 @@ func createLocations(pathRules []dataplane.PathRule, listenerPort int32) []http.
136
117
137
118
// RequestRedirect and proxying are mutually exclusive.
138
119
if r .Filters .RequestRedirect != nil {
139
- loc .Return = createReturnValForRedirectFilter (r .Filters .RequestRedirect , listenerPort )
140
- locs = append (locs , loc )
120
+ ret := createReturnValForRedirectFilter (r .Filters .RequestRedirect , listenerPort )
121
+ for i := range buildLocations {
122
+ buildLocations [i ].Return = ret
123
+ }
124
+ locs = append (locs , buildLocations ... )
141
125
continue
142
126
}
143
127
144
- backendName := backendGroupName (r .BackendGroup )
145
- loc .ProxySetHeaders = generateProxySetHeaders (r .Filters .RequestHeaderModifiers )
146
-
147
- if backendGroupNeedsSplit (r .BackendGroup ) {
148
- loc .ProxyPass = createProxyPassForVar (backendName )
149
- } else {
150
- loc .ProxyPass = createProxyPass (backendName )
128
+ proxySetHeaders := generateProxySetHeaders (r .Filters .RequestHeaderModifiers )
129
+ for i := range buildLocations {
130
+ buildLocations [i ].ProxySetHeaders = proxySetHeaders
151
131
}
152
132
153
- locs = append (locs , loc )
133
+ proxyPass := createProxyPass (r .BackendGroup )
134
+ for i := range buildLocations {
135
+ buildLocations [i ].ProxyPass = proxyPass
136
+ }
137
+ locs = append (locs , buildLocations ... )
154
138
}
155
139
156
140
if len (matches ) > 0 {
157
- // FIXME(sberman): De-dupe matches and associated locations
158
- // so we don't need nginx/njs to perform unnecessary matching.
159
- // https://github.com/nginxinc/nginx-kubernetes-gateway/issues/662
160
- b , err := json .Marshal (matches )
161
- if err != nil {
162
- // panic is safe here because we should never fail to marshal the match unless we constructed it incorrectly.
163
- panic (fmt .Errorf ("could not marshal http match: %w" , err ))
141
+ matchesStr := convertMatchesToString (matches )
142
+ for i := range extLocations {
143
+ extLocations [i ].HTTPMatchVar = matchesStr
164
144
}
165
-
166
- pathLoc := http.Location {
167
- Path : rule .Path ,
168
- Exact : rule .PathType == dataplane .PathTypeExact ,
169
- HTTPMatchVar : string (b ),
170
- }
171
-
172
- locs = append (locs , pathLoc )
145
+ locs = append (locs , extLocations ... )
173
146
}
174
147
}
175
148
@@ -180,6 +153,87 @@ func createLocations(pathRules []dataplane.PathRule, listenerPort int32) []http.
180
153
return locs
181
154
}
182
155
156
+ // pathAndTypeMap contains a map of paths and any path types defined for that path
157
+ // for example, {/foo: {exact: {}, prefix: {}}}
158
+ type pathAndTypeMap map [string ]map [dataplane.PathType ]struct {}
159
+
160
+ // To calculate the maximum number of locations, we need to take into account the following:
161
+ // 1. Each match rule for a path rule will have one location.
162
+ // 2. Each path rule may have an additional location if it contains non-path-only matches.
163
+ // 3. Each prefix path rule may have an additional location if it doesn't contain trailing slash.
164
+ // 4. There may be an additional location for the default root path.
165
+ // We also return a map of all paths and their types.
166
+ func getMaxLocationCountAndPathMap (pathRules []dataplane.PathRule ) (int , pathAndTypeMap ) {
167
+ maxLocs := 1
168
+ pathsAndTypes := make (pathAndTypeMap )
169
+ for _ , rule := range pathRules {
170
+ maxLocs += len (rule .MatchRules ) + 2
171
+ if pathsAndTypes [rule .Path ] == nil {
172
+ pathsAndTypes [rule .Path ] = map [dataplane.PathType ]struct {}{
173
+ rule .PathType : {},
174
+ }
175
+ } else {
176
+ pathsAndTypes [rule.Path ][rule.PathType ] = struct {}{}
177
+ }
178
+ }
179
+
180
+ return maxLocs , pathsAndTypes
181
+ }
182
+
183
+ func initializeExternalLocations (
184
+ rule dataplane.PathRule ,
185
+ pathsAndTypes pathAndTypeMap ,
186
+ ) []http.Location {
187
+ extLocations := make ([]http.Location , 0 , 2 )
188
+ externalLocPath := createPath (rule )
189
+ // If the path type is Prefix and doesn't contain a trailing slash, then we need a second location
190
+ // that handles the Exact prefix case (if it doesn't already exist), and the first location is updated
191
+ // to handle the trailing slash prefix case (if it doesn't already exist)
192
+ if isNonSlashedPrefixPath (rule .PathType , externalLocPath ) {
193
+ // if Exact path and/or trailing slash Prefix path already exists, this means some routing rule
194
+ // configures it. The routing rule location has priority over this location, so we don't try to
195
+ // overwrite it and we don't add a duplicate location to NGINX because that will cause an NGINX config error.
196
+ _ , exactPathExists := pathsAndTypes [rule.Path ][dataplane.PathTypeExact ]
197
+ var trailingSlashPrefixPathExists bool
198
+ if pathTypes , exists := pathsAndTypes [rule .Path + "/" ]; exists {
199
+ _ , trailingSlashPrefixPathExists = pathTypes [dataplane .PathTypePrefix ]
200
+ }
201
+
202
+ if exactPathExists && trailingSlashPrefixPathExists {
203
+ return []http.Location {}
204
+ }
205
+
206
+ if ! trailingSlashPrefixPathExists {
207
+ externalLocTrailing := http.Location {
208
+ Path : externalLocPath + "/" ,
209
+ }
210
+ extLocations = append (extLocations , externalLocTrailing )
211
+ }
212
+ if ! exactPathExists {
213
+ externalLocExact := http.Location {
214
+ Path : exactPath (externalLocPath ),
215
+ }
216
+ extLocations = append (extLocations , externalLocExact )
217
+ }
218
+ } else {
219
+ externalLoc := http.Location {
220
+ Path : externalLocPath ,
221
+ }
222
+ extLocations = []http.Location {externalLoc }
223
+ }
224
+
225
+ return extLocations
226
+ }
227
+
228
+ func initializeInternalLocation (
229
+ rule dataplane.PathRule ,
230
+ matchRuleIdx int ,
231
+ match v1beta1.HTTPRouteMatch ,
232
+ ) (http.Location , httpMatch ) {
233
+ path := createPathForMatch (rule .Path , rule .PathType , matchRuleIdx )
234
+ return createMatchLocation (path ), createHTTPMatch (match , path )
235
+ }
236
+
183
237
func createReturnValForRedirectFilter (filter * v1beta1.HTTPRequestRedirectFilter , listenerPort int32 ) * http.Return {
184
238
if filter == nil {
185
239
return nil
@@ -308,12 +362,13 @@ func isPathOnlyMatch(match v1beta1.HTTPRouteMatch) bool {
308
362
return match .Method == nil && match .Headers == nil && match .QueryParams == nil
309
363
}
310
364
311
- func createProxyPass (address string ) string {
312
- return "http://" + address
313
- }
365
+ func createProxyPass (backendGroup dataplane.BackendGroup ) string {
366
+ backendName := backendGroupName (backendGroup )
367
+ if backendGroupNeedsSplit (backendGroup ) {
368
+ return "http://$" + convertStringToSafeVariableName (backendName )
369
+ }
314
370
315
- func createProxyPassForVar (variable string ) string {
316
- return "http://$" + convertStringToSafeVariableName (variable )
371
+ return "http://" + backendName
317
372
}
318
373
319
374
func createMatchLocation (path string ) http.Location {
@@ -369,6 +424,33 @@ func convertSetHeaders(headers []dataplane.HTTPHeader) []http.Header {
369
424
return locHeaders
370
425
}
371
426
427
+ func convertMatchesToString (matches []httpMatch ) string {
428
+ // FIXME(sberman): De-dupe matches and associated locations
429
+ // so we don't need nginx/njs to perform unnecessary matching.
430
+ // https://github.com/nginxinc/nginx-kubernetes-gateway/issues/662
431
+ b , err := json .Marshal (matches )
432
+ if err != nil {
433
+ // panic is safe here because we should never fail to marshal the match unless we constructed it incorrectly.
434
+ panic (fmt .Errorf ("could not marshal http match: %w" , err ))
435
+ }
436
+
437
+ return string (b )
438
+ }
439
+
440
+ func exactPath (path string ) string {
441
+ return fmt .Sprintf ("= %s" , path )
442
+ }
443
+
444
+ // createPath builds the location path depending on the path type.
445
+ func createPath (rule dataplane.PathRule ) string {
446
+ switch rule .PathType {
447
+ case dataplane .PathTypeExact :
448
+ return exactPath (rule .Path )
449
+ default :
450
+ return rule .Path
451
+ }
452
+ }
453
+
372
454
func createPathForMatch (path string , pathType dataplane.PathType , routeIdx int ) string {
373
455
return fmt .Sprintf ("%s_%s_route%d" , path , pathType , routeIdx )
374
456
}
@@ -379,3 +461,8 @@ func createDefaultRootLocation() http.Location {
379
461
Return : & http.Return {Code : http .StatusNotFound },
380
462
}
381
463
}
464
+
465
+ // isNonSlashedPrefixPath returns whether or not a path is of type Prefix and does not contain a trailing slash
466
+ func isNonSlashedPrefixPath (pathType dataplane.PathType , path string ) bool {
467
+ return pathType == dataplane .PathTypePrefix && ! strings .HasSuffix (path , "/" )
468
+ }
0 commit comments