mirror of
https://github.com/documize/community.git
synced 2025-07-19 05:09:42 +02:00
upgraded gorilla mux dependency, improved anon viewing
This commit is contained in:
parent
7ce3adb65e
commit
1d2109aa44
18 changed files with 2411 additions and 402 deletions
|
@ -16,7 +16,6 @@ package category
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
@ -281,20 +280,17 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// GetSummary returns number of documents and users for space categories.
|
// GetSummary returns number of documents and users for space categories.
|
||||||
func (h *Handler) GetSummary(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) GetSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Println("ctx.UserID")
|
|
||||||
|
|
||||||
method := "category.GetSummary"
|
method := "category.GetSummary"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
|
||||||
spaceID := request.Param(r, "spaceID")
|
spaceID := request.Param(r, "spaceID")
|
||||||
if len(spaceID) == 0 {
|
if len(spaceID) == 0 {
|
||||||
response.WriteMissingDataError(w, method, "spaceID")
|
response.WriteMissingDataError(w, method, "spaceID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner)
|
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
|
||||||
if !ok || !ctx.Authenticated {
|
if !ok {
|
||||||
response.WriteForbiddenError(w)
|
response.WriteForbiddenError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -400,7 +396,7 @@ func (h *Handler) GetDocumentCategoryMembership(w http.ResponseWriter, r *http.R
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.DocumentAdd, pm.DocumentEdit) {
|
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.SpaceView, pm.DocumentAdd, pm.DocumentEdit) {
|
||||||
response.WriteForbiddenError(w)
|
response.WriteForbiddenError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,10 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
|
||||||
selected.forEach((s) => {
|
selected.forEach((s) => {
|
||||||
let cats = this.set('categories', categories);
|
let cats = this.set('categories', categories);
|
||||||
let cat = categories.findBy('id', s.id);
|
let cat = categories.findBy('id', s.id);
|
||||||
cat.set('selected', true);
|
if (is.not.undefined(cat)) {
|
||||||
this.set('categories', cats);
|
cat.set('selected', true);
|
||||||
|
this.set('categories', cats);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
|
||||||
didUpdateAttrs() {
|
didUpdateAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.set('selectedDocuments', []);
|
this.set('selectedDocuments', []);
|
||||||
|
this.set('filteredDocs', []);
|
||||||
},
|
},
|
||||||
|
|
||||||
didRender() {
|
didRender() {
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
RSVP,
|
||||||
inject: { service }
|
inject: { service }
|
||||||
} = Ember;
|
} = Ember;
|
||||||
|
|
||||||
|
@ -26,6 +27,10 @@ export default Ember.Service.extend({
|
||||||
getUserPins() {
|
getUserPins() {
|
||||||
let userId = this.get('session.user.id');
|
let userId = this.get('session.user.id');
|
||||||
|
|
||||||
|
if (!this.get('session.authenticated')) {
|
||||||
|
return new RSVP.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
return this.get('ajax').request(`pin/${userId}`, {
|
return this.get('ajax').request(`pin/${userId}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
|
|
@ -20,11 +20,12 @@
|
||||||
{{#if permissions.documentEdit}}
|
{{#if permissions.documentEdit}}
|
||||||
<li class="item">{{#link-to 'document.history'}}History{{/link-to}}</li>
|
<li class="item">{{#link-to 'document.history'}}History{{/link-to}}</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<li class="divider"/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if permissions.documentTemplate}}
|
{{#if permissions.documentTemplate}}
|
||||||
<li class="item" id="save-template-button">Template</li>
|
<li class="item" id="save-template-button">Template</li>
|
||||||
|
<li class="divider"/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<li class="divider"/>
|
|
||||||
<li class="item" id="print-document-button" {{action 'onPrintDocument'}}>Print</li>
|
<li class="item" id="print-document-button" {{action 'onPrintDocument'}}>Print</li>
|
||||||
{{#if permissions.documentDelete}}
|
{{#if permissions.documentDelete}}
|
||||||
<li class="item danger" id="delete-document-button">Delete</li>
|
<li class="item danger" id="delete-document-button">Delete</li>
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<div class="round-button button-gray" id="space-unpin-button" data-tooltip="Remove favorite" data-tooltip-position="top center" {{action 'onUnpin'}}>
|
<div class="round-button button-gray" id="space-unpin-button" data-tooltip="Remove favorite" data-tooltip-position="top center" {{action 'onUnpin'}}>
|
||||||
<i class="material-icons">favorite</i>
|
<i class="material-icons">favorite</i>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else if session.authenticated}}
|
||||||
<div class="round-button button-gray" id="space-pin-button" data-tooltip="Mark favorite" data-tooltip-position="top center">
|
<div class="round-button button-gray" id="space-pin-button" data-tooltip="Mark favorite" data-tooltip-position="top center">
|
||||||
<i class="material-icons">favorite_border</i>
|
<i class="material-icons">favorite_border</i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,15 +74,17 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#unless pinState.isPinned}}
|
{{#if session.authenticated}}
|
||||||
{{#dropdown-dialog target="space-pin-button" position="bottom right" button="Pin" color="flat-green" onAction=(action 'onPin') focusOn="pin-space-name"}}
|
{{#unless pinState.isPinned}}
|
||||||
<div class="input-control">
|
{{#dropdown-dialog target="space-pin-button" position="bottom right" button="Pin" color="flat-green" onAction=(action 'onPin') focusOn="pin-space-name"}}
|
||||||
<label>Favorite Space</label>
|
<div class="input-control">
|
||||||
<div class="tip">Provide short name</div>
|
<label>Favorite Space</label>
|
||||||
{{input type='text' id="pin-space-name" value=pinState.newName}}
|
<div class="tip">Provide short name</div>
|
||||||
</div>
|
{{input type='text' id="pin-space-name" value=pinState.newName}}
|
||||||
{{/dropdown-dialog}}
|
</div>
|
||||||
{{/unless}}
|
{{/dropdown-dialog}}
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if permissions.spaceOwner}}
|
{{#if permissions.spaceOwner}}
|
||||||
{{#dropdown-dialog target="space-delete-button" position="bottom right" button="Delete" color="flat-red" onAction=(action 'deleteSpace')}}
|
{{#dropdown-dialog target="space-delete-button" position="bottom right" button="Delete" color="flat-red" onAction=(action 'deleteSpace')}}
|
||||||
|
|
392
vendor/github.com/gorilla/mux/README.md
generated
vendored
392
vendor/github.com/gorilla/mux/README.md
generated
vendored
|
@ -1,7 +1,391 @@
|
||||||
mux
|
gorilla/mux
|
||||||
===
|
===
|
||||||
[](https://travis-ci.org/gorilla/mux)
|
[](https://godoc.org/github.com/gorilla/mux)
|
||||||
|
[](https://travis-ci.org/gorilla/mux)
|
||||||
|
[](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
||||||
|
|
||||||
gorilla/mux is a powerful URL router and dispatcher.
|

|
||||||
|
|
||||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux
|
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, paths and query values 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)
|
||||||
|
* [Static Files](#static-files)
|
||||||
|
* [Registered URLs](#registered-urls)
|
||||||
|
* [Walking Routes](#walking-routes)
|
||||||
|
* [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")
|
||||||
|
```
|
||||||
|
|
||||||
|
Routes are tested in the order they were added to the router. If two routes match, the first one wins:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/specific", specificHandler)
|
||||||
|
r.PathPrefix("/").Handler(catchAllHandler)
|
||||||
|
```
|
||||||
|
|
||||||
|
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"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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).Methods("POST")
|
||||||
|
r.HandleFunc("/articles", handler).Methods("GET")
|
||||||
|
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
|
||||||
|
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||||
|
t, err := route.GetPathTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// p will contain regular expression is compatible with regular expression in Perl, Python, and other languages.
|
||||||
|
// for instance the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'
|
||||||
|
p, err := route.GetPathRegexp()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := route.GetMethods()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(strings.Join(m, ","), t, p)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
http.Handle("/", r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Files
|
||||||
|
|
||||||
|
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
|
||||||
|
`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 and query value variables:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Host("{subdomain}.domain.com").
|
||||||
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
Queries("filter", "{filter}").
|
||||||
|
HandlerFunc(ArticleHandler).
|
||||||
|
Name("article")
|
||||||
|
|
||||||
|
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
||||||
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
|
"category", "technology",
|
||||||
|
"id", "42",
|
||||||
|
"filter", "gorilla")
|
||||||
|
```
|
||||||
|
|
||||||
|
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")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Walking Routes
|
||||||
|
|
||||||
|
The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
|
||||||
|
the following prints all of the registered routes:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", handler)
|
||||||
|
r.HandleFunc("/products", handler).Methods("POST")
|
||||||
|
r.HandleFunc("/articles", handler).Methods("GET")
|
||||||
|
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
|
||||||
|
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
||||||
|
t, err := route.GetPathTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// p will contain a regular expression that is compatible with regular expressions in Perl, Python, and other languages.
|
||||||
|
// For example, the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'.
|
||||||
|
p, err := route.GetPathRegexp()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := route.GetMethods()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(strings.Join(m, ","), t, p)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
28
vendor/github.com/gorilla/mux/bench_test.go
generated
vendored
28
vendor/github.com/gorilla/mux/bench_test.go
generated
vendored
|
@ -6,6 +6,7 @@ package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,3 +20,30 @@ func BenchmarkMux(b *testing.B) {
|
||||||
router.ServeHTTP(nil, request)
|
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/github.com/gorilla/mux/context_gorilla.go
generated
vendored
Normal file
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
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/github.com/gorilla/mux/context_gorilla_test.go
generated
vendored
Normal file
40
vendor/github.com/gorilla/mux/context_gorilla_test.go
generated
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/github.com/gorilla/mux/context_native.go
generated
vendored
Normal file
24
vendor/github.com/gorilla/mux/context_native.go
generated
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/github.com/gorilla/mux/context_native_test.go
generated
vendored
Normal file
32
vendor/github.com/gorilla/mux/context_native_test.go
generated
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)
|
||||||
|
}
|
75
vendor/github.com/gorilla/mux/doc.go
generated
vendored
75
vendor/github.com/gorilla/mux/doc.go
generated
vendored
|
@ -3,7 +3,7 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Package gorilla/mux implements a request router and dispatcher.
|
Package mux implements a request router and dispatcher.
|
||||||
|
|
||||||
The name mux stands for "HTTP request multiplexer". Like the standard
|
The name mux stands for "HTTP request multiplexer". Like the standard
|
||||||
http.ServeMux, mux.Router matches incoming requests against a list of
|
http.ServeMux, mux.Router matches incoming requests against a list of
|
||||||
|
@ -12,8 +12,8 @@ or other conditions. The main features are:
|
||||||
|
|
||||||
* Requests can be matched based on URL host, path, path prefix, schemes,
|
* Requests can be matched based on URL host, path, path prefix, schemes,
|
||||||
header and query values, HTTP methods or using custom matchers.
|
header and query values, HTTP methods or using custom matchers.
|
||||||
* URL hosts and paths can have variables with an optional regular
|
* URL hosts, paths and query values can have variables with an optional
|
||||||
expression.
|
regular expression.
|
||||||
* Registered URLs can be built, or "reversed", which helps maintaining
|
* Registered URLs can be built, or "reversed", which helps maintaining
|
||||||
references to resources.
|
references to resources.
|
||||||
* Routes can be used as subrouters: nested routes are only tested if the
|
* Routes can be used as subrouters: nested routes are only tested if the
|
||||||
|
@ -47,12 +47,21 @@ variable will be anything until the next slash. For example:
|
||||||
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
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
|
The names are used to create a map of route variables which can be retrieved
|
||||||
calling mux.Vars():
|
calling mux.Vars():
|
||||||
|
|
||||||
vars := mux.Vars(request)
|
vars := mux.Vars(request)
|
||||||
category := vars["category"]
|
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
|
And this is all you need to know about the basic usage. More advanced options
|
||||||
are explained below.
|
are explained below.
|
||||||
|
|
||||||
|
@ -60,8 +69,8 @@ Routes can also be restricted to a domain or subdomain. Just define a host
|
||||||
pattern to be matched. They can also have variables:
|
pattern to be matched. They can also have variables:
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
// Only matches if domain is "www.domain.com".
|
// Only matches if domain is "www.example.com".
|
||||||
r.Host("www.domain.com")
|
r.Host("www.example.com")
|
||||||
// Matches a dynamic subdomain.
|
// Matches a dynamic subdomain.
|
||||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
|
|
||||||
|
@ -89,12 +98,12 @@ There are several other matchers that can be added. To match path prefixes:
|
||||||
|
|
||||||
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||||
return r.ProtoMajor == 0
|
return r.ProtoMajor == 0
|
||||||
})
|
})
|
||||||
|
|
||||||
...and finally, it is possible to combine several matchers in a single route:
|
...and finally, it is possible to combine several matchers in a single route:
|
||||||
|
|
||||||
r.HandleFunc("/products", ProductsHandler).
|
r.HandleFunc("/products", ProductsHandler).
|
||||||
Host("www.domain.com").
|
Host("www.example.com").
|
||||||
Methods("GET").
|
Methods("GET").
|
||||||
Schemes("http")
|
Schemes("http")
|
||||||
|
|
||||||
|
@ -103,11 +112,11 @@ a way to group several routes that share the same requirements.
|
||||||
We call it "subrouting".
|
We call it "subrouting".
|
||||||
|
|
||||||
For example, let's say we have several URLs that should only match when the
|
For example, let's say we have several URLs that should only match when the
|
||||||
host is "www.domain.com". Create a route for that host and get a "subrouter"
|
host is "www.example.com". Create a route for that host and get a "subrouter"
|
||||||
from it:
|
from it:
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
s := r.Host("www.domain.com").Subrouter()
|
s := r.Host("www.example.com").Subrouter()
|
||||||
|
|
||||||
Then register routes in the subrouter:
|
Then register routes in the subrouter:
|
||||||
|
|
||||||
|
@ -116,7 +125,7 @@ Then register routes in the subrouter:
|
||||||
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
|
||||||
The three URL paths we registered above will only be tested if the domain is
|
The three URL paths we registered above will only be tested if the domain is
|
||||||
"www.domain.com", because the subrouter is tested first. This is not
|
"www.example.com", because the subrouter is tested first. This is not
|
||||||
only convenient, but also optimizes request matching. You can create
|
only convenient, but also optimizes request matching. You can create
|
||||||
subrouters combining any attribute matchers accepted by a route.
|
subrouters combining any attribute matchers accepted by a route.
|
||||||
|
|
||||||
|
@ -136,6 +145,31 @@ the inner routes use it as base for their paths:
|
||||||
// "/products/{key}/details"
|
// "/products/{key}/details"
|
||||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
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.
|
Now let's see how to build registered URLs.
|
||||||
|
|
||||||
Routes can be named. All routes that define a name can have their URLs built,
|
Routes can be named. All routes that define a name can have their URLs built,
|
||||||
|
@ -154,24 +188,33 @@ key/value pairs for the route variables. For the previous route, we would do:
|
||||||
|
|
||||||
"/articles/technology/42"
|
"/articles/technology/42"
|
||||||
|
|
||||||
This also works for host variables:
|
This also works for host and query value variables:
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.Host("{subdomain}.domain.com").
|
r.Host("{subdomain}.domain.com").
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
Path("/articles/{category}/{id:[0-9]+}").
|
||||||
|
Queries("filter", "{filter}").
|
||||||
HandlerFunc(ArticleHandler).
|
HandlerFunc(ArticleHandler).
|
||||||
Name("article")
|
Name("article")
|
||||||
|
|
||||||
// url.String() will be "http://news.domain.com/articles/technology/42"
|
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
"category", "technology",
|
"category", "technology",
|
||||||
"id", "42")
|
"id", "42",
|
||||||
|
"filter", "gorilla")
|
||||||
|
|
||||||
All variables defined in the route are required, and their values must
|
All variables defined in the route are required, and their values must
|
||||||
conform to the corresponding patterns. These requirements guarantee that a
|
conform to the corresponding patterns. These requirements guarantee that a
|
||||||
generated URL will always match a registered route -- the only exception is
|
generated URL will always match a registered route -- the only exception is
|
||||||
for explicitly defined "build-only" routes which never match.
|
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:
|
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,
|
use the methods URLHost() or URLPath() instead. For the previous route,
|
||||||
we would do:
|
we would do:
|
||||||
|
@ -193,7 +236,7 @@ as well:
|
||||||
|
|
||||||
// "http://news.domain.com/articles/technology/42"
|
// "http://news.domain.com/articles/technology/42"
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
url, err := r.Get("article").URL("subdomain", "news",
|
||||||
"category", "technology",
|
"category", "technology",
|
||||||
"id", "42")
|
"id", "42")
|
||||||
*/
|
*/
|
||||||
package mux
|
package mux
|
||||||
|
|
293
vendor/github.com/gorilla/mux/mux.go
generated
vendored
293
vendor/github.com/gorilla/mux/mux.go
generated
vendored
|
@ -5,11 +5,16 @@
|
||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
"github.com/gorilla/context"
|
var (
|
||||||
|
ErrMethodMismatch = errors.New("method is not allowed")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewRouter returns a new router instance.
|
// NewRouter returns a new router instance.
|
||||||
|
@ -38,6 +43,10 @@ func NewRouter() *Router {
|
||||||
type Router struct {
|
type Router struct {
|
||||||
// Configurable Handler to be used when no route matches.
|
// Configurable Handler to be used when no route matches.
|
||||||
NotFoundHandler http.Handler
|
NotFoundHandler http.Handler
|
||||||
|
|
||||||
|
// Configurable Handler to be used when the request method does not match the route.
|
||||||
|
MethodNotAllowedHandler http.Handler
|
||||||
|
|
||||||
// Parent route, if this is a subrouter.
|
// Parent route, if this is a subrouter.
|
||||||
parent parentRoute
|
parent parentRoute
|
||||||
// Routes to be matched, in order.
|
// Routes to be matched, in order.
|
||||||
|
@ -46,8 +55,14 @@ type Router struct {
|
||||||
namedRoutes map[string]*Route
|
namedRoutes map[string]*Route
|
||||||
// See Router.StrictSlash(). This defines the flag for new routes.
|
// See Router.StrictSlash(). This defines the flag for new routes.
|
||||||
strictSlash bool
|
strictSlash bool
|
||||||
// If true, do not clear the request context after handling the request
|
// 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
|
KeepContext bool
|
||||||
|
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
||||||
|
useEncodedPath bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match matches registered routes against the request.
|
// Match matches registered routes against the request.
|
||||||
|
@ -57,6 +72,17 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if match.MatchErr == ErrMethodMismatch && r.MethodNotAllowedHandler != nil {
|
||||||
|
match.Handler = r.MethodNotAllowedHandler
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closest match for a router (includes sub-routers)
|
||||||
|
if r.NotFoundHandler != nil {
|
||||||
|
match.Handler = r.NotFoundHandler
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,35 +91,44 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
// When there is a match, the route variables can be retrieved calling
|
// When there is a match, the route variables can be retrieved calling
|
||||||
// mux.Vars(request).
|
// mux.Vars(request).
|
||||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
// Clean path to canonical form and redirect.
|
if !r.skipClean {
|
||||||
if p := cleanPath(req.URL.Path); p != req.URL.Path {
|
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 droping the query string and #whatever from query.
|
// 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:
|
// 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
|
// http://code.google.com/p/go/issues/detail?id=5252
|
||||||
url := *req.URL
|
url := *req.URL
|
||||||
url.Path = p
|
url.Path = p
|
||||||
p = url.String()
|
p = url.String()
|
||||||
|
|
||||||
w.Header().Set("Location", p)
|
w.Header().Set("Location", p)
|
||||||
w.WriteHeader(http.StatusMovedPermanently)
|
w.WriteHeader(http.StatusMovedPermanently)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var match RouteMatch
|
var match RouteMatch
|
||||||
var handler http.Handler
|
var handler http.Handler
|
||||||
if r.Match(req, &match) {
|
if r.Match(req, &match) {
|
||||||
handler = match.Handler
|
handler = match.Handler
|
||||||
setVars(req, match.Vars)
|
req = setVars(req, match.Vars)
|
||||||
setCurrentRoute(req, match.Route)
|
req = setCurrentRoute(req, match.Route)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if handler == nil && match.MatchErr == ErrMethodMismatch {
|
||||||
|
handler = methodNotAllowedHandler()
|
||||||
|
}
|
||||||
|
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
handler = r.NotFoundHandler
|
handler = http.NotFoundHandler()
|
||||||
if handler == nil {
|
|
||||||
handler = http.NotFoundHandler()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.KeepContext {
|
if !r.KeepContext {
|
||||||
defer context.Clear(req)
|
defer contextClear(req)
|
||||||
}
|
}
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
}
|
}
|
||||||
|
@ -128,10 +163,45 @@ func (r *Router) StrictSlash(value bool) *Router {
|
||||||
return r
|
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
|
// parentRoute
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (r *Router) getBuildScheme() string {
|
||||||
|
if r.parent != nil {
|
||||||
|
return r.parent.getBuildScheme()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
// getNamedRoutes returns the map where named routes are registered.
|
||||||
func (r *Router) getNamedRoutes() map[string]*Route {
|
func (r *Router) getNamedRoutes() map[string]*Route {
|
||||||
if r.namedRoutes == nil {
|
if r.namedRoutes == nil {
|
||||||
|
@ -152,13 +222,20 @@ func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
||||||
return nil
|
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
|
// Route factories
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// NewRoute registers an empty route.
|
// NewRoute registers an empty route.
|
||||||
func (r *Router) NewRoute() *Route {
|
func (r *Router) NewRoute() *Route {
|
||||||
route := &Route{parent: r, strictSlash: r.strictSlash}
|
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
||||||
r.routes = append(r.routes, route)
|
r.routes = append(r.routes, route)
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
@ -224,6 +301,59 @@ func (r *Router) Schemes(schemes ...string) *Route {
|
||||||
return r.NewRoute().Schemes(schemes...)
|
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 {
|
||||||
|
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 {
|
||||||
|
ancestors = append(ancestors, t)
|
||||||
|
err := h.walk(walkFn, ancestors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ancestors = ancestors[:len(ancestors)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
// Context
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -233,6 +363,11 @@ type RouteMatch struct {
|
||||||
Route *Route
|
Route *Route
|
||||||
Handler http.Handler
|
Handler http.Handler
|
||||||
Vars map[string]string
|
Vars map[string]string
|
||||||
|
|
||||||
|
// MatchErr is set to appropriate matching error
|
||||||
|
// It is set to ErrMethodMismatch if there is a mismatch in
|
||||||
|
// the request method and route method
|
||||||
|
MatchErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
type contextKey int
|
type contextKey int
|
||||||
|
@ -244,32 +379,58 @@ const (
|
||||||
|
|
||||||
// Vars returns the route variables for the current request, if any.
|
// Vars returns the route variables for the current request, if any.
|
||||||
func Vars(r *http.Request) map[string]string {
|
func Vars(r *http.Request) map[string]string {
|
||||||
if rv := context.Get(r, varsKey); rv != nil {
|
if rv := contextGet(r, varsKey); rv != nil {
|
||||||
return rv.(map[string]string)
|
return rv.(map[string]string)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentRoute returns the matched route for the current request, if any.
|
// 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 {
|
func CurrentRoute(r *http.Request) *Route {
|
||||||
if rv := context.Get(r, routeKey); rv != nil {
|
if rv := contextGet(r, routeKey); rv != nil {
|
||||||
return rv.(*Route)
|
return rv.(*Route)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setVars(r *http.Request, val interface{}) {
|
func setVars(r *http.Request, val interface{}) *http.Request {
|
||||||
context.Set(r, varsKey, val)
|
return contextSet(r, varsKey, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCurrentRoute(r *http.Request, val interface{}) {
|
func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
|
||||||
context.Set(r, routeKey, val)
|
return contextSet(r, routeKey, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers
|
// 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.
|
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||||
// Borrowed from the net/http package.
|
// Borrowed from the net/http package.
|
||||||
func cleanPath(p string) string {
|
func cleanPath(p string) string {
|
||||||
|
@ -285,6 +446,7 @@ func cleanPath(p string) string {
|
||||||
if p[len(p)-1] == '/' && np != "/" {
|
if p[len(p)-1] == '/' && np != "/" {
|
||||||
np += "/"
|
np += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
return np
|
return np
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,13 +462,24 @@ func uniqueVars(s1, s2 []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapFromPairs converts variadic string parameters to a string map.
|
// checkPairs returns the count of strings passed in, and an error if
|
||||||
func mapFromPairs(pairs ...string) (map[string]string, error) {
|
// the count is not an even number.
|
||||||
|
func checkPairs(pairs ...string) (int, error) {
|
||||||
length := len(pairs)
|
length := len(pairs)
|
||||||
if length%2 != 0 {
|
if length%2 != 0 {
|
||||||
return nil, fmt.Errorf(
|
return length, fmt.Errorf(
|
||||||
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
"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)
|
m := make(map[string]string, length/2)
|
||||||
for i := 0; i < length; i += 2 {
|
for i := 0; i < length; i += 2 {
|
||||||
m[pairs[i]] = pairs[i+1]
|
m[pairs[i]] = pairs[i+1]
|
||||||
|
@ -314,6 +487,24 @@ func mapFromPairs(pairs ...string) (map[string]string, error) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mapFromPairsToRegex converts variadic string parameters 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.
|
// matchInArray returns true if the given string value is in the array.
|
||||||
func matchInArray(arr []string, value string) bool {
|
func matchInArray(arr []string, value string) bool {
|
||||||
for _, v := range arr {
|
for _, v := range arr {
|
||||||
|
@ -324,9 +515,8 @@ func matchInArray(arr []string, value string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchMap returns true if the given key/value pairs exist in a given map.
|
// matchMapWithString returns true if the given key/value pairs exist in a given map.
|
||||||
func matchMap(toCheck map[string]string, toMatch map[string][]string,
|
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
|
||||||
canonicalKey bool) bool {
|
|
||||||
for k, v := range toCheck {
|
for k, v := range toCheck {
|
||||||
// Check if key exists.
|
// Check if key exists.
|
||||||
if canonicalKey {
|
if canonicalKey {
|
||||||
|
@ -351,3 +541,40 @@ func matchMap(toCheck map[string]string, toMatch map[string][]string,
|
||||||
}
|
}
|
||||||
return true
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodNotAllowed replies to the request with an HTTP status code 405.
|
||||||
|
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodNotAllowedHandler returns a simple request handler
|
||||||
|
// that replies to each request with a status code 405.
|
||||||
|
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
|
||||||
|
|
1461
vendor/github.com/gorilla/mux/mux_test.go
generated
vendored
1461
vendor/github.com/gorilla/mux/mux_test.go
generated
vendored
File diff suppressed because it is too large
Load diff
26
vendor/github.com/gorilla/mux/old_test.go
generated
vendored
26
vendor/github.com/gorilla/mux/old_test.go
generated
vendored
|
@ -36,10 +36,6 @@ func NewRecorder() *ResponseRecorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
|
||||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
|
||||||
const DefaultRemoteAddr = "1.2.3.4"
|
|
||||||
|
|
||||||
// Header returns the response headers.
|
// Header returns the response headers.
|
||||||
func (rw *ResponseRecorder) Header() http.Header {
|
func (rw *ResponseRecorder) Header() http.Header {
|
||||||
return rw.HeaderMap
|
return rw.HeaderMap
|
||||||
|
@ -125,12 +121,7 @@ func TestRouteMatchers(t *testing.T) {
|
||||||
var routeMatch RouteMatch
|
var routeMatch RouteMatch
|
||||||
matched := router.Match(request, &routeMatch)
|
matched := router.Match(request, &routeMatch)
|
||||||
if matched != shouldMatch {
|
if matched != shouldMatch {
|
||||||
// Need better messages. :)
|
t.Errorf("Expected: %v\nGot: %v\nRequest: %v %v", shouldMatch, matched, request.Method, url)
|
||||||
if matched {
|
|
||||||
t.Errorf("Should match.")
|
|
||||||
} else {
|
|
||||||
t.Errorf("Should not match.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if matched {
|
if matched {
|
||||||
|
@ -192,7 +183,6 @@ func TestRouteMatchers(t *testing.T) {
|
||||||
match(true)
|
match(true)
|
||||||
|
|
||||||
// 2nd route --------------------------------------------------------------
|
// 2nd route --------------------------------------------------------------
|
||||||
|
|
||||||
// Everything match.
|
// Everything match.
|
||||||
reset2()
|
reset2()
|
||||||
match(true)
|
match(true)
|
||||||
|
@ -545,7 +535,7 @@ func TestMatchedRouteName(t *testing.T) {
|
||||||
router := NewRouter()
|
router := NewRouter()
|
||||||
route := router.NewRoute().Path("/products/").Name(routeName)
|
route := router.NewRoute().Path("/products/").Name(routeName)
|
||||||
|
|
||||||
url := "http://www.domain.com/products/"
|
url := "http://www.example.com/products/"
|
||||||
request, _ := http.NewRequest("GET", url, nil)
|
request, _ := http.NewRequest("GET", url, nil)
|
||||||
var rv RouteMatch
|
var rv RouteMatch
|
||||||
ok := router.Match(request, &rv)
|
ok := router.Match(request, &rv)
|
||||||
|
@ -563,10 +553,10 @@ func TestMatchedRouteName(t *testing.T) {
|
||||||
func TestSubRouting(t *testing.T) {
|
func TestSubRouting(t *testing.T) {
|
||||||
// Example from docs.
|
// Example from docs.
|
||||||
router := NewRouter()
|
router := NewRouter()
|
||||||
subrouter := router.NewRoute().Host("www.domain.com").Subrouter()
|
subrouter := router.NewRoute().Host("www.example.com").Subrouter()
|
||||||
route := subrouter.NewRoute().Path("/products/").Name("products")
|
route := subrouter.NewRoute().Path("/products/").Name("products")
|
||||||
|
|
||||||
url := "http://www.domain.com/products/"
|
url := "http://www.example.com/products/"
|
||||||
request, _ := http.NewRequest("GET", url, nil)
|
request, _ := http.NewRequest("GET", url, nil)
|
||||||
var rv RouteMatch
|
var rv RouteMatch
|
||||||
ok := router.Match(request, &rv)
|
ok := router.Match(request, &rv)
|
||||||
|
@ -576,10 +566,10 @@ func TestSubRouting(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u, _ := router.Get("products").URL()
|
u, _ := router.Get("products").URL()
|
||||||
builtUrl := u.String()
|
builtURL := u.String()
|
||||||
// Yay, subroute aware of the domain when building!
|
// Yay, subroute aware of the domain when building!
|
||||||
if builtUrl != url {
|
if builtURL != url {
|
||||||
t.Errorf("Expected %q, got %q.", url, builtUrl)
|
t.Errorf("Expected %q, got %q.", url, builtURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,7 +681,7 @@ func TestNewRegexp(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for pattern, paths := range tests {
|
for pattern, paths := range tests {
|
||||||
p, _ = newRouteRegexp(pattern, false, false, false, false)
|
p, _ = newRouteRegexp(pattern, false, false, false, false, false)
|
||||||
for path, result := range paths {
|
for path, result := range paths {
|
||||||
matches = p.regexp.FindStringSubmatch(path)
|
matches = p.regexp.FindStringSubmatch(path)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
|
|
132
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
132
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ import (
|
||||||
// Previously we accepted only Python-like identifiers for variable
|
// Previously we accepted only Python-like identifiers for variable
|
||||||
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
// 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.
|
// name and pattern can't be empty, and names can't contain a colon.
|
||||||
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
|
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
|
||||||
// Check if it is well-formed.
|
// Check if it is well-formed.
|
||||||
idxs, errBraces := braceIndices(tpl)
|
idxs, errBraces := braceIndices(tpl)
|
||||||
if errBraces != nil {
|
if errBraces != nil {
|
||||||
|
@ -34,8 +35,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
|
||||||
// Now let's parse it.
|
// Now let's parse it.
|
||||||
defaultPattern := "[^/]+"
|
defaultPattern := "[^/]+"
|
||||||
if matchQuery {
|
if matchQuery {
|
||||||
defaultPattern = "[^?&]+"
|
defaultPattern = ".*"
|
||||||
matchPrefix = true
|
|
||||||
} else if matchHost {
|
} else if matchHost {
|
||||||
defaultPattern = "[^.]+"
|
defaultPattern = "[^.]+"
|
||||||
matchPrefix = false
|
matchPrefix = false
|
||||||
|
@ -53,9 +53,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
|
||||||
varsN := make([]string, len(idxs)/2)
|
varsN := make([]string, len(idxs)/2)
|
||||||
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
||||||
pattern := bytes.NewBufferString("")
|
pattern := bytes.NewBufferString("")
|
||||||
if !matchQuery {
|
pattern.WriteByte('^')
|
||||||
pattern.WriteByte('^')
|
|
||||||
}
|
|
||||||
reverse := bytes.NewBufferString("")
|
reverse := bytes.NewBufferString("")
|
||||||
var end int
|
var end int
|
||||||
var err error
|
var err error
|
||||||
|
@ -75,9 +73,11 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
|
||||||
tpl[idxs[i]:end])
|
tpl[idxs[i]:end])
|
||||||
}
|
}
|
||||||
// Build the regexp pattern.
|
// Build the regexp pattern.
|
||||||
fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
|
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
||||||
|
|
||||||
// Build the reverse template.
|
// Build the reverse template.
|
||||||
fmt.Fprintf(reverse, "%s%%s", raw)
|
fmt.Fprintf(reverse, "%s%%s", raw)
|
||||||
|
|
||||||
// Append variable name and compiled pattern.
|
// Append variable name and compiled pattern.
|
||||||
varsN[i/2] = name
|
varsN[i/2] = name
|
||||||
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
||||||
|
@ -91,6 +91,12 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
|
||||||
if strictSlash {
|
if strictSlash {
|
||||||
pattern.WriteString("[/]?")
|
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 {
|
if !matchPrefix {
|
||||||
pattern.WriteByte('$')
|
pattern.WriteByte('$')
|
||||||
}
|
}
|
||||||
|
@ -103,16 +109,24 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
|
||||||
if errCompile != nil {
|
if errCompile != nil {
|
||||||
return nil, errCompile
|
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!
|
// Done!
|
||||||
return &routeRegexp{
|
return &routeRegexp{
|
||||||
template: template,
|
template: template,
|
||||||
matchHost: matchHost,
|
matchHost: matchHost,
|
||||||
matchQuery: matchQuery,
|
matchQuery: matchQuery,
|
||||||
strictSlash: strictSlash,
|
strictSlash: strictSlash,
|
||||||
regexp: reg,
|
useEncodedPath: useEncodedPath,
|
||||||
reverse: reverse.String(),
|
regexp: reg,
|
||||||
varsN: varsN,
|
reverse: reverse.String(),
|
||||||
varsR: varsR,
|
varsN: varsN,
|
||||||
|
varsR: varsR,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +141,9 @@ type routeRegexp struct {
|
||||||
matchQuery bool
|
matchQuery bool
|
||||||
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
|
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
|
||||||
strictSlash bool
|
strictSlash bool
|
||||||
|
// Determines whether to use encoded path from getPath function or unencoded
|
||||||
|
// req.URL.Path for path matching
|
||||||
|
useEncodedPath bool
|
||||||
// Expanded regexp.
|
// Expanded regexp.
|
||||||
regexp *regexp.Regexp
|
regexp *regexp.Regexp
|
||||||
// Reverse template.
|
// Reverse template.
|
||||||
|
@ -141,26 +158,29 @@ type routeRegexp struct {
|
||||||
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
if !r.matchHost {
|
if !r.matchHost {
|
||||||
if r.matchQuery {
|
if r.matchQuery {
|
||||||
return r.regexp.MatchString(req.URL.RawQuery)
|
return r.matchQueryString(req)
|
||||||
} else {
|
|
||||||
return r.regexp.MatchString(req.URL.Path)
|
|
||||||
}
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
|
return r.regexp.MatchString(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.regexp.MatchString(getHost(req))
|
return r.regexp.MatchString(getHost(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
// url builds a URL part using the given values.
|
// url builds a URL part using the given values.
|
||||||
func (r *routeRegexp) url(pairs ...string) (string, error) {
|
func (r *routeRegexp) url(values map[string]string) (string, error) {
|
||||||
values, err := mapFromPairs(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
urlValues := make([]interface{}, len(r.varsN))
|
urlValues := make([]interface{}, len(r.varsN))
|
||||||
for k, v := range r.varsN {
|
for k, v := range r.varsN {
|
||||||
value, ok := values[v]
|
value, ok := values[v]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("mux: missing route variable %q", v)
|
return "", fmt.Errorf("mux: missing route variable %q", v)
|
||||||
}
|
}
|
||||||
|
if r.matchQuery {
|
||||||
|
value = url.QueryEscape(value)
|
||||||
|
}
|
||||||
urlValues[k] = value
|
urlValues[k] = value
|
||||||
}
|
}
|
||||||
rv := fmt.Sprintf(r.reverse, urlValues...)
|
rv := fmt.Sprintf(r.reverse, urlValues...)
|
||||||
|
@ -179,11 +199,31 @@ func (r *routeRegexp) url(pairs ...string) (string, error) {
|
||||||
return rv, nil
|
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.
|
// braceIndices returns the first level curly brace indices from a string.
|
||||||
// It returns an error in case of unbalanced braces.
|
// It returns an error in case of unbalanced braces.
|
||||||
func braceIndices(s string) ([]int, error) {
|
func braceIndices(s string) ([]int, error) {
|
||||||
var level, idx int
|
var level, idx int
|
||||||
idxs := make([]int, 0)
|
var idxs []int
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
switch s[i] {
|
switch s[i] {
|
||||||
case '{':
|
case '{':
|
||||||
|
@ -204,6 +244,11 @@ func braceIndices(s string) ([]int, error) {
|
||||||
return idxs, nil
|
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
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -219,23 +264,24 @@ type routeRegexpGroup struct {
|
||||||
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||||
// Store host variables.
|
// Store host variables.
|
||||||
if v.host != nil {
|
if v.host != nil {
|
||||||
hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
|
host := getHost(req)
|
||||||
if hostVars != nil {
|
matches := v.host.regexp.FindStringSubmatchIndex(host)
|
||||||
for k, v := range v.host.varsN {
|
if len(matches) > 0 {
|
||||||
m.Vars[v] = hostVars[k+1]
|
extractVars(host, matches, v.host.varsN, m.Vars)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
path := req.URL.Path
|
||||||
|
if r.useEncodedPath {
|
||||||
|
path = getPath(req)
|
||||||
|
}
|
||||||
// Store path variables.
|
// Store path variables.
|
||||||
if v.path != nil {
|
if v.path != nil {
|
||||||
pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
|
matches := v.path.regexp.FindStringSubmatchIndex(path)
|
||||||
if pathVars != nil {
|
if len(matches) > 0 {
|
||||||
for k, v := range v.path.varsN {
|
extractVars(path, matches, v.path.varsN, m.Vars)
|
||||||
m.Vars[v] = pathVars[k+1]
|
|
||||||
}
|
|
||||||
// Check if we should redirect.
|
// Check if we should redirect.
|
||||||
if v.path.strictSlash {
|
if v.path.strictSlash {
|
||||||
p1 := strings.HasSuffix(req.URL.Path, "/")
|
p1 := strings.HasSuffix(path, "/")
|
||||||
p2 := strings.HasSuffix(v.path.template, "/")
|
p2 := strings.HasSuffix(v.path.template, "/")
|
||||||
if p1 != p2 {
|
if p1 != p2 {
|
||||||
u, _ := url.Parse(req.URL.String())
|
u, _ := url.Parse(req.URL.String())
|
||||||
|
@ -250,13 +296,11 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Store query string variables.
|
// Store query string variables.
|
||||||
rawQuery := req.URL.RawQuery
|
|
||||||
for _, q := range v.queries {
|
for _, q := range v.queries {
|
||||||
queryVars := q.regexp.FindStringSubmatch(rawQuery)
|
queryURL := q.getURLQuery(req)
|
||||||
if queryVars != nil {
|
matches := q.regexp.FindStringSubmatchIndex(queryURL)
|
||||||
for k, v := range q.varsN {
|
if len(matches) > 0 {
|
||||||
m.Vars[v] = queryVars[k+1]
|
extractVars(queryURL, matches, q.varsN, m.Vars)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,3 +318,9 @@ func getHost(r *http.Request) string {
|
||||||
return host
|
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]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
237
vendor/github.com/gorilla/mux/route.go
generated
vendored
237
vendor/github.com/gorilla/mux/route.go
generated
vendored
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,12 +26,25 @@ type Route struct {
|
||||||
// If true, when the path pattern is "/path/", accessing "/path" will
|
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||||
// redirect to the former and vice versa.
|
// redirect to the former and vice versa.
|
||||||
strictSlash bool
|
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
|
||||||
|
// The scheme used when building URLs.
|
||||||
|
buildScheme string
|
||||||
// If true, this route never matches: it is only used to build URLs.
|
// If true, this route never matches: it is only used to build URLs.
|
||||||
buildOnly bool
|
buildOnly bool
|
||||||
// The name used to build URLs.
|
// The name used to build URLs.
|
||||||
name string
|
name string
|
||||||
// Error resulted from building a route.
|
// Error resulted from building a route.
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
buildVarsFunc BuildVarsFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) SkipClean() bool {
|
||||||
|
return r.skipClean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match matches the route against the request.
|
// Match matches the route against the request.
|
||||||
|
@ -38,12 +52,27 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
if r.buildOnly || r.err != nil {
|
if r.buildOnly || r.err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var matchErr error
|
||||||
|
|
||||||
// Match everything.
|
// Match everything.
|
||||||
for _, m := range r.matchers {
|
for _, m := range r.matchers {
|
||||||
if matched := m.Match(req, match); !matched {
|
if matched := m.Match(req, match); !matched {
|
||||||
|
if _, ok := m.(methodMatcher); ok {
|
||||||
|
matchErr = ErrMethodMismatch
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchErr = nil
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matchErr != nil {
|
||||||
|
match.MatchErr = matchErr
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
match.MatchErr = nil
|
||||||
// Yay, we have a match. Let's collect some info about it.
|
// Yay, we have a match. Let's collect some info about it.
|
||||||
if match.Route == nil {
|
if match.Route == nil {
|
||||||
match.Route = r
|
match.Route = r
|
||||||
|
@ -54,6 +83,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||||
if match.Vars == nil {
|
if match.Vars == nil {
|
||||||
match.Vars = make(map[string]string)
|
match.Vars = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set variables.
|
// Set variables.
|
||||||
if r.regexp != nil {
|
if r.regexp != nil {
|
||||||
r.regexp.setMatch(req, match, r)
|
r.regexp.setMatch(req, match, r)
|
||||||
|
@ -141,14 +171,14 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
|
||||||
}
|
}
|
||||||
r.regexp = r.getRegexpGroup()
|
r.regexp = r.getRegexpGroup()
|
||||||
if !matchHost && !matchQuery {
|
if !matchHost && !matchQuery {
|
||||||
if len(tpl) == 0 || tpl[0] != '/' {
|
if len(tpl) > 0 && tpl[0] != '/' {
|
||||||
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||||
}
|
}
|
||||||
if r.regexp.path != nil {
|
if r.regexp.path != nil {
|
||||||
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
|
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -186,7 +216,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
|
||||||
type headerMatcher map[string]string
|
type headerMatcher map[string]string
|
||||||
|
|
||||||
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
return matchMap(m, r.Header, true)
|
return matchMapWithString(m, r.Header, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers adds a matcher for request header values.
|
// Headers adds a matcher for request header values.
|
||||||
|
@ -197,22 +227,46 @@ func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
// "X-Requested-With", "XMLHttpRequest")
|
// "X-Requested-With", "XMLHttpRequest")
|
||||||
//
|
//
|
||||||
// The above route will only match if both request header values match.
|
// 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.
|
||||||
// It the value is an empty string, it will match any value if the key is set.
|
|
||||||
func (r *Route) Headers(pairs ...string) *Route {
|
func (r *Route) Headers(pairs ...string) *Route {
|
||||||
if r.err == nil {
|
if r.err == nil {
|
||||||
var headers map[string]string
|
var headers map[string]string
|
||||||
headers, r.err = mapFromPairs(pairs...)
|
headers, r.err = mapFromPairsToString(pairs...)
|
||||||
return r.addMatcher(headerMatcher(headers))
|
return r.addMatcher(headerMatcher(headers))
|
||||||
}
|
}
|
||||||
return r
|
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 -----------------------------------------------------------------------
|
||||||
|
|
||||||
// Host adds a matcher for the URL host.
|
// Host adds a matcher for the URL host.
|
||||||
// It accepts a template with zero or more URL variables enclosed by {}.
|
// It accepts a template with zero or more URL variables enclosed by {}.
|
||||||
// Variables can define an optional regexp pattern to me matched:
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
//
|
//
|
||||||
// - {name} matches anything until the next dot.
|
// - {name} matches anything until the next dot.
|
||||||
//
|
//
|
||||||
|
@ -221,7 +275,7 @@ func (r *Route) Headers(pairs ...string) *Route {
|
||||||
// For example:
|
// For example:
|
||||||
//
|
//
|
||||||
// r := mux.NewRouter()
|
// r := mux.NewRouter()
|
||||||
// r.Host("www.domain.com")
|
// r.Host("www.example.com")
|
||||||
// r.Host("{subdomain}.domain.com")
|
// r.Host("{subdomain}.domain.com")
|
||||||
// r.Host("{subdomain:[a-z]+}.domain.com")
|
// r.Host("{subdomain:[a-z]+}.domain.com")
|
||||||
//
|
//
|
||||||
|
@ -237,6 +291,7 @@ func (r *Route) Host(tpl string) *Route {
|
||||||
// MatcherFunc is the function signature used by custom matchers.
|
// MatcherFunc is the function signature used by custom matchers.
|
||||||
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
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 {
|
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
||||||
return m(r, match)
|
return m(r, match)
|
||||||
}
|
}
|
||||||
|
@ -270,7 +325,7 @@ func (r *Route) Methods(methods ...string) *Route {
|
||||||
// Path adds a matcher for the URL path.
|
// Path adds a matcher for the URL path.
|
||||||
// It accepts a template with zero or more URL variables enclosed by {}. The
|
// It accepts a template with zero or more URL variables enclosed by {}. The
|
||||||
// template must start with a "/".
|
// template must start with a "/".
|
||||||
// Variables can define an optional regexp pattern to me matched:
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
//
|
//
|
||||||
// - {name} matches anything until the next slash.
|
// - {name} matches anything until the next slash.
|
||||||
//
|
//
|
||||||
|
@ -321,7 +376,7 @@ func (r *Route) PathPrefix(tpl string) *Route {
|
||||||
//
|
//
|
||||||
// It the value is an empty string, it will match any value if the key is set.
|
// 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 me matched:
|
// Variables can define an optional regexp pattern to be matched:
|
||||||
//
|
//
|
||||||
// - {name} matches anything until the next slash.
|
// - {name} matches anything until the next slash.
|
||||||
//
|
//
|
||||||
|
@ -334,7 +389,7 @@ func (r *Route) Queries(pairs ...string) *Route {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for i := 0; i < length; i += 2 {
|
for i := 0; i < length; i += 2 {
|
||||||
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil {
|
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -357,9 +412,25 @@ func (r *Route) Schemes(schemes ...string) *Route {
|
||||||
for k, v := range schemes {
|
for k, v := range schemes {
|
||||||
schemes[k] = strings.ToLower(v)
|
schemes[k] = strings.ToLower(v)
|
||||||
}
|
}
|
||||||
|
if r.buildScheme == "" && len(schemes) > 0 {
|
||||||
|
r.buildScheme = schemes[0]
|
||||||
|
}
|
||||||
return r.addMatcher(schemeMatcher(schemes))
|
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 ------------------------------------------------------------------
|
||||||
|
|
||||||
// Subrouter creates a subrouter for the route.
|
// Subrouter creates a subrouter for the route.
|
||||||
|
@ -367,7 +438,7 @@ func (r *Route) Schemes(schemes ...string) *Route {
|
||||||
// It will test the inner routes only if the parent route matched. For example:
|
// It will test the inner routes only if the parent route matched. For example:
|
||||||
//
|
//
|
||||||
// r := mux.NewRouter()
|
// r := mux.NewRouter()
|
||||||
// s := r.Host("www.domain.com").Subrouter()
|
// s := r.Host("www.example.com").Subrouter()
|
||||||
// s.HandleFunc("/products/", ProductsHandler)
|
// s.HandleFunc("/products/", ProductsHandler)
|
||||||
// s.HandleFunc("/products/{key}", ProductHandler)
|
// s.HandleFunc("/products/{key}", ProductHandler)
|
||||||
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||||
|
@ -422,24 +493,38 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
||||||
if r.regexp == nil {
|
if r.regexp == nil {
|
||||||
return nil, errors.New("mux: route doesn't have a host or path")
|
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
|
var scheme, host, path string
|
||||||
var err error
|
queries := make([]string, 0, len(r.regexp.queries))
|
||||||
if r.regexp.host != nil {
|
if r.regexp.host != nil {
|
||||||
// Set a default scheme.
|
if host, err = r.regexp.host.url(values); err != nil {
|
||||||
scheme = "http"
|
|
||||||
if host, err = r.regexp.host.url(pairs...); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
scheme = "http"
|
||||||
|
if s := r.getBuildScheme(); s != "" {
|
||||||
|
scheme = s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if r.regexp.path != nil {
|
if r.regexp.path != nil {
|
||||||
if path, err = r.regexp.path.url(pairs...); err != nil {
|
if path, err = r.regexp.path.url(values); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, q := range r.regexp.queries {
|
||||||
|
var query string
|
||||||
|
if query, err = q.url(values); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
queries = append(queries, query)
|
||||||
|
}
|
||||||
return &url.URL{
|
return &url.URL{
|
||||||
Scheme: scheme,
|
Scheme: scheme,
|
||||||
Host: host,
|
Host: host,
|
||||||
Path: path,
|
Path: path,
|
||||||
|
RawQuery: strings.Join(queries, "&"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,14 +538,22 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
||||||
if r.regexp == nil || r.regexp.host == nil {
|
if r.regexp == nil || r.regexp.host == nil {
|
||||||
return nil, errors.New("mux: route doesn't have a host")
|
return nil, errors.New("mux: route doesn't have a host")
|
||||||
}
|
}
|
||||||
host, err := r.regexp.host.url(pairs...)
|
values, err := r.prepareVars(pairs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &url.URL{
|
host, err := r.regexp.host.url(values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u := &url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: host,
|
Host: host,
|
||||||
}, nil
|
}
|
||||||
|
if s := r.getBuildScheme(); s != "" {
|
||||||
|
u.Scheme = s
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLPath builds the path part of the URL for a route. See Route.URL().
|
// URLPath builds the path part of the URL for a route. See Route.URL().
|
||||||
|
@ -473,7 +566,11 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
if r.regexp == nil || r.regexp.path == nil {
|
||||||
return nil, errors.New("mux: route doesn't have a path")
|
return nil, errors.New("mux: route doesn't have a path")
|
||||||
}
|
}
|
||||||
path, err := r.regexp.path.url(pairs...)
|
values, err := r.prepareVars(pairs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
path, err := r.regexp.path.url(values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -482,14 +579,106 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
||||||
}, nil
|
}, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPathRegexp returns the expanded regular expression used to match route path.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An error will be returned if the route does not define a path.
|
||||||
|
func (r *Route) GetPathRegexp() (string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return "", r.err
|
||||||
|
}
|
||||||
|
if r.regexp == nil || r.regexp.path == nil {
|
||||||
|
return "", errors.New("mux: route does not have a path")
|
||||||
|
}
|
||||||
|
return r.regexp.path.regexp.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMethods returns the methods the route matches against
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// against third-party services.
|
||||||
|
// An empty list will be returned if route does not have methods.
|
||||||
|
func (r *Route) GetMethods() ([]string, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return nil, r.err
|
||||||
|
}
|
||||||
|
for _, m := range r.matchers {
|
||||||
|
if methods, ok := m.(methodMatcher); ok {
|
||||||
|
return []string(methods), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHostTemplate returns the template used to build the
|
||||||
|
// route match.
|
||||||
|
// This is useful for building simple REST API documentation and for instrumentation
|
||||||
|
// 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
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// parentRoute allows routes to know about parent host and path definitions.
|
// parentRoute allows routes to know about parent host and path definitions.
|
||||||
type parentRoute interface {
|
type parentRoute interface {
|
||||||
|
getBuildScheme() string
|
||||||
getNamedRoutes() map[string]*Route
|
getNamedRoutes() map[string]*Route
|
||||||
getRegexpGroup() *routeRegexpGroup
|
getRegexpGroup() *routeRegexpGroup
|
||||||
|
buildVars(map[string]string) map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Route) getBuildScheme() string {
|
||||||
|
if r.buildScheme != "" {
|
||||||
|
return r.buildScheme
|
||||||
|
}
|
||||||
|
if r.parent != nil {
|
||||||
|
return r.parent.getBuildScheme()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
// getNamedRoutes returns the map where named routes are registered.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue