Skip to content

Commit 7a26037

Browse files
committed
Exact PathMatch support for HTTPRoutes (nginx#603)
* Exact PathMatch support for HTTPRoutes Add support for Exact PathMatch in an HTTPRoute. Internal locations now include the type of path (prefix, exact, regex) in the path name to distinguish between the possibility of the same path name being used with different path match types. nginx conf is updated to include "= " in the location path if that path has been defined as "Exact".
1 parent ea1957b commit 7a26037

15 files changed

+434
-141
lines changed

docs/gateway-api-compatibility.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Fields:
8383
* `hostnames` - partially supported. Wildcard binding is not supported: a hostname like `example.com` will not bind to a listener with the hostname `*.example.com`. However, `example.com` will bind to a listener with the empty hostname.
8484
* `rules`
8585
* `matches`
86-
* `path` - partially supported. Only `PathPrefix` type.
86+
* `path` - partially supported. Only `PathPrefix` and `Exact` types.
8787
* `headers` - partially supported. Only `Exact` type.
8888
* `queryParams` - partially supported. Only `Exact` type.
8989
* `method` - supported.

examples/cafe-example/cafe-routes.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ spec:
3030
rules:
3131
- matches:
3232
- path:
33-
type: PathPrefix
33+
type: Exact
3434
value: /tea
3535
backendRefs:
3636
- name: tea

internal/nginx/config/http/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Location struct {
1616
ProxyPass string
1717
HTTPMatchVar string
1818
Internal bool
19+
Exact bool
1920
}
2021

2122
// Return represents an HTTP return.

internal/nginx/config/servers.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,11 @@ func createLocations(pathRules []dataplane.PathRule, listenerPort int) []http.Lo
102102
// generate a standard location block without http_matches.
103103
if len(rule.MatchRules) == 1 && isPathOnlyMatch(m) {
104104
loc = http.Location{
105-
Path: rule.Path,
105+
Path: rule.Path,
106+
Exact: rule.PathType == dataplane.PathTypeExact,
106107
}
107108
} else {
108-
path := createPathForMatch(rule.Path, matchRuleIdx)
109+
path := createPathForMatch(rule.Path, rule.PathType, matchRuleIdx)
109110
loc = createMatchLocation(path)
110111
matches = append(matches, createHTTPMatch(m, path))
111112
}
@@ -151,6 +152,7 @@ func createLocations(pathRules []dataplane.PathRule, listenerPort int) []http.Lo
151152

152153
pathLoc := http.Location{
153154
Path: rule.Path,
155+
Exact: rule.PathType == dataplane.PathTypeExact,
154156
HTTPMatchVar: string(b),
155157
}
156158

@@ -304,8 +306,8 @@ func createMatchLocation(path string) http.Location {
304306
}
305307
}
306308

307-
func createPathForMatch(path string, routeIdx int) string {
308-
return fmt.Sprintf("%s_route%d", path, routeIdx)
309+
func createPathForMatch(path string, pathType dataplane.PathType, routeIdx int) string {
310+
return fmt.Sprintf("%s_%s_route%d", path, pathType, routeIdx)
309311
}
310312

311313
func createDefaultRootLocation() http.Location {

internal/nginx/config/servers_template.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ server {
3030
server_name {{ $s.ServerName }};
3131
3232
{{ range $l := $s.Locations }}
33-
location {{ $l.Path }} {
33+
location {{ if $l.Exact }}= {{ end }}{{ $l.Path }} {
3434
{{ if $l.Internal -}}
3535
internal;
3636
{{ end }}

internal/nginx/config/servers_test.go

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,14 @@ func TestCreateServers(t *testing.T) {
181181
{
182182
Path: &v1beta1.HTTPPathMatch{
183183
Value: helpers.GetStringPointer("/"),
184+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
184185
},
185186
Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodPost),
186187
},
187188
{
188189
Path: &v1beta1.HTTPPathMatch{
189190
Value: helpers.GetStringPointer("/"),
191+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
190192
},
191193
Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodPatch),
192194
},
@@ -195,6 +197,7 @@ func TestCreateServers(t *testing.T) {
195197
Value: helpers.GetStringPointer(
196198
"/", // should generate an "any" httpmatch since other matches exists for /
197199
),
200+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
198201
},
199202
},
200203
},
@@ -205,6 +208,7 @@ func TestCreateServers(t *testing.T) {
205208
{
206209
Path: &v1beta1.HTTPPathMatch{
207210
Value: helpers.GetStringPointer("/test"),
211+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
208212
},
209213
Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodGet),
210214
Headers: []v1beta1.HTTPHeaderMatch{
@@ -245,6 +249,7 @@ func TestCreateServers(t *testing.T) {
245249
{
246250
Path: &v1beta1.HTTPPathMatch{
247251
Value: helpers.GetStringPointer("/path-only"),
252+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
248253
},
249254
},
250255
},
@@ -255,6 +260,7 @@ func TestCreateServers(t *testing.T) {
255260
{
256261
Path: &v1beta1.HTTPPathMatch{
257262
Value: helpers.GetStringPointer("/redirect-implicit-port"),
263+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
258264
},
259265
},
260266
},
@@ -266,6 +272,7 @@ func TestCreateServers(t *testing.T) {
266272
{
267273
Path: &v1beta1.HTTPPathMatch{
268274
Value: helpers.GetStringPointer("/redirect-explicit-port"),
275+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
269276
},
270277
},
271278
},
@@ -277,10 +284,34 @@ func TestCreateServers(t *testing.T) {
277284
{
278285
Path: &v1beta1.HTTPPathMatch{
279286
Value: helpers.GetPointer("/invalid-filter"),
287+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
280288
},
281289
},
282290
},
283291
},
292+
{
293+
// A match using type Exact
294+
Matches: []v1beta1.HTTPRouteMatch{
295+
{
296+
Path: &v1beta1.HTTPPathMatch{
297+
Value: helpers.GetPointer("/exact"),
298+
Type: helpers.GetPointer(v1beta1.PathMatchExact),
299+
},
300+
},
301+
},
302+
},
303+
{
304+
// A match using type Exact with method
305+
Matches: []v1beta1.HTTPRouteMatch{
306+
{
307+
Path: &v1beta1.HTTPPathMatch{
308+
Value: helpers.GetPointer("/test"),
309+
Type: helpers.GetPointer(v1beta1.PathMatchExact),
310+
},
311+
Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodGet),
312+
},
313+
},
314+
},
284315
},
285316
},
286317
}
@@ -338,7 +369,8 @@ func TestCreateServers(t *testing.T) {
338369

339370
cafePathRules := []dataplane.PathRule{
340371
{
341-
Path: "/",
372+
Path: "/",
373+
PathType: dataplane.PathTypePrefix,
342374
MatchRules: []dataplane.MatchRule{
343375
{
344376
MatchIdx: 0,
@@ -361,7 +393,8 @@ func TestCreateServers(t *testing.T) {
361393
},
362394
},
363395
{
364-
Path: "/test",
396+
Path: "/test",
397+
PathType: dataplane.PathTypePrefix,
365398
MatchRules: []dataplane.MatchRule{
366399
{
367400
MatchIdx: 0,
@@ -372,7 +405,8 @@ func TestCreateServers(t *testing.T) {
372405
},
373406
},
374407
{
375-
Path: "/path-only",
408+
Path: "/path-only",
409+
PathType: dataplane.PathTypePrefix,
376410
MatchRules: []dataplane.MatchRule{
377411
{
378412
MatchIdx: 0,
@@ -383,7 +417,8 @@ func TestCreateServers(t *testing.T) {
383417
},
384418
},
385419
{
386-
Path: "/redirect-implicit-port",
420+
Path: "/redirect-implicit-port",
421+
PathType: dataplane.PathTypePrefix,
387422
MatchRules: []dataplane.MatchRule{
388423
{
389424
MatchIdx: 0,
@@ -399,7 +434,8 @@ func TestCreateServers(t *testing.T) {
399434
},
400435
},
401436
{
402-
Path: "/redirect-explicit-port",
437+
Path: "/redirect-explicit-port",
438+
PathType: dataplane.PathTypePrefix,
403439
MatchRules: []dataplane.MatchRule{
404440
{
405441
MatchIdx: 0,
@@ -416,7 +452,8 @@ func TestCreateServers(t *testing.T) {
416452
},
417453
},
418454
{
419-
Path: "/invalid-filter",
455+
Path: "/invalid-filter",
456+
PathType: dataplane.PathTypePrefix,
420457
MatchRules: []dataplane.MatchRule{
421458
{
422459
MatchIdx: 0,
@@ -429,6 +466,30 @@ func TestCreateServers(t *testing.T) {
429466
},
430467
},
431468
},
469+
{
470+
Path: "/exact",
471+
PathType: dataplane.PathTypeExact,
472+
MatchRules: []dataplane.MatchRule{
473+
{
474+
MatchIdx: 0,
475+
RuleIdx: 6,
476+
Source: hr,
477+
BackendGroup: fooGroup,
478+
},
479+
},
480+
},
481+
{
482+
Path: "/test",
483+
PathType: dataplane.PathTypeExact,
484+
MatchRules: []dataplane.MatchRule{
485+
{
486+
MatchIdx: 0,
487+
RuleIdx: 7,
488+
Source: hr,
489+
BackendGroup: fooGroup,
490+
},
491+
},
492+
},
432493
}
433494

434495
httpServers := []dataplane.VirtualServer{
@@ -461,16 +522,22 @@ func TestCreateServers(t *testing.T) {
461522
}
462523

463524
slashMatches := []httpMatch{
464-
{Method: v1beta1.HTTPMethodPost, RedirectPath: "/_route0"},
465-
{Method: v1beta1.HTTPMethodPatch, RedirectPath: "/_route1"},
466-
{Any: true, RedirectPath: "/_route2"},
525+
{Method: v1beta1.HTTPMethodPost, RedirectPath: "/_prefix_route0"},
526+
{Method: v1beta1.HTTPMethodPatch, RedirectPath: "/_prefix_route1"},
527+
{Any: true, RedirectPath: "/_prefix_route2"},
467528
}
468529
testMatches := []httpMatch{
469530
{
470531
Method: v1beta1.HTTPMethodGet,
471532
Headers: []string{"Version:V1", "test:foo", "my-header:my-value"},
472533
QueryParams: []string{"GrEat=EXAMPLE", "test=foo=bar"},
473-
RedirectPath: "/test_route0",
534+
RedirectPath: "/test_prefix_route0",
535+
},
536+
}
537+
exactMatches := []httpMatch{
538+
{
539+
Method: v1beta1.HTTPMethodGet,
540+
RedirectPath: "/test_exact_route0",
474541
},
475542
}
476543

@@ -482,17 +549,17 @@ func TestCreateServers(t *testing.T) {
482549

483550
return []http.Location{
484551
{
485-
Path: "/_route0",
552+
Path: "/_prefix_route0",
486553
Internal: true,
487554
ProxyPass: "http://test_foo_80",
488555
},
489556
{
490-
Path: "/_route1",
557+
Path: "/_prefix_route1",
491558
Internal: true,
492559
ProxyPass: "http://test_foo_80",
493560
},
494561
{
495-
Path: "/_route2",
562+
Path: "/_prefix_route2",
496563
Internal: true,
497564
ProxyPass: "http://test_foo_80",
498565
},
@@ -501,7 +568,7 @@ func TestCreateServers(t *testing.T) {
501568
HTTPMatchVar: expectedMatchString(slashMatches),
502569
},
503570
{
504-
Path: "/test_route0",
571+
Path: "/test_prefix_route0",
505572
Internal: true,
506573
ProxyPass: "http://$test__route1_rule1",
507574
},
@@ -533,6 +600,21 @@ func TestCreateServers(t *testing.T) {
533600
Code: http.StatusInternalServerError,
534601
},
535602
},
603+
{
604+
Path: "/exact",
605+
ProxyPass: "http://test_foo_80",
606+
Exact: true,
607+
},
608+
{
609+
Path: "/test_exact_route0",
610+
ProxyPass: "http://test_foo_80",
611+
Internal: true,
612+
},
613+
{
614+
Path: "/test",
615+
HTTPMatchVar: expectedMatchString(exactMatches),
616+
Exact: true,
617+
},
536618
}
537619
}
538620

@@ -579,11 +661,13 @@ func TestCreateLocationsRootPath(t *testing.T) {
579661
{
580662
Path: &v1beta1.HTTPPathMatch{
581663
Value: helpers.GetStringPointer("/path-1"),
664+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
582665
},
583666
},
584667
{
585668
Path: &v1beta1.HTTPPathMatch{
586669
Value: helpers.GetStringPointer("/path-2"),
670+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
587671
},
588672
},
589673
},
@@ -596,6 +680,7 @@ func TestCreateLocationsRootPath(t *testing.T) {
596680
route.Spec.Rules[0].Matches = append(route.Spec.Rules[0].Matches, v1beta1.HTTPRouteMatch{
597681
Path: &v1beta1.HTTPPathMatch{
598682
Value: helpers.GetStringPointer("/"),
683+
Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix),
599684
},
600685
})
601686
}
@@ -1089,10 +1174,25 @@ func TestCreateMatchLocation(t *testing.T) {
10891174
}
10901175

10911176
func TestCreatePathForMatch(t *testing.T) {
1092-
expected := "/path_route1"
1177+
g := NewGomegaWithT(t)
10931178

1094-
result := createPathForMatch("/path", 1)
1095-
if result != expected {
1096-
t.Errorf("createPathForMatch() returned %q but expected %q", result, expected)
1179+
tests := []struct {
1180+
expected string
1181+
pathType dataplane.PathType
1182+
panic bool
1183+
}{
1184+
{
1185+
expected: "/path_prefix_route1",
1186+
pathType: dataplane.PathTypePrefix,
1187+
},
1188+
{
1189+
expected: "/path_exact_route1",
1190+
pathType: dataplane.PathTypeExact,
1191+
},
1192+
}
1193+
1194+
for _, tc := range tests {
1195+
result := createPathForMatch("/path", tc.pathType, 1)
1196+
g.Expect(result).To(Equal(tc.expected))
10971197
}
10981198
}

0 commit comments

Comments
 (0)