mirror of
https://github.com/hoernschen/dendrite.git
synced 2024-12-27 07:28:27 +00:00
Add gorilla/mux
This commit is contained in:
parent
450e0f1570
commit
154110b7c2
14 changed files with 4650 additions and 1 deletions
8
vendor/manifest
vendored
8
vendor/manifest
vendored
|
@ -59,6 +59,12 @@
|
||||||
"revision": "7db9049039a047d955fe8c19b83c8ff5abd765c7",
|
"revision": "7db9049039a047d955fe8c19b83c8ff5abd765c7",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "github.com/gorilla/mux",
|
||||||
|
"repository": "https://github.com/gorilla/mux",
|
||||||
|
"revision": "392c28fe23e1c45ddba891b0320b3b5df220beea",
|
||||||
|
"branch": "master"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/klauspost/crc32",
|
"importpath": "github.com/klauspost/crc32",
|
||||||
"repository": "https://github.com/klauspost/crc32",
|
"repository": "https://github.com/klauspost/crc32",
|
||||||
|
@ -188,4 +194,4 @@
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
27
vendor/src/github.com/gorilla/mux/LICENSE
vendored
Normal file
27
vendor/src/github.com/gorilla/mux/LICENSE
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
339
vendor/src/github.com/gorilla/mux/README.md
vendored
Normal file
339
vendor/src/github.com/gorilla/mux/README.md
vendored
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
|
||||||
|
|
||||||
|
http://www.gorillatoolkit.org/pkg/mux
|
||||||
|
|
||||||
|
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
||||||
|
their respective handler.
|
||||||
|
|
||||||
|
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
|
||||||
|
|
||||||
|
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
||||||
|
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
|
||||||
|
* URL hosts and paths can have variables with an optional regular expression.
|
||||||
|
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
|
||||||
|
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
* [Install](#install)
|
||||||
|
* [Examples](#examples)
|
||||||
|
* [Matching Routes](#matching-routes)
|
||||||
|
* [Listing Routes](#listing-routes)
|
||||||
|
* [Static Files](#static-files)
|
||||||
|
* [Registered URLs](#registered-urls)
|
||||||
|
* [Full Example](#full-example)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -u github.com/gorilla/mux
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Let's start registering a couple of URL paths and handlers:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", HomeHandler)
|
||||||
|
r.HandleFunc("/products", ProductsHandler)
|
||||||
|
r.HandleFunc("/articles", ArticlesHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
|
||||||
|
|
||||||
|
Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, "Category: %v\n", vars["category"])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And this is all you need to know about the basic usage. More advanced options are explained below.
|
||||||
|
|
||||||
|
### Matching Routes
|
||||||
|
|
||||||
|
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Only matches if domain is "www.example.com".
|
||||||
|
r.Host("www.example.com")
|
||||||
|
// Matches a dynamic subdomain.
|
||||||
|
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
```
|
||||||
|
|
||||||
|
There are several other matchers that can be added. To match path prefixes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.PathPrefix("/products/")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or HTTP methods:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Methods("GET", "POST")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or URL schemes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Schemes("https")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or header values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Headers("X-Requested-With", "XMLHttpRequest")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or query values:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.Queries("key", "value")
|
||||||
|
```
|
||||||
|
|
||||||
|
...or to use a custom matcher function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||||
|
return r.ProtoMajor == 0
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
...and finally, it is possible to combine several matchers in a single route:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.HandleFunc("/products", ProductsHandler).
|
||||||
|
Host("www.example.com").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("http")
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
|
||||||
|
|
||||||
|
For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("www.example.com").Subrouter()
|
||||||
|
```
|
||||||
|
|
||||||
|
Then register routes in the subrouter:
|
||||||
|
|
||||||
|
```go
|
||||||
|
s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
|
||||||
|
|
||||||
|
Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
|
||||||
|
|
||||||
|
There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.PathPrefix("/products").Subrouter()
|
||||||
|
// "/products/"
|
||||||
|
s.HandleFunc("/", ProductsHandler)
|
||||||
|
// "/products/{key}/"
|
||||||
|
s.HandleFunc("/{key}/", ProductHandler)
|
||||||
|
// "/products/{key}/details"
|
||||||
|
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"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
r.HandleFunc("/products", handler)
|
||||||
|
r.HandleFunc("/articles", handler)
|
||||||
|
r.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
|
||||||
|
}
|
||||||
|
fmt.Println(t)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Files
|
||||||
|
|
||||||
|
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
|
||||||
|
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
|
||||||
|
request that matches "/static/*". This makes it easy to serve static files with mux:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
var dir string
|
||||||
|
|
||||||
|
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||||
|
flag.Parse()
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// This will serve files under http://localhost:8000/static/<filename>
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registered URLs
|
||||||
|
|
||||||
|
Now let's see how to build registered URLs.
|
||||||
|
|
||||||
|
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
```
|
||||||
|
|
||||||
|
To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
...and the result will be a `url.URL` with the following path:
|
||||||
|
|
||||||
|
```
|
||||||
|
"/articles/technology/42"
|
||||||
|
```
|
||||||
|
|
||||||
|
This also works for host variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Host("{subdomain}.domain.com").
|
||||||
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// url.String() will be "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
|
||||||
|
|
||||||
|
Regex support also exists for matching Headers within a route. For example, we could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
||||||
|
```
|
||||||
|
|
||||||
|
...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
|
||||||
|
|
||||||
|
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// "http://news.domain.com/"
|
||||||
|
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||||
|
|
||||||
|
// "/articles/technology/42"
|
||||||
|
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
And if you use subrouters, host and path defined separately can be built as well:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("{subdomain}.domain.com").Subrouter()
|
||||||
|
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
Here's a complete, runnable example of a small `mux` based server:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"log"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func YourHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("Gorilla!\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Routes consist of a path and a handler function.
|
||||||
|
r.HandleFunc("/", YourHandler)
|
||||||
|
|
||||||
|
// Bind to a port and pass our router in
|
||||||
|
log.Fatal(http.ListenAndServe(":8000", r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD licensed. See the LICENSE file for details.
|
49
vendor/src/github.com/gorilla/mux/bench_test.go
vendored
Normal file
49
vendor/src/github.com/gorilla/mux/bench_test.go
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkMux(b *testing.B) {
|
||||||
|
router := new(Router)
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
router.HandleFunc("/v1/{v1}", handler)
|
||||||
|
|
||||||
|
request, _ := http.NewRequest("GET", "/v1/anything", nil)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
router.ServeHTTP(nil, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
|
||||||
|
router := new(Router)
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
router.HandleFunc("/v1/{v1:(a|b)}", handler)
|
||||||
|
|
||||||
|
requestA, _ := http.NewRequest("GET", "/v1/a", nil)
|
||||||
|
requestB, _ := http.NewRequest("GET", "/v1/b", nil)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
router.ServeHTTP(nil, requestA)
|
||||||
|
router.ServeHTTP(nil, requestB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkManyPathVariables(b *testing.B) {
|
||||||
|
router := new(Router)
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
router.HandleFunc("/v1/{v1}/{v2}/{v3}/{v4}/{v5}", handler)
|
||||||
|
|
||||||
|
matchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4/5", nil)
|
||||||
|
notMatchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4", nil)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
router.ServeHTTP(nil, matchingRequest)
|
||||||
|
router.ServeHTTP(recorder, notMatchingRequest)
|
||||||
|
}
|
||||||
|
}
|
26
vendor/src/github.com/gorilla/mux/context_gorilla.go
vendored
Normal file
26
vendor/src/github.com/gorilla/mux/context_gorilla.go
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func contextGet(r *http.Request, key interface{}) interface{} {
|
||||||
|
return context.Get(r, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
||||||
|
if val == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Set(r, key, val)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClear(r *http.Request) {
|
||||||
|
context.Clear(r)
|
||||||
|
}
|
40
vendor/src/github.com/gorilla/mux/context_gorilla_test.go
vendored
Normal file
40
vendor/src/github.com/gorilla/mux/context_gorilla_test.go
vendored
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// +build !go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that the context is cleared or not cleared properly depending on
|
||||||
|
// the configuration of the router
|
||||||
|
func TestKeepContext(t *testing.T) {
|
||||||
|
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
|
||||||
|
r := NewRouter()
|
||||||
|
r.HandleFunc("/", func1).Name("func1")
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "http://localhost/", nil)
|
||||||
|
context.Set(req, "t", 1)
|
||||||
|
|
||||||
|
res := new(http.ResponseWriter)
|
||||||
|
r.ServeHTTP(*res, req)
|
||||||
|
|
||||||
|
if _, ok := context.GetOk(req, "t"); ok {
|
||||||
|
t.Error("Context should have been cleared at end of request")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.KeepContext = true
|
||||||
|
|
||||||
|
req, _ = http.NewRequest("GET", "http://localhost/", nil)
|
||||||
|
context.Set(req, "t", 1)
|
||||||
|
|
||||||
|
r.ServeHTTP(*res, req)
|
||||||
|
if _, ok := context.GetOk(req, "t"); !ok {
|
||||||
|
t.Error("Context should NOT have been cleared at end of request")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
vendor/src/github.com/gorilla/mux/context_native.go
vendored
Normal file
24
vendor/src/github.com/gorilla/mux/context_native.go
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func contextGet(r *http.Request, key interface{}) interface{} {
|
||||||
|
return r.Context().Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
||||||
|
if val == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.WithContext(context.WithValue(r.Context(), key, val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClear(r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
32
vendor/src/github.com/gorilla/mux/context_native_test.go
vendored
Normal file
32
vendor/src/github.com/gorilla/mux/context_native_test.go
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNativeContextMiddleware(t *testing.T) {
|
||||||
|
withTimeout := func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
h.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
r := NewRouter()
|
||||||
|
r.Handle("/path/{foo}", withTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := Vars(r)
|
||||||
|
if vars["foo"] != "bar" {
|
||||||
|
t.Fatal("Expected foo var to be set")
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
rec := NewRecorder()
|
||||||
|
req := newRequest("GET", "/path/bar")
|
||||||
|
r.ServeHTTP(rec, req)
|
||||||
|
}
|
240
vendor/src/github.com/gorilla/mux/doc.go
vendored
Normal file
240
vendor/src/github.com/gorilla/mux/doc.go
vendored
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package mux implements a request router and dispatcher.
|
||||||
|
|
||||||
|
The name mux stands for "HTTP request multiplexer". Like the standard
|
||||||
|
http.ServeMux, mux.Router matches incoming requests against a list of
|
||||||
|
registered routes and calls a handler for the route that matches the URL
|
||||||
|
or other conditions. The main features are:
|
||||||
|
|
||||||
|
* Requests can be matched based on URL host, path, path prefix, schemes,
|
||||||
|
header and query values, HTTP methods or using custom matchers.
|
||||||
|
* URL hosts and paths can have variables with an optional regular
|
||||||
|
expression.
|
||||||
|
* Registered URLs can be built, or "reversed", which helps maintaining
|
||||||
|
references to resources.
|
||||||
|
* Routes can be used as subrouters: nested routes are only tested if the
|
||||||
|
parent route matches. This is useful to define groups of routes that
|
||||||
|
share common conditions like a host, a path prefix or other repeated
|
||||||
|
attributes. As a bonus, this optimizes request matching.
|
||||||
|
* It implements the http.Handler interface so it is compatible with the
|
||||||
|
standard http.ServeMux.
|
||||||
|
|
||||||
|
Let's start registering a couple of URL paths and handlers:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", HomeHandler)
|
||||||
|
r.HandleFunc("/products", ProductsHandler)
|
||||||
|
r.HandleFunc("/articles", ArticlesHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
Here we register three routes mapping URL paths to handlers. This is
|
||||||
|
equivalent to how http.HandleFunc() works: if an incoming request URL matches
|
||||||
|
one of the paths, the corresponding handler is called passing
|
||||||
|
(http.ResponseWriter, *http.Request) as parameters.
|
||||||
|
|
||||||
|
Paths can have variables. They are defined using the format {name} or
|
||||||
|
{name:pattern}. If a regular expression pattern is not defined, the matched
|
||||||
|
variable will be anything until the next slash. For example:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||||
|
|
||||||
|
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
|
||||||
|
|
||||||
|
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
|
||||||
|
|
||||||
|
The names are used to create a map of route variables which can be retrieved
|
||||||
|
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.
|
||||||
|
|
||||||
|
Routes can also be restricted to a domain or subdomain. Just define a host
|
||||||
|
pattern to be matched. They can also have variables:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// Only matches if domain is "www.example.com".
|
||||||
|
r.Host("www.example.com")
|
||||||
|
// Matches a dynamic subdomain.
|
||||||
|
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
|
||||||
|
There are several other matchers that can be added. To match path prefixes:
|
||||||
|
|
||||||
|
r.PathPrefix("/products/")
|
||||||
|
|
||||||
|
...or HTTP methods:
|
||||||
|
|
||||||
|
r.Methods("GET", "POST")
|
||||||
|
|
||||||
|
...or URL schemes:
|
||||||
|
|
||||||
|
r.Schemes("https")
|
||||||
|
|
||||||
|
...or header values:
|
||||||
|
|
||||||
|
r.Headers("X-Requested-With", "XMLHttpRequest")
|
||||||
|
|
||||||
|
...or query values:
|
||||||
|
|
||||||
|
r.Queries("key", "value")
|
||||||
|
|
||||||
|
...or to use a custom matcher function:
|
||||||
|
|
||||||
|
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||||
|
return r.ProtoMajor == 0
|
||||||
|
})
|
||||||
|
|
||||||
|
...and finally, it is possible to combine several matchers in a single route:
|
||||||
|
|
||||||
|
r.HandleFunc("/products", ProductsHandler).
|
||||||
|
Host("www.example.com").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("http")
|
||||||
|
|
||||||
|
Setting the same matching conditions again and again can be boring, so we have
|
||||||
|
a way to group several routes that share the same requirements.
|
||||||
|
We call it "subrouting".
|
||||||
|
|
||||||
|
For example, let's say we have several URLs that should only match when the
|
||||||
|
host is "www.example.com". Create a route for that host and get a "subrouter"
|
||||||
|
from it:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("www.example.com").Subrouter()
|
||||||
|
|
||||||
|
Then register routes in the subrouter:
|
||||||
|
|
||||||
|
s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
|
||||||
|
The three URL paths we registered above will only be tested if the domain is
|
||||||
|
"www.example.com", because the subrouter is tested first. This is not
|
||||||
|
only convenient, but also optimizes request matching. You can create
|
||||||
|
subrouters combining any attribute matchers accepted by a route.
|
||||||
|
|
||||||
|
Subrouters can be used to create domain or path "namespaces": you define
|
||||||
|
subrouters in a central place and then parts of the app can register its
|
||||||
|
paths relatively to a given subrouter.
|
||||||
|
|
||||||
|
There's one more thing about subroutes. When a subrouter has a path prefix,
|
||||||
|
the inner routes use it as base for their paths:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.PathPrefix("/products").Subrouter()
|
||||||
|
// "/products/"
|
||||||
|
s.HandleFunc("/", ProductsHandler)
|
||||||
|
// "/products/{key}/"
|
||||||
|
s.HandleFunc("/{key}/", ProductHandler)
|
||||||
|
// "/products/{key}/details"
|
||||||
|
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||||
|
|
||||||
|
Note that the path provided to PathPrefix() represents a "wildcard": calling
|
||||||
|
PathPrefix("/static/").Handler(...) means that the handler will be passed any
|
||||||
|
request that matches "/static/*". This makes it easy to serve static files with mux:
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var dir string
|
||||||
|
|
||||||
|
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||||
|
flag.Parse()
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// This will serve files under http://localhost:8000/static/<filename>
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
Now let's see how to build registered URLs.
|
||||||
|
|
||||||
|
Routes can be named. All routes that define a name can have their URLs built,
|
||||||
|
or "reversed". We define a name calling Name() on a route. For example:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
To build a URL, get the route and call the URL() method, passing a sequence of
|
||||||
|
key/value pairs for the route variables. For the previous route, we would do:
|
||||||
|
|
||||||
|
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
|
||||||
|
...and the result will be a url.URL with the following path:
|
||||||
|
|
||||||
|
"/articles/technology/42"
|
||||||
|
|
||||||
|
This also works for host variables:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Host("{subdomain}.domain.com").
|
||||||
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// url.String() will be "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
|
||||||
|
All variables defined in the route are required, and their values must
|
||||||
|
conform to the corresponding patterns. These requirements guarantee that a
|
||||||
|
generated URL will always match a registered route -- the only exception is
|
||||||
|
for explicitly defined "build-only" routes which never match.
|
||||||
|
|
||||||
|
Regex support also exists for matching Headers within a route. For example, we could do:
|
||||||
|
|
||||||
|
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
||||||
|
|
||||||
|
...and the route will match both requests with a Content-Type of `application/json` as well as
|
||||||
|
`application/text`
|
||||||
|
|
||||||
|
There's also a way to build only the URL host or path for a route:
|
||||||
|
use the methods URLHost() or URLPath() instead. For the previous route,
|
||||||
|
we would do:
|
||||||
|
|
||||||
|
// "http://news.domain.com/"
|
||||||
|
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||||
|
|
||||||
|
// "/articles/technology/42"
|
||||||
|
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
||||||
|
|
||||||
|
And if you use subrouters, host and path defined separately can be built
|
||||||
|
as well:
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
s := r.Host("{subdomain}.domain.com").Subrouter()
|
||||||
|
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// "http://news.domain.com/articles/technology/42"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42")
|
||||||
|
*/
|
||||||
|
package mux
|
542
vendor/src/github.com/gorilla/mux/mux.go
vendored
Normal file
542
vendor/src/github.com/gorilla/mux/mux.go
vendored
Normal file
|
@ -0,0 +1,542 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRouter returns a new router instance.
|
||||||
|
func NewRouter() *Router {
|
||||||
|
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router registers routes to be matched and dispatches a handler.
|
||||||
|
//
|
||||||
|
// It implements the http.Handler interface, so it can be registered to serve
|
||||||
|
// requests:
|
||||||
|
//
|
||||||
|
// var router = mux.NewRouter()
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// http.Handle("/", router)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Or, for Google App Engine, register it in a init() function:
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// http.Handle("/", router)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This will send all incoming requests to the router.
|
||||||
|
type Router struct {
|
||||||
|
// Configurable Handler to be used when no route matches.
|
||||||
|
NotFoundHandler http.Handler
|
||||||
|
// Parent route, if this is a subrouter.
|
||||||
|
parent parentRoute
|
||||||
|
// Routes to be matched, in order.
|
||||||
|
routes []*Route
|
||||||
|
// Routes by name for URL building.
|
||||||
|
namedRoutes map[string]*Route
|
||||||
|
// See Router.StrictSlash(). This defines the flag for new routes.
|
||||||
|
strictSlash bool
|
||||||
|
// See Router.SkipClean(). This defines the flag for new routes.
|
||||||
|
skipClean bool
|
||||||
|
// If true, do not clear the request context after handling the request.
|
||||||
|
// This has no effect when go1.7+ is used, since the context is stored
|
||||||
|
// on the request itself.
|
||||||
|
KeepContext bool
|
||||||
|
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
||||||
|
useEncodedPath bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches registered routes against the request.
|
||||||
|
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
for _, route := range r.routes {
|
||||||
|
if route.Match(req, match) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closest match for a router (includes sub-routers)
|
||||||
|
if r.NotFoundHandler != nil {
|
||||||
|
match.Handler = r.NotFoundHandler
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP dispatches the handler registered in the matched route.
|
||||||
|
//
|
||||||
|
// When there is a match, the route variables can be retrieved calling
|
||||||
|
// mux.Vars(request).
|
||||||
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if !r.skipClean {
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
|
// Clean path to canonical form and redirect.
|
||||||
|
if p := cleanPath(path); p != path {
|
||||||
|
|
||||||
|
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
|
||||||
|
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
||||||
|
// http://code.google.com/p/go/issues/detail?id=5252
|
||||||
|
url := *req.URL
|
||||||
|
url.Path = p
|
||||||
|
p = url.String()
|
||||||
|
|
||||||
|
w.Header().Set("Location", p)
|
||||||
|
w.WriteHeader(http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var match RouteMatch
|
||||||
|
var handler http.Handler
|
||||||
|
if r.Match(req, &match) {
|
||||||
|
handler = match.Handler
|
||||||
|
req = setVars(req, match.Vars)
|
||||||
|
req = setCurrentRoute(req, match.Route)
|
||||||
|
}
|
||||||
|
if handler == nil {
|
||||||
|
handler = http.NotFoundHandler()
|
||||||
|
}
|
||||||
|
if !r.KeepContext {
|
||||||
|
defer contextClear(req)
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a route registered with the given name.
|
||||||
|
func (r *Router) Get(name string) *Route {
|
||||||
|
return r.getNamedRoutes()[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoute returns a route registered with the given name. This method
|
||||||
|
// was renamed to Get() and remains here for backwards compatibility.
|
||||||
|
func (r *Router) GetRoute(name string) *Route {
|
||||||
|
return r.getNamedRoutes()[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
||||||
|
// value is false.
|
||||||
|
//
|
||||||
|
// When true, if the route path is "/path/", accessing "/path" will redirect
|
||||||
|
// to the former and vice versa. In other words, your application will always
|
||||||
|
// see the path as specified in the route.
|
||||||
|
//
|
||||||
|
// When false, if the route path is "/path", accessing "/path/" will not match
|
||||||
|
// this route and vice versa.
|
||||||
|
//
|
||||||
|
// Special case: when a route sets a path prefix using the PathPrefix() method,
|
||||||
|
// strict slash is ignored for that route because the redirect behavior can't
|
||||||
|
// be determined from a prefix alone. However, any subrouters created from that
|
||||||
|
// route inherit the original StrictSlash setting.
|
||||||
|
func (r *Router) StrictSlash(value bool) *Router {
|
||||||
|
r.strictSlash = value
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipClean defines the path cleaning behaviour for new routes. The initial
|
||||||
|
// value is false. Users should be careful about which routes are not cleaned
|
||||||
|
//
|
||||||
|
// When true, if the route path is "/path//to", it will remain with the double
|
||||||
|
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
|
||||||
|
//
|
||||||
|
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
|
||||||
|
// become /fetch/http/xkcd.com/534
|
||||||
|
func (r *Router) SkipClean(value bool) *Router {
|
||||||
|
r.skipClean = value
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseEncodedPath tells the router to match the encoded original path
|
||||||
|
// to the routes.
|
||||||
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
|
||||||
|
// This behavior has the drawback of needing to match routes against
|
||||||
|
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
|
||||||
|
// to r.URL.Path will not affect routing when this flag is on and thus may
|
||||||
|
// induce unintended behavior.
|
||||||
|
//
|
||||||
|
// If not called, the router will match the unencoded path to the routes.
|
||||||
|
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
|
||||||
|
func (r *Router) UseEncodedPath() *Router {
|
||||||
|
r.useEncodedPath = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// parentRoute
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// getNamedRoutes returns the map where named routes are registered.
|
||||||
|
func (r *Router) getNamedRoutes() map[string]*Route {
|
||||||
|
if r.namedRoutes == nil {
|
||||||
|
if r.parent != nil {
|
||||||
|
r.namedRoutes = r.parent.getNamedRoutes()
|
||||||
|
} else {
|
||||||
|
r.namedRoutes = make(map[string]*Route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.namedRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRegexpGroup returns regexp definitions from the parent route, if any.
|
||||||
|
func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
||||||
|
if r.parent != nil {
|
||||||
|
return r.parent.getRegexpGroup()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) buildVars(m map[string]string) map[string]string {
|
||||||
|
if r.parent != nil {
|
||||||
|
m = r.parent.buildVars(m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Route factories
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// NewRoute registers an empty route.
|
||||||
|
func (r *Router) NewRoute() *Route {
|
||||||
|
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
||||||
|
r.routes = append(r.routes, route)
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path() and Route.Handler().
|
||||||
|
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
||||||
|
return r.NewRoute().Path(path).Handler(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleFunc registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path() and Route.HandlerFunc().
|
||||||
|
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
|
||||||
|
*http.Request)) *Route {
|
||||||
|
return r.NewRoute().Path(path).HandlerFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers registers a new route with a matcher for request header values.
|
||||||
|
// See Route.Headers().
|
||||||
|
func (r *Router) Headers(pairs ...string) *Route {
|
||||||
|
return r.NewRoute().Headers(pairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host registers a new route with a matcher for the URL host.
|
||||||
|
// See Route.Host().
|
||||||
|
func (r *Router) Host(tpl string) *Route {
|
||||||
|
return r.NewRoute().Host(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc registers a new route with a custom matcher function.
|
||||||
|
// See Route.MatcherFunc().
|
||||||
|
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
|
||||||
|
return r.NewRoute().MatcherFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods registers a new route with a matcher for HTTP methods.
|
||||||
|
// See Route.Methods().
|
||||||
|
func (r *Router) Methods(methods ...string) *Route {
|
||||||
|
return r.NewRoute().Methods(methods...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path registers a new route with a matcher for the URL path.
|
||||||
|
// See Route.Path().
|
||||||
|
func (r *Router) Path(tpl string) *Route {
|
||||||
|
return r.NewRoute().Path(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathPrefix registers a new route with a matcher for the URL path prefix.
|
||||||
|
// See Route.PathPrefix().
|
||||||
|
func (r *Router) PathPrefix(tpl string) *Route {
|
||||||
|
return r.NewRoute().PathPrefix(tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries registers a new route with a matcher for URL query values.
|
||||||
|
// See Route.Queries().
|
||||||
|
func (r *Router) Queries(pairs ...string) *Route {
|
||||||
|
return r.NewRoute().Queries(pairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes registers a new route with a matcher for URL schemes.
|
||||||
|
// See Route.Schemes().
|
||||||
|
func (r *Router) Schemes(schemes ...string) *Route {
|
||||||
|
return r.NewRoute().Schemes(schemes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVarsFunc registers a new route with a custom function for modifying
|
||||||
|
// route variables before building a URL.
|
||||||
|
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||||
|
return r.NewRoute().BuildVarsFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk walks the router and all its sub-routers, calling walkFn for each route
|
||||||
|
// in the tree. The routes are walked in the order they were added. Sub-routers
|
||||||
|
// are explored depth-first.
|
||||||
|
func (r *Router) Walk(walkFn WalkFunc) error {
|
||||||
|
return r.walk(walkFn, []*Route{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipRouter is used as a return value from WalkFuncs to indicate that the
|
||||||
|
// router that walk is about to descend down to should be skipped.
|
||||||
|
var SkipRouter = errors.New("skip this router")
|
||||||
|
|
||||||
|
// WalkFunc is the type of the function called for each route visited by Walk.
|
||||||
|
// At every invocation, it is given the current route, and the current router,
|
||||||
|
// and a list of ancestor routes that lead to the current route.
|
||||||
|
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
|
||||||
|
|
||||||
|
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
||||||
|
for _, t := range r.routes {
|
||||||
|
if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := walkFn(t, r, ancestors)
|
||||||
|
if err == SkipRouter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, sr := range t.matchers {
|
||||||
|
if h, ok := sr.(*Router); ok {
|
||||||
|
err := h.walk(walkFn, ancestors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if h, ok := t.handler.(*Router); ok {
|
||||||
|
ancestors = append(ancestors, t)
|
||||||
|
err := h.walk(walkFn, ancestors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ancestors = ancestors[:len(ancestors)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Context
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// RouteMatch stores information about a matched route.
|
||||||
|
type RouteMatch struct {
|
||||||
|
Route *Route
|
||||||
|
Handler http.Handler
|
||||||
|
Vars map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextKey int
|
||||||
|
|
||||||
|
const (
|
||||||
|
varsKey contextKey = iota
|
||||||
|
routeKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vars returns the route variables for the current request, if any.
|
||||||
|
func Vars(r *http.Request) map[string]string {
|
||||||
|
if rv := contextGet(r, varsKey); rv != nil {
|
||||||
|
return rv.(map[string]string)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentRoute returns the matched route for the current request, if any.
|
||||||
|
// This only works when called inside the handler of the matched route
|
||||||
|
// because the matched route is stored in the request context which is cleared
|
||||||
|
// after the handler returns, unless the KeepContext option is set on the
|
||||||
|
// Router.
|
||||||
|
func CurrentRoute(r *http.Request) *Route {
|
||||||
|
if rv := contextGet(r, routeKey); rv != nil {
|
||||||
|
return rv.(*Route)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setVars(r *http.Request, val interface{}) *http.Request {
|
||||||
|
return contextSet(r, varsKey, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
|
||||||
|
return contextSet(r, routeKey, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// getPath returns the escaped path if possible; doing what URL.EscapedPath()
|
||||||
|
// which was added in go1.5 does
|
||||||
|
func getPath(req *http.Request) string {
|
||||||
|
if req.RequestURI != "" {
|
||||||
|
// Extract the path from RequestURI (which is escaped unlike URL.Path)
|
||||||
|
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
|
||||||
|
// for < 1.5 server side workaround
|
||||||
|
// http://localhost/path/here?v=1 -> /path/here
|
||||||
|
path := req.RequestURI
|
||||||
|
path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
|
||||||
|
path = strings.TrimPrefix(path, req.URL.Host)
|
||||||
|
if i := strings.LastIndex(path, "?"); i > -1 {
|
||||||
|
path = path[:i]
|
||||||
|
}
|
||||||
|
if i := strings.LastIndex(path, "#"); i > -1 {
|
||||||
|
path = path[:i]
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return req.URL.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||||
|
// Borrowed from the net/http package.
|
||||||
|
func cleanPath(p string) string {
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
if p[0] != '/' {
|
||||||
|
p = "/" + p
|
||||||
|
}
|
||||||
|
np := path.Clean(p)
|
||||||
|
// path.Clean removes trailing slash except for root;
|
||||||
|
// put the trailing slash back if necessary.
|
||||||
|
if p[len(p)-1] == '/' && np != "/" {
|
||||||
|
np += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return np
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniqueVars returns an error if two slices contain duplicated strings.
|
||||||
|
func uniqueVars(s1, s2 []string) error {
|
||||||
|
for _, v1 := range s1 {
|
||||||
|
for _, v2 := range s2 {
|
||||||
|
if v1 == v2 {
|
||||||
|
return fmt.Errorf("mux: duplicated route variable %q", v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPairs returns the count of strings passed in, and an error if
|
||||||
|
// the count is not an even number.
|
||||||
|
func checkPairs(pairs ...string) (int, error) {
|
||||||
|
length := len(pairs)
|
||||||
|
if length%2 != 0 {
|
||||||
|
return length, fmt.Errorf(
|
||||||
|
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||||
|
}
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapFromPairsToString converts variadic string parameters to a
|
||||||
|
// string to string map.
|
||||||
|
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
|
||||||
|
length, err := checkPairs(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := make(map[string]string, length/2)
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
m[pairs[i]] = pairs[i+1]
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapFromPairsToRegex converts variadic string paramers to a
|
||||||
|
// string to regex map.
|
||||||
|
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
|
||||||
|
length, err := checkPairs(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := make(map[string]*regexp.Regexp, length/2)
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
regex, err := regexp.Compile(pairs[i+1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m[pairs[i]] = regex
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchInArray returns true if the given string value is in the array.
|
||||||
|
func matchInArray(arr []string, value string) bool {
|
||||||
|
for _, v := range arr {
|
||||||
|
if v == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchMapWithString returns true if the given key/value pairs exist in a given map.
|
||||||
|
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
|
||||||
|
for k, v := range toCheck {
|
||||||
|
// Check if key exists.
|
||||||
|
if canonicalKey {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
}
|
||||||
|
if values := toMatch[k]; values == nil {
|
||||||
|
return false
|
||||||
|
} else if v != "" {
|
||||||
|
// If value was defined as an empty string we only check that the
|
||||||
|
// key exists. Otherwise we also check for equality.
|
||||||
|
valueExists := false
|
||||||
|
for _, value := range values {
|
||||||
|
if v == value {
|
||||||
|
valueExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valueExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
|
||||||
|
// the given regex
|
||||||
|
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
|
||||||
|
for k, v := range toCheck {
|
||||||
|
// Check if key exists.
|
||||||
|
if canonicalKey {
|
||||||
|
k = http.CanonicalHeaderKey(k)
|
||||||
|
}
|
||||||
|
if values := toMatch[k]; values == nil {
|
||||||
|
return false
|
||||||
|
} else if v != nil {
|
||||||
|
// If value was defined as an empty string we only check that the
|
||||||
|
// key exists. Otherwise we also check for equality.
|
||||||
|
valueExists := false
|
||||||
|
for _, value := range values {
|
||||||
|
if v.MatchString(value) {
|
||||||
|
valueExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valueExists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
1655
vendor/src/github.com/gorilla/mux/mux_test.go
vendored
Normal file
1655
vendor/src/github.com/gorilla/mux/mux_test.go
vendored
Normal file
File diff suppressed because it is too large
Load diff
710
vendor/src/github.com/gorilla/mux/old_test.go
vendored
Normal file
710
vendor/src/github.com/gorilla/mux/old_test.go
vendored
Normal file
|
@ -0,0 +1,710 @@
|
||||||
|
// Old tests ported to Go1. This is a mess. Want to drop it one day.
|
||||||
|
|
||||||
|
// Copyright 2011 Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// ResponseRecorder
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||||
|
// records its mutations for later inspection in tests.
|
||||||
|
type ResponseRecorder struct {
|
||||||
|
Code int // the HTTP response code from WriteHeader
|
||||||
|
HeaderMap http.Header // the HTTP response headers
|
||||||
|
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||||
|
Flushed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecorder returns an initialized ResponseRecorder.
|
||||||
|
func NewRecorder() *ResponseRecorder {
|
||||||
|
return &ResponseRecorder{
|
||||||
|
HeaderMap: make(http.Header),
|
||||||
|
Body: new(bytes.Buffer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header returns the response headers.
|
||||||
|
func (rw *ResponseRecorder) Header() http.Header {
|
||||||
|
return rw.HeaderMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write always succeeds and writes to rw.Body, if not nil.
|
||||||
|
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||||
|
if rw.Body != nil {
|
||||||
|
rw.Body.Write(buf)
|
||||||
|
}
|
||||||
|
if rw.Code == 0 {
|
||||||
|
rw.Code = http.StatusOK
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader sets rw.Code.
|
||||||
|
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||||
|
rw.Code = code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush sets rw.Flushed to true.
|
||||||
|
func (rw *ResponseRecorder) Flush() {
|
||||||
|
rw.Flushed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestRouteMatchers(t *testing.T) {
|
||||||
|
var scheme, host, path, query, method string
|
||||||
|
var headers map[string]string
|
||||||
|
var resultVars map[bool]map[string]string
|
||||||
|
|
||||||
|
router := NewRouter()
|
||||||
|
router.NewRoute().Host("{var1}.google.com").
|
||||||
|
Path("/{var2:[a-z]+}/{var3:[0-9]+}").
|
||||||
|
Queries("foo", "bar").
|
||||||
|
Methods("GET").
|
||||||
|
Schemes("https").
|
||||||
|
Headers("x-requested-with", "XMLHttpRequest")
|
||||||
|
router.NewRoute().Host("www.{var4}.com").
|
||||||
|
PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
|
||||||
|
Queries("baz", "ding").
|
||||||
|
Methods("POST").
|
||||||
|
Schemes("http").
|
||||||
|
Headers("Content-Type", "application/json")
|
||||||
|
|
||||||
|
reset := func() {
|
||||||
|
// Everything match.
|
||||||
|
scheme = "https"
|
||||||
|
host = "www.google.com"
|
||||||
|
path = "/product/42"
|
||||||
|
query = "?foo=bar"
|
||||||
|
method = "GET"
|
||||||
|
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
|
||||||
|
resultVars = map[bool]map[string]string{
|
||||||
|
true: {"var1": "www", "var2": "product", "var3": "42"},
|
||||||
|
false: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset2 := func() {
|
||||||
|
// Everything match.
|
||||||
|
scheme = "http"
|
||||||
|
host = "www.google.com"
|
||||||
|
path = "/foo/product/42/path/that/is/ignored"
|
||||||
|
query = "?baz=ding"
|
||||||
|
method = "POST"
|
||||||
|
headers = map[string]string{"Content-Type": "application/json"}
|
||||||
|
resultVars = map[bool]map[string]string{
|
||||||
|
true: {"var4": "google", "var5": "product", "var6": "42"},
|
||||||
|
false: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match := func(shouldMatch bool) {
|
||||||
|
url := scheme + "://" + host + path + query
|
||||||
|
request, _ := http.NewRequest(method, url, nil)
|
||||||
|
for key, value := range headers {
|
||||||
|
request.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
matched := router.Match(request, &routeMatch)
|
||||||
|
if matched != shouldMatch {
|
||||||
|
// Need better messages. :)
|
||||||
|
if matched {
|
||||||
|
t.Errorf("Should match.")
|
||||||
|
} else {
|
||||||
|
t.Errorf("Should not match.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
currentRoute := routeMatch.Route
|
||||||
|
if currentRoute == nil {
|
||||||
|
t.Errorf("Expected a current route.")
|
||||||
|
}
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
expectedVars := resultVars[shouldMatch]
|
||||||
|
if len(vars) != len(expectedVars) {
|
||||||
|
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
||||||
|
}
|
||||||
|
for name, value := range vars {
|
||||||
|
if expectedVars[name] != value {
|
||||||
|
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1st route --------------------------------------------------------------
|
||||||
|
|
||||||
|
// Everything match.
|
||||||
|
reset()
|
||||||
|
match(true)
|
||||||
|
|
||||||
|
// Scheme doesn't match.
|
||||||
|
reset()
|
||||||
|
scheme = "http"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Host doesn't match.
|
||||||
|
reset()
|
||||||
|
host = "www.mygoogle.com"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Path doesn't match.
|
||||||
|
reset()
|
||||||
|
path = "/product/notdigits"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Query doesn't match.
|
||||||
|
reset()
|
||||||
|
query = "?foo=baz"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Method doesn't match.
|
||||||
|
reset()
|
||||||
|
method = "POST"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Header doesn't match.
|
||||||
|
reset()
|
||||||
|
headers = map[string]string{}
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Everything match, again.
|
||||||
|
reset()
|
||||||
|
match(true)
|
||||||
|
|
||||||
|
// 2nd route --------------------------------------------------------------
|
||||||
|
|
||||||
|
// Everything match.
|
||||||
|
reset2()
|
||||||
|
match(true)
|
||||||
|
|
||||||
|
// Scheme doesn't match.
|
||||||
|
reset2()
|
||||||
|
scheme = "https"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Host doesn't match.
|
||||||
|
reset2()
|
||||||
|
host = "sub.google.com"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Path doesn't match.
|
||||||
|
reset2()
|
||||||
|
path = "/bar/product/42"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Query doesn't match.
|
||||||
|
reset2()
|
||||||
|
query = "?foo=baz"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Method doesn't match.
|
||||||
|
reset2()
|
||||||
|
method = "GET"
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Header doesn't match.
|
||||||
|
reset2()
|
||||||
|
headers = map[string]string{}
|
||||||
|
match(false)
|
||||||
|
|
||||||
|
// Everything match, again.
|
||||||
|
reset2()
|
||||||
|
match(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerMatcherTest struct {
|
||||||
|
matcher headerMatcher
|
||||||
|
headers map[string]string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerMatcherTests = []headerMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
||||||
|
headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
|
||||||
|
headers: map[string]string{"X-Requested-With": "anything"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
||||||
|
headers: map[string]string{},
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostMatcherTest struct {
|
||||||
|
matcher *Route
|
||||||
|
url string
|
||||||
|
vars map[string]string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostMatcherTests = []hostMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
||||||
|
url: "http://abc.def.ghi/",
|
||||||
|
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
||||||
|
url: "http://a.b.c/",
|
||||||
|
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodMatcherTest struct {
|
||||||
|
matcher methodMatcher
|
||||||
|
method string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var methodMatcherTests = []methodMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "GET",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "POST",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "PUT",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||||
|
method: "DELETE",
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathMatcherTest struct {
|
||||||
|
matcher *Route
|
||||||
|
url string
|
||||||
|
vars map[string]string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathMatcherTests = []pathMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
||||||
|
url: "http://localhost:8080/123/456/789",
|
||||||
|
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
||||||
|
url: "http://localhost:8080/1/2/3",
|
||||||
|
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type schemeMatcherTest struct {
|
||||||
|
matcher schemeMatcher
|
||||||
|
url string
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var schemeMatcherTests = []schemeMatcherTest{
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"http", "https"}),
|
||||||
|
url: "http://localhost:8080/",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"http", "https"}),
|
||||||
|
url: "https://localhost:8080/",
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"https"}),
|
||||||
|
url: "http://localhost:8080/",
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: schemeMatcher([]string{"http"}),
|
||||||
|
url: "https://localhost:8080/",
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type urlBuildingTest struct {
|
||||||
|
route *Route
|
||||||
|
vars []string
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlBuildingTests = []urlBuildingTest{
|
||||||
|
{
|
||||||
|
route: new(Route).Host("foo.domain.com"),
|
||||||
|
vars: []string{},
|
||||||
|
url: "http://foo.domain.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Host("{subdomain}.domain.com"),
|
||||||
|
vars: []string{"subdomain", "bar"},
|
||||||
|
url: "http://bar.domain.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Host("foo.domain.com").Path("/articles"),
|
||||||
|
vars: []string{},
|
||||||
|
url: "http://foo.domain.com/articles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Path("/articles"),
|
||||||
|
vars: []string{},
|
||||||
|
url: "/articles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
|
||||||
|
vars: []string{"category", "technology", "id", "42"},
|
||||||
|
url: "/articles/technology/42",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
|
||||||
|
vars: []string{"subdomain", "foo", "category", "technology", "id", "42"},
|
||||||
|
url: "http://foo.domain.com/articles/technology/42",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderMatcher(t *testing.T) {
|
||||||
|
for _, v := range headerMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
for key, value := range v.headers {
|
||||||
|
request.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, request.Header)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostMatcher(t *testing.T) {
|
||||||
|
for _, v := range hostMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", v.url, nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result {
|
||||||
|
if len(vars) != len(v.vars) {
|
||||||
|
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
||||||
|
}
|
||||||
|
for name, value := range vars {
|
||||||
|
if v.vars[name] != value {
|
||||||
|
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(vars) != 0 {
|
||||||
|
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethodMatcher(t *testing.T) {
|
||||||
|
for _, v := range methodMatcherTests {
|
||||||
|
request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.method)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathMatcher(t *testing.T) {
|
||||||
|
for _, v := range pathMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", v.url, nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result {
|
||||||
|
if len(vars) != len(v.vars) {
|
||||||
|
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
||||||
|
}
|
||||||
|
for name, value := range vars {
|
||||||
|
if v.vars[name] != value {
|
||||||
|
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(vars) != 0 {
|
||||||
|
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSchemeMatcher(t *testing.T) {
|
||||||
|
for _, v := range schemeMatcherTests {
|
||||||
|
request, _ := http.NewRequest("GET", v.url, nil)
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
result := v.matcher.Match(request, &routeMatch)
|
||||||
|
if result != v.result {
|
||||||
|
if v.result {
|
||||||
|
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUrlBuilding(t *testing.T) {
|
||||||
|
|
||||||
|
for _, v := range urlBuildingTests {
|
||||||
|
u, _ := v.route.URL(v.vars...)
|
||||||
|
url := u.String()
|
||||||
|
if url != v.url {
|
||||||
|
t.Errorf("expected %v, got %v", v.url, url)
|
||||||
|
/*
|
||||||
|
reversePath := ""
|
||||||
|
reverseHost := ""
|
||||||
|
if v.route.pathTemplate != nil {
|
||||||
|
reversePath = v.route.pathTemplate.Reverse
|
||||||
|
}
|
||||||
|
if v.route.hostTemplate != nil {
|
||||||
|
reverseHost = v.route.hostTemplate.Reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
||||||
|
router := NewRouter()
|
||||||
|
router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
|
||||||
|
|
||||||
|
url, _ := router.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
expected := "/articles/technology/42"
|
||||||
|
if url.String() != expected {
|
||||||
|
t.Errorf("Expected %v, got %v", expected, url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchedRouteName(t *testing.T) {
|
||||||
|
routeName := "stock"
|
||||||
|
router := NewRouter()
|
||||||
|
route := router.NewRoute().Path("/products/").Name(routeName)
|
||||||
|
|
||||||
|
url := "http://www.example.com/products/"
|
||||||
|
request, _ := http.NewRequest("GET", url, nil)
|
||||||
|
var rv RouteMatch
|
||||||
|
ok := router.Match(request, &rv)
|
||||||
|
|
||||||
|
if !ok || rv.Route != route {
|
||||||
|
t.Errorf("Expected same route, got %+v.", rv.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
retName := rv.Route.GetName()
|
||||||
|
if retName != routeName {
|
||||||
|
t.Errorf("Expected %q, got %q.", routeName, retName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubRouting(t *testing.T) {
|
||||||
|
// Example from docs.
|
||||||
|
router := NewRouter()
|
||||||
|
subrouter := router.NewRoute().Host("www.example.com").Subrouter()
|
||||||
|
route := subrouter.NewRoute().Path("/products/").Name("products")
|
||||||
|
|
||||||
|
url := "http://www.example.com/products/"
|
||||||
|
request, _ := http.NewRequest("GET", url, nil)
|
||||||
|
var rv RouteMatch
|
||||||
|
ok := router.Match(request, &rv)
|
||||||
|
|
||||||
|
if !ok || rv.Route != route {
|
||||||
|
t.Errorf("Expected same route, got %+v.", rv.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, _ := router.Get("products").URL()
|
||||||
|
builtURL := u.String()
|
||||||
|
// Yay, subroute aware of the domain when building!
|
||||||
|
if builtURL != url {
|
||||||
|
t.Errorf("Expected %q, got %q.", url, builtURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariableNames(t *testing.T) {
|
||||||
|
route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
|
||||||
|
if route.err == nil {
|
||||||
|
t.Errorf("Expected error for duplicated variable names")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedirectSlash(t *testing.T) {
|
||||||
|
var route *Route
|
||||||
|
var routeMatch RouteMatch
|
||||||
|
r := NewRouter()
|
||||||
|
|
||||||
|
r.StrictSlash(false)
|
||||||
|
route = r.NewRoute()
|
||||||
|
if route.strictSlash != false {
|
||||||
|
t.Errorf("Expected false redirectSlash.")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.StrictSlash(true)
|
||||||
|
route = r.NewRoute()
|
||||||
|
if route.strictSlash != true {
|
||||||
|
t.Errorf("Expected true redirectSlash.")
|
||||||
|
}
|
||||||
|
|
||||||
|
route = new(Route)
|
||||||
|
route.strictSlash = true
|
||||||
|
route.Path("/{arg1}/{arg2:[0-9]+}/")
|
||||||
|
request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
|
||||||
|
routeMatch = RouteMatch{}
|
||||||
|
_ = route.Match(request, &routeMatch)
|
||||||
|
vars := routeMatch.Vars
|
||||||
|
if vars["arg1"] != "foo" {
|
||||||
|
t.Errorf("Expected foo.")
|
||||||
|
}
|
||||||
|
if vars["arg2"] != "123" {
|
||||||
|
t.Errorf("Expected 123.")
|
||||||
|
}
|
||||||
|
rsp := NewRecorder()
|
||||||
|
routeMatch.Handler.ServeHTTP(rsp, request)
|
||||||
|
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
|
||||||
|
t.Errorf("Expected redirect header.")
|
||||||
|
}
|
||||||
|
|
||||||
|
route = new(Route)
|
||||||
|
route.strictSlash = true
|
||||||
|
route.Path("/{arg1}/{arg2:[0-9]+}")
|
||||||
|
request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
|
||||||
|
routeMatch = RouteMatch{}
|
||||||
|
_ = route.Match(request, &routeMatch)
|
||||||
|
vars = routeMatch.Vars
|
||||||
|
if vars["arg1"] != "foo" {
|
||||||
|
t.Errorf("Expected foo.")
|
||||||
|
}
|
||||||
|
if vars["arg2"] != "123" {
|
||||||
|
t.Errorf("Expected 123.")
|
||||||
|
}
|
||||||
|
rsp = NewRecorder()
|
||||||
|
routeMatch.Handler.ServeHTTP(rsp, request)
|
||||||
|
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
|
||||||
|
t.Errorf("Expected redirect header.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for the new regexp library, still not available in stable Go.
|
||||||
|
func TestNewRegexp(t *testing.T) {
|
||||||
|
var p *routeRegexp
|
||||||
|
var matches []string
|
||||||
|
|
||||||
|
tests := map[string]map[string][]string{
|
||||||
|
"/{foo:a{2}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/aa": {"aa"},
|
||||||
|
"/aaa": nil,
|
||||||
|
"/aaaa": nil,
|
||||||
|
},
|
||||||
|
"/{foo:a{2,}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/aa": {"aa"},
|
||||||
|
"/aaa": {"aaa"},
|
||||||
|
"/aaaa": {"aaaa"},
|
||||||
|
},
|
||||||
|
"/{foo:a{2,3}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/aa": {"aa"},
|
||||||
|
"/aaa": {"aaa"},
|
||||||
|
"/aaaa": nil,
|
||||||
|
},
|
||||||
|
"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
|
||||||
|
"/a": nil,
|
||||||
|
"/ab": nil,
|
||||||
|
"/abc": nil,
|
||||||
|
"/abcd": nil,
|
||||||
|
"/abc/ab": {"abc", "ab"},
|
||||||
|
"/abc/abc": nil,
|
||||||
|
"/abcd/ab": nil,
|
||||||
|
},
|
||||||
|
`/{foo:\w{3,}}/{bar:\d{2,}}`: {
|
||||||
|
"/a": nil,
|
||||||
|
"/ab": nil,
|
||||||
|
"/abc": nil,
|
||||||
|
"/abc/1": nil,
|
||||||
|
"/abc/12": {"abc", "12"},
|
||||||
|
"/abcd/12": {"abcd", "12"},
|
||||||
|
"/abcd/123": {"abcd", "123"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for pattern, paths := range tests {
|
||||||
|
p, _ = newRouteRegexp(pattern, false, false, false, false, false)
|
||||||
|
for path, result := range paths {
|
||||||
|
matches = p.regexp.FindStringSubmatch(path)
|
||||||
|
if result == nil {
|
||||||
|
if matches != nil {
|
||||||
|
t.Errorf("%v should not match %v.", pattern, path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(matches) != len(result)+1 {
|
||||||
|
t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
|
||||||
|
} else {
|
||||||
|
for k, v := range result {
|
||||||
|
if matches[k+1] != v {
|
||||||
|
t.Errorf("Expected %v, got %v.", v, matches[k+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
323
vendor/src/github.com/gorilla/mux/regexp.go
vendored
Normal file
323
vendor/src/github.com/gorilla/mux/regexp.go
vendored
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newRouteRegexp parses a route template and returns a routeRegexp,
|
||||||
|
// used to match a host, a path or a query string.
|
||||||
|
//
|
||||||
|
// It will extract named variables, assemble a regexp to be matched, create
|
||||||
|
// a "reverse" template to build URLs and compile regexps to validate variable
|
||||||
|
// values used in URL building.
|
||||||
|
//
|
||||||
|
// Previously we accepted only Python-like identifiers for variable
|
||||||
|
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
||||||
|
// name and pattern can't be empty, and names can't contain a colon.
|
||||||
|
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
|
||||||
|
// Check if it is well-formed.
|
||||||
|
idxs, errBraces := braceIndices(tpl)
|
||||||
|
if errBraces != nil {
|
||||||
|
return nil, errBraces
|
||||||
|
}
|
||||||
|
// Backup the original.
|
||||||
|
template := tpl
|
||||||
|
// Now let's parse it.
|
||||||
|
defaultPattern := "[^/]+"
|
||||||
|
if matchQuery {
|
||||||
|
defaultPattern = "[^?&]*"
|
||||||
|
} else if matchHost {
|
||||||
|
defaultPattern = "[^.]+"
|
||||||
|
matchPrefix = false
|
||||||
|
}
|
||||||
|
// Only match strict slash if not matching
|
||||||
|
if matchPrefix || matchHost || matchQuery {
|
||||||
|
strictSlash = false
|
||||||
|
}
|
||||||
|
// Set a flag for strictSlash.
|
||||||
|
endSlash := false
|
||||||
|
if strictSlash && strings.HasSuffix(tpl, "/") {
|
||||||
|
tpl = tpl[:len(tpl)-1]
|
||||||
|
endSlash = true
|
||||||
|
}
|
||||||
|
varsN := make([]string, len(idxs)/2)
|
||||||
|
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
||||||
|
pattern := bytes.NewBufferString("")
|
||||||
|
pattern.WriteByte('^')
|
||||||
|
reverse := bytes.NewBufferString("")
|
||||||
|
var end int
|
||||||
|
var err error
|
||||||
|
for i := 0; i < len(idxs); i += 2 {
|
||||||
|
// Set all values we are interested in.
|
||||||
|
raw := tpl[end:idxs[i]]
|
||||||
|
end = idxs[i+1]
|
||||||
|
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
|
||||||
|
name := parts[0]
|
||||||
|
patt := defaultPattern
|
||||||
|
if len(parts) == 2 {
|
||||||
|
patt = parts[1]
|
||||||
|
}
|
||||||
|
// Name or pattern can't be empty.
|
||||||
|
if name == "" || patt == "" {
|
||||||
|
return nil, fmt.Errorf("mux: missing name or pattern in %q",
|
||||||
|
tpl[idxs[i]:end])
|
||||||
|
}
|
||||||
|
// Build the regexp pattern.
|
||||||
|
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
||||||
|
|
||||||
|
// Build the reverse template.
|
||||||
|
fmt.Fprintf(reverse, "%s%%s", raw)
|
||||||
|
|
||||||
|
// Append variable name and compiled pattern.
|
||||||
|
varsN[i/2] = name
|
||||||
|
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the remaining.
|
||||||
|
raw := tpl[end:]
|
||||||
|
pattern.WriteString(regexp.QuoteMeta(raw))
|
||||||
|
if strictSlash {
|
||||||
|
pattern.WriteString("[/]?")
|
||||||
|
}
|
||||||
|
if matchQuery {
|
||||||
|
// Add the default pattern if the query value is empty
|
||||||
|
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
|
||||||
|
pattern.WriteString(defaultPattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matchPrefix {
|
||||||
|
pattern.WriteByte('$')
|
||||||
|
}
|
||||||
|
reverse.WriteString(raw)
|
||||||
|
if endSlash {
|
||||||
|
reverse.WriteByte('/')
|
||||||
|
}
|
||||||
|
// Compile full regexp.
|
||||||
|
reg, errCompile := regexp.Compile(pattern.String())
|
||||||
|
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,
|
||||||
|
matchHost: matchHost,
|
||||||
|
matchQuery: matchQuery,
|
||||||
|
strictSlash: strictSlash,
|
||||||
|
useEncodedPath: useEncodedPath,
|
||||||
|
regexp: reg,
|
||||||
|
reverse: reverse.String(),
|
||||||
|
varsN: varsN,
|
||||||
|
varsR: varsR,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// routeRegexp stores a regexp to match a host or path and information to
|
||||||
|
// collect and validate route variables.
|
||||||
|
type routeRegexp struct {
|
||||||
|
// The unmodified template.
|
||||||
|
template string
|
||||||
|
// True for host match, false for path or query string match.
|
||||||
|
matchHost bool
|
||||||
|
// True for query string match, false for path and host match.
|
||||||
|
matchQuery bool
|
||||||
|
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
|
||||||
|
strictSlash bool
|
||||||
|
// Determines whether to use encoded path from getPath function or unencoded
|
||||||
|
// req.URL.Path for path matching
|
||||||
|
useEncodedPath bool
|
||||||
|
// Expanded regexp.
|
||||||
|
regexp *regexp.Regexp
|
||||||
|
// Reverse template.
|
||||||
|
reverse string
|
||||||
|
// Variable names.
|
||||||
|
varsN []string
|
||||||
|
// Variable regexps (validators).
|
||||||
|
varsR []*regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches the regexp against the URL host or path.
|
||||||
|
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
if !r.matchHost {
|
||||||
|
if r.matchQuery {
|
||||||
|
return r.matchQueryString(req)
|
||||||
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
|
return r.regexp.MatchString(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.regexp.MatchString(getHost(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
// url builds a URL part using the given values.
|
||||||
|
func (r *routeRegexp) url(values map[string]string) (string, error) {
|
||||||
|
urlValues := make([]interface{}, len(r.varsN))
|
||||||
|
for k, v := range r.varsN {
|
||||||
|
value, ok := values[v]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("mux: missing route variable %q", v)
|
||||||
|
}
|
||||||
|
urlValues[k] = value
|
||||||
|
}
|
||||||
|
rv := fmt.Sprintf(r.reverse, urlValues...)
|
||||||
|
if !r.regexp.MatchString(rv) {
|
||||||
|
// The URL is checked against the full regexp, instead of checking
|
||||||
|
// individual variables. This is faster but to provide a good error
|
||||||
|
// message, we check individual regexps if the URL doesn't match.
|
||||||
|
for k, v := range r.varsN {
|
||||||
|
if !r.varsR[k].MatchString(values[v]) {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"mux: variable %q doesn't match, expected %q", values[v],
|
||||||
|
r.varsR[k].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getURLQuery returns a single query parameter from a request URL.
|
||||||
|
// For a URL with foo=bar&baz=ding, we return only the relevant key
|
||||||
|
// value pair for the routeRegexp.
|
||||||
|
func (r *routeRegexp) getURLQuery(req *http.Request) string {
|
||||||
|
if !r.matchQuery {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
templateKey := strings.SplitN(r.template, "=", 2)[0]
|
||||||
|
for key, vals := range req.URL.Query() {
|
||||||
|
if key == templateKey && len(vals) > 0 {
|
||||||
|
return key + "=" + vals[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
|
||||||
|
return r.regexp.MatchString(r.getURLQuery(req))
|
||||||
|
}
|
||||||
|
|
||||||
|
// braceIndices returns the first level curly brace indices from a string.
|
||||||
|
// It returns an error in case of unbalanced braces.
|
||||||
|
func braceIndices(s string) ([]int, error) {
|
||||||
|
var level, idx int
|
||||||
|
var idxs []int
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '{':
|
||||||
|
if level++; level == 1 {
|
||||||
|
idx = i
|
||||||
|
}
|
||||||
|
case '}':
|
||||||
|
if level--; level == 0 {
|
||||||
|
idxs = append(idxs, idx, i+1)
|
||||||
|
} else if level < 0 {
|
||||||
|
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if level != 0 {
|
||||||
|
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||||
|
}
|
||||||
|
return idxs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// varGroupName builds a capturing group name for the indexed variable.
|
||||||
|
func varGroupName(idx int) string {
|
||||||
|
return "v" + strconv.Itoa(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// routeRegexpGroup
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// routeRegexpGroup groups the route matchers that carry variables.
|
||||||
|
type routeRegexpGroup struct {
|
||||||
|
host *routeRegexp
|
||||||
|
path *routeRegexp
|
||||||
|
queries []*routeRegexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMatch extracts the variables from the URL once a route matches.
|
||||||
|
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||||
|
// Store host variables.
|
||||||
|
if v.host != nil {
|
||||||
|
host := getHost(req)
|
||||||
|
matches := v.host.regexp.FindStringSubmatchIndex(host)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(host, matches, v.host.varsN, m.Vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
|
// Store path variables.
|
||||||
|
if v.path != nil {
|
||||||
|
matches := v.path.regexp.FindStringSubmatchIndex(path)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(path, matches, v.path.varsN, m.Vars)
|
||||||
|
// Check if we should redirect.
|
||||||
|
if v.path.strictSlash {
|
||||||
|
p1 := strings.HasSuffix(path, "/")
|
||||||
|
p2 := strings.HasSuffix(v.path.template, "/")
|
||||||
|
if p1 != p2 {
|
||||||
|
u, _ := url.Parse(req.URL.String())
|
||||||
|
if p1 {
|
||||||
|
u.Path = u.Path[:len(u.Path)-1]
|
||||||
|
} else {
|
||||||
|
u.Path += "/"
|
||||||
|
}
|
||||||
|
m.Handler = http.RedirectHandler(u.String(), 301)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Store query string variables.
|
||||||
|
for _, q := range v.queries {
|
||||||
|
queryURL := q.getURLQuery(req)
|
||||||
|
matches := q.regexp.FindStringSubmatchIndex(queryURL)
|
||||||
|
if len(matches) > 0 {
|
||||||
|
extractVars(queryURL, matches, q.varsN, m.Vars)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHost tries its best to return the request host.
|
||||||
|
func getHost(r *http.Request) string {
|
||||||
|
if r.URL.IsAbs() {
|
||||||
|
return r.URL.Host
|
||||||
|
}
|
||||||
|
host := r.Host
|
||||||
|
// Slice off any port information.
|
||||||
|
if i := strings.Index(host, ":"); i != -1 {
|
||||||
|
host = host[:i]
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
||||||
|
for i, name := range names {
|
||||||
|
output[name] = input[matches[2*i+2]:matches[2*i+3]]
|
||||||
|
}
|
||||||
|
}
|
636
vendor/src/github.com/gorilla/mux/route.go
vendored
Normal file
636
vendor/src/github.com/gorilla/mux/route.go
vendored
Normal file
|
@ -0,0 +1,636 @@
|
||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Route stores information to match a request and build URLs.
|
||||||
|
type Route struct {
|
||||||
|
// Parent where the route was registered (a Router).
|
||||||
|
parent parentRoute
|
||||||
|
// Request handler for the route.
|
||||||
|
handler http.Handler
|
||||||
|
// List of matchers.
|
||||||
|
matchers []matcher
|
||||||
|
// Manager for the variables from host and path.
|
||||||
|
regexp *routeRegexpGroup
|
||||||
|
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||||
|
// redirect to the former and vice versa.
|
||||||
|
strictSlash bool
|
||||||
|
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
||||||
|
// will not redirect
|
||||||
|
skipClean bool
|
||||||
|
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
||||||
|
useEncodedPath bool
|
||||||
|
// If true, this route never matches: it is only used to build URLs.
|
||||||
|
buildOnly bool
|
||||||
|
// The name used to build URLs.
|
||||||
|
name string
|
||||||
|
// Error resulted from building a route.
|
||||||
|
err error
|
||||||
|
|
||||||
|
buildVarsFunc BuildVarsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) SkipClean() bool {
|
||||||
|
return r.skipClean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches the route against the request.
|
||||||
|
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
|
if r.buildOnly || r.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Match everything.
|
||||||
|
for _, m := range r.matchers {
|
||||||
|
if matched := m.Match(req, match); !matched {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Yay, we have a match. Let's collect some info about it.
|
||||||
|
if match.Route == nil {
|
||||||
|
match.Route = r
|
||||||
|
}
|
||||||
|
if match.Handler == nil {
|
||||||
|
match.Handler = r.handler
|
||||||
|
}
|
||||||
|
if match.Vars == nil {
|
||||||
|
match.Vars = make(map[string]string)
|
||||||
|
}
|
||||||
|
// Set variables.
|
||||||
|
if r.regexp != nil {
|
||||||
|
r.regexp.setMatch(req, match, r)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Route attributes
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GetError returns an error resulted from building the route, if any.
|
||||||
|
func (r *Route) GetError() error {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildOnly sets the route to never match: it is only used to build URLs.
|
||||||
|
func (r *Route) BuildOnly() *Route {
|
||||||
|
r.buildOnly = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Handler sets a handler for the route.
|
||||||
|
func (r *Route) Handler(handler http.Handler) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
r.handler = handler
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc sets a handler function for the route.
|
||||||
|
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
|
||||||
|
return r.Handler(http.HandlerFunc(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandler returns the handler for the route, if any.
|
||||||
|
func (r *Route) GetHandler() http.Handler {
|
||||||
|
return r.handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Name sets the name for the route, used to build URLs.
|
||||||
|
// If the name was registered already it will be overwritten.
|
||||||
|
func (r *Route) Name(name string) *Route {
|
||||||
|
if r.name != "" {
|
||||||
|
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
||||||
|
r.name, name)
|
||||||
|
}
|
||||||
|
if r.err == nil {
|
||||||
|
r.name = name
|
||||||
|
r.getNamedRoutes()[name] = r
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetName returns the name for the route, if any.
|
||||||
|
func (r *Route) GetName() string {
|
||||||
|
return r.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Matchers
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// matcher types try to match a request.
|
||||||
|
type matcher interface {
|
||||||
|
Match(*http.Request, *RouteMatch) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// addMatcher adds a matcher to the route.
|
||||||
|
func (r *Route) addMatcher(m matcher) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
r.matchers = append(r.matchers, m)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRegexpMatcher adds a host or path matcher and builder to a route.
|
||||||
|
func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
|
||||||
|
if r.err != nil {
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
r.regexp = r.getRegexpGroup()
|
||||||
|
if !matchHost && !matchQuery {
|
||||||
|
if tpl == "/" && (len(tpl) == 0 || tpl[0] != '/') {
|
||||||
|
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||||
|
}
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, q := range r.regexp.queries {
|
||||||
|
if err = uniqueVars(rr.varsN, q.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matchHost {
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.regexp.host = rr
|
||||||
|
} else {
|
||||||
|
if r.regexp.host != nil {
|
||||||
|
if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matchQuery {
|
||||||
|
r.regexp.queries = append(r.regexp.queries, rr)
|
||||||
|
} else {
|
||||||
|
r.regexp.path = rr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.addMatcher(rr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// headerMatcher matches the request against header values.
|
||||||
|
type headerMatcher map[string]string
|
||||||
|
|
||||||
|
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchMapWithString(m, r.Header, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers adds a matcher for request header values.
|
||||||
|
// It accepts a sequence of key/value pairs to be matched. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Headers("Content-Type", "application/json",
|
||||||
|
// "X-Requested-With", "XMLHttpRequest")
|
||||||
|
//
|
||||||
|
// The above route will only match if both request header values match.
|
||||||
|
// If the value is an empty string, it will match any value if the key is set.
|
||||||
|
func (r *Route) Headers(pairs ...string) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
var headers map[string]string
|
||||||
|
headers, r.err = mapFromPairsToString(pairs...)
|
||||||
|
return r.addMatcher(headerMatcher(headers))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerRegexMatcher matches the request against the route given a regex for the header
|
||||||
|
type headerRegexMatcher map[string]*regexp.Regexp
|
||||||
|
|
||||||
|
func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchMapWithRegex(m, r.Header, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
|
||||||
|
// support. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HeadersRegexp("Content-Type", "application/(text|json)",
|
||||||
|
// "X-Requested-With", "XMLHttpRequest")
|
||||||
|
//
|
||||||
|
// The above route will only match if both the request header matches both regular expressions.
|
||||||
|
// It the value is an empty string, it will match any value if the key is set.
|
||||||
|
func (r *Route) HeadersRegexp(pairs ...string) *Route {
|
||||||
|
if r.err == nil {
|
||||||
|
var headers map[string]*regexp.Regexp
|
||||||
|
headers, r.err = mapFromPairsToRegex(pairs...)
|
||||||
|
return r.addMatcher(headerRegexMatcher(headers))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Host adds a matcher for the URL host.
|
||||||
|
// It accepts a template with zero or more URL variables enclosed by {}.
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next dot.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Host("www.example.com")
|
||||||
|
// r.Host("{subdomain}.domain.com")
|
||||||
|
// r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
//
|
||||||
|
// Variable names must be unique in a given route. They can be retrieved
|
||||||
|
// calling mux.Vars(request).
|
||||||
|
func (r *Route) Host(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, true, false, false)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc ----------------------------------------------------------------
|
||||||
|
|
||||||
|
// MatcherFunc is the function signature used by custom matchers.
|
||||||
|
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
||||||
|
|
||||||
|
// Match returns the match for a given request.
|
||||||
|
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return m(r, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatcherFunc adds a custom function to be used as request matcher.
|
||||||
|
func (r *Route) MatcherFunc(f MatcherFunc) *Route {
|
||||||
|
return r.addMatcher(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// methodMatcher matches the request against HTTP methods.
|
||||||
|
type methodMatcher []string
|
||||||
|
|
||||||
|
func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchInArray(m, r.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods adds a matcher for HTTP methods.
|
||||||
|
// It accepts a sequence of one or more methods to be matched, e.g.:
|
||||||
|
// "GET", "POST", "PUT".
|
||||||
|
func (r *Route) Methods(methods ...string) *Route {
|
||||||
|
for k, v := range methods {
|
||||||
|
methods[k] = strings.ToUpper(v)
|
||||||
|
}
|
||||||
|
return r.addMatcher(methodMatcher(methods))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Path adds a matcher for the URL path.
|
||||||
|
// It accepts a template with zero or more URL variables enclosed by {}. The
|
||||||
|
// template must start with a "/".
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next slash.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Path("/products/").Handler(ProductsHandler)
|
||||||
|
// r.Path("/products/{key}").Handler(ProductsHandler)
|
||||||
|
// r.Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
// Handler(ArticleHandler)
|
||||||
|
//
|
||||||
|
// Variable names must be unique in a given route. They can be retrieved
|
||||||
|
// calling mux.Vars(request).
|
||||||
|
func (r *Route) Path(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, false, false, false)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathPrefix -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// PathPrefix adds a matcher for the URL path prefix. This matches if the given
|
||||||
|
// template is a prefix of the full URL path. See Route.Path() for details on
|
||||||
|
// the tpl argument.
|
||||||
|
//
|
||||||
|
// Note that it does not treat slashes specially ("/foobar/" will be matched by
|
||||||
|
// the prefix "/foo") so you may want to use a trailing slash here.
|
||||||
|
//
|
||||||
|
// Also note that the setting of Router.StrictSlash() has no effect on routes
|
||||||
|
// with a PathPrefix matcher.
|
||||||
|
func (r *Route) PathPrefix(tpl string) *Route {
|
||||||
|
r.err = r.addRegexpMatcher(tpl, false, true, false)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Queries adds a matcher for URL query values.
|
||||||
|
// It accepts a sequence of key/value pairs. Values may define variables.
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
|
||||||
|
//
|
||||||
|
// The above route will only match if the URL contains the defined queries
|
||||||
|
// values, e.g.: ?foo=bar&id=42.
|
||||||
|
//
|
||||||
|
// It the value is an empty string, it will match any value if the key is set.
|
||||||
|
//
|
||||||
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
|
//
|
||||||
|
// - {name} matches anything until the next slash.
|
||||||
|
//
|
||||||
|
// - {name:pattern} matches the given regexp pattern.
|
||||||
|
func (r *Route) Queries(pairs ...string) *Route {
|
||||||
|
length := len(pairs)
|
||||||
|
if length%2 != 0 {
|
||||||
|
r.err = fmt.Errorf(
|
||||||
|
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i := 0; i < length; i += 2 {
|
||||||
|
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// schemeMatcher matches the request against URL schemes.
|
||||||
|
type schemeMatcher []string
|
||||||
|
|
||||||
|
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
|
return matchInArray(m, r.URL.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schemes adds a matcher for URL schemes.
|
||||||
|
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
|
||||||
|
func (r *Route) Schemes(schemes ...string) *Route {
|
||||||
|
for k, v := range schemes {
|
||||||
|
schemes[k] = strings.ToLower(v)
|
||||||
|
}
|
||||||
|
return r.addMatcher(schemeMatcher(schemes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildVarsFunc --------------------------------------------------------------
|
||||||
|
|
||||||
|
// BuildVarsFunc is the function signature used by custom build variable
|
||||||
|
// functions (which can modify route variables before a route's URL is built).
|
||||||
|
type BuildVarsFunc func(map[string]string) map[string]string
|
||||||
|
|
||||||
|
// BuildVarsFunc adds a custom function to be used to modify build variables
|
||||||
|
// before a route's URL is built.
|
||||||
|
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
||||||
|
r.buildVarsFunc = f
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subrouter ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Subrouter creates a subrouter for the route.
|
||||||
|
//
|
||||||
|
// It will test the inner routes only if the parent route matched. For example:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// s := r.Host("www.example.com").Subrouter()
|
||||||
|
// s.HandleFunc("/products/", ProductsHandler)
|
||||||
|
// s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
|
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
//
|
||||||
|
// Here, the routes registered in the subrouter won't be tested if the host
|
||||||
|
// doesn't match.
|
||||||
|
func (r *Route) Subrouter() *Router {
|
||||||
|
router := &Router{parent: r, strictSlash: r.strictSlash}
|
||||||
|
r.addMatcher(router)
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// URL building
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// URL builds a URL for the route.
|
||||||
|
//
|
||||||
|
// It accepts a sequence of key/value pairs for the route variables. For
|
||||||
|
// example, given this route:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
// Name("article")
|
||||||
|
//
|
||||||
|
// ...a URL for it can be built using:
|
||||||
|
//
|
||||||
|
// url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||||
|
//
|
||||||
|
// ...which will return an url.URL with the following path:
|
||||||
|
//
|
||||||
|
// "/articles/technology/42"
|
||||||
|
//
|
||||||
|
// This also works for host variables:
|
||||||
|
//
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
// r.Host("{subdomain}.domain.com").
|
||||||
|
// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||||
|
// Name("article")
|
||||||
|
//
|
||||||
|
// // url.String() will be "http://news.domain.com/articles/technology/42"
|
||||||
|
// url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
// "category", "technology",
|
||||||
|
// "id", "42")
|
||||||
|
//
|
||||||
|
// All variables defined in the route are required, and their values must
|
||||||
|
// conform to the corresponding patterns.
|
||||||
|
func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a host or path")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.regexp.path != nil {
|
||||||
|
if path, err = r.regexp.path.url(values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: scheme,
|
||||||
|
Host: host,
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLHost builds the host part of the URL for a route. See Route.URL().
|
||||||
|
//
|
||||||
|
// The route must have a host defined.
|
||||||
|
func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.host == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a host")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host, err := r.regexp.host.url(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: host,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLPath builds the path part of the URL for a route. See Route.URL().
|
||||||
|
//
|
||||||
|
// The route must have a path defined.
|
||||||
|
func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.path == nil {
|
||||||
|
return nil, errors.New("mux: route doesn't have a path")
|
||||||
|
}
|
||||||
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
path, err := r.regexp.path.url(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &url.URL{
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPathTemplate returns the template used to build the
|
||||||
|
// route match.
|
||||||
|
// 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) GetPathTemplate() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.path == nil {
|
||||||
|
return "", errors.New("mux: route doesn't have a path")
|
||||||
|
}
|
||||||
|
return r.regexp.path.template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostTemplate returns the template used to build the
|
||||||
|
// route match.
|
||||||
|
// 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 host.
|
||||||
|
func (r *Route) GetHostTemplate() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.host == nil {
|
||||||
|
return "", errors.New("mux: route doesn't have a host")
|
||||||
|
}
|
||||||
|
return r.regexp.host.template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareVars converts the route variable pairs into a map. If the route has a
|
||||||
|
// BuildVarsFunc, it is invoked.
|
||||||
|
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
||||||
|
m, err := mapFromPairsToString(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return r.buildVars(m), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) buildVars(m map[string]string) map[string]string {
|
||||||
|
if r.parent != nil {
|
||||||
|
m = r.parent.buildVars(m)
|
||||||
|
}
|
||||||
|
if r.buildVarsFunc != nil {
|
||||||
|
m = r.buildVarsFunc(m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// parentRoute
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// parentRoute allows routes to know about parent host and path definitions.
|
||||||
|
type parentRoute interface {
|
||||||
|
getNamedRoutes() map[string]*Route
|
||||||
|
getRegexpGroup() *routeRegexpGroup
|
||||||
|
buildVars(map[string]string) map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNamedRoutes returns the map where named routes are registered.
|
||||||
|
func (r *Route) getNamedRoutes() map[string]*Route {
|
||||||
|
if r.parent == nil {
|
||||||
|
// During tests router is not always set.
|
||||||
|
r.parent = NewRouter()
|
||||||
|
}
|
||||||
|
return r.parent.getNamedRoutes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRegexpGroup returns regexp definitions from this route.
|
||||||
|
func (r *Route) getRegexpGroup() *routeRegexpGroup {
|
||||||
|
if r.regexp == nil {
|
||||||
|
if r.parent == nil {
|
||||||
|
// During tests router is not always set.
|
||||||
|
r.parent = NewRouter()
|
||||||
|
}
|
||||||
|
regexp := r.parent.getRegexpGroup()
|
||||||
|
if regexp == nil {
|
||||||
|
r.regexp = new(routeRegexpGroup)
|
||||||
|
} else {
|
||||||
|
// Copy.
|
||||||
|
r.regexp = &routeRegexpGroup{
|
||||||
|
host: regexp.host,
|
||||||
|
path: regexp.path,
|
||||||
|
queries: regexp.queries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.regexp
|
||||||
|
}
|
Loading…
Reference in a new issue