summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/gorilla
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2016-05-12 23:56:07 -0400
committerChristopher Speller <crspeller@gmail.com>2016-05-12 23:56:07 -0400
commit38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8 (patch)
treea4fde09672192b97d453ad605b030bd5a10c5a45 /vendor/github.com/gorilla
parent84d2482ddbff9564c9ad75b2d30af66e3ddfd44d (diff)
downloadchat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.gz
chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.bz2
chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.zip
Moving to glide
Diffstat (limited to 'vendor/github.com/gorilla')
-rw-r--r--vendor/github.com/gorilla/context/context_test.go161
-rw-r--r--vendor/github.com/gorilla/handlers/canonical_test.go127
-rw-r--r--vendor/github.com/gorilla/handlers/compress_test.go154
-rw-r--r--vendor/github.com/gorilla/handlers/cors_test.go336
-rw-r--r--vendor/github.com/gorilla/handlers/handlers_test.go354
-rw-r--r--vendor/github.com/gorilla/handlers/proxy_headers_test.go100
-rw-r--r--vendor/github.com/gorilla/handlers/recovery_test.go44
-rw-r--r--vendor/github.com/gorilla/mux/bench_test.go49
-rw-r--r--vendor/github.com/gorilla/mux/mux_test.go1471
-rw-r--r--vendor/github.com/gorilla/mux/old_test.go710
-rw-r--r--vendor/github.com/gorilla/websocket/bench_test.go19
-rw-r--r--vendor/github.com/gorilla/websocket/client_server_test.go451
-rw-r--r--vendor/github.com/gorilla/websocket/client_test.go72
-rw-r--r--vendor/github.com/gorilla/websocket/conn_test.go402
-rw-r--r--vendor/github.com/gorilla/websocket/example_test.go46
-rw-r--r--vendor/github.com/gorilla/websocket/examples/autobahn/README.md13
-rw-r--r--vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json14
-rw-r--r--vendor/github.com/gorilla/websocket/examples/autobahn/server.go246
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/README.md20
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/conn.go105
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/home.html92
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/hub.go51
-rw-r--r--vendor/github.com/gorilla/websocket/examples/chat/main.go39
-rw-r--r--vendor/github.com/gorilla/websocket/examples/command/README.md19
-rw-r--r--vendor/github.com/gorilla/websocket/examples/command/home.html96
-rw-r--r--vendor/github.com/gorilla/websocket/examples/command/main.go188
-rw-r--r--vendor/github.com/gorilla/websocket/examples/echo/README.md17
-rw-r--r--vendor/github.com/gorilla/websocket/examples/echo/client.go81
-rw-r--r--vendor/github.com/gorilla/websocket/examples/echo/server.go132
-rw-r--r--vendor/github.com/gorilla/websocket/examples/filewatch/README.md9
-rw-r--r--vendor/github.com/gorilla/websocket/examples/filewatch/main.go193
-rw-r--r--vendor/github.com/gorilla/websocket/json_test.go119
-rw-r--r--vendor/github.com/gorilla/websocket/server_test.go51
-rw-r--r--vendor/github.com/gorilla/websocket/util_test.go34
34 files changed, 6015 insertions, 0 deletions
diff --git a/vendor/github.com/gorilla/context/context_test.go b/vendor/github.com/gorilla/context/context_test.go
new file mode 100644
index 000000000..9814c501e
--- /dev/null
+++ b/vendor/github.com/gorilla/context/context_test.go
@@ -0,0 +1,161 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+ "net/http"
+ "testing"
+)
+
+type keyType int
+
+const (
+ key1 keyType = iota
+ key2
+)
+
+func TestContext(t *testing.T) {
+ assertEqual := func(val interface{}, exp interface{}) {
+ if val != exp {
+ t.Errorf("Expected %v, got %v.", exp, val)
+ }
+ }
+
+ r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+ emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+
+ // Get()
+ assertEqual(Get(r, key1), nil)
+
+ // Set()
+ Set(r, key1, "1")
+ assertEqual(Get(r, key1), "1")
+ assertEqual(len(data[r]), 1)
+
+ Set(r, key2, "2")
+ assertEqual(Get(r, key2), "2")
+ assertEqual(len(data[r]), 2)
+
+ //GetOk
+ value, ok := GetOk(r, key1)
+ assertEqual(value, "1")
+ assertEqual(ok, true)
+
+ value, ok = GetOk(r, "not exists")
+ assertEqual(value, nil)
+ assertEqual(ok, false)
+
+ Set(r, "nil value", nil)
+ value, ok = GetOk(r, "nil value")
+ assertEqual(value, nil)
+ assertEqual(ok, true)
+
+ // GetAll()
+ values := GetAll(r)
+ assertEqual(len(values), 3)
+
+ // GetAll() for empty request
+ values = GetAll(emptyR)
+ if values != nil {
+ t.Error("GetAll didn't return nil value for invalid request")
+ }
+
+ // GetAllOk()
+ values, ok = GetAllOk(r)
+ assertEqual(len(values), 3)
+ assertEqual(ok, true)
+
+ // GetAllOk() for empty request
+ values, ok = GetAllOk(emptyR)
+ assertEqual(value, nil)
+ assertEqual(ok, false)
+
+ // Delete()
+ Delete(r, key1)
+ assertEqual(Get(r, key1), nil)
+ assertEqual(len(data[r]), 2)
+
+ Delete(r, key2)
+ assertEqual(Get(r, key2), nil)
+ assertEqual(len(data[r]), 1)
+
+ // Clear()
+ Clear(r)
+ assertEqual(len(data), 0)
+}
+
+func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
+ <-wait
+ for i := 0; i < iterations; i++ {
+ Get(r, key)
+ }
+ done <- struct{}{}
+
+}
+
+func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
+ <-wait
+ for i := 0; i < iterations; i++ {
+ Set(r, key, value)
+ }
+ done <- struct{}{}
+
+}
+
+func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
+
+ b.StopTimer()
+ r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+ done := make(chan struct{})
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ wait := make(chan struct{})
+
+ for i := 0; i < numReaders; i++ {
+ go parallelReader(r, "test", iterations, wait, done)
+ }
+
+ for i := 0; i < numWriters; i++ {
+ go parallelWriter(r, "test", "123", iterations, wait, done)
+ }
+
+ close(wait)
+
+ for i := 0; i < numReaders+numWriters; i++ {
+ <-done
+ }
+
+ }
+
+}
+
+func BenchmarkMutexSameReadWrite1(b *testing.B) {
+ benchmarkMutex(b, 1, 1, 32)
+}
+func BenchmarkMutexSameReadWrite2(b *testing.B) {
+ benchmarkMutex(b, 2, 2, 32)
+}
+func BenchmarkMutexSameReadWrite4(b *testing.B) {
+ benchmarkMutex(b, 4, 4, 32)
+}
+func BenchmarkMutex1(b *testing.B) {
+ benchmarkMutex(b, 2, 8, 32)
+}
+func BenchmarkMutex2(b *testing.B) {
+ benchmarkMutex(b, 16, 4, 64)
+}
+func BenchmarkMutex3(b *testing.B) {
+ benchmarkMutex(b, 1, 2, 128)
+}
+func BenchmarkMutex4(b *testing.B) {
+ benchmarkMutex(b, 128, 32, 256)
+}
+func BenchmarkMutex5(b *testing.B) {
+ benchmarkMutex(b, 1024, 2048, 64)
+}
+func BenchmarkMutex6(b *testing.B) {
+ benchmarkMutex(b, 2048, 1024, 512)
+}
diff --git a/vendor/github.com/gorilla/handlers/canonical_test.go b/vendor/github.com/gorilla/handlers/canonical_test.go
new file mode 100644
index 000000000..615e4b056
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/canonical_test.go
@@ -0,0 +1,127 @@
+package handlers
+
+import (
+ "bufio"
+ "bytes"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+)
+
+func TestCleanHost(t *testing.T) {
+ tests := []struct {
+ in, want string
+ }{
+ {"www.google.com", "www.google.com"},
+ {"www.google.com foo", "www.google.com"},
+ {"www.google.com/foo", "www.google.com"},
+ {" first character is a space", ""},
+ }
+ for _, tt := range tests {
+ got := cleanHost(tt.in)
+ if tt.want != got {
+ t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want)
+ }
+ }
+}
+
+func TestCanonicalHost(t *testing.T) {
+ gorilla := "http://www.gorillatoolkit.org"
+
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "http://www.example.com/")
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ // Test a re-direct: should return a 302 Found.
+ CanonicalHost(gorilla, http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusFound {
+ t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusFound)
+ }
+
+ if rr.Header().Get("Location") != gorilla+r.URL.Path {
+ t.Fatalf("bad re-direct: got %q want %q", rr.Header().Get("Location"), gorilla+r.URL.Path)
+ }
+
+}
+
+func TestKeepsQueryString(t *testing.T) {
+ google := "https://www.google.com"
+
+ rr := httptest.NewRecorder()
+ querystring := url.Values{"q": {"golang"}, "format": {"json"}}.Encode()
+ r := newRequest("GET", "http://www.example.com/search?"+querystring)
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+ CanonicalHost(google, http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ want := google + r.URL.Path + "?" + querystring
+ if rr.Header().Get("Location") != want {
+ t.Fatalf("bad re-direct: got %q want %q", rr.Header().Get("Location"), want)
+ }
+}
+
+func TestBadDomain(t *testing.T) {
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "http://www.example.com/")
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ // Test a bad domain - should return 200 OK.
+ CanonicalHost("%", http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK)
+ }
+}
+
+func TestEmptyHost(t *testing.T) {
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "http://www.example.com/")
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ // Test a domain that returns an empty url.Host from url.Parse.
+ CanonicalHost("hello.com", http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK)
+ }
+}
+
+func TestHeaderWrites(t *testing.T) {
+ gorilla := "http://www.gorillatoolkit.org"
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ })
+
+ // Catch the log output to ensure we don't write multiple headers.
+ var b bytes.Buffer
+ buf := bufio.NewWriter(&b)
+ tl := log.New(buf, "test: ", log.Lshortfile)
+
+ srv := httptest.NewServer(
+ CanonicalHost(gorilla, http.StatusFound)(testHandler))
+ defer srv.Close()
+ srv.Config.ErrorLog = tl
+
+ _, err := http.Get(srv.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = buf.Flush()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // We rely on the error not changing: net/http does not export it.
+ if strings.Contains(b.String(), "multiple response.WriteHeader calls") {
+ t.Fatalf("re-direct did not return early: multiple header writes")
+ }
+}
diff --git a/vendor/github.com/gorilla/handlers/compress_test.go b/vendor/github.com/gorilla/handlers/compress_test.go
new file mode 100644
index 000000000..6f07f440d
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/compress_test.go
@@ -0,0 +1,154 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "bufio"
+ "io"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "strconv"
+ "testing"
+)
+
+var contentType = "text/plain; charset=utf-8"
+
+func compressedRequest(w *httptest.ResponseRecorder, compression string) {
+ CompressHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Length", strconv.Itoa(9*1024))
+ w.Header().Set("Content-Type", contentType)
+ for i := 0; i < 1024; i++ {
+ io.WriteString(w, "Gorilla!\n")
+ }
+ })).ServeHTTP(w, &http.Request{
+ Method: "GET",
+ Header: http.Header{
+ "Accept-Encoding": []string{compression},
+ },
+ })
+
+}
+
+func TestCompressHandlerNoCompression(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "")
+ if enc := w.HeaderMap.Get("Content-Encoding"); enc != "" {
+ t.Errorf("wrong content encoding, got %q want %q", enc, "")
+ }
+ if ct := w.HeaderMap.Get("Content-Type"); ct != contentType {
+ t.Errorf("wrong content type, got %q want %q", ct, contentType)
+ }
+ if w.Body.Len() != 1024*9 {
+ t.Errorf("wrong len, got %d want %d", w.Body.Len(), 1024*9)
+ }
+ if l := w.HeaderMap.Get("Content-Length"); l != "9216" {
+ t.Errorf("wrong content-length. got %q expected %d", l, 1024*9)
+ }
+}
+
+func TestCompressHandlerGzip(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "gzip")
+ if w.HeaderMap.Get("Content-Encoding") != "gzip" {
+ t.Errorf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip")
+ }
+ if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" {
+ t.Errorf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ }
+ if w.Body.Len() != 72 {
+ t.Errorf("wrong len, got %d want %d", w.Body.Len(), 72)
+ }
+ if l := w.HeaderMap.Get("Content-Length"); l != "" {
+ t.Errorf("wrong content-length. got %q expected %q", l, "")
+ }
+}
+
+func TestCompressHandlerDeflate(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "deflate")
+ if w.HeaderMap.Get("Content-Encoding") != "deflate" {
+ t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "deflate")
+ }
+ if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" {
+ t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ }
+ if w.Body.Len() != 54 {
+ t.Fatalf("wrong len, got %d want %d", w.Body.Len(), 54)
+ }
+}
+
+func TestCompressHandlerGzipDeflate(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "gzip, deflate ")
+ if w.HeaderMap.Get("Content-Encoding") != "gzip" {
+ t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip")
+ }
+ if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" {
+ t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ }
+}
+
+type fullyFeaturedResponseWriter struct{}
+
+// Header/Write/WriteHeader implement the http.ResponseWriter interface.
+func (fullyFeaturedResponseWriter) Header() http.Header {
+ return http.Header{}
+}
+func (fullyFeaturedResponseWriter) Write([]byte) (int, error) {
+ return 0, nil
+}
+func (fullyFeaturedResponseWriter) WriteHeader(int) {}
+
+// Flush implements the http.Flusher interface.
+func (fullyFeaturedResponseWriter) Flush() {}
+
+// Hijack implements the http.Hijacker interface.
+func (fullyFeaturedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ return nil, nil, nil
+}
+
+// CloseNotify implements the http.CloseNotifier interface.
+func (fullyFeaturedResponseWriter) CloseNotify() <-chan bool {
+ return nil
+}
+
+func TestCompressHandlerPreserveInterfaces(t *testing.T) {
+ // Compile time validation fullyFeaturedResponseWriter implements all the
+ // interfaces we're asserting in the test case below.
+ var (
+ _ http.Flusher = fullyFeaturedResponseWriter{}
+ _ http.CloseNotifier = fullyFeaturedResponseWriter{}
+ _ http.Hijacker = fullyFeaturedResponseWriter{}
+ )
+ var h http.Handler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ comp := r.Header.Get("Accept-Encoding")
+ if _, ok := rw.(*compressResponseWriter); !ok {
+ t.Fatalf("ResponseWriter wasn't wrapped by compressResponseWriter, got %T type", rw)
+ }
+ if _, ok := rw.(http.Flusher); !ok {
+ t.Errorf("ResponseWriter lost http.Flusher interface for %q", comp)
+ }
+ if _, ok := rw.(http.CloseNotifier); !ok {
+ t.Errorf("ResponseWriter lost http.CloseNotifier interface for %q", comp)
+ }
+ if _, ok := rw.(http.Hijacker); !ok {
+ t.Errorf("ResponseWriter lost http.Hijacker interface for %q", comp)
+ }
+ })
+ h = CompressHandler(h)
+ var (
+ rw fullyFeaturedResponseWriter
+ )
+ r, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatalf("Failed to create test request: %v", err)
+ }
+ r.Header.Set("Accept-Encoding", "gzip")
+ h.ServeHTTP(rw, r)
+
+ r.Header.Set("Accept-Encoding", "deflate")
+ h.ServeHTTP(rw, r)
+}
diff --git a/vendor/github.com/gorilla/handlers/cors_test.go b/vendor/github.com/gorilla/handlers/cors_test.go
new file mode 100644
index 000000000..c63913eee
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/cors_test.go
@@ -0,0 +1,336 @@
+package handlers
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestDefaultCORSHandlerReturnsOk(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusFound)
+ }
+}
+
+func TestDefaultCORSHandlerReturnsOkWithOrigin(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusFound)
+ }
+}
+
+func TestCORSHandlerIgnoreOptionsFallsThrough(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusTeapot)
+ })
+
+ CORS(IgnoreOptions())(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusTeapot {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusTeapot)
+ }
+}
+
+func TestCORSHandlerSetsExposedHeaders(t *testing.T) {
+ // Test default configuration.
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(ExposedHeaders([]string{"X-CORS-TEST"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsExposeHeadersHeader)
+ if header != "X-Cors-Test" {
+ t.Fatal("bad header: expected X-Cors-Test header, got empty header for method.")
+ }
+}
+
+func TestCORSHandlerUnsetRequestMethodForPreflightBadRequest(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedMethods([]string{"DELETE"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusBadRequest {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusBadRequest)
+ }
+}
+
+func TestCORSHandlerInvalidRequestMethodForPreflightMethodNotAllowed(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "DELETE")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusMethodNotAllowed {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusMethodNotAllowed)
+ }
+}
+
+func TestCORSHandlerOptionsRequestMustNotBePassedToNextHandler(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "GET")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("Options request must not be passed to next handler")
+ })
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+}
+
+func TestCORSHandlerAllowedMethodForPreflight(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "DELETE")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedMethods([]string{"DELETE"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowMethodsHeader)
+ if header != "DELETE" {
+ t.Fatalf("bad header: expected DELETE method header, got empty header.")
+ }
+}
+
+func TestCORSHandlerAllowMethodsNotSetForSimpleRequestPreflight(t *testing.T) {
+ for _, method := range defaultCorsMethods {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, method)
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowMethodsHeader)
+ if header != "" {
+ t.Fatalf("bad header: expected empty method header, got %s.", header)
+ }
+ }
+}
+
+func TestCORSHandlerAllowedHeaderNotSetForSimpleRequestPreflight(t *testing.T) {
+ for _, simpleHeader := range defaultCorsHeaders {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "GET")
+ r.Header.Set(corsRequestHeadersHeader, simpleHeader)
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowHeadersHeader)
+ if header != "" {
+ t.Fatalf("bad header: expected empty header, got %s.", header)
+ }
+ }
+}
+
+func TestCORSHandlerAllowedHeaderForPreflight(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "POST")
+ r.Header.Set(corsRequestHeadersHeader, "Content-Type")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedHeaders([]string{"Content-Type"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowHeadersHeader)
+ if header != "Content-Type" {
+ t.Fatalf("bad header: expected Content-Type header, got empty header.")
+ }
+}
+
+func TestCORSHandlerInvalidHeaderForPreflightForbidden(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "POST")
+ r.Header.Set(corsRequestHeadersHeader, "Content-Type")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusForbidden {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusForbidden)
+ }
+}
+
+func TestCORSHandlerMaxAgeForPreflight(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "POST")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(MaxAge(3500))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsMaxAgeHeader)
+ if header != "600" {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsMaxAgeHeader, "600", header)
+ }
+}
+
+func TestCORSHandlerAllowedCredentials(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowCredentials())(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowCredentialsHeader)
+ if header != "true" {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowCredentialsHeader, "true", header)
+ }
+}
+
+func TestCORSHandlerMultipleAllowOriginsSetsVaryHeader(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedOrigins([]string{r.URL.String(), "http://google.com"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsVaryHeader)
+ if header != corsOriginHeader {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsVaryHeader, corsOriginHeader, header)
+ }
+}
+
+func TestCORSWithMultipleHandlers(t *testing.T) {
+ var lastHandledBy string
+ corsMiddleware := CORS()
+
+ testHandler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ lastHandledBy = "testHandler1"
+ })
+ testHandler2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ lastHandledBy = "testHandler2"
+ })
+
+ r1 := newRequest("GET", "http://www.example.com/")
+ rr1 := httptest.NewRecorder()
+ handler1 := corsMiddleware(testHandler1)
+
+ corsMiddleware(testHandler2)
+
+ handler1.ServeHTTP(rr1, r1)
+ if lastHandledBy != "testHandler1" {
+ t.Fatalf("bad CORS() registration: Handler served should be Handler registered")
+ }
+}
+
+func TestCORSHandlerWithCustomValidator(t *testing.T) {
+ r := newRequest("GET", "http://a.example.com")
+ r.Header.Set("Origin", r.URL.String())
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ originValidator := func(origin string) bool {
+ if strings.HasSuffix(origin, ".example.com") {
+ return true
+ }
+ return false
+ }
+
+ CORS(AllowedOriginValidator(originValidator))(testHandler).ServeHTTP(rr, r)
+ header := rr.HeaderMap.Get(corsAllowOriginHeader)
+ if header != r.URL.String() {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowOriginHeader, r.URL.String(), header)
+ }
+
+}
diff --git a/vendor/github.com/gorilla/handlers/handlers_test.go b/vendor/github.com/gorilla/handlers/handlers_test.go
new file mode 100644
index 000000000..6ea7c7fa6
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/handlers_test.go
@@ -0,0 +1,354 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "bytes"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+ "time"
+)
+
+const (
+ ok = "ok\n"
+ notAllowed = "Method not allowed\n"
+)
+
+var okHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ w.Write([]byte(ok))
+})
+
+func newRequest(method, url string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ return req
+}
+
+func TestMethodHandler(t *testing.T) {
+ tests := []struct {
+ req *http.Request
+ handler http.Handler
+ code int
+ allow string // Contents of the Allow header
+ body string
+ }{
+ // No handlers
+ {newRequest("GET", "/foo"), MethodHandler{}, http.StatusMethodNotAllowed, "", notAllowed},
+ {newRequest("OPTIONS", "/foo"), MethodHandler{}, http.StatusOK, "", ""},
+
+ // A single handler
+ {newRequest("GET", "/foo"), MethodHandler{"GET": okHandler}, http.StatusOK, "", ok},
+ {newRequest("POST", "/foo"), MethodHandler{"GET": okHandler}, http.StatusMethodNotAllowed, "GET", notAllowed},
+
+ // Multiple handlers
+ {newRequest("GET", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "", ok},
+ {newRequest("POST", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "", ok},
+ {newRequest("DELETE", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusMethodNotAllowed, "GET, POST", notAllowed},
+ {newRequest("OPTIONS", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "GET, POST", ""},
+
+ // Override OPTIONS
+ {newRequest("OPTIONS", "/foo"), MethodHandler{"OPTIONS": okHandler}, http.StatusOK, "", ok},
+ }
+
+ for i, test := range tests {
+ rec := httptest.NewRecorder()
+ test.handler.ServeHTTP(rec, test.req)
+ if rec.Code != test.code {
+ t.Fatalf("%d: wrong code, got %d want %d", i, rec.Code, test.code)
+ }
+ if allow := rec.HeaderMap.Get("Allow"); allow != test.allow {
+ t.Fatalf("%d: wrong Allow, got %s want %s", i, allow, test.allow)
+ }
+ if body := rec.Body.String(); body != test.body {
+ t.Fatalf("%d: wrong body, got %q want %q", i, body, test.body)
+ }
+ }
+}
+
+func TestWriteLog(t *testing.T) {
+ loc, err := time.LoadLocation("Europe/Warsaw")
+ if err != nil {
+ panic(err)
+ }
+ ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc)
+
+ // A typical request with an OK response
+ req := newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+
+ buf := new(bytes.Buffer)
+ writeLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log := buf.String()
+
+ expected := "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // CONNECT request over http/2.0
+ req = &http.Request{
+ Method: "CONNECT",
+ Proto: "HTTP/2.0",
+ ProtoMajor: 2,
+ ProtoMinor: 0,
+ URL: &url.URL{Host: "www.example.com:443"},
+ Host: "www.example.com:443",
+ RemoteAddr: "192.168.100.5",
+ }
+
+ buf = new(bytes.Buffer)
+ writeLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"CONNECT www.example.com:443 HTTP/2.0\" 200 100\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Request with an unauthorized user
+ req = newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+ req.URL.User = url.User("kamil")
+
+ buf.Reset()
+ writeLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500)
+ log = buf.String()
+
+ expected = "192.168.100.5 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 401 500\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Request with url encoded parameters
+ req = newRequest("GET", "http://example.com/test?abc=hello%20world&a=b%3F")
+ req.RemoteAddr = "192.168.100.5"
+
+ buf.Reset()
+ writeLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET /test?abc=hello%20world&a=b%3F HTTP/1.1\" 200 100\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+}
+
+func TestWriteCombinedLog(t *testing.T) {
+ loc, err := time.LoadLocation("Europe/Warsaw")
+ if err != nil {
+ panic(err)
+ }
+ ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc)
+
+ // A typical request with an OK response
+ req := newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+ req.Header.Set("Referer", "http://example.com")
+ req.Header.Set(
+ "User-Agent",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.33 "+
+ "(KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33",
+ )
+
+ buf := new(bytes.Buffer)
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log := buf.String()
+
+ expected := "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // CONNECT request over http/2.0
+ req1 := &http.Request{
+ Method: "CONNECT",
+ Host: "www.example.com:443",
+ Proto: "HTTP/2.0",
+ ProtoMajor: 2,
+ ProtoMinor: 0,
+ RemoteAddr: "192.168.100.5",
+ Header: http.Header{},
+ URL: &url.URL{Host: "www.example.com:443"},
+ }
+ req1.Header.Set("Referer", "http://example.com")
+ req1.Header.Set(
+ "User-Agent",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.33 "+
+ "(KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33",
+ )
+
+ buf = new(bytes.Buffer)
+ writeCombinedLog(buf, req1, *req1.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"CONNECT www.example.com:443 HTTP/2.0\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Request with an unauthorized user
+ req.URL.User = url.User("kamil")
+
+ buf.Reset()
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500)
+ log = buf.String()
+
+ expected = "192.168.100.5 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 401 500 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Test with remote ipv6 address
+ req.RemoteAddr = "::1"
+
+ buf.Reset()
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "::1 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Test remote ipv6 addr, with port
+ req.RemoteAddr = net.JoinHostPort("::1", "65000")
+
+ buf.Reset()
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "::1 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+}
+
+func TestLogPathRewrites(t *testing.T) {
+ var buf bytes.Buffer
+
+ handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ req.URL.Path = "/" // simulate http.StripPrefix and friends
+ w.WriteHeader(200)
+ })
+ logger := LoggingHandler(&buf, handler)
+
+ logger.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf"))
+
+ if !strings.Contains(buf.String(), "GET /subdir/asdf HTTP") {
+ t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "GET /subdir/asdf HTTP")
+ }
+}
+
+func BenchmarkWriteLog(b *testing.B) {
+ loc, err := time.LoadLocation("Europe/Warsaw")
+ if err != nil {
+ b.Fatalf(err.Error())
+ }
+ ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc)
+
+ req := newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+
+ b.ResetTimer()
+
+ buf := &bytes.Buffer{}
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ writeLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500)
+ }
+}
+
+func TestContentTypeHandler(t *testing.T) {
+ tests := []struct {
+ Method string
+ AllowContentTypes []string
+ ContentType string
+ Code int
+ }{
+ {"POST", []string{"application/json"}, "application/json", http.StatusOK},
+ {"POST", []string{"application/json", "application/xml"}, "application/json", http.StatusOK},
+ {"POST", []string{"application/json"}, "application/json; charset=utf-8", http.StatusOK},
+ {"POST", []string{"application/json"}, "application/json+xxx", http.StatusUnsupportedMediaType},
+ {"POST", []string{"application/json"}, "text/plain", http.StatusUnsupportedMediaType},
+ {"GET", []string{"application/json"}, "", http.StatusOK},
+ {"GET", []string{}, "", http.StatusOK},
+ }
+ for _, test := range tests {
+ r, err := http.NewRequest(test.Method, "/", nil)
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+
+ h := ContentTypeHandler(okHandler, test.AllowContentTypes...)
+ r.Header.Set("Content-Type", test.ContentType)
+ w := httptest.NewRecorder()
+ h.ServeHTTP(w, r)
+ if w.Code != test.Code {
+ t.Errorf("expected %d, got %d", test.Code, w.Code)
+ }
+ }
+}
+
+func TestHTTPMethodOverride(t *testing.T) {
+ var tests = []struct {
+ Method string
+ OverrideMethod string
+ ExpectedMethod string
+ }{
+ {"POST", "PUT", "PUT"},
+ {"POST", "PATCH", "PATCH"},
+ {"POST", "DELETE", "DELETE"},
+ {"PUT", "DELETE", "PUT"},
+ {"GET", "GET", "GET"},
+ {"HEAD", "HEAD", "HEAD"},
+ {"GET", "PUT", "GET"},
+ {"HEAD", "DELETE", "HEAD"},
+ }
+
+ for _, test := range tests {
+ h := HTTPMethodOverrideHandler(okHandler)
+ reqs := make([]*http.Request, 0, 2)
+
+ rHeader, err := http.NewRequest(test.Method, "/", nil)
+ if err != nil {
+ t.Error(err)
+ }
+ rHeader.Header.Set(HTTPMethodOverrideHeader, test.OverrideMethod)
+ reqs = append(reqs, rHeader)
+
+ f := url.Values{HTTPMethodOverrideFormKey: []string{test.OverrideMethod}}
+ rForm, err := http.NewRequest(test.Method, "/", strings.NewReader(f.Encode()))
+ if err != nil {
+ t.Error(err)
+ }
+ rForm.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ reqs = append(reqs, rForm)
+
+ for _, r := range reqs {
+ w := httptest.NewRecorder()
+ h.ServeHTTP(w, r)
+ if r.Method != test.ExpectedMethod {
+ t.Errorf("Expected %s, got %s", test.ExpectedMethod, r.Method)
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/handlers/proxy_headers_test.go b/vendor/github.com/gorilla/handlers/proxy_headers_test.go
new file mode 100644
index 000000000..85282ef7d
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/proxy_headers_test.go
@@ -0,0 +1,100 @@
+package handlers
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+type headerTable struct {
+ key string // header key
+ val string // header val
+ expected string // expected result
+}
+
+func TestGetIP(t *testing.T) {
+ headers := []headerTable{
+ {xForwardedFor, "8.8.8.8", "8.8.8.8"}, // Single address
+ {xForwardedFor, "8.8.8.8, 8.8.4.4", "8.8.8.8"}, // Multiple
+ {xForwardedFor, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address
+ {xForwardedFor, "", ""}, // None
+ {xRealIP, "8.8.8.8", "8.8.8.8"}, // Single address
+ {xRealIP, "8.8.8.8, 8.8.4.4", "8.8.8.8, 8.8.4.4"}, // Multiple
+ {xRealIP, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address
+ {xRealIP, "", ""}, // None
+ {forwarded, `for="_gazonk"`, "_gazonk"}, // Hostname
+ {forwarded, `For="[2001:db8:cafe::17]:4711`, `[2001:db8:cafe::17]:4711`}, // IPv6 address
+ {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, `192.0.2.60`}, // Multiple params
+ {forwarded, `for=192.0.2.43, for=198.51.100.17`, "192.0.2.43"}, // Multiple params
+ {forwarded, `for="workstation.local",for=198.51.100.17`, "workstation.local"}, // Hostname
+ }
+
+ for _, v := range headers {
+ req := &http.Request{
+ Header: http.Header{
+ v.key: []string{v.val},
+ }}
+ res := getIP(req)
+ if res != v.expected {
+ t.Fatalf("wrong header for %s: got %s want %s", v.key, res,
+ v.expected)
+ }
+ }
+}
+
+func TestGetScheme(t *testing.T) {
+ headers := []headerTable{
+ {xForwardedProto, "https", "https"},
+ {xForwardedProto, "http", "http"},
+ {xForwardedProto, "HTTP", "http"},
+ {forwarded, `For="[2001:db8:cafe::17]:4711`, ""}, // No proto
+ {forwarded, `for=192.0.2.43, for=198.51.100.17;proto=https`, "https"}, // Multiple params before proto
+ {forwarded, `for=172.32.10.15; proto=https;by=127.0.0.1`, "https"}, // Space before proto
+ {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, "http"}, // Multiple params
+ }
+
+ for _, v := range headers {
+ req := &http.Request{
+ Header: http.Header{
+ v.key: []string{v.val},
+ },
+ }
+ res := getScheme(req)
+ if res != v.expected {
+ t.Fatalf("wrong header for %s: got %s want %s", v.key, res,
+ v.expected)
+ }
+ }
+}
+
+// Test the middleware end-to-end
+func TestProxyHeaders(t *testing.T) {
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "/")
+
+ r.Header.Set(xForwardedFor, "8.8.8.8")
+ r.Header.Set(xForwardedProto, "https")
+
+ var addr string
+ var proto string
+ ProxyHeaders(http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ addr = r.RemoteAddr
+ proto = r.URL.Scheme
+ })).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("bad status: got %d want %d", rr.Code, http.StatusOK)
+ }
+
+ if addr != r.Header.Get(xForwardedFor) {
+ t.Fatalf("wrong address: got %s want %s", addr,
+ r.Header.Get(xForwardedFor))
+ }
+
+ if proto != r.Header.Get(xForwardedProto) {
+ t.Fatalf("wrong address: got %s want %s", proto,
+ r.Header.Get(xForwardedProto))
+ }
+
+}
diff --git a/vendor/github.com/gorilla/handlers/recovery_test.go b/vendor/github.com/gorilla/handlers/recovery_test.go
new file mode 100644
index 000000000..1ae0e5805
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/recovery_test.go
@@ -0,0 +1,44 @@
+package handlers
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestRecoveryLoggerWithDefaultOptions(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+
+ handler := RecoveryHandler()
+ handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ panic("Unexpected error!")
+ })
+
+ recovery := handler(handlerFunc)
+ recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf"))
+
+ if !strings.Contains(buf.String(), "Unexpected error!") {
+ t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!")
+ }
+}
+
+func TestRecoveryLoggerWithCustomLogger(t *testing.T) {
+ var buf bytes.Buffer
+ var logger = log.New(&buf, "", log.LstdFlags)
+
+ handler := RecoveryHandler(RecoveryLogger(logger), PrintRecoveryStack(false))
+ handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ panic("Unexpected error!")
+ })
+
+ recovery := handler(handlerFunc)
+ recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf"))
+
+ if !strings.Contains(buf.String(), "Unexpected error!") {
+ t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!")
+ }
+}
diff --git a/vendor/github.com/gorilla/mux/bench_test.go b/vendor/github.com/gorilla/mux/bench_test.go
new file mode 100644
index 000000000..946289b92
--- /dev/null
+++ b/vendor/github.com/gorilla/mux/bench_test.go
@@ -0,0 +1,49 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func BenchmarkMux(b *testing.B) {
+ router := new(Router)
+ handler := func(w http.ResponseWriter, r *http.Request) {}
+ router.HandleFunc("/v1/{v1}", handler)
+
+ request, _ := http.NewRequest("GET", "/v1/anything", nil)
+ for i := 0; i < b.N; i++ {
+ router.ServeHTTP(nil, request)
+ }
+}
+
+func BenchmarkMuxAlternativeInRegexp(b *testing.B) {
+ router := new(Router)
+ handler := func(w http.ResponseWriter, r *http.Request) {}
+ router.HandleFunc("/v1/{v1:(a|b)}", handler)
+
+ requestA, _ := http.NewRequest("GET", "/v1/a", nil)
+ requestB, _ := http.NewRequest("GET", "/v1/b", nil)
+ for i := 0; i < b.N; i++ {
+ router.ServeHTTP(nil, requestA)
+ router.ServeHTTP(nil, requestB)
+ }
+}
+
+func BenchmarkManyPathVariables(b *testing.B) {
+ router := new(Router)
+ handler := func(w http.ResponseWriter, r *http.Request) {}
+ router.HandleFunc("/v1/{v1}/{v2}/{v3}/{v4}/{v5}", handler)
+
+ matchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4/5", nil)
+ notMatchingRequest, _ := http.NewRequest("GET", "/v1/1/2/3/4", nil)
+ recorder := httptest.NewRecorder()
+ for i := 0; i < b.N; i++ {
+ router.ServeHTTP(nil, matchingRequest)
+ router.ServeHTTP(recorder, notMatchingRequest)
+ }
+}
diff --git a/vendor/github.com/gorilla/mux/mux_test.go b/vendor/github.com/gorilla/mux/mux_test.go
new file mode 100644
index 000000000..777d063c0
--- /dev/null
+++ b/vendor/github.com/gorilla/mux/mux_test.go
@@ -0,0 +1,1471 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+ "testing"
+
+ "github.com/gorilla/context"
+)
+
+func (r *Route) GoString() string {
+ matchers := make([]string, len(r.matchers))
+ for i, m := range r.matchers {
+ matchers[i] = fmt.Sprintf("%#v", m)
+ }
+ return fmt.Sprintf("&Route{matchers:[]matcher{%s}}", strings.Join(matchers, ", "))
+}
+
+func (r *routeRegexp) GoString() string {
+ return fmt.Sprintf("&routeRegexp{template: %q, matchHost: %t, matchQuery: %t, strictSlash: %t, regexp: regexp.MustCompile(%q), reverse: %q, varsN: %v, varsR: %v", r.template, r.matchHost, r.matchQuery, r.strictSlash, r.regexp.String(), r.reverse, r.varsN, r.varsR)
+}
+
+type routeTest struct {
+ title string // title of the test
+ route *Route // the route being tested
+ request *http.Request // a request to test the route
+ vars map[string]string // the expected vars of the match
+ host string // the expected host of the match
+ path string // the expected path of the match
+ path_template string // the expected path template to match
+ host_template 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
+}
+
+func TestHost(t *testing.T) {
+ // newRequestHost a new request with a method, url, and host header
+ newRequestHost := func(method, url, host string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ req.Host = host
+ return req
+ }
+
+ tests := []routeTest{
+ {
+ title: "Host route match",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route, wrong host in request URL",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Host route with port, match",
+ route: new(Route).Host("aaa.bbb.ccc:1234"),
+ request: newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc:1234",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route with port, wrong port in request URL",
+ route: new(Route).Host("aaa.bbb.ccc:1234"),
+ request: newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc:1234",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Host route, match with host in request header",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route, wrong host in request header",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: false,
+ },
+ // BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
+ {
+ title: "Host route with port, wrong host in request header",
+ route: new(Route).Host("aaa.bbb.ccc:1234"),
+ request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc:1234",
+ path: "",
+ 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: "",
+ host_template: `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: "",
+ host_template: `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: "",
+ host_template: `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: "",
+ host_template: `{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: "",
+ host_template: `{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: "",
+ host_template: `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: "",
+ host_template: `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: "",
+ host_template: `{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",
+ path_template: `/{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",
+ path_template: `/{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",
+ path_template: `/{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",
+ path_template: `/{category:a|b/c}/{product}/{id:[0-9]+}`,
+ shouldMatch: true,
+ },
+ }
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestPath(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "Path route, match",
+ route: new(Route).Path("/111/222/333"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/222/333",
+ shouldMatch: true,
+ },
+ {
+ title: "Path route, match with trailing slash in request and path",
+ route: new(Route).Path("/111/"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ 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",
+ path_template: `/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/",
+ path_template: `/111`,
+ shouldMatch: false,
+ },
+ {
+ title: "Path route, wrong path in request in request URL",
+ route: new(Route).Path("/111/222/333"),
+ request: newRequest("GET", "http://localhost/1/2/3"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/222/333",
+ 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",
+ path_template: `/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",
+ path_template: `/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",
+ path_template: `/{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",
+ path_template: `/{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",
+ path_template: `/{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",
+ path_template: `/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",
+ path_template: `/{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",
+ path_template: `/{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",
+ path_template: `/{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)
+ }
+}
+
+func TestPathPrefix(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "PathPrefix route, match",
+ route: new(Route).PathPrefix("/111"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: true,
+ },
+ {
+ title: "PathPrefix route, match substring",
+ route: new(Route).PathPrefix("/1"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "/1",
+ shouldMatch: true,
+ },
+ {
+ title: "PathPrefix route, URL prefix in request does not match",
+ route: new(Route).PathPrefix("/111"),
+ request: newRequest("GET", "http://localhost/1/2/3"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ 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",
+ path_template: `/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",
+ path_template: `/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",
+ path_template: `/{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",
+ path_template: `/{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: "",
+ path_template: `/111/222/333`,
+ host_template: `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: "",
+ path_template: `/111/222/333`,
+ host_template: `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",
+ path_template: `/111/{v2:[0-9]{3}}/333`,
+ host_template: `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",
+ path_template: `/111/{v2:[0-9]{3}}/333`,
+ host_template: `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",
+ path_template: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
+ host_template: `{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",
+ path_template: `/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}`,
+ host_template: `{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}`,
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestHeaders(t *testing.T) {
+ // newRequestHeaders creates a new request with a method, url, and headers
+ newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ for k, v := range headers {
+ req.Header.Add(k, v)
+ }
+ return req
+ }
+
+ tests := []routeTest{
+ {
+ title: "Headers route, match",
+ route: new(Route).Headers("foo", "bar", "baz", "ding"),
+ request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Headers route, bad header values",
+ route: new(Route).Headers("foo", "bar", "baz", "ding"),
+ request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Headers route, regex header values to match",
+ route: new(Route).Headers("foo", "ba[zr]"),
+ request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar"}),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Headers route, regex header values to match",
+ route: new(Route).HeadersRegexp("foo", "ba[zr]"),
+ request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "baz"}),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+
+}
+
+func TestMethods(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "Methods route, match GET",
+ route: new(Route).Methods("GET", "POST"),
+ request: newRequest("GET", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Methods route, match POST",
+ route: new(Route).Methods("GET", "POST"),
+ request: newRequest("POST", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Methods route, bad method",
+ route: new(Route).Methods("GET", "POST"),
+ request: newRequest("PUT", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestQueries(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "Queries route, match",
+ route: new(Route).Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://localhost?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: "",
+ path_template: `/api`,
+ host_template: `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: "",
+ path_template: `/api`,
+ host_template: `www.example.com`,
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route, bad query",
+ route: new(Route).Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://localhost?foo=bar&baz=dong"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with pattern, match",
+ route: new(Route).Queries("foo", "{v1}"),
+ request: newRequest("GET", "http://localhost?foo=bar"),
+ vars: map[string]string{"v1": "bar"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with multiple patterns, match",
+ route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
+ request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+ vars: map[string]string{"v1": "bar", "v2": "ding"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with regexp pattern, match",
+ route: new(Route).Queries("foo", "{v1:[0-9]+}"),
+ request: newRequest("GET", "http://localhost?foo=10"),
+ vars: map[string]string{"v1": "10"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with regexp pattern, regexp does not match",
+ route: new(Route).Queries("foo", "{v1:[0-9]+}"),
+ request: newRequest("GET", "http://localhost?foo=a"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with regexp pattern with quantifier, match",
+ route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+ request: newRequest("GET", "http://localhost?foo=1"),
+ vars: map[string]string{"v1": "1"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with regexp pattern with quantifier, additional variable in query string, match",
+ route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+ request: newRequest("GET", "http://localhost?bar=2&foo=1"),
+ vars: map[string]string{"v1": "1"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with regexp pattern with quantifier, regexp does not match",
+ route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+ request: newRequest("GET", "http://localhost?foo=12"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with regexp pattern with quantifier, additional capturing group",
+ route: new(Route).Queries("foo", "{v1:[0-9]{1}(a|b)}"),
+ request: newRequest("GET", "http://localhost?foo=1a"),
+ vars: map[string]string{"v1": "1a"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with regexp pattern with quantifier, additional variable in query string, regexp does not match",
+ route: new(Route).Queries("foo", "{v1:[0-9]{1}}"),
+ request: newRequest("GET", "http://localhost?foo=12"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with hyphenated name, match",
+ route: new(Route).Queries("foo", "{v-1}"),
+ request: newRequest("GET", "http://localhost?foo=bar"),
+ vars: map[string]string{"v-1": "bar"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with multiple hyphenated names, match",
+ route: new(Route).Queries("foo", "{v-1}", "baz", "{v-2}"),
+ request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+ vars: map[string]string{"v-1": "bar", "v-2": "ding"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with hyphenate name and pattern, match",
+ route: new(Route).Queries("foo", "{v-1:[0-9]+}"),
+ request: newRequest("GET", "http://localhost?foo=10"),
+ vars: map[string]string{"v-1": "10"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with hyphenated name and pattern with quantifier, additional capturing group",
+ route: new(Route).Queries("foo", "{v-1:[0-9]{1}(a|b)}"),
+ request: newRequest("GET", "http://localhost?foo=1a"),
+ vars: map[string]string{"v-1": "1a"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with empty value, should match",
+ route: new(Route).Queries("foo", ""),
+ request: newRequest("GET", "http://localhost?foo=bar"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with empty value and no parameter in request, should not match",
+ route: new(Route).Queries("foo", ""),
+ request: newRequest("GET", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with empty value and empty parameter in request, should match",
+ route: new(Route).Queries("foo", ""),
+ request: newRequest("GET", "http://localhost?foo="),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with overlapping value, should not match",
+ route: new(Route).Queries("foo", "bar"),
+ request: newRequest("GET", "http://localhost?foo=barfoo"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with no parameter in request, should not match",
+ route: new(Route).Queries("foo", "{bar}"),
+ request: newRequest("GET", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with empty parameter in request, should match",
+ route: new(Route).Queries("foo", "{bar}"),
+ request: newRequest("GET", "http://localhost?foo="),
+ vars: map[string]string{"foo": ""},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route, bad submatch",
+ route: new(Route).Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://localhost?fffoo=bar&baz=dingggg"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestSchemes(t *testing.T) {
+ tests := []routeTest{
+ // Schemes
+ {
+ title: "Schemes route, match https",
+ route: new(Route).Schemes("https", "ftp"),
+ request: newRequest("GET", "https://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Schemes route, match ftp",
+ route: new(Route).Schemes("https", "ftp"),
+ request: newRequest("GET", "ftp://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Schemes route, bad scheme",
+ route: new(Route).Schemes("https", "ftp"),
+ request: newRequest("GET", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestMatcherFunc(t *testing.T) {
+ m := func(r *http.Request, m *RouteMatch) bool {
+ if r.URL.Host == "aaa.bbb.ccc" {
+ return true
+ }
+ return false
+ }
+
+ tests := []routeTest{
+ {
+ title: "MatchFunc route, match",
+ route: new(Route).MatcherFunc(m),
+ request: newRequest("GET", "http://aaa.bbb.ccc"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "MatchFunc route, non-match",
+ route: new(Route).MatcherFunc(m),
+ request: newRequest("GET", "http://aaa.222.ccc"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestBuildVarsFunc(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "BuildVarsFunc set on route",
+ route: new(Route).Path(`/111/{v1:\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
+ vars["v1"] = "3"
+ vars["v2"] = "a"
+ return vars
+ }),
+ request: newRequest("GET", "http://localhost/111/2"),
+ path: "/111/3a",
+ path_template: `/111/{v1:\d}{v2:.*}`,
+ shouldMatch: true,
+ },
+ {
+ title: "BuildVarsFunc set on route and parent route",
+ route: new(Route).PathPrefix(`/{v1:\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
+ vars["v1"] = "2"
+ return vars
+ }).Subrouter().Path(`/{v2:\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
+ vars["v2"] = "b"
+ return vars
+ }),
+ request: newRequest("GET", "http://localhost/1/a"),
+ path: "/2/b",
+ path_template: `/{v1:\d}/{v2:\w}`,
+ shouldMatch: true,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestSubRouter(t *testing.T) {
+ subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
+ subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
+
+ 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",
+ path_template: `/{v2:[a-z]+}`,
+ host_template: `{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",
+ path_template: `/{v2:[a-z]+}`,
+ host_template: `{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",
+ path_template: `/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",
+ path_template: `/foo/{v1}/baz/{v2}`,
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestNamedRoutes(t *testing.T) {
+ r1 := NewRouter()
+ r1.NewRoute().Name("a")
+ r1.NewRoute().Name("b")
+ r1.NewRoute().Name("c")
+
+ r2 := r1.NewRoute().Subrouter()
+ r2.NewRoute().Name("d")
+ r2.NewRoute().Name("e")
+ r2.NewRoute().Name("f")
+
+ r3 := r2.NewRoute().Subrouter()
+ r3.NewRoute().Name("g")
+ r3.NewRoute().Name("h")
+ r3.NewRoute().Name("i")
+
+ if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
+ t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
+ } else if r1.Get("i") == nil {
+ t.Errorf("Subroute name not registered")
+ }
+}
+
+func TestStrictSlash(t *testing.T) {
+ r := NewRouter()
+ r.StrictSlash(true)
+
+ tests := []routeTest{
+ {
+ title: "Redirect path without slash",
+ route: r.NewRoute().Path("/111/"),
+ request: newRequest("GET", "http://localhost/111"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ shouldMatch: true,
+ shouldRedirect: true,
+ },
+ {
+ title: "Do not redirect path with slash",
+ route: r.NewRoute().Path("/111/"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ shouldMatch: true,
+ shouldRedirect: false,
+ },
+ {
+ title: "Redirect path with slash",
+ route: r.NewRoute().Path("/111"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: true,
+ shouldRedirect: true,
+ },
+ {
+ title: "Do not redirect path without slash",
+ route: r.NewRoute().Path("/111"),
+ request: newRequest("GET", "http://localhost/111"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: true,
+ shouldRedirect: false,
+ },
+ {
+ title: "Propagate StrictSlash to subrouters",
+ route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
+ request: newRequest("GET", "http://localhost/static/images"),
+ vars: map[string]string{},
+ host: "",
+ path: "/static/images/",
+ shouldMatch: true,
+ shouldRedirect: true,
+ },
+ {
+ title: "Ignore StrictSlash for path prefix",
+ route: r.NewRoute().PathPrefix("/static/"),
+ request: newRequest("GET", "http://localhost/static/logo.png"),
+ vars: map[string]string{},
+ host: "",
+ path: "/static/",
+ shouldMatch: true,
+ shouldRedirect: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ testTemplate(t, test)
+ }
+}
+
+func TestWalkSingleDepth(t *testing.T) {
+ r0 := NewRouter()
+ r1 := NewRouter()
+ r2 := NewRouter()
+
+ r0.Path("/g")
+ r0.Path("/o")
+ r0.Path("/d").Handler(r1)
+ r0.Path("/r").Handler(r2)
+ r0.Path("/a")
+
+ r1.Path("/z")
+ r1.Path("/i")
+ r1.Path("/l")
+ r1.Path("/l")
+
+ r2.Path("/i")
+ r2.Path("/l")
+ r2.Path("/l")
+
+ paths := []string{"g", "o", "r", "i", "l", "l", "a"}
+ depths := []int{0, 0, 0, 1, 1, 1, 0}
+ i := 0
+ err := r0.Walk(func(route *Route, router *Router, ancestors []*Route) error {
+ matcher := route.matchers[0].(*routeRegexp)
+ if matcher.template == "/d" {
+ return SkipRouter
+ }
+ if len(ancestors) != depths[i] {
+ t.Errorf(`Expected depth of %d at i = %d; got "%d"`, depths[i], i, len(ancestors))
+ }
+ if matcher.template != "/"+paths[i] {
+ t.Errorf(`Expected "/%s" at i = %d; got "%s"`, paths[i], i, matcher.template)
+ }
+ i++
+ return nil
+ })
+ if err != nil {
+ panic(err)
+ }
+ if i != len(paths) {
+ t.Errorf("Expected %d routes, found %d", len(paths), i)
+ }
+}
+
+func TestWalkNested(t *testing.T) {
+ router := NewRouter()
+
+ g := router.Path("/g").Subrouter()
+ o := g.PathPrefix("/o").Subrouter()
+ r := o.PathPrefix("/r").Subrouter()
+ i := r.PathPrefix("/i").Subrouter()
+ l1 := i.PathPrefix("/l").Subrouter()
+ l2 := l1.PathPrefix("/l").Subrouter()
+ l2.Path("/a")
+
+ paths := []string{"/g", "/g/o", "/g/o/r", "/g/o/r/i", "/g/o/r/i/l", "/g/o/r/i/l/l", "/g/o/r/i/l/l/a"}
+ idx := 0
+ err := router.Walk(func(route *Route, router *Router, ancestors []*Route) error {
+ path := paths[idx]
+ tpl := route.regexp.path.template
+ if tpl != path {
+ t.Errorf(`Expected %s got %s`, path, tpl)
+ }
+ idx++
+ return nil
+ })
+ if err != nil {
+ panic(err)
+ }
+ if idx != len(paths) {
+ t.Errorf("Expected %d routes, found %d", len(paths), idx)
+ }
+}
+
+func TestSubrouterErrorHandling(t *testing.T) {
+ superRouterCalled := false
+ subRouterCalled := false
+
+ router := NewRouter()
+ router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ superRouterCalled = true
+ })
+ subRouter := router.PathPrefix("/bign8").Subrouter()
+ subRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ subRouterCalled = true
+ })
+
+ req, _ := http.NewRequest("GET", "http://localhost/bign8/was/here", nil)
+ router.ServeHTTP(NewRecorder(), req)
+
+ if superRouterCalled {
+ t.Error("Super router 404 handler called when sub-router 404 handler is available.")
+ }
+ if !subRouterCalled {
+ t.Error("Sub-router 404 handler was not called.")
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+func getRouteTemplate(route *Route) string {
+ 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)
+}
+
+func testRoute(t *testing.T, test routeTest) {
+ request := test.request
+ route := test.route
+ vars := test.vars
+ shouldMatch := test.shouldMatch
+ host := test.host
+ path := test.path
+ url := test.host + test.path
+ shouldRedirect := test.shouldRedirect
+
+ var match RouteMatch
+ ok := route.Match(request, &match)
+ if ok != shouldMatch {
+ msg := "Should match"
+ if !shouldMatch {
+ msg = "Should not match"
+ }
+ t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
+ return
+ }
+ if shouldMatch {
+ if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
+ t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
+ return
+ }
+ if host != "" {
+ u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
+ if host != u.Host {
+ t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
+ return
+ }
+ }
+ if path != "" {
+ u, _ := route.URLPath(mapToPairs(match.Vars)...)
+ if path != u.Path {
+ t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
+ return
+ }
+ }
+ if url != "" {
+ u, _ := route.URL(mapToPairs(match.Vars)...)
+ if url != u.Host+u.Path {
+ t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
+ return
+ }
+ }
+ if shouldRedirect && match.Handler == nil {
+ t.Errorf("(%v) Did not redirect", test.title)
+ return
+ }
+ if !shouldRedirect && match.Handler != nil {
+ t.Errorf("(%v) Unexpected redirect", test.title)
+ return
+ }
+ }
+}
+
+func testTemplate(t *testing.T, test routeTest) {
+ route := test.route
+ path_template := test.path_template
+ if len(path_template) == 0 {
+ path_template = test.path
+ }
+ host_template := test.host_template
+ if len(host_template) == 0 {
+ host_template = test.host
+ }
+
+ path_tmpl, path_err := route.GetPathTemplate()
+ if path_err == nil && path_tmpl != path_template {
+ t.Errorf("(%v) GetPathTemplate not equal: expected %v, got %v", test.title, path_template, path_tmpl)
+ }
+
+ host_tmpl, host_err := route.GetHostTemplate()
+ if host_err == nil && host_tmpl != host_template {
+ t.Errorf("(%v) GetHostTemplate not equal: expected %v, got %v", test.title, host_template, host_tmpl)
+ }
+}
+
+// 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")
+ }
+
+}
+
+type TestA301ResponseWriter struct {
+ hh http.Header
+ status int
+}
+
+func (ho TestA301ResponseWriter) Header() http.Header {
+ return http.Header(ho.hh)
+}
+
+func (ho TestA301ResponseWriter) Write(b []byte) (int, error) {
+ return 0, nil
+}
+
+func (ho TestA301ResponseWriter) WriteHeader(code int) {
+ ho.status = code
+}
+
+func Test301Redirect(t *testing.T) {
+ m := make(http.Header)
+
+ func1 := func(w http.ResponseWriter, r *http.Request) {}
+ func2 := func(w http.ResponseWriter, r *http.Request) {}
+
+ r := NewRouter()
+ r.HandleFunc("/api/", func2).Name("func2")
+ r.HandleFunc("/", func1).Name("func1")
+
+ req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
+
+ res := TestA301ResponseWriter{
+ hh: m,
+ status: 0,
+ }
+ r.ServeHTTP(&res, req)
+
+ if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
+ t.Errorf("Should have complete URL with query string")
+ }
+}
+
+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"
+ func1 := func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, expected)
+ }
+ func2 := func(http.ResponseWriter, *http.Request) {}
+
+ r := NewRouter()
+ s := r.Headers("SomeSpecialHeader", "").Subrouter()
+ s.HandleFunc("/", func1).Name("func1")
+ r.HandleFunc("/", func2).Name("func2")
+
+ req, _ := http.NewRequest("GET", "http://localhost/", nil)
+ req.Header.Add("SomeSpecialHeader", "foo")
+ match := new(RouteMatch)
+ matched := r.Match(req, match)
+ if !matched {
+ t.Errorf("Should match request")
+ }
+ if match.Route.GetName() != "func1" {
+ t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
+ }
+ resp := NewRecorder()
+ match.Handler.ServeHTTP(resp, req)
+ if resp.Body.String() != expected {
+ t.Errorf("Expecting %q", expected)
+ }
+}
+
+// mapToPairs converts a string map to a slice of string pairs
+func mapToPairs(m map[string]string) []string {
+ var i int
+ p := make([]string, len(m)*2)
+ for k, v := range m {
+ p[i] = k
+ p[i+1] = v
+ i += 2
+ }
+ return p
+}
+
+// stringMapEqual checks the equality of two string maps
+func stringMapEqual(m1, m2 map[string]string) bool {
+ nil1 := m1 == nil
+ nil2 := m2 == nil
+ if nil1 != nil2 || len(m1) != len(m2) {
+ return false
+ }
+ for k, v := range m1 {
+ if v != m2[k] {
+ return false
+ }
+ }
+ return true
+}
+
+// newRequest is a helper function to create a new request with a method and url
+func newRequest(method, url string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ return req
+}
diff --git a/vendor/github.com/gorilla/mux/old_test.go b/vendor/github.com/gorilla/mux/old_test.go
new file mode 100644
index 000000000..c385a2519
--- /dev/null
+++ b/vendor/github.com/gorilla/mux/old_test.go
@@ -0,0 +1,710 @@
+// Old tests ported to Go1. This is a mess. Want to drop it one day.
+
+// Copyright 2011 Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "bytes"
+ "net/http"
+ "testing"
+)
+
+// ----------------------------------------------------------------------------
+// ResponseRecorder
+// ----------------------------------------------------------------------------
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// ResponseRecorder is an implementation of http.ResponseWriter that
+// records its mutations for later inspection in tests.
+type ResponseRecorder struct {
+ Code int // the HTTP response code from WriteHeader
+ HeaderMap http.Header // the HTTP response headers
+ Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+ Flushed bool
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewRecorder() *ResponseRecorder {
+ return &ResponseRecorder{
+ HeaderMap: make(http.Header),
+ Body: new(bytes.Buffer),
+ }
+}
+
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+ return rw.HeaderMap
+}
+
+// Write always succeeds and writes to rw.Body, if not nil.
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
+ if rw.Body != nil {
+ rw.Body.Write(buf)
+ }
+ if rw.Code == 0 {
+ rw.Code = http.StatusOK
+ }
+ return len(buf), nil
+}
+
+// WriteHeader sets rw.Code.
+func (rw *ResponseRecorder) WriteHeader(code int) {
+ rw.Code = code
+}
+
+// Flush sets rw.Flushed to true.
+func (rw *ResponseRecorder) Flush() {
+ rw.Flushed = true
+}
+
+// ----------------------------------------------------------------------------
+
+func TestRouteMatchers(t *testing.T) {
+ var scheme, host, path, query, method string
+ var headers map[string]string
+ var resultVars map[bool]map[string]string
+
+ router := NewRouter()
+ router.NewRoute().Host("{var1}.google.com").
+ Path("/{var2:[a-z]+}/{var3:[0-9]+}").
+ Queries("foo", "bar").
+ Methods("GET").
+ Schemes("https").
+ Headers("x-requested-with", "XMLHttpRequest")
+ router.NewRoute().Host("www.{var4}.com").
+ PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
+ Queries("baz", "ding").
+ Methods("POST").
+ Schemes("http").
+ Headers("Content-Type", "application/json")
+
+ reset := func() {
+ // Everything match.
+ scheme = "https"
+ host = "www.google.com"
+ path = "/product/42"
+ query = "?foo=bar"
+ method = "GET"
+ headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
+ resultVars = map[bool]map[string]string{
+ true: {"var1": "www", "var2": "product", "var3": "42"},
+ false: {},
+ }
+ }
+
+ reset2 := func() {
+ // Everything match.
+ scheme = "http"
+ host = "www.google.com"
+ path = "/foo/product/42/path/that/is/ignored"
+ query = "?baz=ding"
+ method = "POST"
+ headers = map[string]string{"Content-Type": "application/json"}
+ resultVars = map[bool]map[string]string{
+ true: {"var4": "google", "var5": "product", "var6": "42"},
+ false: {},
+ }
+ }
+
+ match := func(shouldMatch bool) {
+ url := scheme + "://" + host + path + query
+ request, _ := http.NewRequest(method, url, nil)
+ for key, value := range headers {
+ request.Header.Add(key, value)
+ }
+
+ var routeMatch RouteMatch
+ matched := router.Match(request, &routeMatch)
+ if matched != shouldMatch {
+ // Need better messages. :)
+ if matched {
+ t.Errorf("Should match.")
+ } else {
+ t.Errorf("Should not match.")
+ }
+ }
+
+ if matched {
+ currentRoute := routeMatch.Route
+ if currentRoute == nil {
+ t.Errorf("Expected a current route.")
+ }
+ vars := routeMatch.Vars
+ expectedVars := resultVars[shouldMatch]
+ if len(vars) != len(expectedVars) {
+ t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+ }
+ for name, value := range vars {
+ if expectedVars[name] != value {
+ t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+ }
+ }
+ }
+ }
+
+ // 1st route --------------------------------------------------------------
+
+ // Everything match.
+ reset()
+ match(true)
+
+ // Scheme doesn't match.
+ reset()
+ scheme = "http"
+ match(false)
+
+ // Host doesn't match.
+ reset()
+ host = "www.mygoogle.com"
+ match(false)
+
+ // Path doesn't match.
+ reset()
+ path = "/product/notdigits"
+ match(false)
+
+ // Query doesn't match.
+ reset()
+ query = "?foo=baz"
+ match(false)
+
+ // Method doesn't match.
+ reset()
+ method = "POST"
+ match(false)
+
+ // Header doesn't match.
+ reset()
+ headers = map[string]string{}
+ match(false)
+
+ // Everything match, again.
+ reset()
+ match(true)
+
+ // 2nd route --------------------------------------------------------------
+
+ // Everything match.
+ reset2()
+ match(true)
+
+ // Scheme doesn't match.
+ reset2()
+ scheme = "https"
+ match(false)
+
+ // Host doesn't match.
+ reset2()
+ host = "sub.google.com"
+ match(false)
+
+ // Path doesn't match.
+ reset2()
+ path = "/bar/product/42"
+ match(false)
+
+ // Query doesn't match.
+ reset2()
+ query = "?foo=baz"
+ match(false)
+
+ // Method doesn't match.
+ reset2()
+ method = "GET"
+ match(false)
+
+ // Header doesn't match.
+ reset2()
+ headers = map[string]string{}
+ match(false)
+
+ // Everything match, again.
+ reset2()
+ match(true)
+}
+
+type headerMatcherTest struct {
+ matcher headerMatcher
+ headers map[string]string
+ result bool
+}
+
+var headerMatcherTests = []headerMatcherTest{
+ {
+ matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+ headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
+ result: true,
+ },
+ {
+ matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
+ headers: map[string]string{"X-Requested-With": "anything"},
+ result: true,
+ },
+ {
+ matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+ headers: map[string]string{},
+ result: false,
+ },
+}
+
+type hostMatcherTest struct {
+ matcher *Route
+ url string
+ vars map[string]string
+ result bool
+}
+
+var hostMatcherTests = []hostMatcherTest{
+ {
+ matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+ url: "http://abc.def.ghi/",
+ vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+ result: true,
+ },
+ {
+ matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+ url: "http://a.b.c/",
+ vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+ result: false,
+ },
+}
+
+type methodMatcherTest struct {
+ matcher methodMatcher
+ method string
+ result bool
+}
+
+var methodMatcherTests = []methodMatcherTest{
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "GET",
+ result: true,
+ },
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "POST",
+ result: true,
+ },
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "PUT",
+ result: true,
+ },
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "DELETE",
+ result: false,
+ },
+}
+
+type pathMatcherTest struct {
+ matcher *Route
+ url string
+ vars map[string]string
+ result bool
+}
+
+var pathMatcherTests = []pathMatcherTest{
+ {
+ matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+ url: "http://localhost:8080/123/456/789",
+ vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+ result: true,
+ },
+ {
+ matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+ url: "http://localhost:8080/1/2/3",
+ vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+ result: false,
+ },
+}
+
+type schemeMatcherTest struct {
+ matcher schemeMatcher
+ url string
+ result bool
+}
+
+var schemeMatcherTests = []schemeMatcherTest{
+ {
+ matcher: schemeMatcher([]string{"http", "https"}),
+ url: "http://localhost:8080/",
+ result: true,
+ },
+ {
+ matcher: schemeMatcher([]string{"http", "https"}),
+ url: "https://localhost:8080/",
+ result: true,
+ },
+ {
+ matcher: schemeMatcher([]string{"https"}),
+ url: "http://localhost:8080/",
+ result: false,
+ },
+ {
+ matcher: schemeMatcher([]string{"http"}),
+ url: "https://localhost:8080/",
+ result: false,
+ },
+}
+
+type urlBuildingTest struct {
+ route *Route
+ vars []string
+ url string
+}
+
+var urlBuildingTests = []urlBuildingTest{
+ {
+ route: new(Route).Host("foo.domain.com"),
+ vars: []string{},
+ url: "http://foo.domain.com",
+ },
+ {
+ route: new(Route).Host("{subdomain}.domain.com"),
+ vars: []string{"subdomain", "bar"},
+ url: "http://bar.domain.com",
+ },
+ {
+ route: new(Route).Host("foo.domain.com").Path("/articles"),
+ vars: []string{},
+ url: "http://foo.domain.com/articles",
+ },
+ {
+ route: new(Route).Path("/articles"),
+ vars: []string{},
+ url: "/articles",
+ },
+ {
+ route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
+ vars: []string{"category", "technology", "id", "42"},
+ url: "/articles/technology/42",
+ },
+ {
+ route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
+ vars: []string{"subdomain", "foo", "category", "technology", "id", "42"},
+ url: "http://foo.domain.com/articles/technology/42",
+ },
+}
+
+func TestHeaderMatcher(t *testing.T) {
+ for _, v := range headerMatcherTests {
+ request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+ for key, value := range v.headers {
+ request.Header.Add(key, value)
+ }
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, request.Header)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
+ }
+ }
+ }
+}
+
+func TestHostMatcher(t *testing.T) {
+ for _, v := range hostMatcherTests {
+ request, _ := http.NewRequest("GET", v.url, nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ vars := routeMatch.Vars
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.url)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+ }
+ }
+ if result {
+ if len(vars) != len(v.vars) {
+ t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+ }
+ for name, value := range vars {
+ if v.vars[name] != value {
+ t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+ }
+ }
+ } else {
+ if len(vars) != 0 {
+ t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+ }
+ }
+ }
+}
+
+func TestMethodMatcher(t *testing.T) {
+ for _, v := range methodMatcherTests {
+ request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.method)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.method)
+ }
+ }
+ }
+}
+
+func TestPathMatcher(t *testing.T) {
+ for _, v := range pathMatcherTests {
+ request, _ := http.NewRequest("GET", v.url, nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ vars := routeMatch.Vars
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.url)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+ }
+ }
+ if result {
+ if len(vars) != len(v.vars) {
+ t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+ }
+ for name, value := range vars {
+ if v.vars[name] != value {
+ t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+ }
+ }
+ } else {
+ if len(vars) != 0 {
+ t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+ }
+ }
+ }
+}
+
+func TestSchemeMatcher(t *testing.T) {
+ for _, v := range schemeMatcherTests {
+ request, _ := http.NewRequest("GET", v.url, nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.url)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+ }
+ }
+ }
+}
+
+func TestUrlBuilding(t *testing.T) {
+
+ for _, v := range urlBuildingTests {
+ u, _ := v.route.URL(v.vars...)
+ url := u.String()
+ if url != v.url {
+ t.Errorf("expected %v, got %v", v.url, url)
+ /*
+ reversePath := ""
+ reverseHost := ""
+ if v.route.pathTemplate != nil {
+ reversePath = v.route.pathTemplate.Reverse
+ }
+ if v.route.hostTemplate != nil {
+ reverseHost = v.route.hostTemplate.Reverse
+ }
+
+ t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
+ */
+ }
+ }
+
+ ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
+ }
+
+ router := NewRouter()
+ router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
+
+ url, _ := router.Get("article").URL("category", "technology", "id", "42")
+ expected := "/articles/technology/42"
+ if url.String() != expected {
+ t.Errorf("Expected %v, got %v", expected, url.String())
+ }
+}
+
+func TestMatchedRouteName(t *testing.T) {
+ routeName := "stock"
+ router := NewRouter()
+ route := router.NewRoute().Path("/products/").Name(routeName)
+
+ url := "http://www.example.com/products/"
+ request, _ := http.NewRequest("GET", url, nil)
+ var rv RouteMatch
+ ok := router.Match(request, &rv)
+
+ if !ok || rv.Route != route {
+ t.Errorf("Expected same route, got %+v.", rv.Route)
+ }
+
+ retName := rv.Route.GetName()
+ if retName != routeName {
+ t.Errorf("Expected %q, got %q.", routeName, retName)
+ }
+}
+
+func TestSubRouting(t *testing.T) {
+ // Example from docs.
+ router := NewRouter()
+ subrouter := router.NewRoute().Host("www.example.com").Subrouter()
+ route := subrouter.NewRoute().Path("/products/").Name("products")
+
+ url := "http://www.example.com/products/"
+ request, _ := http.NewRequest("GET", url, nil)
+ var rv RouteMatch
+ ok := router.Match(request, &rv)
+
+ if !ok || rv.Route != route {
+ t.Errorf("Expected same route, got %+v.", rv.Route)
+ }
+
+ u, _ := router.Get("products").URL()
+ builtURL := u.String()
+ // Yay, subroute aware of the domain when building!
+ if builtURL != url {
+ t.Errorf("Expected %q, got %q.", url, builtURL)
+ }
+}
+
+func TestVariableNames(t *testing.T) {
+ route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
+ if route.err == nil {
+ t.Errorf("Expected error for duplicated variable names")
+ }
+}
+
+func TestRedirectSlash(t *testing.T) {
+ var route *Route
+ var routeMatch RouteMatch
+ r := NewRouter()
+
+ r.StrictSlash(false)
+ route = r.NewRoute()
+ if route.strictSlash != false {
+ t.Errorf("Expected false redirectSlash.")
+ }
+
+ r.StrictSlash(true)
+ route = r.NewRoute()
+ if route.strictSlash != true {
+ t.Errorf("Expected true redirectSlash.")
+ }
+
+ route = new(Route)
+ route.strictSlash = true
+ route.Path("/{arg1}/{arg2:[0-9]+}/")
+ request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
+ routeMatch = RouteMatch{}
+ _ = route.Match(request, &routeMatch)
+ vars := routeMatch.Vars
+ if vars["arg1"] != "foo" {
+ t.Errorf("Expected foo.")
+ }
+ if vars["arg2"] != "123" {
+ t.Errorf("Expected 123.")
+ }
+ rsp := NewRecorder()
+ routeMatch.Handler.ServeHTTP(rsp, request)
+ if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
+ t.Errorf("Expected redirect header.")
+ }
+
+ route = new(Route)
+ route.strictSlash = true
+ route.Path("/{arg1}/{arg2:[0-9]+}")
+ request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
+ routeMatch = RouteMatch{}
+ _ = route.Match(request, &routeMatch)
+ vars = routeMatch.Vars
+ if vars["arg1"] != "foo" {
+ t.Errorf("Expected foo.")
+ }
+ if vars["arg2"] != "123" {
+ t.Errorf("Expected 123.")
+ }
+ rsp = NewRecorder()
+ routeMatch.Handler.ServeHTTP(rsp, request)
+ if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
+ t.Errorf("Expected redirect header.")
+ }
+}
+
+// Test for the new regexp library, still not available in stable Go.
+func TestNewRegexp(t *testing.T) {
+ var p *routeRegexp
+ var matches []string
+
+ tests := map[string]map[string][]string{
+ "/{foo:a{2}}": {
+ "/a": nil,
+ "/aa": {"aa"},
+ "/aaa": nil,
+ "/aaaa": nil,
+ },
+ "/{foo:a{2,}}": {
+ "/a": nil,
+ "/aa": {"aa"},
+ "/aaa": {"aaa"},
+ "/aaaa": {"aaaa"},
+ },
+ "/{foo:a{2,3}}": {
+ "/a": nil,
+ "/aa": {"aa"},
+ "/aaa": {"aaa"},
+ "/aaaa": nil,
+ },
+ "/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
+ "/a": nil,
+ "/ab": nil,
+ "/abc": nil,
+ "/abcd": nil,
+ "/abc/ab": {"abc", "ab"},
+ "/abc/abc": nil,
+ "/abcd/ab": nil,
+ },
+ `/{foo:\w{3,}}/{bar:\d{2,}}`: {
+ "/a": nil,
+ "/ab": nil,
+ "/abc": nil,
+ "/abc/1": nil,
+ "/abc/12": {"abc", "12"},
+ "/abcd/12": {"abcd", "12"},
+ "/abcd/123": {"abcd", "123"},
+ },
+ }
+
+ for pattern, paths := range tests {
+ p, _ = newRouteRegexp(pattern, false, false, false, false)
+ for path, result := range paths {
+ matches = p.regexp.FindStringSubmatch(path)
+ if result == nil {
+ if matches != nil {
+ t.Errorf("%v should not match %v.", pattern, path)
+ }
+ } else {
+ if len(matches) != len(result)+1 {
+ t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
+ } else {
+ for k, v := range result {
+ if matches[k+1] != v {
+ t.Errorf("Expected %v, got %v.", v, matches[k+1])
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/bench_test.go b/vendor/github.com/gorilla/websocket/bench_test.go
new file mode 100644
index 000000000..f66fc36bc
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/bench_test.go
@@ -0,0 +1,19 @@
+// Copyright 2014 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 (
+ "testing"
+)
+
+func BenchmarkMaskBytes(b *testing.B) {
+ var key [4]byte
+ data := make([]byte, 1024)
+ pos := 0
+ for i := 0; i < b.N; i++ {
+ pos = maskBytes(key, pos, data)
+ }
+ b.SetBytes(int64(len(data)))
+}
diff --git a/vendor/github.com/gorilla/websocket/client_server_test.go b/vendor/github.com/gorilla/websocket/client_server_test.go
new file mode 100644
index 000000000..3f7345dde
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/client_server_test.go
@@ -0,0 +1,451 @@
+// Copyright 2013 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 (
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/base64"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+var cstUpgrader = Upgrader{
+ Subprotocols: []string{"p0", "p1"},
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
+ http.Error(w, reason.Error(), status)
+ },
+}
+
+var cstDialer = Dialer{
+ Subprotocols: []string{"p1", "p2"},
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+}
+
+type cstHandler struct{ *testing.T }
+
+type cstServer struct {
+ *httptest.Server
+ URL string
+}
+
+const (
+ cstPath = "/a/b"
+ cstRawQuery = "x=y"
+ cstRequestURI = cstPath + "?" + cstRawQuery
+)
+
+func newServer(t *testing.T) *cstServer {
+ var s cstServer
+ s.Server = httptest.NewServer(cstHandler{t})
+ s.Server.URL += cstRequestURI
+ s.URL = makeWsProto(s.Server.URL)
+ return &s
+}
+
+func newTLSServer(t *testing.T) *cstServer {
+ var s cstServer
+ s.Server = httptest.NewTLSServer(cstHandler{t})
+ s.Server.URL += cstRequestURI
+ s.URL = makeWsProto(s.Server.URL)
+ return &s
+}
+
+func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != cstPath {
+ t.Logf("path=%v, want %v", r.URL.Path, cstPath)
+ http.Error(w, "bad path", 400)
+ return
+ }
+ if r.URL.RawQuery != cstRawQuery {
+ t.Logf("query=%v, want %v", r.URL.RawQuery, cstRawQuery)
+ http.Error(w, "bad path", 400)
+ return
+ }
+ subprotos := Subprotocols(r)
+ if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) {
+ t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols)
+ http.Error(w, "bad protocol", 400)
+ return
+ }
+ ws, err := cstUpgrader.Upgrade(w, r, http.Header{"Set-Cookie": {"sessionID=1234"}})
+ if err != nil {
+ t.Logf("Upgrade: %v", err)
+ return
+ }
+ defer ws.Close()
+
+ if ws.Subprotocol() != "p1" {
+ t.Logf("Subprotocol() = %s, want p1", ws.Subprotocol())
+ ws.Close()
+ return
+ }
+ op, rd, err := ws.NextReader()
+ if err != nil {
+ t.Logf("NextReader: %v", err)
+ return
+ }
+ wr, err := ws.NextWriter(op)
+ if err != nil {
+ t.Logf("NextWriter: %v", err)
+ return
+ }
+ if _, err = io.Copy(wr, rd); err != nil {
+ t.Logf("NextWriter: %v", err)
+ return
+ }
+ if err := wr.Close(); err != nil {
+ t.Logf("Close: %v", err)
+ return
+ }
+}
+
+func makeWsProto(s string) string {
+ return "ws" + strings.TrimPrefix(s, "http")
+}
+
+func sendRecv(t *testing.T, ws *Conn) {
+ const message = "Hello World!"
+ if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil {
+ t.Fatalf("SetWriteDeadline: %v", err)
+ }
+ if err := ws.WriteMessage(TextMessage, []byte(message)); err != nil {
+ t.Fatalf("WriteMessage: %v", err)
+ }
+ if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
+ t.Fatalf("SetReadDeadline: %v", err)
+ }
+ _, p, err := ws.ReadMessage()
+ if err != nil {
+ t.Fatalf("ReadMessage: %v", err)
+ }
+ if string(p) != message {
+ t.Fatalf("message=%s, want %s", p, message)
+ }
+}
+
+func TestProxyDial(t *testing.T) {
+
+ s := newServer(t)
+ defer s.Close()
+
+ surl, _ := url.Parse(s.URL)
+
+ cstDialer.Proxy = http.ProxyURL(surl)
+
+ connect := false
+ origHandler := s.Server.Config.Handler
+
+ // Capture the request Host header.
+ s.Server.Config.Handler = http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "CONNECT" {
+ connect = true
+ w.WriteHeader(200)
+ return
+ }
+
+ if !connect {
+ t.Log("connect not recieved")
+ http.Error(w, "connect not recieved", 405)
+ return
+ }
+ origHandler.ServeHTTP(w, r)
+ })
+
+ ws, _, err := cstDialer.Dial(s.URL, nil)
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer ws.Close()
+ sendRecv(t, ws)
+
+ cstDialer.Proxy = http.ProxyFromEnvironment
+}
+
+func TestProxyAuthorizationDial(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ surl, _ := url.Parse(s.URL)
+ surl.User = url.UserPassword("username", "password")
+ cstDialer.Proxy = http.ProxyURL(surl)
+
+ connect := false
+ origHandler := s.Server.Config.Handler
+
+ // Capture the request Host header.
+ s.Server.Config.Handler = http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ proxyAuth := r.Header.Get("Proxy-Authorization")
+ expectedProxyAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte("username:password"))
+ if r.Method == "CONNECT" && proxyAuth == expectedProxyAuth {
+ connect = true
+ w.WriteHeader(200)
+ return
+ }
+
+ if !connect {
+ t.Log("connect with proxy authorization not recieved")
+ http.Error(w, "connect with proxy authorization not recieved", 405)
+ return
+ }
+ origHandler.ServeHTTP(w, r)
+ })
+
+ ws, _, err := cstDialer.Dial(s.URL, nil)
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer ws.Close()
+ sendRecv(t, ws)
+
+ cstDialer.Proxy = http.ProxyFromEnvironment
+}
+
+func TestDial(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ ws, _, err := cstDialer.Dial(s.URL, nil)
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer ws.Close()
+ sendRecv(t, ws)
+}
+
+func TestDialTLS(t *testing.T) {
+ s := newTLSServer(t)
+ defer s.Close()
+
+ certs := x509.NewCertPool()
+ for _, c := range s.TLS.Certificates {
+ roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1])
+ if err != nil {
+ t.Fatalf("error parsing server's root cert: %v", err)
+ }
+ for _, root := range roots {
+ certs.AddCert(root)
+ }
+ }
+
+ u, _ := url.Parse(s.URL)
+ d := cstDialer
+ d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) }
+ d.TLSClientConfig = &tls.Config{RootCAs: certs}
+ ws, _, err := d.Dial("wss://example.com"+cstRequestURI, nil)
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer ws.Close()
+ sendRecv(t, ws)
+}
+
+func xTestDialTLSBadCert(t *testing.T) {
+ // This test is deactivated because of noisy logging from the net/http package.
+ s := newTLSServer(t)
+ defer s.Close()
+
+ ws, _, err := cstDialer.Dial(s.URL, nil)
+ if err == nil {
+ ws.Close()
+ t.Fatalf("Dial: nil")
+ }
+}
+
+func xTestDialTLSNoVerify(t *testing.T) {
+ s := newTLSServer(t)
+ defer s.Close()
+
+ d := cstDialer
+ d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+ ws, _, err := d.Dial(s.URL, nil)
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer ws.Close()
+ sendRecv(t, ws)
+}
+
+func TestDialTimeout(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ d := cstDialer
+ d.HandshakeTimeout = -1
+ ws, _, err := d.Dial(s.URL, nil)
+ if err == nil {
+ ws.Close()
+ t.Fatalf("Dial: nil")
+ }
+}
+
+func TestDialBadScheme(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ ws, _, err := cstDialer.Dial(s.Server.URL, nil)
+ if err == nil {
+ ws.Close()
+ t.Fatalf("Dial: nil")
+ }
+}
+
+func TestDialBadOrigin(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}})
+ if err == nil {
+ ws.Close()
+ t.Fatalf("Dial: nil")
+ }
+ if resp == nil {
+ t.Fatalf("resp=nil, err=%v", err)
+ }
+ if resp.StatusCode != http.StatusForbidden {
+ t.Fatalf("status=%d, want %d", resp.StatusCode, http.StatusForbidden)
+ }
+}
+
+func TestDialBadHeader(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ for _, k := range []string{"Upgrade",
+ "Connection",
+ "Sec-Websocket-Key",
+ "Sec-Websocket-Version",
+ "Sec-Websocket-Protocol"} {
+ h := http.Header{}
+ h.Set(k, "bad")
+ ws, _, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}})
+ if err == nil {
+ ws.Close()
+ t.Errorf("Dial with header %s returned nil", k)
+ }
+ }
+}
+
+func TestBadMethod(t *testing.T) {
+ s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ws, err := cstUpgrader.Upgrade(w, r, nil)
+ if err == nil {
+ t.Errorf("handshake succeeded, expect fail")
+ ws.Close()
+ }
+ }))
+ defer s.Close()
+
+ resp, err := http.PostForm(s.URL, url.Values{})
+ if err != nil {
+ t.Fatalf("PostForm returned error %v", err)
+ }
+ resp.Body.Close()
+ if resp.StatusCode != http.StatusMethodNotAllowed {
+ t.Errorf("Status = %d, want %d", resp.StatusCode, http.StatusMethodNotAllowed)
+ }
+}
+
+func TestHandshake(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {s.URL}})
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer ws.Close()
+
+ var sessionID string
+ for _, c := range resp.Cookies() {
+ if c.Name == "sessionID" {
+ sessionID = c.Value
+ }
+ }
+ if sessionID != "1234" {
+ t.Error("Set-Cookie not received from the server.")
+ }
+
+ if ws.Subprotocol() != "p1" {
+ t.Errorf("ws.Subprotocol() = %s, want p1", ws.Subprotocol())
+ }
+ sendRecv(t, ws)
+}
+
+func TestRespOnBadHandshake(t *testing.T) {
+ const expectedStatus = http.StatusGone
+ const expectedBody = "This is the response body."
+
+ s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(expectedStatus)
+ io.WriteString(w, expectedBody)
+ }))
+ defer s.Close()
+
+ ws, resp, err := cstDialer.Dial(makeWsProto(s.URL), nil)
+ if err == nil {
+ ws.Close()
+ t.Fatalf("Dial: nil")
+ }
+
+ if resp == nil {
+ t.Fatalf("resp=nil, err=%v", err)
+ }
+
+ if resp.StatusCode != expectedStatus {
+ t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus)
+ }
+
+ p, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatalf("ReadFull(resp.Body) returned error %v", err)
+ }
+
+ if string(p) != expectedBody {
+ t.Errorf("resp.Body=%s, want %s", p, expectedBody)
+ }
+}
+
+// TestHostHeader confirms that the host header provided in the call to Dial is
+// sent to the server.
+func TestHostHeader(t *testing.T) {
+ s := newServer(t)
+ defer s.Close()
+
+ specifiedHost := make(chan string, 1)
+ origHandler := s.Server.Config.Handler
+
+ // Capture the request Host header.
+ s.Server.Config.Handler = http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ specifiedHost <- r.Host
+ origHandler.ServeHTTP(w, r)
+ })
+
+ ws, _, err := cstDialer.Dial(s.URL, http.Header{"Host": {"testhost"}})
+ if err != nil {
+ t.Fatalf("Dial: %v", err)
+ }
+ defer ws.Close()
+
+ if gotHost := <-specifiedHost; gotHost != "testhost" {
+ t.Fatalf("gotHost = %q, want \"testhost\"", gotHost)
+ }
+
+ sendRecv(t, ws)
+}
diff --git a/vendor/github.com/gorilla/websocket/client_test.go b/vendor/github.com/gorilla/websocket/client_test.go
new file mode 100644
index 000000000..7d2b0844f
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/client_test.go
@@ -0,0 +1,72 @@
+// Copyright 2014 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 (
+ "net/url"
+ "reflect"
+ "testing"
+)
+
+var parseURLTests = []struct {
+ s string
+ u *url.URL
+ rui string
+}{
+ {"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
+ {"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
+ {"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}, "/"},
+ {"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}, "/"},
+ {"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}, "/a/b"},
+ {"ss://example.com/a/b", nil, ""},
+ {"ws://webmaster@example.com/", nil, ""},
+ {"wss://example.com/a/b?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b", RawQuery: "x=y"}, "/a/b?x=y"},
+ {"wss://example.com?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/", RawQuery: "x=y"}, "/?x=y"},
+}
+
+func TestParseURL(t *testing.T) {
+ for _, tt := range parseURLTests {
+ u, err := parseURL(tt.s)
+ if tt.u != nil && err != nil {
+ t.Errorf("parseURL(%q) returned error %v", tt.s, err)
+ continue
+ }
+ if tt.u == nil {
+ if err == nil {
+ t.Errorf("parseURL(%q) did not return error", tt.s)
+ }
+ continue
+ }
+ if !reflect.DeepEqual(u, tt.u) {
+ t.Errorf("parseURL(%q) = %v, want %v", tt.s, u, tt.u)
+ continue
+ }
+ if u.RequestURI() != tt.rui {
+ t.Errorf("parseURL(%q).RequestURI() = %v, want %v", tt.s, u.RequestURI(), tt.rui)
+ }
+ }
+}
+
+var hostPortNoPortTests = []struct {
+ u *url.URL
+ hostPort, hostNoPort string
+}{
+ {&url.URL{Scheme: "ws", Host: "example.com"}, "example.com:80", "example.com"},
+ {&url.URL{Scheme: "wss", Host: "example.com"}, "example.com:443", "example.com"},
+ {&url.URL{Scheme: "ws", Host: "example.com:7777"}, "example.com:7777", "example.com"},
+ {&url.URL{Scheme: "wss", Host: "example.com:7777"}, "example.com:7777", "example.com"},
+}
+
+func TestHostPortNoPort(t *testing.T) {
+ for _, tt := range hostPortNoPortTests {
+ hostPort, hostNoPort := hostPortNoPort(tt.u)
+ if hostPort != tt.hostPort {
+ t.Errorf("hostPortNoPort(%v) returned hostPort %q, want %q", tt.u, hostPort, tt.hostPort)
+ }
+ if hostNoPort != tt.hostNoPort {
+ t.Errorf("hostPortNoPort(%v) returned hostNoPort %q, want %q", tt.u, hostNoPort, tt.hostNoPort)
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/conn_test.go b/vendor/github.com/gorilla/websocket/conn_test.go
new file mode 100644
index 000000000..0243c1154
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/conn_test.go
@@ -0,0 +1,402 @@
+// Copyright 2013 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 (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "reflect"
+ "testing"
+ "testing/iotest"
+ "time"
+)
+
+var _ net.Error = errWriteTimeout
+
+type fakeNetConn struct {
+ io.Reader
+ io.Writer
+}
+
+func (c fakeNetConn) Close() error { return nil }
+func (c fakeNetConn) LocalAddr() net.Addr { return nil }
+func (c fakeNetConn) RemoteAddr() net.Addr { return nil }
+func (c fakeNetConn) SetDeadline(t time.Time) error { return nil }
+func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil }
+func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil }
+
+func TestFraming(t *testing.T) {
+ frameSizes := []int{0, 1, 2, 124, 125, 126, 127, 128, 129, 65534, 65535, 65536, 65537}
+ var readChunkers = []struct {
+ name string
+ f func(io.Reader) io.Reader
+ }{
+ {"half", iotest.HalfReader},
+ {"one", iotest.OneByteReader},
+ {"asis", func(r io.Reader) io.Reader { return r }},
+ }
+
+ writeBuf := make([]byte, 65537)
+ for i := range writeBuf {
+ writeBuf[i] = byte(i)
+ }
+
+ for _, isServer := range []bool{true, false} {
+ for _, chunker := range readChunkers {
+
+ var connBuf bytes.Buffer
+ wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024)
+ rc := newConn(fakeNetConn{Reader: chunker.f(&connBuf), Writer: nil}, !isServer, 1024, 1024)
+
+ for _, n := range frameSizes {
+ for _, iocopy := range []bool{true, false} {
+ name := fmt.Sprintf("s:%v, r:%s, n:%d c:%v", isServer, chunker.name, n, iocopy)
+
+ w, err := wc.NextWriter(TextMessage)
+ if err != nil {
+ t.Errorf("%s: wc.NextWriter() returned %v", name, err)
+ continue
+ }
+ var nn int
+ if iocopy {
+ var n64 int64
+ n64, err = io.Copy(w, bytes.NewReader(writeBuf[:n]))
+ nn = int(n64)
+ } else {
+ nn, err = w.Write(writeBuf[:n])
+ }
+ if err != nil || nn != n {
+ t.Errorf("%s: w.Write(writeBuf[:n]) returned %d, %v", name, nn, err)
+ continue
+ }
+ err = w.Close()
+ if err != nil {
+ t.Errorf("%s: w.Close() returned %v", name, err)
+ continue
+ }
+
+ opCode, r, err := rc.NextReader()
+ if err != nil || opCode != TextMessage {
+ t.Errorf("%s: NextReader() returned %d, r, %v", name, opCode, err)
+ continue
+ }
+ rbuf, err := ioutil.ReadAll(r)
+ if err != nil {
+ t.Errorf("%s: ReadFull() returned rbuf, %v", name, err)
+ continue
+ }
+
+ if len(rbuf) != n {
+ t.Errorf("%s: len(rbuf) is %d, want %d", name, len(rbuf), n)
+ continue
+ }
+
+ for i, b := range rbuf {
+ if byte(i) != b {
+ t.Errorf("%s: bad byte at offset %d", name, i)
+ break
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+func TestControl(t *testing.T) {
+ const message = "this is a ping/pong messsage"
+ for _, isServer := range []bool{true, false} {
+ for _, isWriteControl := range []bool{true, false} {
+ name := fmt.Sprintf("s:%v, wc:%v", isServer, isWriteControl)
+ var connBuf bytes.Buffer
+ wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024)
+ rc := newConn(fakeNetConn{Reader: &connBuf, Writer: nil}, !isServer, 1024, 1024)
+ if isWriteControl {
+ wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second))
+ } else {
+ w, err := wc.NextWriter(PongMessage)
+ if err != nil {
+ t.Errorf("%s: wc.NextWriter() returned %v", name, err)
+ continue
+ }
+ if _, err := w.Write([]byte(message)); err != nil {
+ t.Errorf("%s: w.Write() returned %v", name, err)
+ continue
+ }
+ if err := w.Close(); err != nil {
+ t.Errorf("%s: w.Close() returned %v", name, err)
+ continue
+ }
+ var actualMessage string
+ rc.SetPongHandler(func(s string) error { actualMessage = s; return nil })
+ rc.NextReader()
+ if actualMessage != message {
+ t.Errorf("%s: pong=%q, want %q", name, actualMessage, message)
+ continue
+ }
+ }
+ }
+ }
+}
+
+func TestCloseBeforeFinalFrame(t *testing.T) {
+ const bufSize = 512
+
+ expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"}
+
+ var b1, b2 bytes.Buffer
+ wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
+ rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
+
+ w, _ := wc.NextWriter(BinaryMessage)
+ w.Write(make([]byte, bufSize+bufSize/2))
+ wc.WriteControl(CloseMessage, FormatCloseMessage(expectedErr.Code, expectedErr.Text), time.Now().Add(10*time.Second))
+ w.Close()
+
+ op, r, err := rc.NextReader()
+ if op != BinaryMessage || err != nil {
+ t.Fatalf("NextReader() returned %d, %v", op, err)
+ }
+ _, err = io.Copy(ioutil.Discard, r)
+ if !reflect.DeepEqual(err, expectedErr) {
+ t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr)
+ }
+ _, _, err = rc.NextReader()
+ if !reflect.DeepEqual(err, expectedErr) {
+ t.Fatalf("NextReader() returned %v, want %v", err, expectedErr)
+ }
+}
+
+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
+
+ var b1, b2 bytes.Buffer
+ wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
+ rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
+
+ w, _ := wc.NextWriter(BinaryMessage)
+ w.Write(make([]byte, bufSize+bufSize/2))
+
+ op, r, err := rc.NextReader()
+ if op != BinaryMessage || err != nil {
+ t.Fatalf("NextReader() returned %d, %v", op, err)
+ }
+ _, err = io.Copy(ioutil.Discard, r)
+ if err != errUnexpectedEOF {
+ t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF)
+ }
+ _, _, err = rc.NextReader()
+ if err != errUnexpectedEOF {
+ t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF)
+ }
+}
+
+func TestReadLimit(t *testing.T) {
+
+ const readLimit = 512
+ message := make([]byte, readLimit+1)
+
+ var b1, b2 bytes.Buffer
+ wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, readLimit-2)
+ rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
+ rc.SetReadLimit(readLimit)
+
+ // Send message at the limit with interleaved pong.
+ w, _ := wc.NextWriter(BinaryMessage)
+ w.Write(message[:readLimit-1])
+ wc.WriteControl(PongMessage, []byte("this is a pong"), time.Now().Add(10*time.Second))
+ w.Write(message[:1])
+ w.Close()
+
+ // Send message larger than the limit.
+ wc.WriteMessage(BinaryMessage, message[:readLimit+1])
+
+ op, _, err := rc.NextReader()
+ if op != BinaryMessage || err != nil {
+ t.Fatalf("1: NextReader() returned %d, %v", op, err)
+ }
+ op, r, err := rc.NextReader()
+ if op != BinaryMessage || err != nil {
+ t.Fatalf("2: NextReader() returned %d, %v", op, err)
+ }
+ _, err = io.Copy(ioutil.Discard, r)
+ if err != ErrReadLimit {
+ t.Fatalf("io.Copy() returned %v", err)
+ }
+}
+
+func TestUnderlyingConn(t *testing.T) {
+ var b1, b2 bytes.Buffer
+ fc := fakeNetConn{Reader: &b1, Writer: &b2}
+ c := newConn(fc, true, 1024, 1024)
+ ul := c.UnderlyingConn()
+ if ul != fc {
+ t.Fatalf("Underlying conn is not what it should be.")
+ }
+}
+
+func TestBufioReadBytes(t *testing.T) {
+
+ // Test calling bufio.ReadBytes for value longer than read buffer size.
+
+ m := make([]byte, 512)
+ m[len(m)-1] = '\n'
+
+ var b1, b2 bytes.Buffer
+ wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, len(m)+64, len(m)+64)
+ rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, len(m)-64, len(m)-64)
+
+ w, _ := wc.NextWriter(BinaryMessage)
+ w.Write(m)
+ w.Close()
+
+ op, r, err := rc.NextReader()
+ if op != BinaryMessage || err != nil {
+ t.Fatalf("NextReader() returned %d, %v", op, err)
+ }
+
+ br := bufio.NewReader(r)
+ p, err := br.ReadBytes('\n')
+ if err != nil {
+ t.Fatalf("ReadBytes() returned %v", err)
+ }
+ if len(p) != len(m) {
+ t.Fatalf("read returnd %d bytes, want %d bytes", len(p), len(m))
+ }
+}
+
+var closeErrorTests = []struct {
+ err error
+ codes []int
+ ok bool
+}{
+ {&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, true},
+ {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, false},
+ {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, true},
+ {errors.New("hello"), []int{CloseNormalClosure}, false},
+}
+
+func TestCloseError(t *testing.T) {
+ for _, tt := range closeErrorTests {
+ ok := IsCloseError(tt.err, tt.codes...)
+ if ok != tt.ok {
+ t.Errorf("IsCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok)
+ }
+ }
+}
+
+var unexpectedCloseErrorTests = []struct {
+ err error
+ codes []int
+ ok bool
+}{
+ {&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, false},
+ {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, true},
+ {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, false},
+ {errors.New("hello"), []int{CloseNormalClosure}, false},
+}
+
+func TestUnexpectedCloseErrors(t *testing.T) {
+ for _, tt := range unexpectedCloseErrorTests {
+ ok := IsUnexpectedCloseError(tt.err, tt.codes...)
+ if ok != tt.ok {
+ t.Errorf("IsUnexpectedCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok)
+ }
+ }
+}
+
+type blockingWriter struct {
+ c1, c2 chan struct{}
+}
+
+func (w blockingWriter) Write(p []byte) (int, error) {
+ // Allow main to continue
+ close(w.c1)
+ // Wait for panic in main
+ <-w.c2
+ return len(p), nil
+}
+
+func TestConcurrentWritePanic(t *testing.T) {
+ w := blockingWriter{make(chan struct{}), make(chan struct{})}
+ c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024)
+ go func() {
+ c.WriteMessage(TextMessage, []byte{})
+ }()
+
+ // wait for goroutine to block in write.
+ <-w.c1
+
+ defer func() {
+ close(w.c2)
+ if v := recover(); v != nil {
+ return
+ }
+ }()
+
+ c.WriteMessage(TextMessage, []byte{})
+ t.Fatal("should not get here")
+}
+
+type failingReader struct{}
+
+func (r failingReader) Read(p []byte) (int, error) {
+ return 0, io.EOF
+}
+
+func TestFailedConnectionReadPanic(t *testing.T) {
+ c := newConn(fakeNetConn{Reader: failingReader{}, Writer: nil}, false, 1024, 1024)
+
+ defer func() {
+ if v := recover(); v != nil {
+ return
+ }
+ }()
+
+ for i := 0; i < 20000; i++ {
+ c.ReadMessage()
+ }
+ t.Fatal("should not get here")
+}
diff --git a/vendor/github.com/gorilla/websocket/example_test.go b/vendor/github.com/gorilla/websocket/example_test.go
new file mode 100644
index 000000000..96449eac7
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/example_test.go
@@ -0,0 +1,46 @@
+// Copyright 2015 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_test
+
+import (
+ "log"
+ "net/http"
+ "testing"
+
+ "github.com/gorilla/websocket"
+)
+
+var (
+ c *websocket.Conn
+ req *http.Request
+)
+
+// The websocket.IsUnexpectedCloseError function is useful for identifying
+// application and protocol errors.
+//
+// This server application works with a client application running in the
+// browser. The client application does not explicitly close the websocket. The
+// 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() {
+
+ for {
+ messageType, p, err := c.ReadMessage()
+ if err != nil {
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
+ log.Printf("error: %v, user-agent: %v", err, req.Header.Get("User-Agent"))
+ }
+ return
+ }
+ processMesage(messageType, p)
+ }
+}
+
+func processMesage(mt int, p []byte) {}
+
+// TestX prevents godoc from showing this entire file in the example. Remove
+// this function when a second example is added.
+func TestX(t *testing.T) {}
diff --git a/vendor/github.com/gorilla/websocket/examples/autobahn/README.md b/vendor/github.com/gorilla/websocket/examples/autobahn/README.md
new file mode 100644
index 000000000..075ac1530
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/autobahn/README.md
@@ -0,0 +1,13 @@
+# Test Server
+
+This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite).
+
+To test the server, run
+
+ go run server.go
+
+and start the client test driver
+
+ wstest -m fuzzingclient -s fuzzingclient.json
+
+When the client completes, it writes a report to reports/clients/index.html.
diff --git a/vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json b/vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json
new file mode 100644
index 000000000..27d5a5b14
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json
@@ -0,0 +1,14 @@
+
+{
+ "options": {"failByDrop": false},
+ "outdir": "./reports/clients",
+ "servers": [
+ {"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}},
+ {"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}},
+ {"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}},
+ {"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}}
+ ],
+ "cases": ["*"],
+ "exclude-cases": [],
+ "exclude-agent-cases": {}
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/autobahn/server.go b/vendor/github.com/gorilla/websocket/examples/autobahn/server.go
new file mode 100644
index 000000000..d96ac84db
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/autobahn/server.go
@@ -0,0 +1,246 @@
+// Copyright 2013 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.
+
+// Command server is a test server for the Autobahn WebSockets Test Suite.
+package main
+
+import (
+ "errors"
+ "flag"
+ "github.com/gorilla/websocket"
+ "io"
+ "log"
+ "net/http"
+ "time"
+ "unicode/utf8"
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 4096,
+ WriteBufferSize: 4096,
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
+}
+
+// echoCopy echoes messages from the client using io.Copy.
+func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println("Upgrade:", err)
+ return
+ }
+ defer conn.Close()
+ for {
+ mt, r, err := conn.NextReader()
+ if err != nil {
+ if err != io.EOF {
+ log.Println("NextReader:", err)
+ }
+ return
+ }
+ if mt == websocket.TextMessage {
+ r = &validator{r: r}
+ }
+ w, err := conn.NextWriter(mt)
+ if err != nil {
+ log.Println("NextWriter:", err)
+ return
+ }
+ if mt == websocket.TextMessage {
+ r = &validator{r: r}
+ }
+ if writerOnly {
+ _, err = io.Copy(struct{ io.Writer }{w}, r)
+ } else {
+ _, err = io.Copy(w, r)
+ }
+ if err != nil {
+ if err == errInvalidUTF8 {
+ conn.WriteControl(websocket.CloseMessage,
+ websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
+ time.Time{})
+ }
+ log.Println("Copy:", err)
+ return
+ }
+ err = w.Close()
+ if err != nil {
+ log.Println("Close:", err)
+ return
+ }
+ }
+}
+
+func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) {
+ echoCopy(w, r, true)
+}
+
+func echoCopyFull(w http.ResponseWriter, r *http.Request) {
+ echoCopy(w, r, false)
+}
+
+// echoReadAll echoes messages from the client by reading the entire message
+// with ioutil.ReadAll.
+func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println("Upgrade:", err)
+ return
+ }
+ defer conn.Close()
+ for {
+ mt, b, err := conn.ReadMessage()
+ if err != nil {
+ if err != io.EOF {
+ log.Println("NextReader:", err)
+ }
+ return
+ }
+ if mt == websocket.TextMessage {
+ if !utf8.Valid(b) {
+ conn.WriteControl(websocket.CloseMessage,
+ websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""),
+ time.Time{})
+ log.Println("ReadAll: invalid utf8")
+ }
+ }
+ if writeMessage {
+ err = conn.WriteMessage(mt, b)
+ if err != nil {
+ log.Println("WriteMessage:", err)
+ }
+ } else {
+ w, err := conn.NextWriter(mt)
+ if err != nil {
+ log.Println("NextWriter:", err)
+ return
+ }
+ if _, err := w.Write(b); err != nil {
+ log.Println("Writer:", err)
+ return
+ }
+ if err := w.Close(); err != nil {
+ log.Println("Close:", err)
+ return
+ }
+ }
+ }
+}
+
+func echoReadAllWriter(w http.ResponseWriter, r *http.Request) {
+ echoReadAll(w, r, false)
+}
+
+func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) {
+ echoReadAll(w, r, true)
+}
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.Error(w, "Not found.", 404)
+ return
+ }
+ if r.Method != "GET" {
+ http.Error(w, "Method not allowed", 405)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ io.WriteString(w, "<html><body>Echo Server</body></html>")
+}
+
+var addr = flag.String("addr", ":9000", "http service address")
+
+func main() {
+ flag.Parse()
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/c", echoCopyWriterOnly)
+ http.HandleFunc("/f", echoCopyFull)
+ http.HandleFunc("/r", echoReadAllWriter)
+ http.HandleFunc("/m", echoReadAllWriteMessage)
+ err := http.ListenAndServe(*addr, nil)
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
+
+type validator struct {
+ state int
+ x rune
+ r io.Reader
+}
+
+var errInvalidUTF8 = errors.New("invalid utf8")
+
+func (r *validator) Read(p []byte) (int, error) {
+ n, err := r.r.Read(p)
+ state := r.state
+ x := r.x
+ for _, b := range p[:n] {
+ state, x = decode(state, x, b)
+ if state == utf8Reject {
+ break
+ }
+ }
+ r.state = state
+ r.x = x
+ if state == utf8Reject || (err == io.EOF && state != utf8Accept) {
+ return n, errInvalidUTF8
+ }
+ return n, err
+}
+
+// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+//
+// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+var utf8d = [...]byte{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
+ 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
+ 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
+ 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
+ 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
+ 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
+ 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
+ 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
+}
+
+const (
+ utf8Accept = 0
+ utf8Reject = 1
+)
+
+func decode(state int, x rune, b byte) (int, rune) {
+ t := utf8d[b]
+ if state != utf8Accept {
+ x = rune(b&0x3f) | (x << 6)
+ } else {
+ x = rune((0xff >> t) & b)
+ }
+ state = int(utf8d[256+state*16+int(t)])
+ return state, x
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/README.md b/vendor/github.com/gorilla/websocket/examples/chat/README.md
new file mode 100644
index 000000000..5df3cf1a3
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/README.md
@@ -0,0 +1,20 @@
+# 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.
+
+## Running the example
+
+The example requires a working Go development environment. The [Getting
+Started](http://golang.org/doc/install) page describes how to install the
+development environment.
+
+Once you have Go up and running, you can download, build and run the example
+using the following commands.
+
+ $ go get github.com/gorilla/websocket
+ $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
+ $ go run *.go
+
+To use the chat example, open http://localhost:8080/ in your browser.
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/conn.go b/vendor/github.com/gorilla/websocket/examples/chat/conn.go
new file mode 100644
index 000000000..40fd38c2c
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/conn.go
@@ -0,0 +1,105 @@
+// Copyright 2013 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 main
+
+import (
+ "github.com/gorilla/websocket"
+ "log"
+ "net/http"
+ "time"
+)
+
+const (
+ // Time allowed to write a message to the peer.
+ writeWait = 10 * time.Second
+
+ // Time allowed to read the next pong message from the peer.
+ pongWait = 60 * time.Second
+
+ // Send pings to peer with this period. Must be less than pongWait.
+ pingPeriod = (pongWait * 9) / 10
+
+ // Maximum message size allowed from peer.
+ maxMessageSize = 512
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+}
+
+// connection is an middleman between the websocket connection and the hub.
+type connection struct {
+ // The websocket connection.
+ ws *websocket.Conn
+
+ // Buffered channel of outbound messages.
+ send chan []byte
+}
+
+// readPump pumps messages from the websocket connection to the hub.
+func (c *connection) readPump() {
+ defer func() {
+ h.unregister <- c
+ c.ws.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 })
+ for {
+ _, message, err := c.ws.ReadMessage()
+ if err != nil {
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
+ log.Printf("error: %v", err)
+ }
+ break
+ }
+ h.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)
+}
+
+// writePump pumps messages from the hub to the websocket connection.
+func (c *connection) writePump() {
+ ticker := time.NewTicker(pingPeriod)
+ defer func() {
+ ticker.Stop()
+ c.ws.Close()
+ }()
+ for {
+ select {
+ case message, ok := <-c.send:
+ if !ok {
+ c.write(websocket.CloseMessage, []byte{})
+ return
+ }
+ if err := c.write(websocket.TextMessage, message); err != nil {
+ return
+ }
+ case <-ticker.C:
+ if err := c.write(websocket.PingMessage, []byte{}); err != nil {
+ return
+ }
+ }
+ }
+}
+
+// serveWs handles websocket requests from the peer.
+func serveWs(w http.ResponseWriter, r *http.Request) {
+ ws, 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()
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/home.html b/vendor/github.com/gorilla/websocket/examples/chat/home.html
new file mode 100644
index 000000000..29599225c
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/home.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Chat Example</title>
+<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
+<script type="text/javascript">
+ $(function() {
+
+ var conn;
+ var msg = $("#msg");
+ var log = $("#log");
+
+ function appendLog(msg) {
+ var d = log[0]
+ var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
+ msg.appendTo(log)
+ if (doScroll) {
+ d.scrollTop = d.scrollHeight - d.clientHeight;
+ }
+ }
+
+ $("#form").submit(function() {
+ if (!conn) {
+ return false;
+ }
+ if (!msg.val()) {
+ return false;
+ }
+ conn.send(msg.val());
+ msg.val("");
+ return false
+ });
+
+ if (window["WebSocket"]) {
+ conn = new WebSocket("ws://{{$}}/ws");
+ conn.onclose = function(evt) {
+ appendLog($("<div><b>Connection closed.</b></div>"))
+ }
+ conn.onmessage = function(evt) {
+ appendLog($("<div/>").text(evt.data))
+ }
+ } else {
+ appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
+ }
+ });
+</script>
+<style type="text/css">
+html {
+ overflow: hidden;
+}
+
+body {
+ overflow: hidden;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ background: gray;
+}
+
+#log {
+ background: white;
+ margin: 0;
+ padding: 0.5em 0.5em 0.5em 0.5em;
+ position: absolute;
+ top: 0.5em;
+ left: 0.5em;
+ right: 0.5em;
+ bottom: 3em;
+ overflow: auto;
+}
+
+#form {
+ padding: 0 0.5em 0 0.5em;
+ margin: 0;
+ position: absolute;
+ bottom: 1em;
+ left: 0px;
+ width: 100%;
+ overflow: hidden;
+}
+
+</style>
+</head>
+<body>
+<div id="log"></div>
+<form id="form">
+ <input type="submit" value="Send" />
+ <input type="text" id="msg" size="64"/>
+</form>
+</body>
+</html>
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/hub.go b/vendor/github.com/gorilla/websocket/examples/chat/hub.go
new file mode 100644
index 000000000..449ba753d
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/hub.go
@@ -0,0 +1,51 @@
+// Copyright 2013 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 main
+
+// hub maintains the set of active connections and broadcasts messages to the
+// connections.
+type hub struct {
+ // Registered connections.
+ connections map[*connection]bool
+
+ // Inbound messages from the connections.
+ broadcast chan []byte
+
+ // Register requests from the connections.
+ register chan *connection
+
+ // Unregister requests from connections.
+ unregister chan *connection
+}
+
+var h = hub{
+ broadcast: make(chan []byte),
+ register: make(chan *connection),
+ unregister: make(chan *connection),
+ connections: make(map[*connection]bool),
+}
+
+func (h *hub) run() {
+ for {
+ select {
+ case c := <-h.register:
+ h.connections[c] = true
+ case c := <-h.unregister:
+ if _, ok := h.connections[c]; ok {
+ delete(h.connections, c)
+ close(c.send)
+ }
+ case m := <-h.broadcast:
+ for c := range h.connections {
+ select {
+ case c.send <- m:
+ default:
+ close(c.send)
+ delete(h.connections, c)
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/chat/main.go b/vendor/github.com/gorilla/websocket/examples/chat/main.go
new file mode 100644
index 000000000..3c4448d72
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/chat/main.go
@@ -0,0 +1,39 @@
+// Copyright 2013 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 main
+
+import (
+ "flag"
+ "log"
+ "net/http"
+ "text/template"
+)
+
+var addr = flag.String("addr", ":8080", "http service address")
+var homeTempl = template.Must(template.ParseFiles("home.html"))
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.Error(w, "Not found", 404)
+ return
+ }
+ if r.Method != "GET" {
+ http.Error(w, "Method not allowed", 405)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ homeTempl.Execute(w, r.Host)
+}
+
+func main() {
+ flag.Parse()
+ go h.run()
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/ws", serveWs)
+ err := http.ListenAndServe(*addr, nil)
+ if err != nil {
+ log.Fatal("ListenAndServe: ", err)
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/command/README.md b/vendor/github.com/gorilla/websocket/examples/command/README.md
new file mode 100644
index 000000000..c30d3979a
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/command/README.md
@@ -0,0 +1,19 @@
+# Command example
+
+This example connects a websocket connection to stdin and stdout of a command.
+Received messages are written to stdin followed by a `\n`. Each line read from
+from standard out is sent as a message to the client.
+
+ $ go get github.com/gorilla/websocket
+ $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command`
+ $ go run main.go <command and arguments to run>
+ # Open http://localhost:8080/ .
+
+Try the following commands.
+
+ # Echo sent messages to the output area.
+ $ go run main.go cat
+
+ # Run a shell.Try sending "ls" and "cat main.go".
+ $ go run main.go sh
+
diff --git a/vendor/github.com/gorilla/websocket/examples/command/home.html b/vendor/github.com/gorilla/websocket/examples/command/home.html
new file mode 100644
index 000000000..72fd02b2a
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/command/home.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Command Example</title>
+<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
+<script type="text/javascript">
+ $(function() {
+
+ var conn;
+ var msg = $("#msg");
+ var log = $("#log");
+
+ function appendLog(msg) {
+ var d = log[0]
+ var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
+ msg.appendTo(log)
+ if (doScroll) {
+ d.scrollTop = d.scrollHeight - d.clientHeight;
+ }
+ }
+
+ $("#form").submit(function() {
+ if (!conn) {
+ return false;
+ }
+ if (!msg.val()) {
+ return false;
+ }
+ conn.send(msg.val());
+ msg.val("");
+ return false
+ });
+
+ if (window["WebSocket"]) {
+ conn = new WebSocket("ws://{{$}}/ws");
+ conn.onclose = function(evt) {
+ appendLog($("<div><b>Connection closed.</b></div>"))
+ }
+ conn.onmessage = function(evt) {
+ appendLog($("<pre/>").text(evt.data))
+ }
+ } else {
+ appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
+ }
+ });
+</script>
+<style type="text/css">
+html {
+ overflow: hidden;
+}
+
+body {
+ overflow: hidden;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ background: gray;
+}
+
+#log {
+ background: white;
+ margin: 0;
+ padding: 0.5em 0.5em 0.5em 0.5em;
+ position: absolute;
+ top: 0.5em;
+ left: 0.5em;
+ right: 0.5em;
+ bottom: 3em;
+ overflow: auto;
+}
+
+#log pre {
+ margin: 0;
+}
+
+#form {
+ padding: 0 0.5em 0 0.5em;
+ margin: 0;
+ position: absolute;
+ bottom: 1em;
+ left: 0px;
+ width: 100%;
+ overflow: hidden;
+}
+
+</style>
+</head>
+<body>
+<div id="log"></div>
+<form id="form">
+ <input type="submit" value="Send" />
+ <input type="text" id="msg" size="64"/>
+</form>
+</body>
+</html>
diff --git a/vendor/github.com/gorilla/websocket/examples/command/main.go b/vendor/github.com/gorilla/websocket/examples/command/main.go
new file mode 100644
index 000000000..f3f022edb
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/command/main.go
@@ -0,0 +1,188 @@
+// Copyright 2015 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 main
+
+import (
+ "bufio"
+ "flag"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "os/exec"
+ "text/template"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+var (
+ addr = flag.String("addr", "127.0.0.1:8080", "http service address")
+ cmdPath string
+ homeTempl = template.Must(template.ParseFiles("home.html"))
+)
+
+const (
+ // Time allowed to write a message to the peer.
+ writeWait = 10 * time.Second
+
+ // Maximum message size allowed from peer.
+ maxMessageSize = 8192
+
+ // Time allowed to read the next pong message from the peer.
+ pongWait = 60 * time.Second
+
+ // Send pings to peer with this period. Must be less than pongWait.
+ pingPeriod = (pongWait * 9) / 10
+)
+
+func pumpStdin(ws *websocket.Conn, w io.Writer) {
+ defer ws.Close()
+ ws.SetReadLimit(maxMessageSize)
+ ws.SetReadDeadline(time.Now().Add(pongWait))
+ ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, message, err := ws.ReadMessage()
+ if err != nil {
+ break
+ }
+ message = append(message, '\n')
+ if _, err := w.Write(message); err != nil {
+ break
+ }
+ }
+}
+
+func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
+ defer func() {
+ ws.Close()
+ close(done)
+ }()
+ s := bufio.NewScanner(r)
+ for s.Scan() {
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
+ break
+ }
+ }
+ if s.Err() != nil {
+ log.Println("scan:", s.Err())
+ }
+}
+
+func ping(ws *websocket.Conn, done chan struct{}) {
+ ticker := time.NewTicker(pingPeriod)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
+ log.Println("ping:", err)
+ }
+ case <-done:
+ return
+ }
+ }
+}
+
+func internalError(ws *websocket.Conn, msg string, err error) {
+ log.Println(msg, err)
+ ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
+}
+
+var upgrader = websocket.Upgrader{}
+
+func serveWs(w http.ResponseWriter, r *http.Request) {
+ ws, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Println("upgrade:", err)
+ return
+ }
+
+ defer ws.Close()
+
+ outr, outw, err := os.Pipe()
+ if err != nil {
+ internalError(ws, "stdout:", err)
+ return
+ }
+ defer outr.Close()
+ defer outw.Close()
+
+ inr, inw, err := os.Pipe()
+ if err != nil {
+ internalError(ws, "stdin:", err)
+ return
+ }
+ defer inr.Close()
+ defer inw.Close()
+
+ proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{
+ Files: []*os.File{inr, outw, outw},
+ })
+ if err != nil {
+ internalError(ws, "start:", err)
+ return
+ }
+
+ inr.Close()
+ outw.Close()
+
+ stdoutDone := make(chan struct{})
+ go pumpStdout(ws, outr, stdoutDone)
+ go ping(ws, stdoutDone)
+
+ pumpStdin(ws, inw)
+
+ // Some commands will exit when stdin is closed.
+ inw.Close()
+
+ // Other commands need a bonk on the head.
+ if err := proc.Signal(os.Interrupt); err != nil {
+ log.Println("inter:", err)
+ }
+
+ select {
+ case <-stdoutDone:
+ case <-time.After(time.Second):
+ // A bigger bonk on the head.
+ if err := proc.Signal(os.Kill); err != nil {
+ log.Println("term:", err)
+ }
+ <-stdoutDone
+ }
+
+ if _, err := proc.Wait(); err != nil {
+ log.Println("wait:", err)
+ }
+}
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.Error(w, "Not found", 404)
+ return
+ }
+ if r.Method != "GET" {
+ http.Error(w, "Method not allowed", 405)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ homeTempl.Execute(w, r.Host)
+}
+
+func main() {
+ flag.Parse()
+ if len(flag.Args()) < 1 {
+ log.Fatal("must specify at least one argument")
+ }
+ var err error
+ cmdPath, err = exec.LookPath(flag.Args()[0])
+ if err != nil {
+ log.Fatal(err)
+ }
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/ws", serveWs)
+ log.Fatal(http.ListenAndServe(*addr, nil))
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/echo/README.md b/vendor/github.com/gorilla/websocket/examples/echo/README.md
new file mode 100644
index 000000000..6ad79ed76
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/echo/README.md
@@ -0,0 +1,17 @@
+# Client and server example
+
+This example shows a simple client and server.
+
+The server echoes messages sent to it. The client sends a message every second
+and prints all messages received.
+
+To run the example, start the server:
+
+ $ go run server.go
+
+Next, start the client:
+
+ $ go run client.go
+
+The server includes a simple web client. To use the client, open
+http://127.0.0.1:8080 in the browser and follow the instructions on the page.
diff --git a/vendor/github.com/gorilla/websocket/examples/echo/client.go b/vendor/github.com/gorilla/websocket/examples/echo/client.go
new file mode 100644
index 000000000..6578094e7
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/echo/client.go
@@ -0,0 +1,81 @@
+// Copyright 2015 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 ignore
+
+package main
+
+import (
+ "flag"
+ "log"
+ "net/url"
+ "os"
+ "os/signal"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+var addr = flag.String("addr", "localhost:8080", "http service address")
+
+func main() {
+ flag.Parse()
+ log.SetFlags(0)
+
+ interrupt := make(chan os.Signal, 1)
+ signal.Notify(interrupt, os.Interrupt)
+
+ u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
+ log.Printf("connecting to %s", u.String())
+
+ c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
+ if err != nil {
+ log.Fatal("dial:", err)
+ }
+ defer c.Close()
+
+ done := make(chan struct{})
+
+ go func() {
+ defer c.Close()
+ defer close(done)
+ for {
+ _, message, err := c.ReadMessage()
+ if err != nil {
+ log.Println("read:", err)
+ return
+ }
+ log.Printf("recv: %s", message)
+ }
+ }()
+
+ ticker := time.NewTicker(time.Second)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case t := <-ticker.C:
+ err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
+ if err != nil {
+ log.Println("write:", err)
+ return
+ }
+ case <-interrupt:
+ log.Println("interrupt")
+ // To cleanly close a connection, a client should send a close
+ // frame and wait for the server to close the connection.
+ err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ if err != nil {
+ log.Println("write close:", err)
+ return
+ }
+ select {
+ case <-done:
+ case <-time.After(time.Second):
+ }
+ c.Close()
+ return
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/examples/echo/server.go b/vendor/github.com/gorilla/websocket/examples/echo/server.go
new file mode 100644
index 000000000..a685b0974
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/echo/server.go
@@ -0,0 +1,132 @@
+// Copyright 2015 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 ignore
+
+package main
+
+import (
+ "flag"
+ "html/template"
+ "log"
+ "net/http"
+
+ "github.com/gorilla/websocket"
+)
+
+var addr = flag.String("addr", "localhost:8080", "http service address")
+
+var upgrader = websocket.Upgrader{} // use default options
+
+func echo(w http.ResponseWriter, r *http.Request) {
+ c, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Print("upgrade:", err)
+ return
+ }
+ defer c.Close()
+ for {
+ mt, message, err := c.ReadMessage()
+ if err != nil {
+ log.Println("read:", err)
+ break
+ }
+ log.Printf("recv: %s", message)
+ err = c.WriteMessage(mt, message)
+ if err != nil {
+ log.Println("write:", err)
+ break
+ }
+ }
+}
+
+func home(w http.ResponseWriter, r *http.Request) {
+ homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
+}
+
+func main() {
+ flag.Parse()
+ log.SetFlags(0)
+ http.HandleFunc("/echo", echo)
+ http.HandleFunc("/", home)
+ log.Fatal(http.ListenAndServe(*addr, nil))
+}
+
+var homeTemplate = template.Must(template.New("").Parse(`
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<script>
+window.addEventListener("load", function(evt) {
+
+ var output = document.getElementById("output");
+ var input = document.getElementById("input");
+ var ws;
+
+ var print = function(message) {
+ var d = document.createElement("div");
+ d.innerHTML = message;
+ output.appendChild(d);
+ };
+
+ document.getElementById("open").onclick = function(evt) {
+ if (ws) {
+ return false;
+ }
+ ws = new WebSocket("{{.}}");
+ ws.onopen = function(evt) {
+ print("OPEN");
+ }
+ ws.onclose = function(evt) {
+ print("CLOSE");
+ ws = null;
+ }
+ ws.onmessage = function(evt) {
+ print("RESPONSE: " + evt.data);
+ }
+ ws.onerror = function(evt) {
+ print("ERROR: " + evt.data);
+ }
+ return false;
+ };
+
+ document.getElementById("send").onclick = function(evt) {
+ if (!ws) {
+ return false;
+ }
+ print("SEND: " + input.value);
+ ws.send(input.value);
+ return false;
+ };
+
+ document.getElementById("close").onclick = function(evt) {
+ if (!ws) {
+ return false;
+ }
+ ws.close();
+ return false;
+ };
+
+});
+</script>
+</head>
+<body>
+<table>
+<tr><td valign="top" width="50%">
+<p>Click "Open" to create a connection to the server,
+"Send" to send a message to the server and "Close" to close the connection.
+You can change the message and send multiple times.
+<p>
+<form>
+<button id="open">Open</button>
+<button id="close">Close</button>
+<p><input id="input" type="text" value="Hello world!">
+<button id="send">Send</button>
+</form>
+</td><td valign="top" width="50%">
+<div id="output"></div>
+</td></tr></table>
+</body>
+</html>
+`))
diff --git a/vendor/github.com/gorilla/websocket/examples/filewatch/README.md b/vendor/github.com/gorilla/websocket/examples/filewatch/README.md
new file mode 100644
index 000000000..ca4931f3b
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/filewatch/README.md
@@ -0,0 +1,9 @@
+# File Watch example.
+
+This example sends a file to the browser client for display whenever the file is modified.
+
+ $ go get github.com/gorilla/websocket
+ $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/filewatch`
+ $ go run main.go <name of file to watch>
+ # Open http://localhost:8080/ .
+ # Modify the file to see it update in the browser.
diff --git a/vendor/github.com/gorilla/websocket/examples/filewatch/main.go b/vendor/github.com/gorilla/websocket/examples/filewatch/main.go
new file mode 100644
index 000000000..2ac2b324f
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/examples/filewatch/main.go
@@ -0,0 +1,193 @@
+// Copyright 2013 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 main
+
+import (
+ "flag"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+ "text/template"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+const (
+ // Time allowed to write the file to the client.
+ writeWait = 10 * time.Second
+
+ // Time allowed to read the next pong message from the client.
+ pongWait = 60 * time.Second
+
+ // Send pings to client with this period. Must be less than pongWait.
+ pingPeriod = (pongWait * 9) / 10
+
+ // Poll file for changes with this period.
+ filePeriod = 10 * time.Second
+)
+
+var (
+ addr = flag.String("addr", ":8080", "http service address")
+ homeTempl = template.Must(template.New("").Parse(homeHTML))
+ filename string
+ upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ }
+)
+
+func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) {
+ fi, err := os.Stat(filename)
+ if err != nil {
+ return nil, lastMod, err
+ }
+ if !fi.ModTime().After(lastMod) {
+ return nil, lastMod, nil
+ }
+ p, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, fi.ModTime(), err
+ }
+ return p, fi.ModTime(), nil
+}
+
+func reader(ws *websocket.Conn) {
+ defer ws.Close()
+ ws.SetReadLimit(512)
+ ws.SetReadDeadline(time.Now().Add(pongWait))
+ ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, _, err := ws.ReadMessage()
+ if err != nil {
+ break
+ }
+ }
+}
+
+func writer(ws *websocket.Conn, lastMod time.Time) {
+ lastError := ""
+ pingTicker := time.NewTicker(pingPeriod)
+ fileTicker := time.NewTicker(filePeriod)
+ defer func() {
+ pingTicker.Stop()
+ fileTicker.Stop()
+ ws.Close()
+ }()
+ for {
+ select {
+ case <-fileTicker.C:
+ var p []byte
+ var err error
+
+ p, lastMod, err = readFileIfModified(lastMod)
+
+ if err != nil {
+ if s := err.Error(); s != lastError {
+ lastError = s
+ p = []byte(lastError)
+ }
+ } else {
+ lastError = ""
+ }
+
+ if p != nil {
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.TextMessage, p); err != nil {
+ return
+ }
+ }
+ case <-pingTicker.C:
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
+ return
+ }
+ }
+ }
+}
+
+func serveWs(w http.ResponseWriter, r *http.Request) {
+ ws, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ if _, ok := err.(websocket.HandshakeError); !ok {
+ log.Println(err)
+ }
+ return
+ }
+
+ var lastMod time.Time
+ if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err == nil {
+ lastMod = time.Unix(0, n)
+ }
+
+ go writer(ws, lastMod)
+ reader(ws)
+}
+
+func serveHome(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.Error(w, "Not found", 404)
+ return
+ }
+ if r.Method != "GET" {
+ http.Error(w, "Method not allowed", 405)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ p, lastMod, err := readFileIfModified(time.Time{})
+ if err != nil {
+ p = []byte(err.Error())
+ lastMod = time.Unix(0, 0)
+ }
+ var v = struct {
+ Host string
+ Data string
+ LastMod string
+ }{
+ r.Host,
+ string(p),
+ strconv.FormatInt(lastMod.UnixNano(), 16),
+ }
+ homeTempl.Execute(w, &v)
+}
+
+func main() {
+ flag.Parse()
+ if flag.NArg() != 1 {
+ log.Fatal("filename not specified")
+ }
+ filename = flag.Args()[0]
+ http.HandleFunc("/", serveHome)
+ http.HandleFunc("/ws", serveWs)
+ if err := http.ListenAndServe(*addr, nil); err != nil {
+ log.Fatal(err)
+ }
+}
+
+const homeHTML = `<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>WebSocket Example</title>
+ </head>
+ <body>
+ <pre id="fileData">{{.Data}}</pre>
+ <script type="text/javascript">
+ (function() {
+ var data = document.getElementById("fileData");
+ var conn = new WebSocket("ws://{{.Host}}/ws?lastMod={{.LastMod}}");
+ conn.onclose = function(evt) {
+ data.textContent = 'Connection closed';
+ }
+ conn.onmessage = function(evt) {
+ console.log('file updated');
+ data.textContent = evt.data;
+ }
+ })();
+ </script>
+ </body>
+</html>
+`
diff --git a/vendor/github.com/gorilla/websocket/json_test.go b/vendor/github.com/gorilla/websocket/json_test.go
new file mode 100644
index 000000000..61100e481
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/json_test.go
@@ -0,0 +1,119 @@
+// Copyright 2013 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 (
+ "bytes"
+ "encoding/json"
+ "io"
+ "reflect"
+ "testing"
+)
+
+func TestJSON(t *testing.T) {
+ var buf bytes.Buffer
+ c := fakeNetConn{&buf, &buf}
+ wc := newConn(c, true, 1024, 1024)
+ rc := newConn(c, false, 1024, 1024)
+
+ var actual, expect struct {
+ A int
+ B string
+ }
+ expect.A = 1
+ expect.B = "hello"
+
+ if err := wc.WriteJSON(&expect); err != nil {
+ t.Fatal("write", err)
+ }
+
+ if err := rc.ReadJSON(&actual); err != nil {
+ t.Fatal("read", err)
+ }
+
+ if !reflect.DeepEqual(&actual, &expect) {
+ t.Fatal("equal", actual, expect)
+ }
+}
+
+func TestPartialJSONRead(t *testing.T) {
+ var buf bytes.Buffer
+ c := fakeNetConn{&buf, &buf}
+ wc := newConn(c, true, 1024, 1024)
+ rc := newConn(c, false, 1024, 1024)
+
+ var v struct {
+ A int
+ B string
+ }
+ v.A = 1
+ v.B = "hello"
+
+ messageCount := 0
+
+ // Partial JSON values.
+
+ data, err := json.Marshal(v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for i := len(data) - 1; i >= 0; i-- {
+ if err := wc.WriteMessage(TextMessage, data[:i]); err != nil {
+ t.Fatal(err)
+ }
+ messageCount++
+ }
+
+ // Whitespace.
+
+ if err := wc.WriteMessage(TextMessage, []byte(" ")); err != nil {
+ t.Fatal(err)
+ }
+ messageCount++
+
+ // Close.
+
+ if err := wc.WriteMessage(CloseMessage, FormatCloseMessage(CloseNormalClosure, "")); err != nil {
+ t.Fatal(err)
+ }
+
+ for i := 0; i < messageCount; i++ {
+ err := rc.ReadJSON(&v)
+ if err != io.ErrUnexpectedEOF {
+ t.Error("read", i, err)
+ }
+ }
+
+ err = rc.ReadJSON(&v)
+ if _, ok := err.(*CloseError); !ok {
+ t.Error("final", err)
+ }
+}
+
+func TestDeprecatedJSON(t *testing.T) {
+ var buf bytes.Buffer
+ c := fakeNetConn{&buf, &buf}
+ wc := newConn(c, true, 1024, 1024)
+ rc := newConn(c, false, 1024, 1024)
+
+ var actual, expect struct {
+ A int
+ B string
+ }
+ expect.A = 1
+ expect.B = "hello"
+
+ if err := WriteJSON(wc, &expect); err != nil {
+ t.Fatal("write", err)
+ }
+
+ if err := ReadJSON(rc, &actual); err != nil {
+ t.Fatal("read", err)
+ }
+
+ if !reflect.DeepEqual(&actual, &expect) {
+ t.Fatal("equal", actual, expect)
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/server_test.go b/vendor/github.com/gorilla/websocket/server_test.go
new file mode 100644
index 000000000..0a28141d6
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/server_test.go
@@ -0,0 +1,51 @@
+// Copyright 2013 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 (
+ "net/http"
+ "reflect"
+ "testing"
+)
+
+var subprotocolTests = []struct {
+ h string
+ protocols []string
+}{
+ {"", nil},
+ {"foo", []string{"foo"}},
+ {"foo,bar", []string{"foo", "bar"}},
+ {"foo, bar", []string{"foo", "bar"}},
+ {" foo, bar", []string{"foo", "bar"}},
+ {" foo, bar ", []string{"foo", "bar"}},
+}
+
+func TestSubprotocols(t *testing.T) {
+ for _, st := range subprotocolTests {
+ r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {st.h}}}
+ protocols := Subprotocols(&r)
+ if !reflect.DeepEqual(st.protocols, protocols) {
+ t.Errorf("SubProtocols(%q) returned %#v, want %#v", st.h, protocols, st.protocols)
+ }
+ }
+}
+
+var isWebSocketUpgradeTests = []struct {
+ ok bool
+ h http.Header
+}{
+ {false, http.Header{"Upgrade": {"websocket"}}},
+ {false, http.Header{"Connection": {"upgrade"}}},
+ {true, http.Header{"Connection": {"upgRade"}, "Upgrade": {"WebSocket"}}},
+}
+
+func TestIsWebSocketUpgrade(t *testing.T) {
+ for _, tt := range isWebSocketUpgradeTests {
+ ok := IsWebSocketUpgrade(&http.Request{Header: tt.h})
+ if tt.ok != ok {
+ t.Errorf("IsWebSocketUpgrade(%v) returned %v, want %v", tt.h, ok, tt.ok)
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/websocket/util_test.go b/vendor/github.com/gorilla/websocket/util_test.go
new file mode 100644
index 000000000..91f70ceb0
--- /dev/null
+++ b/vendor/github.com/gorilla/websocket/util_test.go
@@ -0,0 +1,34 @@
+// Copyright 2014 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 (
+ "net/http"
+ "testing"
+)
+
+var tokenListContainsValueTests = []struct {
+ value string
+ ok bool
+}{
+ {"WebSocket", true},
+ {"WEBSOCKET", true},
+ {"websocket", true},
+ {"websockets", false},
+ {"x websocket", false},
+ {"websocket x", false},
+ {"other,websocket,more", true},
+ {"other, websocket, more", true},
+}
+
+func TestTokenListContainsValue(t *testing.T) {
+ for _, tt := range tokenListContainsValueTests {
+ h := http.Header{"Upgrade": {tt.value}}
+ ok := tokenListContainsValue(h, "Upgrade", "websocket")
+ if ok != tt.ok {
+ t.Errorf("tokenListContainsValue(h, n, %q) = %v, want %v", tt.value, ok, tt.ok)
+ }
+ }
+}