diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index b6fefdc..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "vendor/github.com/pusher/pusher-http-go"]
- path = vendor/github.com/pusher/pusher-http-go
- url = git://github.com/pusher/pusher-http-go.git
diff --git a/functional/SpecRunner.html b/functional/SpecRunner.html
index 0be6663..dd1359a 100644
--- a/functional/SpecRunner.html
+++ b/functional/SpecRunner.html
@@ -11,7 +11,7 @@
-
+
diff --git a/functional/client.go b/functional/client.go
index c2f3529..05e9388 100644
--- a/functional/client.go
+++ b/functional/client.go
@@ -3,6 +3,7 @@ package main
import (
"fmt"
"io/ioutil"
+ "log"
"net/http"
"github.com/pusher/pusher-http-go"
@@ -20,6 +21,7 @@ func init() {
}
func pusherPresenceAuth(res http.ResponseWriter, req *http.Request) {
+ log.Println("Presence Request")
presenceData := pusher.MemberData{
UserId: "1",
UserInfo: map[string]string{},
@@ -39,6 +41,9 @@ func pusherPrivateAuth(res http.ResponseWriter, req *http.Request) {
params, _ := ioutil.ReadAll(req.Body)
response, err := client.AuthenticatePrivateChannel(params)
+ log.Printf("Private Request %s", params)
+ log.Printf("Auth %s", response)
+
if err != nil {
panic(err)
}
diff --git a/glide.lock b/glide.lock
index d0de1eb..6090f89 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,14 +1,14 @@
hash: bfb508bdf4f85c71c7eef4bb9431148ee373dfaf31d001d050d933f80ac4288e
-updated: 2016-02-21T20:13:57.03748112-03:00
+updated: 2016-08-09T22:49:29.988191345-03:00
imports:
- name: github.com/golang/glog
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
- name: github.com/gorilla/context
- version: 1c83b3eabd45b6d76072b66b746c20815fb2872d
+ version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- name: github.com/gorilla/mux
- version: 26a6070f849969ba72b72256e9f14cf519751690
+ version: 780415097119f6f61c55475fe59b66f3c3e9ea53
- name: github.com/gorilla/websocket
- version: 5c91b59efa232fa9a87b705d54101832c498a172
+ version: a69d25be2fe2923a97c2af6849b2f52426f68fc0
- name: github.com/pusher/pusher-http-go
- version: 8d4ffe157699620440932e4d03253a22533f2e43
-devImports: []
+ version: 2bba5f217f6f0f4f0c0a9bb11b945b206b32bec5
+testImports: []
diff --git a/vendor/github.com/gorilla/context/.travis.yml b/vendor/github.com/gorilla/context/.travis.yml
index f983b60..24882fc 100644
--- a/vendor/github.com/gorilla/context/.travis.yml
+++ b/vendor/github.com/gorilla/context/.travis.yml
@@ -1,8 +1,18 @@
language: go
sudo: false
-go:
- - 1.3
- - 1.4
- - 1.5
- - tip
+matrix:
+ include:
+ - go: 1.3
+ - go: 1.4
+ - go: 1.5
+ - go: 1.6
+ - go: tip
+ allow_failures:
+ - go: tip
+
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d .)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -v -race ./...
diff --git a/vendor/github.com/gorilla/context/context_test.go b/vendor/github.com/gorilla/context/context_test.go
index 9814c50..d70e91a 100644
--- a/vendor/github.com/gorilla/context/context_test.go
+++ b/vendor/github.com/gorilla/context/context_test.go
@@ -69,7 +69,7 @@ func TestContext(t *testing.T) {
// GetAllOk() for empty request
values, ok = GetAllOk(emptyR)
- assertEqual(value, nil)
+ assertEqual(len(values), 0)
assertEqual(ok, false)
// Delete()
diff --git a/vendor/github.com/gorilla/mux/.travis.yml b/vendor/github.com/gorilla/mux/.travis.yml
index 83ab8f5..fcbc42c 100644
--- a/vendor/github.com/gorilla/mux/.travis.yml
+++ b/vendor/github.com/gorilla/mux/.travis.yml
@@ -1,14 +1,20 @@
language: go
sudo: false
-go:
- - 1.3
- - 1.4
- - 1.5
- - tip
+
+matrix:
+ include:
+ - go: 1.2
+ - go: 1.3
+ - go: 1.4
+ - go: 1.5
+ - go: 1.6
+ - go: tip
+
install:
- - go get golang.org/x/tools/cmd/vet
+ - # Skip
+
script:
- go get -t -v ./...
- - diff -u <(echo -n) <(gofmt -d -s .)
+ - diff -u <(echo -n) <(gofmt -d .)
- go tool vet .
- go test -v -race ./...
diff --git a/vendor/github.com/gorilla/mux/README.md b/vendor/github.com/gorilla/mux/README.md
index b987c9e..fa79a6b 100644
--- a/vendor/github.com/gorilla/mux/README.md
+++ b/vendor/github.com/gorilla/mux/README.md
@@ -1,17 +1,43 @@
-mux
+gorilla/mux
===
[](https://godoc.org/github.com/gorilla/mux)
[](https://travis-ci.org/gorilla/mux)
-Package `gorilla/mux` implements a request router and dispatcher.
+
+
+http://www.gorillatoolkit.org/pkg/mux
+
+Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
+their respective handler.
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
+* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
* URL hosts and paths can have variables with an optional regular expression.
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
-* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
+
+---
+
+* [Install](#install)
+* [Examples](#examples)
+* [Matching Routes](#matching-routes)
+* [Static Files](#static-files)
+* [Registered URLs](#registered-urls)
+* [Full Example](#full-example)
+
+---
+
+## Install
+
+With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
+
+```sh
+go get -u github.com/gorilla/mux
+```
+
+## Examples
Let's start registering a couple of URL paths and handlers:
@@ -45,6 +71,8 @@ category := 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
@@ -116,7 +144,7 @@ Then register routes in the subrouter:
```go
s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
-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 `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.
@@ -136,6 +164,37 @@ s.HandleFunc("/{key}/", ProductHandler)
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```
+### 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/
+ 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:
@@ -217,7 +276,7 @@ package main
import (
"net/http"
-
+ "log"
"github.com/gorilla/mux"
)
@@ -231,7 +290,7 @@ func main() {
r.HandleFunc("/", YourHandler)
// Bind to a port and pass our router in
- http.ListenAndServe(":8000", r)
+ log.Fatal(http.ListenAndServe(":8000", r))
}
```
diff --git a/vendor/github.com/gorilla/mux/bench_test.go b/vendor/github.com/gorilla/mux/bench_test.go
index c5f97b2..946289b 100644
--- a/vendor/github.com/gorilla/mux/bench_test.go
+++ b/vendor/github.com/gorilla/mux/bench_test.go
@@ -6,6 +6,7 @@ package mux
import (
"net/http"
+ "net/http/httptest"
"testing"
)
@@ -19,3 +20,30 @@ func BenchmarkMux(b *testing.B) {
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)
+ }
+}
diff --git a/vendor/github.com/gorilla/mux/context_gorilla.go b/vendor/github.com/gorilla/mux/context_gorilla.go
new file mode 100644
index 0000000..d7adaa8
--- /dev/null
+++ b/vendor/github.com/gorilla/mux/context_gorilla.go
@@ -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)
+}
diff --git a/vendor/github.com/gorilla/mux/context_gorilla_test.go b/vendor/github.com/gorilla/mux/context_gorilla_test.go
new file mode 100644
index 0000000..ffaf384
--- /dev/null
+++ b/vendor/github.com/gorilla/mux/context_gorilla_test.go
@@ -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")
+ }
+
+}
diff --git a/vendor/github.com/gorilla/mux/context_native.go b/vendor/github.com/gorilla/mux/context_native.go
new file mode 100644
index 0000000..209cbea
--- /dev/null
+++ b/vendor/github.com/gorilla/mux/context_native.go
@@ -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
+}
diff --git a/vendor/github.com/gorilla/mux/context_native_test.go b/vendor/github.com/gorilla/mux/context_native_test.go
new file mode 100644
index 0000000..c150edf
--- /dev/null
+++ b/vendor/github.com/gorilla/mux/context_native_test.go
@@ -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)
+}
diff --git a/vendor/github.com/gorilla/mux/doc.go b/vendor/github.com/gorilla/mux/doc.go
index 49798cb..291ef5e 100644
--- a/vendor/github.com/gorilla/mux/doc.go
+++ b/vendor/github.com/gorilla/mux/doc.go
@@ -3,7 +3,7 @@
// 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
http.ServeMux, mux.Router matches incoming requests against a list of
@@ -136,6 +136,31 @@ the inner routes use it as base for their paths:
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
+Note that the path provided to PathPrefix() represents a "wildcard": calling
+PathPrefix("/static/").Handler(...) means that the handler will be passed any
+request that matches "/static/*". This makes it easy to serve static files with mux:
+
+ func main() {
+ var dir string
+
+ flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
+ flag.Parse()
+ r := mux.NewRouter()
+
+ // This will serve files under http://localhost:8000/static/
+ r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
+
+ srv := &http.Server{
+ Handler: r,
+ Addr: "127.0.0.1:8000",
+ // Good practice: enforce timeouts for servers you create!
+ WriteTimeout: 15 * time.Second,
+ ReadTimeout: 15 * time.Second,
+ }
+
+ log.Fatal(srv.ListenAndServe())
+ }
+
Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built,
diff --git a/vendor/github.com/gorilla/mux/mux.go b/vendor/github.com/gorilla/mux/mux.go
index aabe995..5e0dd69 100644
--- a/vendor/github.com/gorilla/mux/mux.go
+++ b/vendor/github.com/gorilla/mux/mux.go
@@ -10,8 +10,6 @@ import (
"net/http"
"path"
"regexp"
-
- "github.com/gorilla/context"
)
// NewRouter returns a new router instance.
@@ -48,7 +46,11 @@ type Router struct {
namedRoutes map[string]*Route
// See Router.StrictSlash(). This defines the flag for new routes.
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
}
@@ -73,32 +75,34 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
// When there is a match, the route variables can be retrieved calling
// mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- // Clean path to canonical form and redirect.
- if p := cleanPath(req.URL.Path); p != req.URL.Path {
+ if !r.skipClean {
+ // Clean path to canonical form and redirect.
+ if p := cleanPath(req.URL.Path); p != req.URL.Path {
- // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
- // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
- // http://code.google.com/p/go/issues/detail?id=5252
- url := *req.URL
- url.Path = p
- p = url.String()
+ // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
+ // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
+ // http://code.google.com/p/go/issues/detail?id=5252
+ url := *req.URL
+ url.Path = p
+ p = url.String()
- w.Header().Set("Location", p)
- w.WriteHeader(http.StatusMovedPermanently)
- return
+ w.Header().Set("Location", p)
+ w.WriteHeader(http.StatusMovedPermanently)
+ return
+ }
}
var match RouteMatch
var handler http.Handler
if r.Match(req, &match) {
handler = match.Handler
- setVars(req, match.Vars)
- setCurrentRoute(req, match.Route)
+ req = setVars(req, match.Vars)
+ req = setCurrentRoute(req, match.Route)
}
if handler == nil {
handler = http.NotFoundHandler()
}
if !r.KeepContext {
- defer context.Clear(req)
+ defer contextClear(req)
}
handler.ServeHTTP(w, req)
}
@@ -133,6 +137,19 @@ func (r *Router) StrictSlash(value bool) *Router {
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
+}
+
// ----------------------------------------------------------------------------
// parentRoute
// ----------------------------------------------------------------------------
@@ -170,7 +187,7 @@ func (r *Router) buildVars(m map[string]string) map[string]string {
// NewRoute registers an empty route.
func (r *Router) NewRoute() *Route {
- route := &Route{parent: r, strictSlash: r.strictSlash}
+ route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean}
r.routes = append(r.routes, route)
return route
}
@@ -236,7 +253,7 @@ func (r *Router) Schemes(schemes ...string) *Route {
return r.NewRoute().Schemes(schemes...)
}
-// BuildVars registers a new route with a custom function for modifying
+// 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)
@@ -268,6 +285,9 @@ func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
if err == SkipRouter {
continue
}
+ if err != nil {
+ return err
+ }
for _, sr := range t.matchers {
if h, ok := sr.(*Router); ok {
err := h.walk(walkFn, ancestors)
@@ -308,7 +328,7 @@ const (
// Vars returns the route variables for the current request, if any.
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 nil
@@ -320,22 +340,18 @@ func Vars(r *http.Request) map[string]string {
// after the handler returns, unless the KeepContext option is set on the
// Router.
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 nil
}
-func setVars(r *http.Request, val interface{}) {
- if val != nil {
- context.Set(r, varsKey, val)
- }
+func setVars(r *http.Request, val interface{}) *http.Request {
+ return contextSet(r, varsKey, val)
}
-func setCurrentRoute(r *http.Request, val interface{}) {
- if val != nil {
- context.Set(r, routeKey, val)
- }
+func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
+ return contextSet(r, routeKey, val)
}
// ----------------------------------------------------------------------------
@@ -357,6 +373,7 @@ func cleanPath(p string) string {
if p[len(p)-1] == '/' && np != "/" {
np += "/"
}
+
return np
}
diff --git a/vendor/github.com/gorilla/mux/mux_test.go b/vendor/github.com/gorilla/mux/mux_test.go
index 1ea439a..e8e2005 100644
--- a/vendor/github.com/gorilla/mux/mux_test.go
+++ b/vendor/github.com/gorilla/mux/mux_test.go
@@ -5,12 +5,11 @@
package mux
import (
+ "errors"
"fmt"
"net/http"
"strings"
"testing"
-
- "github.com/gorilla/context"
)
func (r *Route) GoString() string {
@@ -32,6 +31,8 @@ type routeTest struct {
vars map[string]string // the expected vars of the match
host string // the expected host of the match
path string // the expected path of the match
+ pathTemplate string // the expected path template to match
+ hostTemplate string // the expected host template to match
shouldMatch bool // whether the request is expected to match the route at all
shouldRedirect bool // whether the request should result in a redirect
}
@@ -113,116 +114,129 @@ func TestHost(t *testing.T) {
shouldMatch: false,
},
{
- title: "Host route with pattern, match",
- route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v1": "bbb"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: true,
+ title: "Host route with pattern, match",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
+ shouldMatch: true,
},
{
- title: "Host route with pattern, additional capturing group, match",
- route: new(Route).Host("aaa.{v1:[a-z]{2}(b|c)}.ccc"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v1": "bbb"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: true,
+ title: "Host route with pattern, additional capturing group, match",
+ route: new(Route).Host("aaa.{v1:[a-z]{2}(b|c)}.ccc"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `aaa.{v1:[a-z]{2}(b|c)}.ccc`,
+ shouldMatch: true,
},
{
- title: "Host route with pattern, wrong host in request URL",
- route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
- request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
- vars: map[string]string{"v1": "bbb"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: false,
+ title: "Host route with pattern, wrong host in request URL",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
+ shouldMatch: false,
},
{
- title: "Host route with multiple patterns, match",
- route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: true,
+ title: "Host route with multiple patterns, match",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+ shouldMatch: true,
},
{
- title: "Host route with multiple patterns, wrong host in request URL",
- route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
- request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
- vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: false,
+ title: "Host route with multiple patterns, wrong host in request URL",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+ shouldMatch: false,
},
{
- title: "Host route with hyphenated name and pattern, match",
- route: new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v-1": "bbb"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: true,
+ title: "Host route with hyphenated name and pattern, match",
+ route: new(Route).Host("aaa.{v-1:[a-z]{3}}.ccc"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v-1": "bbb"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `aaa.{v-1:[a-z]{3}}.ccc`,
+ shouldMatch: true,
},
{
- title: "Host route with hyphenated name and pattern, additional capturing group, match",
- route: new(Route).Host("aaa.{v-1:[a-z]{2}(b|c)}.ccc"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v-1": "bbb"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: true,
+ title: "Host route with hyphenated name and pattern, additional capturing group, match",
+ route: new(Route).Host("aaa.{v-1:[a-z]{2}(b|c)}.ccc"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v-1": "bbb"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `aaa.{v-1:[a-z]{2}(b|c)}.ccc`,
+ shouldMatch: true,
},
{
- title: "Host route with multiple hyphenated names and patterns, match",
- route: new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"},
- host: "aaa.bbb.ccc",
- path: "",
- shouldMatch: true,
+ title: "Host route with multiple hyphenated names and patterns, match",
+ route: new(Route).Host("{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v-1": "aaa", "v-2": "bbb", "v-3": "ccc"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ hostTemplate: `{v-1:[a-z]{3}}.{v-2:[a-z]{3}}.{v-3:[a-z]{3}}`,
+ shouldMatch: true,
},
{
- title: "Path route with single pattern with pipe, match",
- route: new(Route).Path("/{category:a|b/c}"),
- request: newRequest("GET", "http://localhost/a"),
- vars: map[string]string{"category": "a"},
- host: "",
- path: "/a",
- shouldMatch: true,
+ title: "Path route with single pattern with pipe, match",
+ route: new(Route).Path("/{category:a|b/c}"),
+ request: newRequest("GET", "http://localhost/a"),
+ vars: map[string]string{"category": "a"},
+ host: "",
+ path: "/a",
+ pathTemplate: `/{category:a|b/c}`,
+ shouldMatch: true,
},
{
- title: "Path route with single pattern with pipe, match",
- route: new(Route).Path("/{category:a|b/c}"),
- request: newRequest("GET", "http://localhost/b/c"),
- vars: map[string]string{"category": "b/c"},
- host: "",
- path: "/b/c",
- shouldMatch: true,
+ title: "Path route with single pattern with pipe, match",
+ route: new(Route).Path("/{category:a|b/c}"),
+ request: newRequest("GET", "http://localhost/b/c"),
+ vars: map[string]string{"category": "b/c"},
+ host: "",
+ path: "/b/c",
+ pathTemplate: `/{category:a|b/c}`,
+ shouldMatch: true,
},
{
- title: "Path route with multiple patterns with pipe, match",
- route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
- request: newRequest("GET", "http://localhost/a/product_name/1"),
- vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
- host: "",
- path: "/a/product_name/1",
- shouldMatch: true,
+ title: "Path route with multiple patterns with pipe, match",
+ route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
+ request: newRequest("GET", "http://localhost/a/product_name/1"),
+ vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
+ host: "",
+ path: "/a/product_name/1",
+ pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
+ shouldMatch: true,
},
{
- title: "Path route with multiple patterns with pipe, match",
- route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
- request: newRequest("GET", "http://localhost/b/c/product_name/1"),
- vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
- host: "",
- path: "/b/c/product_name/1",
- shouldMatch: true,
+ title: "Path route with multiple patterns with pipe, match",
+ route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
+ request: newRequest("GET", "http://localhost/b/c/product_name/1"),
+ vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
+ host: "",
+ path: "/b/c/product_name/1",
+ pathTemplate: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
+ shouldMatch: true,
},
}
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -247,22 +261,24 @@ func TestPath(t *testing.T) {
shouldMatch: true,
},
{
- title: "Path route, do not match with trailing slash in path",
- route: new(Route).Path("/111/"),
- request: newRequest("GET", "http://localhost/111"),
- vars: map[string]string{},
- host: "",
- path: "/111",
- shouldMatch: false,
+ title: "Path route, do not match with trailing slash in path",
+ route: new(Route).Path("/111/"),
+ request: newRequest("GET", "http://localhost/111"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ pathTemplate: `/111/`,
+ shouldMatch: false,
},
{
- title: "Path route, do not match with trailing slash in request",
- route: new(Route).Path("/111"),
- request: newRequest("GET", "http://localhost/111/"),
- vars: map[string]string{},
- host: "",
- path: "/111/",
- shouldMatch: false,
+ title: "Path route, do not match with trailing slash in request",
+ route: new(Route).Path("/111"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ pathTemplate: `/111`,
+ shouldMatch: false,
},
{
title: "Path route, wrong path in request in request URL",
@@ -274,81 +290,100 @@ func TestPath(t *testing.T) {
shouldMatch: false,
},
{
- title: "Path route with pattern, match",
- route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
- request: newRequest("GET", "http://localhost/111/222/333"),
- vars: map[string]string{"v1": "222"},
- host: "",
- path: "/111/222/333",
- shouldMatch: true,
+ title: "Path route with pattern, match",
+ route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222/333",
+ pathTemplate: `/111/{v1:[0-9]{3}}/333`,
+ shouldMatch: true,
},
{
- title: "Path route with pattern, URL in request does not match",
- route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
- request: newRequest("GET", "http://localhost/111/aaa/333"),
- vars: map[string]string{"v1": "222"},
- host: "",
- path: "/111/222/333",
- shouldMatch: false,
+ title: "Path route with pattern, URL in request does not match",
+ route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222/333",
+ pathTemplate: `/111/{v1:[0-9]{3}}/333`,
+ shouldMatch: false,
},
{
- title: "Path route with multiple patterns, match",
- route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
- request: newRequest("GET", "http://localhost/111/222/333"),
- vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
- host: "",
- path: "/111/222/333",
- shouldMatch: true,
+ title: "Path route with multiple patterns, match",
+ route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+ host: "",
+ path: "/111/222/333",
+ pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
+ shouldMatch: true,
},
{
- title: "Path route with multiple patterns, URL in request does not match",
- route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
- request: newRequest("GET", "http://localhost/111/aaa/333"),
- vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
- host: "",
- path: "/111/222/333",
- shouldMatch: false,
+ title: "Path route with multiple patterns, URL in request does not match",
+ route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+ host: "",
+ path: "/111/222/333",
+ pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}`,
+ shouldMatch: false,
},
{
- title: "Path route with multiple patterns with pipe, match",
- route: new(Route).Path("/{category:a|(b/c)}/{product}/{id:[0-9]+}"),
- request: newRequest("GET", "http://localhost/a/product_name/1"),
- vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
- host: "",
- path: "/a/product_name/1",
- shouldMatch: true,
+ title: "Path route with multiple patterns with pipe, match",
+ route: new(Route).Path("/{category:a|(b/c)}/{product}/{id:[0-9]+}"),
+ request: newRequest("GET", "http://localhost/a/product_name/1"),
+ vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
+ host: "",
+ path: "/a/product_name/1",
+ pathTemplate: `/{category:a|(b/c)}/{product}/{id:[0-9]+}`,
+ shouldMatch: true,
},
{
- title: "Path route with hyphenated name and pattern, match",
- route: new(Route).Path("/111/{v-1:[0-9]{3}}/333"),
- request: newRequest("GET", "http://localhost/111/222/333"),
- vars: map[string]string{"v-1": "222"},
- host: "",
- path: "/111/222/333",
- shouldMatch: true,
+ title: "Path route with hyphenated name and pattern, match",
+ route: new(Route).Path("/111/{v-1:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v-1": "222"},
+ host: "",
+ path: "/111/222/333",
+ pathTemplate: `/111/{v-1:[0-9]{3}}/333`,
+ shouldMatch: true,
},
{
- title: "Path route with multiple hyphenated names and patterns, match",
- route: new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"),
- request: newRequest("GET", "http://localhost/111/222/333"),
- vars: map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"},
- host: "",
- path: "/111/222/333",
- shouldMatch: true,
+ title: "Path route with multiple hyphenated names and patterns, match",
+ route: new(Route).Path("/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v-1": "111", "v-2": "222", "v-3": "333"},
+ host: "",
+ path: "/111/222/333",
+ pathTemplate: `/{v-1:[0-9]{3}}/{v-2:[0-9]{3}}/{v-3:[0-9]{3}}`,
+ shouldMatch: true,
},
{
- title: "Path route with multiple hyphenated names and patterns with pipe, match",
- route: new(Route).Path("/{product-category:a|(b/c)}/{product-name}/{product-id:[0-9]+}"),
- request: newRequest("GET", "http://localhost/a/product_name/1"),
- vars: map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"},
- host: "",
- path: "/a/product_name/1",
- shouldMatch: true,
+ title: "Path route with multiple hyphenated names and patterns with pipe, match",
+ route: new(Route).Path("/{product-category:a|(b/c)}/{product-name}/{product-id:[0-9]+}"),
+ request: newRequest("GET", "http://localhost/a/product_name/1"),
+ vars: map[string]string{"product-category": "a", "product-name": "product_name", "product-id": "1"},
+ host: "",
+ path: "/a/product_name/1",
+ pathTemplate: `/{product-category:a|(b/c)}/{product-name}/{product-id:[0-9]+}`,
+ shouldMatch: true,
+ },
+ {
+ title: "Path route with multiple hyphenated names and patterns with pipe and case insensitive, match",
+ route: new(Route).Path("/{type:(?i:daily|mini|variety)}-{date:\\d{4,4}-\\d{2,2}-\\d{2,2}}"),
+ request: newRequest("GET", "http://localhost/daily-2016-01-01"),
+ vars: map[string]string{"type": "daily", "date": "2016-01-01"},
+ host: "",
+ path: "/daily-2016-01-01",
+ pathTemplate: `/{type:(?i:daily|mini|variety)}-{date:\d{4,4}-\d{2,2}-\d{2,2}}`,
+ shouldMatch: true,
},
}
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -382,108 +417,126 @@ func TestPathPrefix(t *testing.T) {
shouldMatch: false,
},
{
- title: "PathPrefix route with pattern, match",
- route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
- request: newRequest("GET", "http://localhost/111/222/333"),
- vars: map[string]string{"v1": "222"},
- host: "",
- path: "/111/222",
- shouldMatch: true,
+ title: "PathPrefix route with pattern, match",
+ route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222",
+ pathTemplate: `/111/{v1:[0-9]{3}}`,
+ shouldMatch: true,
},
{
- title: "PathPrefix route with pattern, URL prefix in request does not match",
- route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
- request: newRequest("GET", "http://localhost/111/aaa/333"),
- vars: map[string]string{"v1": "222"},
- host: "",
- path: "/111/222",
- shouldMatch: false,
+ title: "PathPrefix route with pattern, URL prefix in request does not match",
+ route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222",
+ pathTemplate: `/111/{v1:[0-9]{3}}`,
+ shouldMatch: false,
},
{
- title: "PathPrefix route with multiple patterns, match",
- route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
- request: newRequest("GET", "http://localhost/111/222/333"),
- vars: map[string]string{"v1": "111", "v2": "222"},
- host: "",
- path: "/111/222",
- shouldMatch: true,
+ title: "PathPrefix route with multiple patterns, match",
+ route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "111", "v2": "222"},
+ host: "",
+ path: "/111/222",
+ pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
+ shouldMatch: true,
},
{
- title: "PathPrefix route with multiple patterns, URL prefix in request does not match",
- route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
- request: newRequest("GET", "http://localhost/111/aaa/333"),
- vars: map[string]string{"v1": "111", "v2": "222"},
- host: "",
- path: "/111/222",
- shouldMatch: false,
+ title: "PathPrefix route with multiple patterns, URL prefix in request does not match",
+ route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "111", "v2": "222"},
+ host: "",
+ path: "/111/222",
+ pathTemplate: `/{v1:[0-9]{3}}/{v2:[0-9]{3}}`,
+ shouldMatch: false,
},
}
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
func TestHostPath(t *testing.T) {
tests := []routeTest{
{
- title: "Host and Path route, match",
- route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{},
- host: "",
- path: "",
- shouldMatch: true,
+ title: "Host and Path route, match",
+ route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ pathTemplate: `/111/222/333`,
+ hostTemplate: `aaa.bbb.ccc`,
+ shouldMatch: true,
},
{
- title: "Host and Path route, wrong host in request URL",
- route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
- request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
- vars: map[string]string{},
- host: "",
- path: "",
- shouldMatch: false,
+ title: "Host and Path route, wrong host in request URL",
+ route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ pathTemplate: `/111/222/333`,
+ hostTemplate: `aaa.bbb.ccc`,
+ shouldMatch: false,
},
{
- title: "Host and Path route with pattern, match",
- route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v1": "bbb", "v2": "222"},
- host: "aaa.bbb.ccc",
- path: "/111/222/333",
- shouldMatch: true,
+ title: "Host and Path route with pattern, match",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb", "v2": "222"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ pathTemplate: `/111/{v2:[0-9]{3}}/333`,
+ hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
+ shouldMatch: true,
},
{
- title: "Host and Path route with pattern, URL in request does not match",
- route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
- request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
- vars: map[string]string{"v1": "bbb", "v2": "222"},
- host: "aaa.bbb.ccc",
- path: "/111/222/333",
- shouldMatch: false,
+ title: "Host and Path route with pattern, URL in request does not match",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb", "v2": "222"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ pathTemplate: `/111/{v2:[0-9]{3}}/333`,
+ hostTemplate: `aaa.{v1:[a-z]{3}}.ccc`,
+ shouldMatch: false,
},
{
- title: "Host and Path route with multiple patterns, match",
- route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
- request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
- vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
- host: "aaa.bbb.ccc",
- path: "/111/222/333",
- shouldMatch: true,
+ title: "Host and Path route with multiple patterns, match",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
+ hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+ shouldMatch: true,
},
{
- title: "Host and Path route with multiple patterns, URL in request does not match",
- route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
- request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
- vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
- host: "aaa.bbb.ccc",
- path: "/111/222/333",
- shouldMatch: false,
+ title: "Host and Path route with multiple patterns, URL in request does not match",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ pathTemplate: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
+ hostTemplate: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+ shouldMatch: false,
},
}
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -541,6 +594,7 @@ func TestHeaders(t *testing.T) {
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -578,6 +632,7 @@ func TestMethods(t *testing.T) {
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -593,22 +648,26 @@ func TestQueries(t *testing.T) {
shouldMatch: true,
},
{
- title: "Queries route, match with a query string",
- route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
- request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
- vars: map[string]string{},
- host: "",
- path: "",
- shouldMatch: true,
+ title: "Queries route, match with a query string",
+ route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ pathTemplate: `/api`,
+ hostTemplate: `www.example.com`,
+ shouldMatch: true,
},
{
- title: "Queries route, match with a query string out of order",
- route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
- request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
- vars: map[string]string{},
- host: "",
- path: "",
- shouldMatch: true,
+ title: "Queries route, match with a query string out of order",
+ route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ pathTemplate: `/api`,
+ hostTemplate: `www.example.com`,
+ shouldMatch: true,
},
{
title: "Queries route, bad query",
@@ -803,6 +862,7 @@ func TestQueries(t *testing.T) {
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -839,6 +899,7 @@ func TestSchemes(t *testing.T) {
}
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -873,6 +934,7 @@ func TestMatcherFunc(t *testing.T) {
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -885,9 +947,10 @@ func TestBuildVarsFunc(t *testing.T) {
vars["v2"] = "a"
return vars
}),
- request: newRequest("GET", "http://localhost/111/2"),
- path: "/111/3a",
- shouldMatch: true,
+ request: newRequest("GET", "http://localhost/111/2"),
+ path: "/111/3a",
+ pathTemplate: `/111/{v1:\d}{v2:.*}`,
+ shouldMatch: true,
},
{
title: "BuildVarsFunc set on route and parent route",
@@ -898,14 +961,16 @@ func TestBuildVarsFunc(t *testing.T) {
vars["v2"] = "b"
return vars
}),
- request: newRequest("GET", "http://localhost/1/a"),
- path: "/2/b",
- shouldMatch: true,
+ request: newRequest("GET", "http://localhost/1/a"),
+ path: "/2/b",
+ pathTemplate: `/{v1:\d}/{v2:\w}`,
+ shouldMatch: true,
},
}
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -915,41 +980,48 @@ func TestSubRouter(t *testing.T) {
tests := []routeTest{
{
- route: subrouter1.Path("/{v2:[a-z]+}"),
- request: newRequest("GET", "http://aaa.google.com/bbb"),
- vars: map[string]string{"v1": "aaa", "v2": "bbb"},
- host: "aaa.google.com",
- path: "/bbb",
- shouldMatch: true,
+ route: subrouter1.Path("/{v2:[a-z]+}"),
+ request: newRequest("GET", "http://aaa.google.com/bbb"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb"},
+ host: "aaa.google.com",
+ path: "/bbb",
+ pathTemplate: `/{v2:[a-z]+}`,
+ hostTemplate: `{v1:[a-z]+}.google.com`,
+ shouldMatch: true,
},
{
- route: subrouter1.Path("/{v2:[a-z]+}"),
- request: newRequest("GET", "http://111.google.com/111"),
- vars: map[string]string{"v1": "aaa", "v2": "bbb"},
- host: "aaa.google.com",
- path: "/bbb",
- shouldMatch: false,
+ route: subrouter1.Path("/{v2:[a-z]+}"),
+ request: newRequest("GET", "http://111.google.com/111"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb"},
+ host: "aaa.google.com",
+ path: "/bbb",
+ pathTemplate: `/{v2:[a-z]+}`,
+ hostTemplate: `{v1:[a-z]+}.google.com`,
+ shouldMatch: false,
},
{
- route: subrouter2.Path("/baz/{v2}"),
- request: newRequest("GET", "http://localhost/foo/bar/baz/ding"),
- vars: map[string]string{"v1": "bar", "v2": "ding"},
- host: "",
- path: "/foo/bar/baz/ding",
- shouldMatch: true,
+ route: subrouter2.Path("/baz/{v2}"),
+ request: newRequest("GET", "http://localhost/foo/bar/baz/ding"),
+ vars: map[string]string{"v1": "bar", "v2": "ding"},
+ host: "",
+ path: "/foo/bar/baz/ding",
+ pathTemplate: `/foo/{v1}/baz/{v2}`,
+ shouldMatch: true,
},
{
- route: subrouter2.Path("/baz/{v2}"),
- request: newRequest("GET", "http://localhost/foo/bar"),
- vars: map[string]string{"v1": "bar", "v2": "ding"},
- host: "",
- path: "/foo/bar/baz/ding",
- shouldMatch: false,
+ route: subrouter2.Path("/baz/{v2}"),
+ request: newRequest("GET", "http://localhost/foo/bar"),
+ vars: map[string]string{"v1": "bar", "v2": "ding"},
+ host: "",
+ path: "/foo/bar/baz/ding",
+ pathTemplate: `/foo/{v1}/baz/{v2}`,
+ shouldMatch: false,
},
}
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -1045,6 +1117,7 @@ func TestStrictSlash(t *testing.T) {
for _, test := range tests {
testRoute(t, test)
+ testTemplate(t, test)
}
}
@@ -1123,6 +1196,42 @@ func TestWalkNested(t *testing.T) {
}
}
+func TestWalkErrorRoute(t *testing.T) {
+ router := NewRouter()
+ router.Path("/g")
+ expectedError := errors.New("error")
+ err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
+ return expectedError
+ })
+ if err != expectedError {
+ t.Errorf("Expected %v routes, found %v", expectedError, err)
+ }
+}
+
+func TestWalkErrorMatcher(t *testing.T) {
+ router := NewRouter()
+ expectedError := router.Path("/g").Subrouter().Path("").GetError()
+ err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
+ return route.GetError()
+ })
+ if err != expectedError {
+ t.Errorf("Expected %v routes, found %v", expectedError, err)
+ }
+}
+
+func TestWalkErrorHandler(t *testing.T) {
+ handler := NewRouter()
+ expectedError := handler.Path("/path").Subrouter().Path("").GetError()
+ router := NewRouter()
+ router.Path("/g").Handler(handler)
+ err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
+ return route.GetError()
+ })
+ if err != expectedError {
+ t.Errorf("Expected %v routes, found %v", expectedError, err)
+ }
+}
+
func TestSubrouterErrorHandling(t *testing.T) {
superRouterCalled := false
subRouterCalled := false
@@ -1152,14 +1261,13 @@ func TestSubrouterErrorHandling(t *testing.T) {
// ----------------------------------------------------------------------------
func getRouteTemplate(route *Route) string {
- host, path := "none", "none"
- if route.regexp != nil {
- if route.regexp.host != nil {
- host = route.regexp.host.template
- }
- if route.regexp.path != nil {
- path = route.regexp.path.template
- }
+ host, err := route.GetHostTemplate()
+ if err != nil {
+ host = "none"
+ }
+ path, err := route.GetPathTemplate()
+ if err != nil {
+ path = "none"
}
return fmt.Sprintf("Host: %v, Path: %v", host, path)
}
@@ -1221,34 +1329,26 @@ func testRoute(t *testing.T, test routeTest) {
}
}
-// 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")
+func testTemplate(t *testing.T, test routeTest) {
+ route := test.route
+ pathTemplate := test.pathTemplate
+ if len(pathTemplate) == 0 {
+ pathTemplate = test.path
+ }
+ hostTemplate := test.hostTemplate
+ if len(hostTemplate) == 0 {
+ hostTemplate = test.host
}
- 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")
+ routePathTemplate, pathErr := route.GetPathTemplate()
+ if pathErr == nil && routePathTemplate != pathTemplate {
+ t.Errorf("(%v) GetPathTemplate not equal: expected %v, got %v", test.title, pathTemplate, routePathTemplate)
}
+ routeHostTemplate, hostErr := route.GetHostTemplate()
+ if hostErr == nil && routeHostTemplate != hostTemplate {
+ t.Errorf("(%v) GetHostTemplate not equal: expected %v, got %v", test.title, hostTemplate, routeHostTemplate)
+ }
}
type TestA301ResponseWriter struct {
@@ -1291,6 +1391,24 @@ func Test301Redirect(t *testing.T) {
}
}
+func TestSkipClean(t *testing.T) {
+ func1 := func(w http.ResponseWriter, r *http.Request) {}
+ func2 := func(w http.ResponseWriter, r *http.Request) {}
+
+ r := NewRouter()
+ r.SkipClean(true)
+ r.HandleFunc("/api/", func2).Name("func2")
+ r.HandleFunc("/", func1).Name("func1")
+
+ req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
+ res := NewRecorder()
+ r.ServeHTTP(res, req)
+
+ if len(res.HeaderMap["Location"]) != 0 {
+ t.Errorf("Shouldn't redirect since skip clean is disabled")
+ }
+}
+
// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
func TestSubrouterHeader(t *testing.T) {
expected := "func1 response"
diff --git a/vendor/github.com/gorilla/mux/old_test.go b/vendor/github.com/gorilla/mux/old_test.go
index 755db48..c385a25 100644
--- a/vendor/github.com/gorilla/mux/old_test.go
+++ b/vendor/github.com/gorilla/mux/old_test.go
@@ -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.
func (rw *ResponseRecorder) Header() http.Header {
return rw.HeaderMap
@@ -576,10 +572,10 @@ func TestSubRouting(t *testing.T) {
}
u, _ := router.Get("products").URL()
- builtUrl := u.String()
+ builtURL := u.String()
// Yay, subroute aware of the domain when building!
- if builtUrl != url {
- t.Errorf("Expected %q, got %q.", url, builtUrl)
+ if builtURL != url {
+ t.Errorf("Expected %q, got %q.", url, builtURL)
}
}
diff --git a/vendor/github.com/gorilla/mux/regexp.go b/vendor/github.com/gorilla/mux/regexp.go
index 06728dd..08710bc 100644
--- a/vendor/github.com/gorilla/mux/regexp.go
+++ b/vendor/github.com/gorilla/mux/regexp.go
@@ -73,14 +73,14 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
tpl[idxs[i]:end])
}
// Build the regexp pattern.
- varIdx := i / 2
- fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(varIdx), patt)
+ fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
+
// Build the reverse template.
fmt.Fprintf(reverse, "%s%%s", raw)
// Append variable name and compiled pattern.
- varsN[varIdx] = name
- varsR[varIdx], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
+ varsN[i/2] = name
+ varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
if err != nil {
return nil, err
}
@@ -148,10 +148,11 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
if !r.matchHost {
if r.matchQuery {
return r.matchQueryString(req)
- } else {
- return r.regexp.MatchString(req.URL.Path)
}
+
+ return r.regexp.MatchString(req.URL.Path)
}
+
return r.regexp.MatchString(getHost(req))
}
@@ -181,10 +182,10 @@ func (r *routeRegexp) url(values map[string]string) (string, error) {
return rv, nil
}
-// getUrlQuery returns a single query parameter from a request URL.
+// 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 {
+func (r *routeRegexp) getURLQuery(req *http.Request) string {
if !r.matchQuery {
return ""
}
@@ -198,14 +199,14 @@ func (r *routeRegexp) getUrlQuery(req *http.Request) string {
}
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
- return r.regexp.MatchString(r.getUrlQuery(req))
+ return r.regexp.MatchString(r.getURLQuery(req))
}
// braceIndices returns the first level curly brace indices from a string.
// It returns an error in case of unbalanced braces.
func braceIndices(s string) ([]int, error) {
var level, idx int
- idxs := make([]int, 0)
+ var idxs []int
for i := 0; i < len(s); i++ {
switch s[i] {
case '{':
@@ -246,30 +247,17 @@ type routeRegexpGroup struct {
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
// Store host variables.
if v.host != nil {
- hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
- if hostVars != nil {
- subexpNames := v.host.regexp.SubexpNames()
- varName := 0
- for i, name := range subexpNames[1:] {
- if name != "" && name == varGroupName(varName) {
- m.Vars[v.host.varsN[varName]] = hostVars[i+1]
- varName++
- }
- }
+ host := getHost(req)
+ matches := v.host.regexp.FindStringSubmatchIndex(host)
+ if len(matches) > 0 {
+ extractVars(host, matches, v.host.varsN, m.Vars)
}
}
// Store path variables.
if v.path != nil {
- pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
- if pathVars != nil {
- subexpNames := v.path.regexp.SubexpNames()
- varName := 0
- for i, name := range subexpNames[1:] {
- if name != "" && name == varGroupName(varName) {
- m.Vars[v.path.varsN[varName]] = pathVars[i+1]
- varName++
- }
- }
+ matches := v.path.regexp.FindStringSubmatchIndex(req.URL.Path)
+ if len(matches) > 0 {
+ extractVars(req.URL.Path, matches, v.path.varsN, m.Vars)
// Check if we should redirect.
if v.path.strictSlash {
p1 := strings.HasSuffix(req.URL.Path, "/")
@@ -288,16 +276,10 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
}
// Store query string variables.
for _, q := range v.queries {
- queryVars := q.regexp.FindStringSubmatch(q.getUrlQuery(req))
- if queryVars != nil {
- subexpNames := q.regexp.SubexpNames()
- varName := 0
- for i, name := range subexpNames[1:] {
- if name != "" && name == varGroupName(varName) {
- m.Vars[q.varsN[varName]] = queryVars[i+1]
- varName++
- }
- }
+ queryURL := q.getURLQuery(req)
+ matches := q.regexp.FindStringSubmatchIndex(queryURL)
+ if len(matches) > 0 {
+ extractVars(queryURL, matches, q.varsN, m.Vars)
}
}
}
@@ -315,3 +297,16 @@ func getHost(r *http.Request) string {
return host
}
+
+func extractVars(input string, matches []int, names []string, output map[string]string) {
+ matchesCount := 0
+ prevEnd := -1
+ for i := 2; i < len(matches) && matchesCount < len(names); i += 2 {
+ if prevEnd < matches[i+1] {
+ value := input[matches[i]:matches[i+1]]
+ output[names[matchesCount]] = value
+ prevEnd = matches[i+1]
+ matchesCount++
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/mux/route.go b/vendor/github.com/gorilla/mux/route.go
index 913432c..6c53f9f 100644
--- a/vendor/github.com/gorilla/mux/route.go
+++ b/vendor/github.com/gorilla/mux/route.go
@@ -26,6 +26,9 @@ type Route struct {
// If true, when the path pattern is "/path/", accessing "/path" will
// redirect to the former and vice versa.
strictSlash bool
+ // If true, when the path pattern is "/path//to", accessing "/path//to"
+ // will not redirect
+ skipClean bool
// If true, this route never matches: it is only used to build URLs.
buildOnly bool
// The name used to build URLs.
@@ -36,6 +39,10 @@ type Route struct {
buildVarsFunc BuildVarsFunc
}
+func (r *Route) SkipClean() bool {
+ return r.skipClean
+}
+
// Match matches the route against the request.
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
if r.buildOnly || r.err != nil {
@@ -217,8 +224,9 @@ func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
return matchMapWithRegex(m, r.Header, true)
}
-// Regular expressions can be used with headers as well.
-// It accepts a sequence of key/value pairs, where the value has regex support. For example
+// 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")
@@ -263,6 +271,7 @@ func (r *Route) Host(tpl string) *Route {
// MatcherFunc is the function signature used by custom matchers.
type MatcherFunc func(*http.Request, *RouteMatch) bool
+// Match returns the match for a given request.
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
return m(r, match)
}
@@ -532,6 +541,36 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
}, nil
}
+// GetPathTemplate returns the template used to build the
+// route match.
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An error will be returned if the route does not define a path.
+func (r *Route) GetPathTemplate() (string, error) {
+ if r.err != nil {
+ return "", r.err
+ }
+ if r.regexp == nil || r.regexp.path == nil {
+ return "", errors.New("mux: route doesn't have a path")
+ }
+ return r.regexp.path.template, nil
+}
+
+// GetHostTemplate returns the template used to build the
+// route match.
+// This is useful for building simple REST API documentation and for instrumentation
+// against third-party services.
+// An error will be returned if the route does not define a host.
+func (r *Route) GetHostTemplate() (string, error) {
+ if r.err != nil {
+ return "", r.err
+ }
+ if r.regexp == nil || r.regexp.host == nil {
+ return "", errors.New("mux: route doesn't have a host")
+ }
+ return r.regexp.host.template, nil
+}
+
// prepareVars converts the route variable pairs into a map. If the route has a
// BuildVarsFunc, it is invoked.
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
diff --git a/vendor/github.com/gorilla/websocket/.gitignore b/vendor/github.com/gorilla/websocket/.gitignore
index 0026861..ac71020 100644
--- a/vendor/github.com/gorilla/websocket/.gitignore
+++ b/vendor/github.com/gorilla/websocket/.gitignore
@@ -20,3 +20,6 @@ _cgo_export.*
_testmain.go
*.exe
+
+.idea/
+*.iml
\ No newline at end of file
diff --git a/vendor/github.com/gorilla/websocket/.travis.yml b/vendor/github.com/gorilla/websocket/.travis.yml
index 8687342..66435ac 100644
--- a/vendor/github.com/gorilla/websocket/.travis.yml
+++ b/vendor/github.com/gorilla/websocket/.travis.yml
@@ -1,6 +1,17 @@
language: go
+sudo: false
-go:
- - 1.1
- - 1.2
- - tip
+matrix:
+ include:
+ - go: 1.4
+ - go: 1.5
+ - go: 1.6
+ - go: tip
+ allow_failures:
+ - go: tip
+
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d .)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -v -race ./...
diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go
index a353e18..879d33e 100644
--- a/vendor/github.com/gorilla/websocket/client.go
+++ b/vendor/github.com/gorilla/websocket/client.go
@@ -298,12 +298,8 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
}
if u.Scheme == "https" {
- cfg := d.TLSClientConfig
- if cfg == nil {
- cfg = &tls.Config{ServerName: hostNoPort}
- } else if cfg.ServerName == "" {
- shallowCopy := *cfg
- cfg = &shallowCopy
+ cfg := cloneTLSConfig(d.TLSClientConfig)
+ if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
}
tlsConn := tls.Client(netConn, cfg)
@@ -348,3 +344,32 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
netConn = nil // to avoid close in defer.
return conn, resp, nil
}
+
+// cloneTLSConfig clones all public fields except the fields
+// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
+// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
+// config in active use.
+func cloneTLSConfig(cfg *tls.Config) *tls.Config {
+ if cfg == nil {
+ return &tls.Config{}
+ }
+ return &tls.Config{
+ Rand: cfg.Rand,
+ Time: cfg.Time,
+ Certificates: cfg.Certificates,
+ NameToCertificate: cfg.NameToCertificate,
+ GetCertificate: cfg.GetCertificate,
+ RootCAs: cfg.RootCAs,
+ NextProtos: cfg.NextProtos,
+ ServerName: cfg.ServerName,
+ ClientAuth: cfg.ClientAuth,
+ ClientCAs: cfg.ClientCAs,
+ InsecureSkipVerify: cfg.InsecureSkipVerify,
+ CipherSuites: cfg.CipherSuites,
+ PreferServerCipherSuites: cfg.PreferServerCipherSuites,
+ ClientSessionCache: cfg.ClientSessionCache,
+ MinVersion: cfg.MinVersion,
+ MaxVersion: cfg.MaxVersion,
+ CurvePreferences: cfg.CurvePreferences,
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/compression.go b/vendor/github.com/gorilla/websocket/compression.go
new file mode 100644
index 0000000..e2ac761
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/compression.go
@@ -0,0 +1,85 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package websocket
+
+import (
+ "compress/flate"
+ "errors"
+ "io"
+ "strings"
+)
+
+func decompressNoContextTakeover(r io.Reader) io.Reader {
+ const tail =
+ // Add four bytes as specified in RFC
+ "\x00\x00\xff\xff" +
+ // Add final block to squelch unexpected EOF error from flate reader.
+ "\x01\x00\x00\xff\xff"
+
+ return flate.NewReader(io.MultiReader(r, strings.NewReader(tail)))
+}
+
+func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) {
+ tw := &truncWriter{w: w}
+ fw, err := flate.NewWriter(tw, 3)
+ return &flateWrapper{fw: fw, tw: tw}, err
+}
+
+// truncWriter is an io.Writer that writes all but the last four bytes of the
+// stream to another io.Writer.
+type truncWriter struct {
+ w io.WriteCloser
+ n int
+ p [4]byte
+}
+
+func (w *truncWriter) Write(p []byte) (int, error) {
+ n := 0
+
+ // fill buffer first for simplicity.
+ if w.n < len(w.p) {
+ n = copy(w.p[w.n:], p)
+ p = p[n:]
+ w.n += n
+ if len(p) == 0 {
+ return n, nil
+ }
+ }
+
+ m := len(p)
+ if m > len(w.p) {
+ m = len(w.p)
+ }
+
+ if nn, err := w.w.Write(w.p[:m]); err != nil {
+ return n + nn, err
+ }
+
+ copy(w.p[:], w.p[m:])
+ copy(w.p[len(w.p)-m:], p[len(p)-m:])
+ nn, err := w.w.Write(p[:len(p)-m])
+ return n + nn, err
+}
+
+type flateWrapper struct {
+ fw *flate.Writer
+ tw *truncWriter
+}
+
+func (w *flateWrapper) Write(p []byte) (int, error) {
+ return w.fw.Write(p)
+}
+
+func (w *flateWrapper) Close() error {
+ err1 := w.fw.Flush()
+ if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
+ return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
+ }
+ err2 := w.tw.w.Close()
+ if err1 != nil {
+ return err1
+ }
+ return err2
+}
diff --git a/vendor/github.com/gorilla/websocket/compression_test.go b/vendor/github.com/gorilla/websocket/compression_test.go
new file mode 100644
index 0000000..cad70fb
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/compression_test.go
@@ -0,0 +1,31 @@
+package websocket
+
+import (
+ "bytes"
+ "io"
+ "testing"
+)
+
+type nopCloser struct{ io.Writer }
+
+func (nopCloser) Close() error { return nil }
+
+func TestTruncWriter(t *testing.T) {
+ const data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijlkmnopqrstuvwxyz987654321"
+ for n := 1; n <= 10; n++ {
+ var b bytes.Buffer
+ w := &truncWriter{w: nopCloser{&b}}
+ p := []byte(data)
+ for len(p) > 0 {
+ m := len(p)
+ if m > n {
+ m = n
+ }
+ w.Write(p[:m])
+ p = p[m:]
+ }
+ if b.String() != data[:len(data)-len(w.p)] {
+ t.Errorf("%d: %q", n, b.String())
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go
index eff26c6..eb4334e 100644
--- a/vendor/github.com/gorilla/websocket/conn.go
+++ b/vendor/github.com/gorilla/websocket/conn.go
@@ -14,14 +14,23 @@ import (
"net"
"strconv"
"time"
+ "unicode/utf8"
)
const (
+ // Frame header byte 0 bits from Section 5.2 of RFC 6455
+ finalBit = 1 << 7
+ rsv1Bit = 1 << 6
+ rsv2Bit = 1 << 5
+ rsv3Bit = 1 << 4
+
+ // Frame header byte 1 bits from Section 5.2 of RFC 6455
+ maskBit = 1 << 7
+
maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask
maxControlFramePayloadSize = 125
- finalBit = 1 << 7
- maskBit = 1 << 7
- writeWait = time.Second
+
+ writeWait = time.Second
defaultReadBufferSize = 4096
defaultWriteBufferSize = 4096
@@ -43,6 +52,8 @@ const (
CloseMessageTooBig = 1009
CloseMandatoryExtension = 1010
CloseInternalServerErr = 1011
+ CloseServiceRestart = 1012
+ CloseTryAgainLater = 1013
CloseTLSHandshake = 1015
)
@@ -184,6 +195,29 @@ func isData(frameType int) bool {
return frameType == TextMessage || frameType == BinaryMessage
}
+var validReceivedCloseCodes = map[int]bool{
+ // see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
+
+ CloseNormalClosure: true,
+ CloseGoingAway: true,
+ CloseProtocolError: true,
+ CloseUnsupportedData: true,
+ CloseNoStatusReceived: false,
+ CloseAbnormalClosure: false,
+ CloseInvalidFramePayloadData: true,
+ ClosePolicyViolation: true,
+ CloseMessageTooBig: true,
+ CloseMandatoryExtension: true,
+ CloseInternalServerErr: true,
+ CloseServiceRestart: true,
+ CloseTryAgainLater: true,
+ CloseTLSHandshake: false,
+}
+
+func isValidReceivedCloseCode(code int) bool {
+ return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
+}
+
func maskBytes(key [4]byte, pos int, b []byte) int {
for i := range b {
b[i] ^= key[pos&3]
@@ -204,24 +238,26 @@ type Conn struct {
subprotocol string
// Write fields
- mu chan bool // used as mutex to protect write to conn and closeSent
- closeSent bool // true if close message was sent
-
- // Message writer fields.
+ mu chan bool // used as mutex to protect write to conn and closeSent
+ closeSent bool // whether close message was sent
writeErr error
writeBuf []byte // frame is constructed in this buffer.
writePos int // end of data in writeBuf.
writeFrameType int // type of the current frame.
- writeSeq int // incremented to invalidate message writers.
writeDeadline time.Time
- isWriting bool // for best-effort concurrent write detection
+ messageWriter *messageWriter // the current low-level message writer
+ writer io.WriteCloser // the current writer returned to the application
+ isWriting bool // for best-effort concurrent write detection
+
+ enableWriteCompression bool
+ writeCompress bool // whether next call to flushFrame should set RSV1
+ newCompressionWriter func(io.WriteCloser) (io.WriteCloser, error)
// Read fields
readErr error
br *bufio.Reader
readRemaining int64 // bytes remaining in current frame.
readFinal bool // true the current message has more frames.
- readSeq int // incremented to invalidate message readers.
readLength int64 // Message size.
readLimit int64 // Maximum message size.
readMaskPos int
@@ -229,6 +265,10 @@ type Conn struct {
handlePong func(string) error
handlePing func(string) error
readErrCount int
+ messageReader *messageReader // the current low-level reader
+
+ readDecompress bool // whether last read frame had RSV1 set
+ newDecompressionReader func(io.Reader) io.Reader
}
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
@@ -238,19 +278,23 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int)
if readBufferSize == 0 {
readBufferSize = defaultReadBufferSize
}
+ if readBufferSize < maxControlFramePayloadSize {
+ readBufferSize = maxControlFramePayloadSize
+ }
if writeBufferSize == 0 {
writeBufferSize = defaultWriteBufferSize
}
c := &Conn{
- isServer: isServer,
- br: bufio.NewReaderSize(conn, readBufferSize),
- conn: conn,
- mu: mu,
- readFinal: true,
- writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize),
- writeFrameType: noFrame,
- writePos: maxFrameHeaderSize,
+ isServer: isServer,
+ br: bufio.NewReaderSize(conn, readBufferSize),
+ conn: conn,
+ mu: mu,
+ readFinal: true,
+ writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize),
+ writeFrameType: noFrame,
+ writePos: maxFrameHeaderSize,
+ enableWriteCompression: true,
}
c.SetPingHandler(nil)
c.SetPongHandler(nil)
@@ -364,8 +408,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
return hideTempErr(err)
}
-// NextWriter returns a writer for the next message to send. The writer's
-// Close method flushes the complete message to the network.
+// NextWriter returns a writer for the next message to send. The writer's Close
+// method flushes the complete message to the network.
//
// There can be at most one open writer on a connection. NextWriter closes the
// previous writer if the application has not already done so.
@@ -374,8 +418,12 @@ func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
return nil, c.writeErr
}
- if c.writeFrameType != noFrame {
- if err := c.flushFrame(true, nil); err != nil {
+ // Close previous writer if not already closed by the application. It's
+ // probably better to return an error in this situation, but we cannot
+ // change this without breaking existing applications.
+ if c.writer != nil {
+ err := c.writer.Close()
+ if err != nil {
return nil, err
}
}
@@ -385,16 +433,32 @@ func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
}
c.writeFrameType = messageType
- return messageWriter{c, c.writeSeq}, nil
+ c.messageWriter = &messageWriter{c}
+
+ var w io.WriteCloser = c.messageWriter
+ if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
+ c.writeCompress = true
+ var err error
+ w, err = c.newCompressionWriter(w)
+ if err != nil {
+ c.writer.Close()
+ return nil, err
+ }
+ }
+
+ return w, nil
}
+// flushFrame writes buffered data and extra as a frame to the network. The
+// final argument indicates that this is the last frame in the message.
func (c *Conn) flushFrame(final bool, extra []byte) error {
length := c.writePos - maxFrameHeaderSize + len(extra)
// Check for invalid control frames.
if isControl(c.writeFrameType) &&
(!final || length > maxControlFramePayloadSize) {
- c.writeSeq++
+ c.messageWriter = nil
+ c.writer = nil
c.writeFrameType = noFrame
c.writePos = maxFrameHeaderSize
return errInvalidControlFrame
@@ -404,6 +468,11 @@ func (c *Conn) flushFrame(final bool, extra []byte) error {
if final {
b0 |= finalBit
}
+ if c.writeCompress {
+ b0 |= rsv1Bit
+ }
+ c.writeCompress = false
+
b1 := byte(0)
if !c.isServer {
b1 |= maskBit
@@ -462,20 +531,18 @@ func (c *Conn) flushFrame(final bool, extra []byte) error {
c.writePos = maxFrameHeaderSize
c.writeFrameType = continuationFrame
if final {
- c.writeSeq++
+ c.messageWriter = nil
+ c.writer = nil
c.writeFrameType = noFrame
}
return c.writeErr
}
-type messageWriter struct {
- c *Conn
- seq int
-}
+type messageWriter struct{ c *Conn }
-func (w messageWriter) err() error {
+func (w *messageWriter) err() error {
c := w.c
- if c.writeSeq != w.seq {
+ if c.messageWriter != w {
return errWriteClosed
}
if c.writeErr != nil {
@@ -484,7 +551,7 @@ func (w messageWriter) err() error {
return nil
}
-func (w messageWriter) ncopy(max int) (int, error) {
+func (w *messageWriter) ncopy(max int) (int, error) {
n := len(w.c.writeBuf) - w.c.writePos
if n <= 0 {
if err := w.c.flushFrame(false, nil); err != nil {
@@ -498,14 +565,14 @@ func (w messageWriter) ncopy(max int) (int, error) {
return n, nil
}
-func (w messageWriter) write(final bool, p []byte) (int, error) {
+func (w *messageWriter) Write(p []byte) (int, error) {
if err := w.err(); err != nil {
return 0, err
}
if len(p) > 2*len(w.c.writeBuf) && w.c.isServer {
// Don't buffer large messages.
- err := w.c.flushFrame(final, p)
+ err := w.c.flushFrame(false, p)
if err != nil {
return 0, err
}
@@ -525,11 +592,7 @@ func (w messageWriter) write(final bool, p []byte) (int, error) {
return nn, nil
}
-func (w messageWriter) Write(p []byte) (int, error) {
- return w.write(false, p)
-}
-
-func (w messageWriter) WriteString(p string) (int, error) {
+func (w *messageWriter) WriteString(p string) (int, error) {
if err := w.err(); err != nil {
return 0, err
}
@@ -547,7 +610,7 @@ func (w messageWriter) WriteString(p string) (int, error) {
return nn, nil
}
-func (w messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
+func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
if err := w.err(); err != nil {
return 0, err
}
@@ -572,7 +635,7 @@ func (w messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
return nn, err
}
-func (w messageWriter) Close() error {
+func (w *messageWriter) Close() error {
if err := w.err(); err != nil {
return err
}
@@ -582,20 +645,22 @@ func (w messageWriter) Close() error {
// WriteMessage is a helper method for getting a writer using NextWriter,
// writing the message and closing the writer.
func (c *Conn) WriteMessage(messageType int, data []byte) error {
- wr, err := c.NextWriter(messageType)
+ w, err := c.NextWriter(messageType)
if err != nil {
return err
}
- w := wr.(messageWriter)
- if _, err := w.write(true, data); err != nil {
+ if _, ok := w.(*messageWriter); ok && c.isServer {
+ // Optimize write as a single frame.
+ n := copy(c.writeBuf[c.writePos:], data)
+ c.writePos += n
+ data = data[n:]
+ err = c.flushFrame(true, data)
return err
}
- if c.writeSeq == w.seq {
- if err := c.flushFrame(true, nil); err != nil {
- return err
- }
+ if _, err = w.Write(data); err != nil {
+ return err
}
- return nil
+ return w.Close()
}
// SetWriteDeadline sets the write deadline on the underlying network
@@ -609,22 +674,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
// Read methods
-// readFull is like io.ReadFull except that io.EOF is never returned.
-func (c *Conn) readFull(p []byte) (err error) {
- var n int
- for n < len(p) && err == nil {
- var nn int
- nn, err = c.br.Read(p[n:])
- n += nn
- }
- if n == len(p) {
- err = nil
- } else if err == io.EOF {
- err = errUnexpectedEOF
- }
- return
-}
-
func (c *Conn) advanceFrame() (int, error) {
// 1. Skip remainder of previous frame.
@@ -637,19 +686,24 @@ func (c *Conn) advanceFrame() (int, error) {
// 2. Read and parse first two bytes of frame header.
- var b [8]byte
- if err := c.readFull(b[:2]); err != nil {
+ p, err := c.read(2)
+ if err != nil {
return noFrame, err
}
- final := b[0]&finalBit != 0
- frameType := int(b[0] & 0xf)
- reserved := int((b[0] >> 4) & 0x7)
- mask := b[1]&maskBit != 0
- c.readRemaining = int64(b[1] & 0x7f)
+ final := p[0]&finalBit != 0
+ frameType := int(p[0] & 0xf)
+ mask := p[1]&maskBit != 0
+ c.readRemaining = int64(p[1] & 0x7f)
- if reserved != 0 {
- return noFrame, c.handleProtocolError("unexpected reserved bits " + strconv.Itoa(reserved))
+ c.readDecompress = false
+ if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
+ c.readDecompress = true
+ p[0] &^= rsv1Bit
+ }
+
+ if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 {
+ return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16))
}
switch frameType {
@@ -678,15 +732,17 @@ func (c *Conn) advanceFrame() (int, error) {
switch c.readRemaining {
case 126:
- if err := c.readFull(b[:2]); err != nil {
+ p, err := c.read(2)
+ if err != nil {
return noFrame, err
}
- c.readRemaining = int64(binary.BigEndian.Uint16(b[:2]))
+ c.readRemaining = int64(binary.BigEndian.Uint16(p))
case 127:
- if err := c.readFull(b[:8]); err != nil {
+ p, err := c.read(8)
+ if err != nil {
return noFrame, err
}
- c.readRemaining = int64(binary.BigEndian.Uint64(b[:8]))
+ c.readRemaining = int64(binary.BigEndian.Uint64(p))
}
// 4. Handle frame masking.
@@ -697,9 +753,11 @@ func (c *Conn) advanceFrame() (int, error) {
if mask {
c.readMaskPos = 0
- if err := c.readFull(c.readMaskKey[:]); err != nil {
+ p, err := c.read(len(c.readMaskKey))
+ if err != nil {
return noFrame, err
}
+ copy(c.readMaskKey[:], p)
}
// 5. For text and binary messages, enforce read limit and return.
@@ -719,9 +777,9 @@ func (c *Conn) advanceFrame() (int, error) {
var payload []byte
if c.readRemaining > 0 {
- payload = make([]byte, c.readRemaining)
+ payload, err = c.read(int(c.readRemaining))
c.readRemaining = 0
- if err := c.readFull(payload); err != nil {
+ if err != nil {
return noFrame, err
}
if c.isServer {
@@ -747,7 +805,13 @@ func (c *Conn) advanceFrame() (int, error) {
if len(payload) >= 2 {
echoMessage = payload[:2]
closeCode = int(binary.BigEndian.Uint16(payload))
+ if !isValidReceivedCloseCode(closeCode) {
+ return noFrame, c.handleProtocolError("invalid close code")
+ }
closeText = string(payload[2:])
+ if !utf8.ValidString(closeText) {
+ return noFrame, c.handleProtocolError("invalid utf8 payload in close frame")
+ }
}
c.WriteControl(CloseMessage, echoMessage, time.Now().Add(writeWait))
return noFrame, &CloseError{Code: closeCode, Text: closeText}
@@ -773,7 +837,7 @@ func (c *Conn) handleProtocolError(message string) error {
// this method return the same error.
func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
- c.readSeq++
+ c.messageReader = nil
c.readLength = 0
for c.readErr == nil {
@@ -783,7 +847,12 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
break
}
if frameType == TextMessage || frameType == BinaryMessage {
- return frameType, messageReader{c, c.readSeq}, nil
+ c.messageReader = &messageReader{c}
+ var r io.Reader = c.messageReader
+ if c.readDecompress {
+ r = c.newDecompressionReader(r)
+ }
+ return frameType, r, nil
}
}
@@ -798,48 +867,48 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
return noFrame, nil, c.readErr
}
-type messageReader struct {
- c *Conn
- seq int
-}
+type messageReader struct{ c *Conn }
-func (r messageReader) Read(b []byte) (int, error) {
-
- if r.seq != r.c.readSeq {
+func (r *messageReader) Read(b []byte) (int, error) {
+ c := r.c
+ if c.messageReader != r {
return 0, io.EOF
}
- for r.c.readErr == nil {
+ for c.readErr == nil {
- if r.c.readRemaining > 0 {
- if int64(len(b)) > r.c.readRemaining {
- b = b[:r.c.readRemaining]
+ if c.readRemaining > 0 {
+ if int64(len(b)) > c.readRemaining {
+ b = b[:c.readRemaining]
}
- n, err := r.c.br.Read(b)
- r.c.readErr = hideTempErr(err)
- if r.c.isServer {
- r.c.readMaskPos = maskBytes(r.c.readMaskKey, r.c.readMaskPos, b[:n])
+ n, err := c.br.Read(b)
+ c.readErr = hideTempErr(err)
+ if c.isServer {
+ c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
}
- r.c.readRemaining -= int64(n)
- return n, r.c.readErr
+ c.readRemaining -= int64(n)
+ if c.readRemaining > 0 && c.readErr == io.EOF {
+ c.readErr = errUnexpectedEOF
+ }
+ return n, c.readErr
}
- if r.c.readFinal {
- r.c.readSeq++
+ if c.readFinal {
+ c.messageReader = nil
return 0, io.EOF
}
- frameType, err := r.c.advanceFrame()
+ frameType, err := c.advanceFrame()
switch {
case err != nil:
- r.c.readErr = hideTempErr(err)
+ c.readErr = hideTempErr(err)
case frameType == TextMessage || frameType == BinaryMessage:
- r.c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
+ c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader")
}
}
- err := r.c.readErr
- if err == io.EOF && r.seq == r.c.readSeq {
+ err := c.readErr
+ if err == io.EOF && c.messageReader == r {
err = errUnexpectedEOF
}
return 0, err
@@ -872,6 +941,11 @@ func (c *Conn) SetReadLimit(limit int64) {
c.readLimit = limit
}
+// PingHandler returns the current ping handler
+func (c *Conn) PingHandler() func(appData string) error {
+ return c.handlePing
+}
+
// SetPingHandler sets the handler for ping messages received from the peer.
// The appData argument to h is the PING frame application data. The default
// ping handler sends a pong to the peer.
@@ -890,6 +964,11 @@ func (c *Conn) SetPingHandler(h func(appData string) error) {
c.handlePing = h
}
+// PongHandler returns the current pong handler
+func (c *Conn) PongHandler() func(appData string) error {
+ return c.handlePong
+}
+
// SetPongHandler sets the handler for pong messages received from the peer.
// The appData argument to h is the PONG frame application data. The default
// pong handler does nothing.
diff --git a/vendor/github.com/gorilla/websocket/conn_read.go b/vendor/github.com/gorilla/websocket/conn_read.go
new file mode 100644
index 0000000..1ea1505
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/conn_read.go
@@ -0,0 +1,18 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build go1.5
+
+package websocket
+
+import "io"
+
+func (c *Conn) read(n int) ([]byte, error) {
+ p, err := c.br.Peek(n)
+ if err == io.EOF {
+ err = errUnexpectedEOF
+ }
+ c.br.Discard(len(p))
+ return p, err
+}
diff --git a/vendor/github.com/gorilla/websocket/conn_read_legacy.go b/vendor/github.com/gorilla/websocket/conn_read_legacy.go
new file mode 100644
index 0000000..018541c
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/conn_read_legacy.go
@@ -0,0 +1,21 @@
+// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !go1.5
+
+package websocket
+
+import "io"
+
+func (c *Conn) read(n int) ([]byte, error) {
+ p, err := c.br.Peek(n)
+ if err == io.EOF {
+ err = errUnexpectedEOF
+ }
+ if len(p) > 0 {
+ // advance over the bytes just read
+ io.ReadFull(c.br, p)
+ }
+ return p, err
+}
diff --git a/vendor/github.com/gorilla/websocket/conn_test.go b/vendor/github.com/gorilla/websocket/conn_test.go
index 04c8dd8..0243c11 100644
--- a/vendor/github.com/gorilla/websocket/conn_test.go
+++ b/vendor/github.com/gorilla/websocket/conn_test.go
@@ -174,6 +174,41 @@ func TestCloseBeforeFinalFrame(t *testing.T) {
}
}
+func TestEOFWithinFrame(t *testing.T) {
+ const bufSize = 64
+
+ for n := 0; ; n++ {
+ var b bytes.Buffer
+ wc := newConn(fakeNetConn{Reader: nil, Writer: &b}, false, 1024, 1024)
+ rc := newConn(fakeNetConn{Reader: &b, Writer: nil}, true, 1024, 1024)
+
+ w, _ := wc.NextWriter(BinaryMessage)
+ w.Write(make([]byte, bufSize))
+ w.Close()
+
+ if n >= b.Len() {
+ break
+ }
+ b.Truncate(n)
+
+ op, r, err := rc.NextReader()
+ if err == errUnexpectedEOF {
+ continue
+ }
+ if op != BinaryMessage || err != nil {
+ t.Fatalf("%d: NextReader() returned %d, %v", n, op, err)
+ }
+ _, err = io.Copy(ioutil.Discard, r)
+ if err != errUnexpectedEOF {
+ t.Fatalf("%d: io.Copy() returned %v, want %v", n, err, errUnexpectedEOF)
+ }
+ _, _, err = rc.NextReader()
+ if err != errUnexpectedEOF {
+ t.Fatalf("%d: NextReader() returned %v, want %v", n, err, errUnexpectedEOF)
+ }
+ }
+}
+
func TestEOFBeforeFinalFrame(t *testing.T) {
const bufSize = 512
diff --git a/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go
index 499b03d..c901a7a 100644
--- a/vendor/github.com/gorilla/websocket/doc.go
+++ b/vendor/github.com/gorilla/websocket/doc.go
@@ -85,18 +85,22 @@
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
// methods to send a control message to the peer.
//
-// Connections handle received ping and pong messages by invoking callback
-// functions set with SetPingHandler and SetPongHandler methods. The default
-// ping handler sends a pong to the client. The callback functions can be
-// invoked from the NextReader, ReadMessage or the message Read method.
-//
// Connections handle received close messages by sending a close message to the
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
// message Read method.
//
-// The application must read the connection to process ping and close messages
-// sent from the peer. If the application is not otherwise interested in
-// messages from the peer, then the application should start a goroutine to
+// Connections handle received ping and pong messages by invoking callback
+// functions set with SetPingHandler and SetPongHandler methods. The callback
+// functions are called from the NextReader, ReadMessage and the message Read
+// methods.
+//
+// The default ping handler sends a pong to the peer. The application's reading
+// goroutine can block for a short time while the handler writes the pong data
+// to the connection.
+//
+// The application must read the connection to process ping, pong and close
+// messages sent from the peer. If the application is not otherwise interested
+// in messages from the peer, then the application should start a goroutine to
// read and discard messages from the peer. A simple example is:
//
// func readLoop(c *websocket.Conn) {
diff --git a/vendor/github.com/gorilla/websocket/example_test.go b/vendor/github.com/gorilla/websocket/example_test.go
index ed22c4a..96449ea 100644
--- a/vendor/github.com/gorilla/websocket/example_test.go
+++ b/vendor/github.com/gorilla/websocket/example_test.go
@@ -12,6 +12,11 @@ import (
"github.com/gorilla/websocket"
)
+var (
+ c *websocket.Conn
+ req *http.Request
+)
+
// The websocket.IsUnexpectedCloseError function is useful for identifying
// application and protocol errors.
//
@@ -20,7 +25,8 @@ import (
// only expected close message from the client has the code
// websocket.CloseGoingAway. All other other close messages are likely the
// result of an application or protocol error and are logged to aid debugging.
-func ExampleIsUnexpectedCloseError(err error, c *websocket.Conn, req *http.Request) {
+func ExampleIsUnexpectedCloseError() {
+
for {
messageType, p, err := c.ReadMessage()
if err != nil {
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/README.md b/vendor/github.com/gorilla/websocket/examples/chat/README.md
index 5df3cf1..f0c365c 100644
--- a/vendor/github.com/gorilla/websocket/examples/chat/README.md
+++ b/vendor/github.com/gorilla/websocket/examples/chat/README.md
@@ -1,8 +1,8 @@
# Chat Example
This application shows how to use use the
-[websocket](https://github.com/gorilla/websocket) package and
-[jQuery](http://jquery.com) to implement a simple web chat application.
+[websocket](https://github.com/gorilla/websocket) package to implement a simple
+web chat application.
## Running the example
@@ -18,3 +18,74 @@ using the following commands.
$ go run *.go
To use the chat example, open http://localhost:8080/ in your browser.
+
+## Server
+
+The server application defines two types, `Client` and `Hub`. The server
+creates an instance of the `Client` type for each websocket connection. A
+`Client` acts as an intermediary between the websocket connection and a single
+instance of the `Hub` type. The `Hub` maintains a set of registered clients and
+broadcasts messages to the clients.
+
+The application runs one goroutine for the `Hub` and two goroutines for each
+`Client`. The goroutines communicate with each other using channels. The `Hub`
+has channels for registering clients, unregistering clients and broadcasting
+messages. A `Client` has a buffered channel of outbound messages. One of the
+client's goroutines reads messages from this channel and writes the messages to
+the websocket. The other client goroutine reads messages from the websocket and
+sends them to the hub.
+
+### Hub
+
+The code for the `Hub` type is in
+[hub.go](https://github.com/gorilla/websocket/blob/master/examples/chat/hub.go).
+The application's `main` function starts the hub's `run` method as a goroutine.
+Clients send requests to the hub using the `register`, `unregister` and
+`broadcast` channels.
+
+The hub registers clients by adding the client pointer as a key in the
+`clients` map. The map value is always true.
+
+The unregister code is a little more complicated. In addition to deleting the
+client pointer from the `clients` map, the hub closes the clients's `send`
+channel to signal the client that no more messages will be sent to the client.
+
+The hub handles messages by looping over the registered clients and sending the
+message to the client's `send` channel. If the client's `send` buffer is full,
+then the hub assumes that the client is dead or stuck. In this case, the hub
+unregisters the client and closes the websocket.
+
+### Client
+
+The code for the `Client` type is in [client.go](https://github.com/gorilla/websocket/blob/master/examples/chat/client.go).
+
+The `wsHandler` function is registered by the application's `main` function as
+an HTTP handler. The handler upgrades the HTTP connection to the WebSocket
+protocol, creates a client, registers the client with the hub and schedules the
+client to be unregistered using a defer statement.
+
+Next, the HTTP handler starts the client's `writePump` method as a goroutine.
+This method transfers messages from the client's send channel to the websocket
+connection. The writer method exits when the channel is closed by the hub or
+there's an error writing to the websocket connection.
+
+Finally, the HTTP handler calls the client's `readPump` method. This method
+transfers inbound messages from the websocket to the hub.
+
+## Frontend
+
+The frontend code is in [home.html](https://github.com/gorilla/websocket/blob/master/examples/chat/home.html).
+
+On document load, the script checks for websocket functionality in the browser.
+If websocket functionality is available, then the script opens a connection to
+the server and registers a callback to handle messages from the server. The
+callback appends the message to the chat log using the appendLog function.
+
+To allow the user to manually scroll through the chat log without interruption
+from new messages, the `appendLog` function checks the scroll position before
+adding new content. If the chat log is scrolled to the bottom, then the
+function scrolls new content into view after adding the content. Otherwise, the
+scroll position is not changed.
+
+The form handler writes the user input to the websocket and clears the input
+field.
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/conn.go b/vendor/github.com/gorilla/websocket/examples/chat/client.go
similarity index 52%
rename from vendor/github.com/gorilla/websocket/examples/chat/conn.go
rename to vendor/github.com/gorilla/websocket/examples/chat/client.go
index 40fd38c..c3c518d 100644
--- a/vendor/github.com/gorilla/websocket/examples/chat/conn.go
+++ b/vendor/github.com/gorilla/websocket/examples/chat/client.go
@@ -5,10 +5,12 @@
package main
import (
- "github.com/gorilla/websocket"
+ "bytes"
"log"
"net/http"
"time"
+
+ "github.com/gorilla/websocket"
)
const (
@@ -25,62 +27,86 @@ const (
maxMessageSize = 512
)
+var (
+ newline = []byte{'\n'}
+ space = []byte{' '}
+)
+
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
-// connection is an middleman between the websocket connection and the hub.
-type connection struct {
+// Client is an middleman between the websocket connection and the hub.
+type Client struct {
+ hub *Hub
+
// The websocket connection.
- ws *websocket.Conn
+ conn *websocket.Conn
// Buffered channel of outbound messages.
send chan []byte
}
// readPump pumps messages from the websocket connection to the hub.
-func (c *connection) readPump() {
+func (c *Client) readPump() {
defer func() {
- h.unregister <- c
- c.ws.Close()
+ c.hub.unregister <- c
+ c.conn.Close()
}()
- c.ws.SetReadLimit(maxMessageSize)
- c.ws.SetReadDeadline(time.Now().Add(pongWait))
- c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ c.conn.SetReadLimit(maxMessageSize)
+ c.conn.SetReadDeadline(time.Now().Add(pongWait))
+ c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
- _, message, err := c.ws.ReadMessage()
+ _, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
log.Printf("error: %v", err)
}
break
}
- h.broadcast <- message
+ message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
+ c.hub.broadcast <- message
}
}
// write writes a message with the given message type and payload.
-func (c *connection) write(mt int, payload []byte) error {
- c.ws.SetWriteDeadline(time.Now().Add(writeWait))
- return c.ws.WriteMessage(mt, payload)
+func (c *Client) write(mt int, payload []byte) error {
+ c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+ return c.conn.WriteMessage(mt, payload)
}
// writePump pumps messages from the hub to the websocket connection.
-func (c *connection) writePump() {
+func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
- c.ws.Close()
+ c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
if !ok {
+ // The hub closed the channel.
c.write(websocket.CloseMessage, []byte{})
return
}
- if err := c.write(websocket.TextMessage, message); err != nil {
+
+ c.conn.SetWriteDeadline(time.Now().Add(writeWait))
+ w, err := c.conn.NextWriter(websocket.TextMessage)
+ if err != nil {
+ return
+ }
+ w.Write(message)
+
+ // Add queued chat messages to the current websocket message.
+ n := len(c.send)
+ for i := 0; i < n; i++ {
+ w.Write(newline)
+ w.Write(<-c.send)
+ }
+
+ if err := w.Close(); err != nil {
return
}
case <-ticker.C:
@@ -92,14 +118,14 @@ func (c *connection) writePump() {
}
// serveWs handles websocket requests from the peer.
-func serveWs(w http.ResponseWriter, r *http.Request) {
- ws, err := upgrader.Upgrade(w, r, nil)
+func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
+ conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
- c := &connection{send: make(chan []byte, 256), ws: ws}
- h.register <- c
- go c.writePump()
- c.readPump()
+ client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
+ client.hub.register <- client
+ go client.writePump()
+ client.readPump()
}
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/home.html b/vendor/github.com/gorilla/websocket/examples/chat/home.html
index 2959922..7262918 100644
--- a/vendor/github.com/gorilla/websocket/examples/chat/home.html
+++ b/vendor/github.com/gorilla/websocket/examples/chat/home.html
@@ -2,47 +2,53 @@
Chat Example
-