Skip to content

Commit bcd8bc7

Browse files
ChrisHineselithrar
authored andcommitted
Support building URLs with non-http schemes. (#260)
* Move misplaced tests and fix comments. * Support building URLs with non-http schemes. - Capture first scheme configured for a route for use when building URLs. - Add new Route.URLScheme method similar to URLHost and URLPath. - Update Route.URLHost and Route.URL to use the captured scheme if present. * Remove Route.URLScheme method. * Remove UTF-8 BOM.
1 parent 751308a commit bcd8bc7

File tree

2 files changed

+167
-84
lines changed

2 files changed

+167
-84
lines changed

mux_test.go

Lines changed: 152 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"errors"
1111
"fmt"
1212
"net/http"
13+
"net/url"
1314
"strings"
1415
"testing"
1516
)
@@ -31,10 +32,11 @@ type routeTest struct {
3132
route *Route // the route being tested
3233
request *http.Request // a request to test the route
3334
vars map[string]string // the expected vars of the match
34-
host string // the expected host of the match
35-
path string // the expected path of the match
36-
pathTemplate string // the expected path template to match
37-
hostTemplate string // the expected host template to match
35+
scheme string // the expected scheme of the built URL
36+
host string // the expected host of the built URL
37+
path string // the expected path of the built URL
38+
pathTemplate string // the expected path template of the route
39+
hostTemplate string // the expected host template of the route
3840
methods []string // the expected route methods
3941
pathRegexp string // the expected path regexp
4042
shouldMatch bool // whether the request is expected to match the route at all
@@ -197,46 +199,6 @@ func TestHost(t *testing.T) {
197199
hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,
198200
shouldMatch: true,
199201
},
200-
{
201-
title: "Path route with single pattern with pipe, match",
202-
route: new(Route).Path("/{category:a|b/c}"),
203-
request: newRequest("GET", "http://localhost/a"),
204-
vars: map[string]string{"category": "a"},
205-
host: "",
206-
path: "/a",
207-
pathTemplate: `/{category:a|b/c}`,
208-
shouldMatch: true,
209-
},
210-
{
211-
title: "Path route with single pattern with pipe, match",
212-
route: new(Route).Path("/{category:a|b/c}"),
213-
request: newRequest("GET", "http://localhost/b/c"),
214-
vars: map[string]string{"category": "b/c"},
215-
host: "",
216-
path: "/b/c",
217-
pathTemplate: `/{category:a|b/c}`,
218-
shouldMatch: true,
219-
},
220-
{
221-
title: "Path route with multiple patterns with pipe, match",
222-
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
223-
request: newRequest("GET", "http://localhost/a/product_name/1"),
224-
vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
225-
host: "",
226-
path: "/a/product_name/1",
227-
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
228-
shouldMatch: true,
229-
},
230-
{
231-
title: "Path route with multiple patterns with pipe, match",
232-
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
233-
request: newRequest("GET", "http://localhost/b/c/product_name/1"),
234-
vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
235-
host: "",
236-
path: "/b/c/product_name/1",
237-
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
238-
shouldMatch: true,
239-
},
240202
}
241203
for _, test := range tests {
242204
testRoute(t, test)
@@ -428,6 +390,46 @@ func TestPath(t *testing.T) {
428390
pathRegexp: `^/(?P<v0>[0-9]*)(?P<v1>[a-z]*)/(?P<v2>[0-9]*)$`,
429391
shouldMatch: true,
430392
},
393+
{
394+
title: "Path route with single pattern with pipe, match",
395+
route: new(Route).Path("/{category:a|b/c}"),
396+
request: newRequest("GET", "http://localhost/a"),
397+
vars: map[string]string{"category": "a"},
398+
host: "",
399+
path: "/a",
400+
pathTemplate: `/{category:a|b/c}`,
401+
shouldMatch: true,
402+
},
403+
{
404+
title: "Path route with single pattern with pipe, match",
405+
route: new(Route).Path("/{category:a|b/c}"),
406+
request: newRequest("GET", "http://localhost/b/c"),
407+
vars: map[string]string{"category": "b/c"},
408+
host: "",
409+
path: "/b/c",
410+
pathTemplate: `/{category:a|b/c}`,
411+
shouldMatch: true,
412+
},
413+
{
414+
title: "Path route with multiple patterns with pipe, match",
415+
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
416+
request: newRequest("GET", "http://localhost/a/product_name/1"),
417+
vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
418+
host: "",
419+
path: "/a/product_name/1",
420+
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
421+
shouldMatch: true,
422+
},
423+
{
424+
title: "Path route with multiple patterns with pipe, match",
425+
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
426+
request: newRequest("GET", "http://localhost/b/c/product_name/1"),
427+
vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
428+
host: "",
429+
path: "/b/c/product_name/1",
430+
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
431+
shouldMatch: true,
432+
},
431433
}
432434

433435
for _, test := range tests {
@@ -516,15 +518,28 @@ func TestPathPrefix(t *testing.T) {
516518
}
517519
}
518520

519-
func TestHostPath(t *testing.T) {
521+
func TestSchemeHostPath(t *testing.T) {
520522
tests := []routeTest{
521523
{
522524
title: "Host and Path route, match",
523525
route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
524526
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
525527
vars: map[string]string{},
526-
host: "",
527-
path: "",
528+
scheme: "http",
529+
host: "aaa.bbb.ccc",
530+
path: "/111/222/333",
531+
pathTemplate: `/111/222/333`,
532+
hostTemplate: `aaa.bbb.ccc`,
533+
shouldMatch: true,
534+
},
535+
{
536+
title: "Scheme, Host, and Path route, match",
537+
route: new(Route).Schemes("https").Host("aaa.bbb.ccc").Path("/111/222/333"),
538+
request: newRequest("GET", "https://aaa.bbb.ccc/111/222/333"),
539+
vars: map[string]string{},
540+
scheme: "https",
541+
host: "aaa.bbb.ccc",
542+
path: "/111/222/333",
528543
pathTemplate: `/111/222/333`,
529544
hostTemplate: `aaa.bbb.ccc`,
530545
shouldMatch: true,
@@ -534,8 +549,9 @@ func TestHostPath(t *testing.T) {
534549
route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
535550
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
536551
vars: map[string]string{},
537-
host: "",
538-
path: "",
552+
scheme: "http",
553+
host: "aaa.bbb.ccc",
554+
path: "/111/222/333",
539555
pathTemplate: `/111/222/333`,
540556
hostTemplate: `aaa.bbb.ccc`,
541557
shouldMatch: false,
@@ -545,6 +561,19 @@ func TestHostPath(t *testing.T) {
545561
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
546562
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
547563
vars: map[string]string{"v1": "bbb", "v2": "222"},
564+
scheme: "http",
565+
host: "aaa.bbb.ccc",
566+
path: "/111/222/333",
567+
pathTemplate: `/111/{v2:[0-9]{3}}/333`,
568+
hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
569+
shouldMatch: true,
570+
},
571+
{
572+
title: "Scheme, Host, and Path route with host and path patterns, match",
573+
route: new(Route).Schemes("ftp", "ssss").Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
574+
request: newRequest("GET", "ssss://aaa.bbb.ccc/111/222/333"),
575+
vars: map[string]string{"v1": "bbb", "v2": "222"},
576+
scheme: "ftp",
548577
host: "aaa.bbb.ccc",
549578
path: "/111/222/333",
550579
pathTemplate: `/111/{v2:[0-9]{3}}/333`,
@@ -556,6 +585,7 @@ func TestHostPath(t *testing.T) {
556585
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
557586
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
558587
vars: map[string]string{"v1": "bbb", "v2": "222"},
588+
scheme: "http",
559589
host: "aaa.bbb.ccc",
560590
path: "/111/222/333",
561591
pathTemplate: `/111/{v2:[0-9]{3}}/333`,
@@ -567,6 +597,7 @@ func TestHostPath(t *testing.T) {
567597
route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
568598
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
569599
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
600+
scheme: "http",
570601
host: "aaa.bbb.ccc",
571602
path: "/111/222/333",
572603
pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
@@ -578,6 +609,7 @@ func TestHostPath(t *testing.T) {
578609
route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
579610
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
580611
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
612+
scheme: "http",
581613
host: "aaa.bbb.ccc",
582614
path: "/111/222/333",
583615
pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
@@ -649,7 +681,6 @@ func TestHeaders(t *testing.T) {
649681
testRoute(t, test)
650682
testTemplate(t, test)
651683
}
652-
653684
}
654685

655686
func TestMethods(t *testing.T) {
@@ -938,30 +969,43 @@ func TestSchemes(t *testing.T) {
938969
tests := []routeTest{
939970
// Schemes
940971
{
941-
title: "Schemes route, match https",
942-
route: new(Route).Schemes("https", "ftp"),
972+
title: "Schemes route, default scheme, match http, build http",
973+
route: new(Route).Host("localhost"),
974+
request: newRequest("GET", "http://localhost"),
975+
scheme: "http",
976+
host: "localhost",
977+
shouldMatch: true,
978+
},
979+
{
980+
title: "Schemes route, match https, build https",
981+
route: new(Route).Schemes("https", "ftp").Host("localhost"),
943982
request: newRequest("GET", "https://localhost"),
944-
vars: map[string]string{},
945-
host: "",
946-
path: "",
983+
scheme: "https",
984+
host: "localhost",
947985
shouldMatch: true,
948986
},
949987
{
950-
title: "Schemes route, match ftp",
951-
route: new(Route).Schemes("https", "ftp"),
988+
title: "Schemes route, match ftp, build https",
989+
route: new(Route).Schemes("https", "ftp").Host("localhost"),
952990
request: newRequest("GET", "ftp://localhost"),
953-
vars: map[string]string{},
954-
host: "",
955-
path: "",
991+
scheme: "https",
992+
host: "localhost",
993+
shouldMatch: true,
994+
},
995+
{
996+
title: "Schemes route, match ftp, build ftp",
997+
route: new(Route).Schemes("ftp", "https").Host("localhost"),
998+
request: newRequest("GET", "ftp://localhost"),
999+
scheme: "ftp",
1000+
host: "localhost",
9561001
shouldMatch: true,
9571002
},
9581003
{
9591004
title: "Schemes route, bad scheme",
960-
route: new(Route).Schemes("https", "ftp"),
1005+
route: new(Route).Schemes("https", "ftp").Host("localhost"),
9611006
request: newRequest("GET", "http://localhost"),
962-
vars: map[string]string{},
963-
host: "",
964-
path: "",
1007+
scheme: "https",
1008+
host: "localhost",
9651009
shouldMatch: false,
9661010
},
9671011
}
@@ -1448,10 +1492,15 @@ func testRoute(t *testing.T, test routeTest) {
14481492
route := test.route
14491493
vars := test.vars
14501494
shouldMatch := test.shouldMatch
1451-
host := test.host
1452-
path := test.path
1453-
url := test.host + test.path
14541495
shouldRedirect := test.shouldRedirect
1496+
uri := url.URL{
1497+
Scheme: test.scheme,
1498+
Host: test.host,
1499+
Path: test.path,
1500+
}
1501+
if uri.Scheme == "" {
1502+
uri.Scheme = "http"
1503+
}
14551504

14561505
var match RouteMatch
14571506
ok := route.Match(request, &match)
@@ -1464,28 +1513,51 @@ func testRoute(t *testing.T, test routeTest) {
14641513
return
14651514
}
14661515
if shouldMatch {
1467-
if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
1516+
if vars != nil && !stringMapEqual(vars, match.Vars) {
14681517
t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
14691518
return
14701519
}
1471-
if host != "" {
1472-
u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
1473-
if host != u.Host {
1474-
t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
1520+
if test.scheme != "" {
1521+
u, err := route.URL(mapToPairs(match.Vars)...)
1522+
if err != nil {
1523+
t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
1524+
}
1525+
if uri.Scheme != u.Scheme {
1526+
t.Errorf("(%v) URLScheme not equal: expected %v, got %v", test.title, uri.Scheme, u.Scheme)
1527+
return
1528+
}
1529+
}
1530+
if test.host != "" {
1531+
u, err := test.route.URLHost(mapToPairs(match.Vars)...)
1532+
if err != nil {
1533+
t.Fatalf("(%v) URLHost error: %v -- %v", test.title, err, getRouteTemplate(route))
1534+
}
1535+
if uri.Scheme != u.Scheme {
1536+
t.Errorf("(%v) URLHost scheme not equal: expected %v, got %v -- %v", test.title, uri.Scheme, u.Scheme, getRouteTemplate(route))
1537+
return
1538+
}
1539+
if uri.Host != u.Host {
1540+
t.Errorf("(%v) URLHost host not equal: expected %v, got %v -- %v", test.title, uri.Host, u.Host, getRouteTemplate(route))
14751541
return
14761542
}
14771543
}
1478-
if path != "" {
1479-
u, _ := route.URLPath(mapToPairs(match.Vars)...)
1480-
if path != u.Path {
1481-
t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
1544+
if test.path != "" {
1545+
u, err := route.URLPath(mapToPairs(match.Vars)...)
1546+
if err != nil {
1547+
t.Fatalf("(%v) URLPath error: %v -- %v", test.title, err, getRouteTemplate(route))
1548+
}
1549+
if uri.Path != u.Path {
1550+
t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, uri.Path, u.Path, getRouteTemplate(route))
14821551
return
14831552
}
14841553
}
1485-
if url != "" {
1486-
u, _ := route.URL(mapToPairs(match.Vars)...)
1487-
if url != u.Host+u.Path {
1488-
t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
1554+
if test.host != "" && test.path != "" {
1555+
u, err := route.URL(mapToPairs(match.Vars)...)
1556+
if err != nil {
1557+
t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
1558+
}
1559+
if expected, got := uri.String(), u.String(); expected != got {
1560+
t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, expected, got, getRouteTemplate(route))
14891561
return
14901562
}
14911563
}

0 commit comments

Comments
 (0)