12 Commits

Author SHA1 Message Date
Chris Hines
bcd8bc72b0 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.
2017-05-20 21:50:13 -07:00
Bulat Gaifullin
751308a60a Updated README 2017-05-20 15:55:46 -07:00
Bulat Gaifullin
b552615e22 Added method Route.GetMethods 2017-05-20 15:55:46 -07:00
Bulat Gaifullin
1856953e53 Added method Route.GetPathRegexp 2017-05-20 15:55:46 -07:00
Carlos Alexandro Becker
4c1c3952b7 fixed typo (#250) 2017-04-26 21:12:50 -07:00
Diego Siqueira
599cba5e7b Fixing Regexp in the benchmark test (#234)
Signed-off-by: DiSiqueira <dieg0@live.com>
2017-02-28 14:43:54 -08:00
Shane Smith-Sahnow
ad4ce0eb16 updating logic in route matcher, cleaner and saner (#235) 2017-02-27 19:44:49 -08:00
Kamil Kisiel
999ef73f5d Merge pull request #232 from DavidJFelix/patch-1
Add sourcegraph badge
2017-02-23 12:25:54 -08:00
David J. Felix
89d16fe9a0 Add sourcegraph badge
Fixes #228
2017-02-23 14:57:28 -05:00
Kamil Kisiel
94e7d24fd2 Add Go 1.8 to .travis.yml 2017-02-17 11:26:16 -08:00
Adam Eijdenberg
392c28fe23 [bugfix] fail fast if regex is incorrectly specified using capturing groups. (#218) 2017-01-18 05:43:44 -08:00
Shawn Smith
cafdb65e9e [docs] Add route listing example to README
* Update README.md

* Update README.md
2017-01-18 05:43:23 -08:00
7 changed files with 316 additions and 87 deletions

View File

@@ -9,6 +9,7 @@ matrix:
- go: 1.5
- go: 1.6
- go: 1.7
- go: 1.8
- go: tip
install:

View File

@@ -2,6 +2,7 @@ gorilla/mux
===
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
@@ -23,6 +24,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
* [Install](#install)
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Listing Routes](#listing-routes)
* [Static Files](#static-files)
* [Registered URLs](#registered-urls)
* [Full Example](#full-example)
@@ -167,6 +169,53 @@ s.HandleFunc("/{key}/", ProductHandler)
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```
### Listing Routes
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
```go
package main
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
return
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Methods("POST").HandleFunc("/products", handler)
r.Methods("GET").HandleFunc("/articles", handler)
r.Methods("GET", "PUT").HandleFunc("/articles/{id}", handler)
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
t, err := route.GetPathTemplate()
if err != nil {
return err
}
// p will contain regular expression is compatible with regular expression in Perl, Python, and other languages.
// for instance the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'
p, err := route.GetPathRegexp()
if err != nil {
return err
}
m, err := route.GetMethods()
if err != nil {
return err
}
fmt.Println(strings.Join(m, ","), t, p)
return nil
})
http.Handle("/", r)
}
```
### Static Files
Note that the path provided to `PathPrefix()` represents a "wildcard": calling

View File

@@ -24,7 +24,7 @@ func BenchmarkMux(b *testing.B) {
func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
router := new(Router)
handler := func(w http.ResponseWriter, r *http.Request) {}
router.HandleFunc("/v1/{v1:(a|b)}", handler)
router.HandleFunc("/v1/{v1:(?:a|b)}", handler)
requestA, _ := http.NewRequest("GET", "/v1/a", nil)
requestB, _ := http.NewRequest("GET", "/v1/b", nil)

5
doc.go
View File

@@ -57,6 +57,11 @@ calling mux.Vars():
vars := mux.Vars(request)
category := vars["category"]
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
when capturing groups were present.
And this is all you need to know about the basic usage. More advanced options
are explained below.

View File

@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"testing"
)
@@ -31,10 +32,13 @@ type routeTest struct {
route *Route // the route being tested
request *http.Request // a request to test the route
vars map[string]string // the expected vars of the match
host string // the expected host of the match
path string // the expected path of the match
pathTemplate string // the expected path template to match
hostTemplate string // the expected host template to match
scheme string // the expected scheme of the built URL
host string // the expected host of the built URL
path string // the expected path of the built URL
pathTemplate string // the expected path template of the route
hostTemplate string // the expected host template of the route
methods []string // the expected route methods
pathRegexp string // the expected path regexp
shouldMatch bool // whether the request is expected to match the route at all
shouldRedirect bool // whether the request should result in a redirect
}
@@ -195,46 +199,6 @@ func TestHost(t *testing.T) {
hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,
shouldMatch: true,
},
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/a"),
vars: map[string]string{"category": "a"},
host: "",
path: "/a",
pathTemplate: `/{category:a|b/c}`,
shouldMatch: true,
},
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/b/c"),
vars: map[string]string{"category": "b/c"},
host: "",
path: "/b/c",
pathTemplate: `/{category:a|b/c}`,
shouldMatch: true,
},
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/a/product_name/1"),
vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
host: "",
path: "/a/product_name/1",
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
shouldMatch: true,
},
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/b/c/product_name/1"),
vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
host: "",
path: "/b/c/product_name/1",
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
shouldMatch: true,
},
}
for _, test := range tests {
testRoute(t, test)
@@ -270,6 +234,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/111",
pathTemplate: `/111/`,
pathRegexp: `^/111/$`,
shouldMatch: false,
},
{
@@ -290,6 +255,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/",
pathTemplate: `/`,
pathRegexp: `^/$`,
shouldMatch: true,
},
{
@@ -333,6 +299,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/111/222/333",
pathTemplate: `/111/{v1:[0-9]{3}}/333`,
pathRegexp: `^/111/(?P<v0>[0-9]{3})/333$`,
shouldMatch: false,
},
{
@@ -343,6 +310,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/111/222/333",
pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
pathRegexp: `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
shouldMatch: true,
},
{
@@ -353,6 +321,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/111/222/333",
pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
pathRegexp: `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
shouldMatch: false,
},
{
@@ -363,6 +332,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/a/product_name/1",
pathTemplate: `/{category:a|(?:b/c)}/{product}/{id:[0-9]+}`,
pathRegexp: `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,
shouldMatch: true,
},
{
@@ -373,6 +343,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/111/222/333",
pathTemplate: `/111/{v-1:[0-9]{3}}/333`,
pathRegexp: `^/111/(?P<v0>[0-9]{3})/333$`,
shouldMatch: true,
},
{
@@ -383,6 +354,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/111/222/333",
pathTemplate: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`,
pathRegexp: `^/(?P<v0>[0-9]{3})/(?P<v1>[0-9]{3})/(?P<v2>[0-9]{3})$`,
shouldMatch: true,
},
{
@@ -393,6 +365,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/a/product_name/1",
pathTemplate: `/{product-category:a|(?:b/c)}/{product-name}/{product-id:[0-9]+}`,
pathRegexp: `^/(?P<v0>a|(?:b/c))/(?P<v1>[^/]+)/(?P<v2>[0-9]+)$`,
shouldMatch: true,
},
{
@@ -403,6 +376,7 @@ func TestPath(t *testing.T) {
host: "",
path: "/daily-2016-01-01",
pathTemplate: `/{type:(?i:daily|mini|variety)}-{date:\d{4,4}-\d{2,2}-\d{2,2}}`,
pathRegexp: `^/(?P<v0>(?i:daily|mini|variety))-(?P<v1>\d{4,4}-\d{2,2}-\d{2,2})$`,
shouldMatch: true,
},
{
@@ -413,6 +387,47 @@ func TestPath(t *testing.T) {
host: "",
path: "/111/222",
pathTemplate: `/{v1:[0-9]*}{v2:[a-z]*}/{v3:[0-9]*}`,
pathRegexp: `^/(?P<v0>[0-9]*)(?P<v1>[a-z]*)/(?P<v2>[0-9]*)$`,
shouldMatch: true,
},
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/a"),
vars: map[string]string{"category": "a"},
host: "",
path: "/a",
pathTemplate: `/{category:a|b/c}`,
shouldMatch: true,
},
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/b/c"),
vars: map[string]string{"category": "b/c"},
host: "",
path: "/b/c",
pathTemplate: `/{category:a|b/c}`,
shouldMatch: true,
},
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/a/product_name/1"),
vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
host: "",
path: "/a/product_name/1",
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
shouldMatch: true,
},
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/b/c/product_name/1"),
vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
host: "",
path: "/b/c/product_name/1",
pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
shouldMatch: true,
},
}
@@ -421,6 +436,7 @@ func TestPath(t *testing.T) {
testRoute(t, test)
testTemplate(t, test)
testUseEscapedRoute(t, test)
testRegexp(t, test)
}
}
@@ -502,15 +518,28 @@ func TestPathPrefix(t *testing.T) {
}
}
func TestHostPath(t *testing.T) {
func TestSchemeHostPath(t *testing.T) {
tests := []routeTest{
{
title: "Host and Path route, match",
route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{},
host: "",
path: "",
scheme: "http",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/111/222/333`,
hostTemplate: `aaa.bbb.ccc`,
shouldMatch: true,
},
{
title: "Scheme, Host, and Path route, match",
route: new(Route).Schemes("https").Host("aaa.bbb.ccc").Path("/111/222/333"),
request: newRequest("GET", "https://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{},
scheme: "https",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/111/222/333`,
hostTemplate: `aaa.bbb.ccc`,
shouldMatch: true,
@@ -520,8 +549,9 @@ func TestHostPath(t *testing.T) {
route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
vars: map[string]string{},
host: "",
path: "",
scheme: "http",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/111/222/333`,
hostTemplate: `aaa.bbb.ccc`,
shouldMatch: false,
@@ -531,6 +561,19 @@ func TestHostPath(t *testing.T) {
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{"v1": "bbb", "v2": "222"},
scheme: "http",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/111/{v2:[0-9]{3}}/333`,
hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
shouldMatch: true,
},
{
title: "Scheme, Host, and Path route with host and path patterns, match",
route: new(Route).Schemes("ftp", "ssss").Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
request: newRequest("GET", "ssss://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{"v1": "bbb", "v2": "222"},
scheme: "ftp",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/111/{v2:[0-9]{3}}/333`,
@@ -542,6 +585,7 @@ func TestHostPath(t *testing.T) {
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
vars: map[string]string{"v1": "bbb", "v2": "222"},
scheme: "http",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/111/{v2:[0-9]{3}}/333`,
@@ -553,6 +597,7 @@ func TestHostPath(t *testing.T) {
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}}"),
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
scheme: "http",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
@@ -564,6 +609,7 @@ func TestHostPath(t *testing.T) {
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}}"),
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
scheme: "http",
host: "aaa.bbb.ccc",
path: "/111/222/333",
pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
@@ -635,7 +681,6 @@ func TestHeaders(t *testing.T) {
testRoute(t, test)
testTemplate(t, test)
}
}
func TestMethods(t *testing.T) {
@@ -647,6 +692,7 @@ func TestMethods(t *testing.T) {
vars: map[string]string{},
host: "",
path: "",
methods: []string{"GET", "POST"},
shouldMatch: true,
},
{
@@ -656,6 +702,7 @@ func TestMethods(t *testing.T) {
vars: map[string]string{},
host: "",
path: "",
methods: []string{"GET", "POST"},
shouldMatch: true,
},
{
@@ -665,13 +712,25 @@ func TestMethods(t *testing.T) {
vars: map[string]string{},
host: "",
path: "",
methods: []string{"GET", "POST"},
shouldMatch: false,
},
{
title: "Route without methods",
route: new(Route),
request: newRequest("PUT", "http://localhost"),
vars: map[string]string{},
host: "",
path: "",
methods: []string{},
shouldMatch: true,
},
}
for _, test := range tests {
testRoute(t, test)
testTemplate(t, test)
testMethods(t, test)
}
}
@@ -910,30 +969,43 @@ func TestSchemes(t *testing.T) {
tests := []routeTest{
// Schemes
{
title: "Schemes route, match https",
route: new(Route).Schemes("https", "ftp"),
request: newRequest("GET", "https://localhost"),
vars: map[string]string{},
host: "",
path: "",
title: "Schemes route, default scheme, match http, build http",
route: new(Route).Host("localhost"),
request: newRequest("GET", "http://localhost"),
scheme: "http",
host: "localhost",
shouldMatch: true,
},
{
title: "Schemes route, match ftp",
route: new(Route).Schemes("https", "ftp"),
title: "Schemes route, match https, build https",
route: new(Route).Schemes("https", "ftp").Host("localhost"),
request: newRequest("GET", "https://localhost"),
scheme: "https",
host: "localhost",
shouldMatch: true,
},
{
title: "Schemes route, match ftp, build https",
route: new(Route).Schemes("https", "ftp").Host("localhost"),
request: newRequest("GET", "ftp://localhost"),
vars: map[string]string{},
host: "",
path: "",
scheme: "https",
host: "localhost",
shouldMatch: true,
},
{
title: "Schemes route, match ftp, build ftp",
route: new(Route).Schemes("ftp", "https").Host("localhost"),
request: newRequest("GET", "ftp://localhost"),
scheme: "ftp",
host: "localhost",
shouldMatch: true,
},
{
title: "Schemes route, bad scheme",
route: new(Route).Schemes("https", "ftp"),
route: new(Route).Schemes("https", "ftp").Host("localhost"),
request: newRequest("GET", "http://localhost"),
vars: map[string]string{},
host: "",
path: "",
scheme: "https",
host: "localhost",
shouldMatch: false,
},
}
@@ -1389,6 +1461,16 @@ func TestSubrouterErrorHandling(t *testing.T) {
}
}
// See: https://github.com/gorilla/mux/issues/200
func TestPanicOnCapturingGroups(t *testing.T) {
defer func() {
if recover() == nil {
t.Errorf("(Test that capturing groups now fail fast) Expected panic, however test completed successfully.\n")
}
}()
NewRouter().NewRoute().Path("/{type:(promo|special)}/{promoId}.json")
}
// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------
@@ -1410,10 +1492,15 @@ func testRoute(t *testing.T, test routeTest) {
route := test.route
vars := test.vars
shouldMatch := test.shouldMatch
host := test.host
path := test.path
url := test.host + test.path
shouldRedirect := test.shouldRedirect
uri := url.URL{
Scheme: test.scheme,
Host: test.host,
Path: test.path,
}
if uri.Scheme == "" {
uri.Scheme = "http"
}
var match RouteMatch
ok := route.Match(request, &match)
@@ -1426,28 +1513,51 @@ func testRoute(t *testing.T, test routeTest) {
return
}
if shouldMatch {
if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
if vars != nil && !stringMapEqual(vars, match.Vars) {
t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
return
}
if host != "" {
u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
if host != u.Host {
t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
if test.scheme != "" {
u, err := route.URL(mapToPairs(match.Vars)...)
if err != nil {
t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
}
if uri.Scheme != u.Scheme {
t.Errorf("(%v) URLScheme not equal: expected %v, got %v", test.title, uri.Scheme, u.Scheme)
return
}
}
if path != "" {
u, _ := route.URLPath(mapToPairs(match.Vars)...)
if path != u.Path {
t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
if test.host != "" {
u, err := test.route.URLHost(mapToPairs(match.Vars)...)
if err != nil {
t.Fatalf("(%v) URLHost error: %v -- %v", test.title, err, getRouteTemplate(route))
}
if uri.Scheme != u.Scheme {
t.Errorf("(%v) URLHost scheme not equal: expected %v, got %v -- %v", test.title, uri.Scheme, u.Scheme, getRouteTemplate(route))
return
}
if uri.Host != u.Host {
t.Errorf("(%v) URLHost host not equal: expected %v, got %v -- %v", test.title, uri.Host, u.Host, getRouteTemplate(route))
return
}
}
if url != "" {
u, _ := route.URL(mapToPairs(match.Vars)...)
if url != u.Host+u.Path {
t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
if test.path != "" {
u, err := route.URLPath(mapToPairs(match.Vars)...)
if err != nil {
t.Fatalf("(%v) URLPath error: %v -- %v", test.title, err, getRouteTemplate(route))
}
if uri.Path != u.Path {
t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, uri.Path, u.Path, getRouteTemplate(route))
return
}
}
if test.host != "" && test.path != "" {
u, err := route.URL(mapToPairs(match.Vars)...)
if err != nil {
t.Fatalf("(%v) URL error: %v -- %v", test.title, err, getRouteTemplate(route))
}
if expected, got := uri.String(), u.String(); expected != got {
t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, expected, got, getRouteTemplate(route))
return
}
}
@@ -1489,6 +1599,22 @@ func testTemplate(t *testing.T, test routeTest) {
}
}
func testMethods(t *testing.T, test routeTest) {
route := test.route
methods, _ := route.GetMethods()
if strings.Join(methods, ",") != strings.Join(test.methods, ",") {
t.Errorf("(%v) GetMethods not equal: expected %v, got %v", test.title, test.methods, methods)
}
}
func testRegexp(t *testing.T, test routeTest) {
route := test.route
routePathRegexp, regexpErr := route.GetPathRegexp()
if test.pathRegexp != "" && regexpErr == nil && routePathRegexp != test.pathRegexp {
t.Errorf("(%v) GetPathRegexp not equal: expected %v, got %v", test.title, test.pathRegexp, routePathRegexp)
}
}
type TestA301ResponseWriter struct {
hh http.Header
status int

View File

@@ -109,6 +109,13 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash,
if errCompile != nil {
return nil, errCompile
}
// Check for capturing groups which used to work in older versions
if reg.NumSubexp() != len(idxs)/2 {
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
}
// Done!
return &routeRegexp{
template: template,

View File

@@ -31,6 +31,8 @@ type Route struct {
skipClean bool
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
useEncodedPath bool
// The scheme used when building URLs.
buildScheme string
// If true, this route never matches: it is only used to build URLs.
buildOnly bool
// The name used to build URLs.
@@ -153,7 +155,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
}
r.regexp = r.getRegexpGroup()
if !matchHost && !matchQuery {
if tpl == "/" && (len(tpl) == 0 || tpl[0] != '/') {
if len(tpl) > 0 && tpl[0] != '/' {
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
}
if r.regexp.path != nil {
@@ -394,6 +396,9 @@ func (r *Route) Schemes(schemes ...string) *Route {
for k, v := range schemes {
schemes[k] = strings.ToLower(v)
}
if r.buildScheme == "" && len(schemes) > 0 {
r.buildScheme = schemes[0]
}
return r.addMatcher(schemeMatcher(schemes))
}
@@ -478,11 +483,13 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
}
var scheme, host, path string
if r.regexp.host != nil {
// Set a default scheme.
scheme = "http"
if host, err = r.regexp.host.url(values); err != nil {
return nil, err
}
scheme = "http"
if r.buildScheme != "" {
scheme = r.buildScheme
}
}
if r.regexp.path != nil {
if path, err = r.regexp.path.url(values); err != nil {
@@ -514,10 +521,14 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
if err != nil {
return nil, err
}
return &url.URL{
u := &url.URL{
Scheme: "http",
Host: host,
}, nil
}
if r.buildScheme != "" {
u.Scheme = r.buildScheme
}
return u, nil
}
// URLPath builds the path part of the URL for a route. See Route.URL().
@@ -558,6 +569,36 @@ func (r *Route) GetPathTemplate() (string, error) {
return r.regexp.path.template, nil
}
// GetPathRegexp returns the expanded regular expression used to match route path.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not define a path.
func (r *Route) GetPathRegexp() (string, error) {
if r.err != nil {
return "", r.err
}
if r.regexp == nil || r.regexp.path == nil {
return "", errors.New("mux: route does not have a path")
}
return r.regexp.path.regexp.String(), nil
}
// GetMethods returns the methods the route matches against
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An empty list will be returned if route does not have methods.
func (r *Route) GetMethods() ([]string, error) {
if r.err != nil {
return nil, r.err
}
for _, m := range r.matchers {
if methods, ok := m.(methodMatcher); ok {
return []string(methods), nil
}
}
return nil, nil
}
// GetHostTemplate returns the template used to build the
// route match.
// This is useful for building simple REST API documentation and for instrumentation