Browse code

Update gorilla/mux, gorilla/context, and kr/pty deps

Docker-DCO-1.1-Signed-off-by: Andrew Page <admwiggin@gmail.com> (github: tianon)

Tianon Gravi authored on 2014/05/21 06:18:52
Showing 30 changed files
... ...
@@ -39,11 +39,11 @@ clone() {
39 39
 	echo done
40 40
 }
41 41
 
42
-clone git github.com/kr/pty 98c7b80083
42
+clone git github.com/kr/pty 67e2db24c8
43 43
 
44
-clone git github.com/gorilla/context 708054d61e5
44
+clone git github.com/gorilla/context b06ed15e1c
45 45
 
46
-clone git github.com/gorilla/mux 9b36453141c
46
+clone git github.com/gorilla/mux 136d54f81f
47 47
 
48 48
 clone git github.com/syndtr/gocapability 3c85049eae
49 49
 
50 50
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+language: go
1
+
2
+go:
3
+  - 1.0
4
+  - 1.1
5
+  - 1.2
6
+  - tip
... ...
@@ -1,5 +1,6 @@
1 1
 context
2 2
 =======
3
+[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
3 4
 
4 5
 gorilla/context is a general purpose registry for global request variables.
5 6
 
... ...
@@ -11,7 +11,7 @@ import (
11 11
 )
12 12
 
13 13
 var (
14
-	mutex sync.Mutex
14
+	mutex sync.RWMutex
15 15
 	data  = make(map[*http.Request]map[interface{}]interface{})
16 16
 	datat = make(map[*http.Request]int64)
17 17
 )
... ...
@@ -19,42 +19,64 @@ var (
19 19
 // Set stores a value for a given key in a given request.
20 20
 func Set(r *http.Request, key, val interface{}) {
21 21
 	mutex.Lock()
22
-	defer mutex.Unlock()
23 22
 	if data[r] == nil {
24 23
 		data[r] = make(map[interface{}]interface{})
25 24
 		datat[r] = time.Now().Unix()
26 25
 	}
27 26
 	data[r][key] = val
27
+	mutex.Unlock()
28 28
 }
29 29
 
30 30
 // Get returns a value stored for a given key in a given request.
31 31
 func Get(r *http.Request, key interface{}) interface{} {
32
-	mutex.Lock()
33
-	defer mutex.Unlock()
32
+	mutex.RLock()
34 33
 	if data[r] != nil {
34
+		mutex.RUnlock()
35 35
 		return data[r][key]
36 36
 	}
37
+	mutex.RUnlock()
37 38
 	return nil
38 39
 }
39 40
 
40 41
 // GetOk returns stored value and presence state like multi-value return of map access.
41 42
 func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
42
-	mutex.Lock()
43
-	defer mutex.Unlock()
43
+	mutex.RLock()
44 44
 	if _, ok := data[r]; ok {
45 45
 		value, ok := data[r][key]
46
+		mutex.RUnlock()
46 47
 		return value, ok
47 48
 	}
49
+	mutex.RUnlock()
48 50
 	return nil, false
49 51
 }
50 52
 
53
+// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
54
+func GetAll(r *http.Request) map[interface{}]interface{} {
55
+	mutex.RLock()
56
+	if context, ok := data[r]; ok {
57
+		mutex.RUnlock()
58
+		return context
59
+	}
60
+	mutex.RUnlock()
61
+	return nil
62
+}
63
+
64
+// GetAllOk returns all stored values for the request as a map. It returns not
65
+// ok if the request was never registered.
66
+func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
67
+	mutex.RLock()
68
+	context, ok := data[r]
69
+	mutex.RUnlock()
70
+	return context, ok
71
+}
72
+
51 73
 // Delete removes a value stored for a given key in a given request.
52 74
 func Delete(r *http.Request, key interface{}) {
53 75
 	mutex.Lock()
54
-	defer mutex.Unlock()
55 76
 	if data[r] != nil {
56 77
 		delete(data[r], key)
57 78
 	}
79
+	mutex.Unlock()
58 80
 }
59 81
 
60 82
 // Clear removes all values stored for a given request.
... ...
@@ -63,8 +85,8 @@ func Delete(r *http.Request, key interface{}) {
63 63
 // variables at the end of a request lifetime. See ClearHandler().
64 64
 func Clear(r *http.Request) {
65 65
 	mutex.Lock()
66
-	defer mutex.Unlock()
67 66
 	clear(r)
67
+	mutex.Unlock()
68 68
 }
69 69
 
70 70
 // clear is Clear without the lock.
... ...
@@ -84,7 +106,6 @@ func clear(r *http.Request) {
84 84
 // periodically until the problem is fixed.
85 85
 func Purge(maxAge int) int {
86 86
 	mutex.Lock()
87
-	defer mutex.Unlock()
88 87
 	count := 0
89 88
 	if maxAge <= 0 {
90 89
 		count = len(data)
... ...
@@ -92,13 +113,14 @@ func Purge(maxAge int) int {
92 92
 		datat = make(map[*http.Request]int64)
93 93
 	} else {
94 94
 		min := time.Now().Unix() - int64(maxAge)
95
-		for r, _ := range data {
95
+		for r := range data {
96 96
 			if datat[r] < min {
97 97
 				clear(r)
98 98
 				count++
99 99
 			}
100 100
 		}
101 101
 	}
102
+	mutex.Unlock()
102 103
 	return count
103 104
 }
104 105
 
... ...
@@ -24,6 +24,7 @@ func TestContext(t *testing.T) {
24 24
 	}
25 25
 
26 26
 	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
27
+	emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
27 28
 
28 29
 	// Get()
29 30
 	assertEqual(Get(r, key1), nil)
... ...
@@ -51,6 +52,26 @@ func TestContext(t *testing.T) {
51 51
 	assertEqual(value, nil)
52 52
 	assertEqual(ok, true)
53 53
 
54
+	// GetAll()
55
+	values := GetAll(r)
56
+	assertEqual(len(values), 3)
57
+
58
+	// GetAll() for empty request
59
+	values = GetAll(emptyR)
60
+	if values != nil {
61
+		t.Error("GetAll didn't return nil value for invalid request")
62
+	}
63
+
64
+	// GetAllOk()
65
+	values, ok = GetAllOk(r)
66
+	assertEqual(len(values), 3)
67
+	assertEqual(ok, true)
68
+
69
+	// GetAllOk() for empty request
70
+	values, ok = GetAllOk(emptyR)
71
+	assertEqual(value, nil)
72
+	assertEqual(ok, false)
73
+
54 74
 	// Delete()
55 75
 	Delete(r, key1)
56 76
 	assertEqual(Get(r, key1), nil)
... ...
@@ -64,3 +85,77 @@ func TestContext(t *testing.T) {
64 64
 	Clear(r)
65 65
 	assertEqual(len(data), 0)
66 66
 }
67
+
68
+func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
69
+	<-wait
70
+	for i := 0; i < iterations; i++ {
71
+		Get(r, key)
72
+	}
73
+	done <- struct{}{}
74
+
75
+}
76
+
77
+func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
78
+	<-wait
79
+	for i := 0; i < iterations; i++ {
80
+		Get(r, key)
81
+	}
82
+	done <- struct{}{}
83
+
84
+}
85
+
86
+func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
87
+
88
+	b.StopTimer()
89
+	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
90
+	done := make(chan struct{})
91
+	b.StartTimer()
92
+
93
+	for i := 0; i < b.N; i++ {
94
+		wait := make(chan struct{})
95
+
96
+		for i := 0; i < numReaders; i++ {
97
+			go parallelReader(r, "test", iterations, wait, done)
98
+		}
99
+
100
+		for i := 0; i < numWriters; i++ {
101
+			go parallelWriter(r, "test", "123", iterations, wait, done)
102
+		}
103
+
104
+		close(wait)
105
+
106
+		for i := 0; i < numReaders+numWriters; i++ {
107
+			<-done
108
+		}
109
+
110
+	}
111
+
112
+}
113
+
114
+func BenchmarkMutexSameReadWrite1(b *testing.B) {
115
+	benchmarkMutex(b, 1, 1, 32)
116
+}
117
+func BenchmarkMutexSameReadWrite2(b *testing.B) {
118
+	benchmarkMutex(b, 2, 2, 32)
119
+}
120
+func BenchmarkMutexSameReadWrite4(b *testing.B) {
121
+	benchmarkMutex(b, 4, 4, 32)
122
+}
123
+func BenchmarkMutex1(b *testing.B) {
124
+	benchmarkMutex(b, 2, 8, 32)
125
+}
126
+func BenchmarkMutex2(b *testing.B) {
127
+	benchmarkMutex(b, 16, 4, 64)
128
+}
129
+func BenchmarkMutex3(b *testing.B) {
130
+	benchmarkMutex(b, 1, 2, 128)
131
+}
132
+func BenchmarkMutex4(b *testing.B) {
133
+	benchmarkMutex(b, 128, 32, 256)
134
+}
135
+func BenchmarkMutex5(b *testing.B) {
136
+	benchmarkMutex(b, 1024, 2048, 64)
137
+}
138
+func BenchmarkMutex6(b *testing.B) {
139
+	benchmarkMutex(b, 2048, 1024, 512)
140
+}
... ...
@@ -3,7 +3,7 @@
3 3
 // license that can be found in the LICENSE file.
4 4
 
5 5
 /*
6
-Package gorilla/context stores values shared during a request lifetime.
6
+Package context stores values shared during a request lifetime.
7 7
 
8 8
 For example, a router can set variables extracted from the URL and later
9 9
 application handlers can access those values, or it can be used to store
10 10
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+language: go
1
+
2
+go:
3
+  - 1.0
4
+  - 1.1
5
+  - 1.2
6
+  - tip
... ...
@@ -1,5 +1,6 @@
1 1
 mux
2 2
 ===
3
+[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux)
3 4
 
4 5
 gorilla/mux is a powerful URL router and dispatcher.
5 6
 
... ...
@@ -134,7 +134,7 @@ the inner routes use it as base for their paths:
134 134
 	// "/products/{key}/"
135 135
 	s.HandleFunc("/{key}/", ProductHandler)
136 136
 	// "/products/{key}/details"
137
-	s.HandleFunc("/{key}/details"), ProductDetailsHandler)
137
+	s.HandleFunc("/{key}/details", ProductDetailsHandler)
138 138
 
139 139
 Now let's see how to build registered URLs.
140 140
 
... ...
@@ -14,7 +14,7 @@ import (
14 14
 
15 15
 // NewRouter returns a new router instance.
16 16
 func NewRouter() *Router {
17
-	return &Router{namedRoutes: make(map[string]*Route)}
17
+	return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
18 18
 }
19 19
 
20 20
 // Router registers routes to be matched and dispatches a handler.
... ...
@@ -46,6 +46,8 @@ type Router struct {
46 46
 	namedRoutes map[string]*Route
47 47
 	// See Router.StrictSlash(). This defines the flag for new routes.
48 48
 	strictSlash bool
49
+	// If true, do not clear the request context after handling the request
50
+	KeepContext bool
49 51
 }
50 52
 
51 53
 // Match matches registered routes against the request.
... ...
@@ -65,6 +67,14 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
65 65
 func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
66 66
 	// Clean path to canonical form and redirect.
67 67
 	if p := cleanPath(req.URL.Path); p != req.URL.Path {
68
+
69
+		// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
70
+		// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:
71
+		// http://code.google.com/p/go/issues/detail?id=5252
72
+		url := *req.URL
73
+		url.Path = p
74
+		p = url.String()
75
+
68 76
 		w.Header().Set("Location", p)
69 77
 		w.WriteHeader(http.StatusMovedPermanently)
70 78
 		return
... ...
@@ -82,7 +92,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
82 82
 		}
83 83
 		handler = r.NotFoundHandler
84 84
 	}
85
-	defer context.Clear(req)
85
+	if !r.KeepContext {
86
+		defer context.Clear(req)
87
+	}
86 88
 	handler.ServeHTTP(w, req)
87 89
 }
88 90
 
... ...
@@ -97,14 +109,20 @@ func (r *Router) GetRoute(name string) *Route {
97 97
 	return r.getNamedRoutes()[name]
98 98
 }
99 99
 
100
-// StrictSlash defines the slash behavior for new routes.
100
+// StrictSlash defines the trailing slash behavior for new routes. The initial
101
+// value is false.
101 102
 //
102 103
 // When true, if the route path is "/path/", accessing "/path" will redirect
103
-// to the former and vice versa.
104
+// to the former and vice versa. In other words, your application will always
105
+// see the path as specified in the route.
106
+//
107
+// When false, if the route path is "/path", accessing "/path/" will not match
108
+// this route and vice versa.
104 109
 //
105
-// Special case: when a route sets a path prefix, strict slash is
106
-// automatically set to false for that route because the redirect behavior
107
-// can't be determined for prefixes.
110
+// Special case: when a route sets a path prefix using the PathPrefix() method,
111
+// strict slash is ignored for that route because the redirect behavior can't
112
+// be determined from a prefix alone. However, any subrouters created from that
113
+// route inherit the original StrictSlash setting.
108 114
 func (r *Router) StrictSlash(value bool) *Router {
109 115
 	r.strictSlash = value
110 116
 	return r
... ...
@@ -8,16 +8,19 @@ import (
8 8
 	"fmt"
9 9
 	"net/http"
10 10
 	"testing"
11
+
12
+	"github.com/gorilla/context"
11 13
 )
12 14
 
13 15
 type routeTest struct {
14
-	title       string            // title of the test
15
-	route       *Route            // the route being tested
16
-	request     *http.Request     // a request to test the route
17
-	vars        map[string]string // the expected vars of the match
18
-	host        string            // the expected host of the match
19
-	path        string            // the expected path of the match
20
-	shouldMatch bool              // whether the request is expected to match the route at all
16
+	title          string            // title of the test
17
+	route          *Route            // the route being tested
18
+	request        *http.Request     // a request to test the route
19
+	vars           map[string]string // the expected vars of the match
20
+	host           string            // the expected host of the match
21
+	path           string            // the expected path of the match
22
+	shouldMatch    bool              // whether the request is expected to match the route at all
23
+	shouldRedirect bool              // whether the request should result in a redirect
21 24
 }
22 25
 
23 26
 func TestHost(t *testing.T) {
... ...
@@ -150,6 +153,33 @@ func TestPath(t *testing.T) {
150 150
 			shouldMatch: true,
151 151
 		},
152 152
 		{
153
+			title:       "Path route, match with trailing slash in request and path",
154
+			route:       new(Route).Path("/111/"),
155
+			request:     newRequest("GET", "http://localhost/111/"),
156
+			vars:        map[string]string{},
157
+			host:        "",
158
+			path:        "/111/",
159
+			shouldMatch: true,
160
+		},
161
+		{
162
+			title:       "Path route, do not match with trailing slash in path",
163
+			route:       new(Route).Path("/111/"),
164
+			request:     newRequest("GET", "http://localhost/111"),
165
+			vars:        map[string]string{},
166
+			host:        "",
167
+			path:        "/111",
168
+			shouldMatch: false,
169
+		},
170
+		{
171
+			title:       "Path route, do not match with trailing slash in request",
172
+			route:       new(Route).Path("/111"),
173
+			request:     newRequest("GET", "http://localhost/111/"),
174
+			vars:        map[string]string{},
175
+			host:        "",
176
+			path:        "/111/",
177
+			shouldMatch: false,
178
+		},
179
+		{
153 180
 			title:       "Path route, wrong path in request in request URL",
154 181
 			route:       new(Route).Path("/111/222/333"),
155 182
 			request:     newRequest("GET", "http://localhost/1/2/3"),
... ...
@@ -213,6 +243,15 @@ func TestPathPrefix(t *testing.T) {
213 213
 			shouldMatch: true,
214 214
 		},
215 215
 		{
216
+			title:       "PathPrefix route, match substring",
217
+			route:       new(Route).PathPrefix("/1"),
218
+			request:     newRequest("GET", "http://localhost/111/222/333"),
219
+			vars:        map[string]string{},
220
+			host:        "",
221
+			path:        "/1",
222
+			shouldMatch: true,
223
+		},
224
+		{
216 225
 			title:       "PathPrefix route, URL prefix in request does not match",
217 226
 			route:       new(Route).PathPrefix("/111"),
218 227
 			request:     newRequest("GET", "http://localhost/1/2/3"),
... ...
@@ -415,6 +454,15 @@ func TestQueries(t *testing.T) {
415 415
 			shouldMatch: true,
416 416
 		},
417 417
 		{
418
+			title:       "Queries route, match with a query string",
419
+			route:       new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
420
+			request:     newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
421
+			vars:        map[string]string{},
422
+			host:        "",
423
+			path:        "",
424
+			shouldMatch: true,
425
+		},
426
+		{
418 427
 			title:       "Queries route, bad query",
419 428
 			route:       new(Route).Queries("foo", "bar", "baz", "ding"),
420 429
 			request:     newRequest("GET", "http://localhost?foo=bar&baz=dong"),
... ...
@@ -568,26 +616,74 @@ func TestNamedRoutes(t *testing.T) {
568 568
 }
569 569
 
570 570
 func TestStrictSlash(t *testing.T) {
571
-	var r *Router
572
-	var req *http.Request
573
-	var route *Route
574
-	var match *RouteMatch
575
-	var matched bool
576
-
577
-	// StrictSlash should be ignored for path prefix.
578
-	// So we register a route ending in slash but it doesn't attempt to add
579
-	// the slash for a path not ending in slash.
580
-	r = NewRouter()
571
+	r := NewRouter()
581 572
 	r.StrictSlash(true)
582
-	route = r.NewRoute().PathPrefix("/static/")
583
-	req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil)
584
-	match = new(RouteMatch)
585
-	matched = r.Match(req, match)
586
-	if !matched {
587
-		t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route))
573
+
574
+	tests := []routeTest{
575
+		{
576
+			title:          "Redirect path without slash",
577
+			route:          r.NewRoute().Path("/111/"),
578
+			request:        newRequest("GET", "http://localhost/111"),
579
+			vars:           map[string]string{},
580
+			host:           "",
581
+			path:           "/111/",
582
+			shouldMatch:    true,
583
+			shouldRedirect: true,
584
+		},
585
+		{
586
+			title:          "Do not redirect path with slash",
587
+			route:          r.NewRoute().Path("/111/"),
588
+			request:        newRequest("GET", "http://localhost/111/"),
589
+			vars:           map[string]string{},
590
+			host:           "",
591
+			path:           "/111/",
592
+			shouldMatch:    true,
593
+			shouldRedirect: false,
594
+		},
595
+		{
596
+			title:          "Redirect path with slash",
597
+			route:          r.NewRoute().Path("/111"),
598
+			request:        newRequest("GET", "http://localhost/111/"),
599
+			vars:           map[string]string{},
600
+			host:           "",
601
+			path:           "/111",
602
+			shouldMatch:    true,
603
+			shouldRedirect: true,
604
+		},
605
+		{
606
+			title:          "Do not redirect path without slash",
607
+			route:          r.NewRoute().Path("/111"),
608
+			request:        newRequest("GET", "http://localhost/111"),
609
+			vars:           map[string]string{},
610
+			host:           "",
611
+			path:           "/111",
612
+			shouldMatch:    true,
613
+			shouldRedirect: false,
614
+		},
615
+		{
616
+			title:          "Propagate StrictSlash to subrouters",
617
+			route:          r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
618
+			request:        newRequest("GET", "http://localhost/static/images"),
619
+			vars:           map[string]string{},
620
+			host:           "",
621
+			path:           "/static/images/",
622
+			shouldMatch:    true,
623
+			shouldRedirect: true,
624
+		},
625
+		{
626
+			title:          "Ignore StrictSlash for path prefix",
627
+			route:          r.NewRoute().PathPrefix("/static/"),
628
+			request:        newRequest("GET", "http://localhost/static/logo.png"),
629
+			vars:           map[string]string{},
630
+			host:           "",
631
+			path:           "/static/",
632
+			shouldMatch:    true,
633
+			shouldRedirect: false,
634
+		},
588 635
 	}
589
-	if match.Handler != nil {
590
-		t.Errorf("Should not redirect")
636
+
637
+	for _, test := range tests {
638
+		testRoute(t, test)
591 639
 	}
592 640
 }
593 641
 
... ...
@@ -616,6 +712,7 @@ func testRoute(t *testing.T, test routeTest) {
616 616
 	host := test.host
617 617
 	path := test.path
618 618
 	url := test.host + test.path
619
+	shouldRedirect := test.shouldRedirect
619 620
 
620 621
 	var match RouteMatch
621 622
 	ok := route.Match(request, &match)
... ...
@@ -653,6 +750,84 @@ func testRoute(t *testing.T, test routeTest) {
653 653
 				return
654 654
 			}
655 655
 		}
656
+		if shouldRedirect && match.Handler == nil {
657
+			t.Errorf("(%v) Did not redirect", test.title)
658
+			return
659
+		}
660
+		if !shouldRedirect && match.Handler != nil {
661
+			t.Errorf("(%v) Unexpected redirect", test.title)
662
+			return
663
+		}
664
+	}
665
+}
666
+
667
+// Tests that the context is cleared or not cleared properly depending on
668
+// the configuration of the router
669
+func TestKeepContext(t *testing.T) {
670
+	func1 := func(w http.ResponseWriter, r *http.Request) {}
671
+
672
+	r := NewRouter()
673
+	r.HandleFunc("/", func1).Name("func1")
674
+
675
+	req, _ := http.NewRequest("GET", "http://localhost/", nil)
676
+	context.Set(req, "t", 1)
677
+
678
+	res := new(http.ResponseWriter)
679
+	r.ServeHTTP(*res, req)
680
+
681
+	if _, ok := context.GetOk(req, "t"); ok {
682
+		t.Error("Context should have been cleared at end of request")
683
+	}
684
+
685
+	r.KeepContext = true
686
+
687
+	req, _ = http.NewRequest("GET", "http://localhost/", nil)
688
+	context.Set(req, "t", 1)
689
+
690
+	r.ServeHTTP(*res, req)
691
+	if _, ok := context.GetOk(req, "t"); !ok {
692
+		t.Error("Context should NOT have been cleared at end of request")
693
+	}
694
+
695
+}
696
+
697
+type TestA301ResponseWriter struct {
698
+	hh     http.Header
699
+	status int
700
+}
701
+
702
+func (ho TestA301ResponseWriter) Header() http.Header {
703
+	return http.Header(ho.hh)
704
+}
705
+
706
+func (ho TestA301ResponseWriter) Write(b []byte) (int, error) {
707
+	return 0, nil
708
+}
709
+
710
+func (ho TestA301ResponseWriter) WriteHeader(code int) {
711
+	ho.status = code
712
+}
713
+
714
+func Test301Redirect(t *testing.T) {
715
+	m := make(http.Header)
716
+
717
+	func1 := func(w http.ResponseWriter, r *http.Request) {}
718
+	func2 := func(w http.ResponseWriter, r *http.Request) {}
719
+
720
+	r := NewRouter()
721
+	r.HandleFunc("/api/", func2).Name("func2")
722
+	r.HandleFunc("/", func1).Name("func1")
723
+
724
+	req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
725
+
726
+	res := TestA301ResponseWriter{
727
+		hh:     m,
728
+		status: 0,
729
+	}
730
+	r.ServeHTTP(&res, req)
731
+
732
+	if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
733
+		t.Errorf("Should have complete URL with query string")
656 734
 	}
657 735
 }
658 736
 
... ...
@@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) {
96 96
 		method = "GET"
97 97
 		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
98 98
 		resultVars = map[bool]map[string]string{
99
-			true:  map[string]string{"var1": "www", "var2": "product", "var3": "42"},
100
-			false: map[string]string{},
99
+			true:  {"var1": "www", "var2": "product", "var3": "42"},
100
+			false: {},
101 101
 		}
102 102
 	}
103 103
 
... ...
@@ -110,8 +110,8 @@ func TestRouteMatchers(t *testing.T) {
110 110
 		method = "POST"
111 111
 		headers = map[string]string{"Content-Type": "application/json"}
112 112
 		resultVars = map[bool]map[string]string{
113
-			true:  map[string]string{"var4": "google", "var5": "product", "var6": "42"},
114
-			false: map[string]string{},
113
+			true:  {"var4": "google", "var5": "product", "var6": "42"},
114
+			false: {},
115 115
 		}
116 116
 	}
117 117
 
... ...
@@ -98,12 +98,13 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout
98 98
 	}
99 99
 	// Done!
100 100
 	return &routeRegexp{
101
-		template:  template,
102
-		matchHost: matchHost,
103
-		regexp:    reg,
104
-		reverse:   reverse.String(),
105
-		varsN:     varsN,
106
-		varsR:     varsR,
101
+		template:    template,
102
+		matchHost:   matchHost,
103
+		strictSlash: strictSlash,
104
+		regexp:      reg,
105
+		reverse:     reverse.String(),
106
+		varsN:       varsN,
107
+		varsR:       varsR,
107 108
 	}, nil
108 109
 }
109 110
 
... ...
@@ -114,6 +115,8 @@ type routeRegexp struct {
114 114
 	template string
115 115
 	// True for host match, false for path match.
116 116
 	matchHost bool
117
+	// The strictSlash value defined on the route, but disabled if PathPrefix was used.
118
+	strictSlash bool
117 119
 	// Expanded regexp.
118 120
 	regexp *regexp.Regexp
119 121
 	// Reverse template.
... ...
@@ -216,7 +219,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
216 216
 				m.Vars[v] = pathVars[k+1]
217 217
 			}
218 218
 			// Check if we should redirect.
219
-			if r.strictSlash {
219
+			if v.path.strictSlash {
220 220
 				p1 := strings.HasSuffix(req.URL.Path, "/")
221 221
 				p2 := strings.HasSuffix(v.path.template, "/")
222 222
 				if p1 != p2 {
... ...
@@ -259,7 +259,8 @@ func (r *Route) Methods(methods ...string) *Route {
259 259
 // Path -----------------------------------------------------------------------
260 260
 
261 261
 // Path adds a matcher for the URL path.
262
-// It accepts a template with zero or more URL variables enclosed by {}.
262
+// It accepts a template with zero or more URL variables enclosed by {}. The
263
+// template must start with a "/".
263 264
 // Variables can define an optional regexp pattern to me matched:
264 265
 //
265 266
 // - {name} matches anything until the next slash.
... ...
@@ -283,9 +284,16 @@ func (r *Route) Path(tpl string) *Route {
283 283
 
284 284
 // PathPrefix -----------------------------------------------------------------
285 285
 
286
-// PathPrefix adds a matcher for the URL path prefix.
286
+// PathPrefix adds a matcher for the URL path prefix. This matches if the given
287
+// template is a prefix of the full URL path. See Route.Path() for details on
288
+// the tpl argument.
289
+//
290
+// Note that it does not treat slashes specially ("/foobar/" will be matched by
291
+// the prefix "/foo") so you may want to use a trailing slash here.
292
+//
293
+// Also note that the setting of Router.StrictSlash() has no effect on routes
294
+// with a PathPrefix matcher.
287 295
 func (r *Route) PathPrefix(tpl string) *Route {
288
-	r.strictSlash = false
289 296
 	r.err = r.addRegexpMatcher(tpl, false, true)
290 297
 	return r
291 298
 }
... ...
@@ -328,7 +336,7 @@ func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
328 328
 }
329 329
 
330 330
 // Schemes adds a matcher for URL schemes.
331
-// It accepts a sequence schemes to be matched, e.g.: "http", "https".
331
+// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
332 332
 func (r *Route) Schemes(schemes ...string) *Route {
333 333
 	for k, v := range schemes {
334 334
 		schemes[k] = strings.ToLower(v)
335 335
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+package pty
1
+
2
+import "syscall"
3
+
4
+func ioctl(fd, cmd, ptr uintptr) error {
5
+	_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
6
+	if e != 0 {
7
+		return e
8
+	}
9
+	return nil
10
+}
0 11
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+// +build darwin dragonfly freebsd netbsd openbsd
1
+
2
+package pty
3
+
4
+// from <sys/ioccom.h>
5
+const (
6
+	_IOC_VOID    uintptr = 0x20000000
7
+	_IOC_OUT     uintptr = 0x40000000
8
+	_IOC_IN      uintptr = 0x80000000
9
+	_IOC_IN_OUT  uintptr = _IOC_OUT | _IOC_IN
10
+	_IOC_DIRMASK         = _IOC_VOID | _IOC_OUT | _IOC_IN
11
+
12
+	_IOC_PARAM_SHIFT = 13
13
+	_IOC_PARAM_MASK  = (1 << _IOC_PARAM_SHIFT) - 1
14
+)
15
+
16
+func _IOC_PARM_LEN(ioctl uintptr) uintptr {
17
+	return (ioctl >> 16) & _IOC_PARAM_MASK
18
+}
19
+
20
+func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
21
+	return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
22
+}
23
+
24
+func _IO(group byte, ioctl_num uintptr) uintptr {
25
+	return _IOC(_IOC_VOID, group, ioctl_num, 0)
26
+}
27
+
28
+func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
29
+	return _IOC(_IOC_OUT, group, ioctl_num, param_len)
30
+}
31
+
32
+func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
33
+	return _IOC(_IOC_IN, group, ioctl_num, param_len)
34
+}
35
+
36
+func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
37
+	return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
38
+}
0 39
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package pty
1
+
2
+// from <asm-generic/ioctl.h>
3
+const (
4
+	_IOC_NRBITS   = 8
5
+	_IOC_TYPEBITS = 8
6
+
7
+	_IOC_SIZEBITS = 14
8
+	_IOC_DIRBITS  = 2
9
+
10
+	_IOC_NRSHIFT   = 0
11
+	_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
12
+	_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
13
+	_IOC_DIRSHIFT  = _IOC_SIZESHIFT + _IOC_SIZEBITS
14
+
15
+	_IOC_NONE  uint = 0
16
+	_IOC_WRITE uint = 1
17
+	_IOC_READ  uint = 2
18
+)
19
+
20
+func _IOC(dir uint, ioctl_type byte, nr byte, size uintptr) uintptr {
21
+	return (uintptr(dir)<<_IOC_DIRSHIFT |
22
+		uintptr(ioctl_type)<<_IOC_TYPESHIFT |
23
+		uintptr(nr)<<_IOC_NRSHIFT |
24
+		size<<_IOC_SIZESHIFT)
25
+}
26
+
27
+func _IO(ioctl_type byte, nr byte) uintptr {
28
+	return _IOC(_IOC_NONE, ioctl_type, nr, 0)
29
+}
30
+
31
+func _IOR(ioctl_type byte, nr byte, size uintptr) uintptr {
32
+	return _IOC(_IOC_READ, ioctl_type, nr, size)
33
+}
34
+
35
+func _IOW(ioctl_type byte, nr byte, size uintptr) uintptr {
36
+	return _IOC(_IOC_WRITE, ioctl_type, nr, size)
37
+}
38
+
39
+func _IOWR(ioctl_type byte, nr byte, size uintptr) uintptr {
40
+	return _IOC(_IOC_READ|_IOC_WRITE, ioctl_type, nr, size)
41
+}
0 42
new file mode 100755
... ...
@@ -0,0 +1,19 @@
0
+#!/usr/bin/env bash
1
+
2
+GOOSARCH="${GOOS}_${GOARCH}"
3
+case "$GOOSARCH" in
4
+_* | *_ | _)
5
+	echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
6
+	exit 1
7
+	;;
8
+esac
9
+
10
+GODEFS="go tool cgo -godefs"
11
+
12
+$GODEFS types.go |gofmt > ztypes_$GOARCH.go
13
+
14
+case $GOOS in
15
+freebsd)
16
+	$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
17
+	;;
18
+esac
... ...
@@ -7,9 +7,6 @@ import (
7 7
 	"unsafe"
8 8
 )
9 9
 
10
-// see ioccom.h
11
-const sys_IOCPARM_MASK = 0x1fff
12
-
13 10
 func open() (pty, tty *os.File, err error) {
14 11
 	p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
15 12
 	if err != nil {
... ...
@@ -39,9 +36,13 @@ func open() (pty, tty *os.File, err error) {
39 39
 }
40 40
 
41 41
 func ptsname(f *os.File) (string, error) {
42
-	var n [(syscall.TIOCPTYGNAME >> 16) & sys_IOCPARM_MASK]byte
42
+	n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
43
+
44
+	err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
45
+	if err != nil {
46
+		return "", err
47
+	}
43 48
 
44
-	ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n)))
45 49
 	for i, c := range n {
46 50
 		if c == 0 {
47 51
 			return string(n[:i]), nil
... ...
@@ -51,19 +52,9 @@ func ptsname(f *os.File) (string, error) {
51 51
 }
52 52
 
53 53
 func grantpt(f *os.File) error {
54
-	var u int
55
-	return ioctl(f.Fd(), syscall.TIOCPTYGRANT, uintptr(unsafe.Pointer(&u)))
54
+	return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
56 55
 }
57 56
 
58 57
 func unlockpt(f *os.File) error {
59
-	var u int
60
-	return ioctl(f.Fd(), syscall.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
61
-}
62
-
63
-func ioctl(fd, cmd, ptr uintptr) error {
64
-	_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
65
-	if e != 0 {
66
-		return syscall.ENOTTY
67
-	}
68
-	return nil
58
+	return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
69 59
 }
... ...
@@ -1,53 +1,73 @@
1 1
 package pty
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"os"
5
-	"strconv"
6 6
 	"syscall"
7 7
 	"unsafe"
8 8
 )
9 9
 
10
-const (
11
-	sys_TIOCGPTN   = 0x4004740F
12
-	sys_TIOCSPTLCK = 0x40045431
13
-)
10
+func posix_openpt(oflag int) (fd int, err error) {
11
+	r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
12
+	fd = int(r0)
13
+	if e1 != 0 {
14
+		err = e1
15
+	}
16
+	return
17
+}
14 18
 
15 19
 func open() (pty, tty *os.File, err error) {
16
-	p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
20
+	fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC)
17 21
 	if err != nil {
18 22
 		return nil, nil, err
19 23
 	}
20 24
 
25
+	p := os.NewFile(uintptr(fd), "/dev/pts")
21 26
 	sname, err := ptsname(p)
22 27
 	if err != nil {
23 28
 		return nil, nil, err
24 29
 	}
25 30
 
26
-	t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
31
+	t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
27 32
 	if err != nil {
28 33
 		return nil, nil, err
29 34
 	}
30 35
 	return p, t, nil
31 36
 }
32 37
 
38
+func isptmaster(fd uintptr) (bool, error) {
39
+	err := ioctl(fd, syscall.TIOCPTMASTER, 0)
40
+	return err == nil, err
41
+}
42
+
43
+var (
44
+	emptyFiodgnameArg fiodgnameArg
45
+	ioctl_FIODGNAME   = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
46
+)
47
+
33 48
 func ptsname(f *os.File) (string, error) {
34
-	var n int
35
-	err := ioctl(f.Fd(), sys_TIOCGPTN, &n)
49
+	master, err := isptmaster(f.Fd())
36 50
 	if err != nil {
37 51
 		return "", err
38 52
 	}
39
-	return "/dev/pts/" + strconv.Itoa(n), nil
40
-}
53
+	if !master {
54
+		return "", syscall.EINVAL
55
+	}
41 56
 
42
-func ioctl(fd uintptr, cmd uintptr, data *int) error {
43
-	_, _, e := syscall.Syscall(
44
-		syscall.SYS_IOCTL,
45
-		fd,
46
-		cmd,
47
-		uintptr(unsafe.Pointer(data)),
57
+	const n = _C_SPECNAMELEN + 1
58
+	var (
59
+		buf = make([]byte, n)
60
+		arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
48 61
 	)
49
-	if e != 0 {
50
-		return syscall.ENOTTY
62
+	err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg)))
63
+	if err != nil {
64
+		return "", err
65
+	}
66
+
67
+	for i, c := range buf {
68
+		if c == 0 {
69
+			return string(buf[:i]), nil
70
+		}
51 71
 	}
52
-	return nil
72
+	return "", errors.New("FIODGNAME string not NUL-terminated")
53 73
 }
... ...
@@ -7,9 +7,9 @@ import (
7 7
 	"unsafe"
8 8
 )
9 9
 
10
-const (
11
-	sys_TIOCGPTN   = 0x80045430
12
-	sys_TIOCSPTLCK = 0x40045431
10
+var (
11
+	ioctl_TIOCGPTN   = _IOR('T', 0x30, unsafe.Sizeof(_C_uint(0))) /* Get Pty Number (of pty-mux device) */
12
+	ioctl_TIOCSPTLCK = _IOW('T', 0x31, unsafe.Sizeof(_C_int(0)))  /* Lock/unlock Pty */
13 13
 )
14 14
 
15 15
 func open() (pty, tty *os.File, err error) {
... ...
@@ -36,28 +36,16 @@ func open() (pty, tty *os.File, err error) {
36 36
 }
37 37
 
38 38
 func ptsname(f *os.File) (string, error) {
39
-	var n int
40
-	err := ioctl(f.Fd(), sys_TIOCGPTN, &n)
39
+	var n _C_uint
40
+	err := ioctl(f.Fd(), ioctl_TIOCGPTN, uintptr(unsafe.Pointer(&n)))
41 41
 	if err != nil {
42 42
 		return "", err
43 43
 	}
44
-	return "/dev/pts/" + strconv.Itoa(n), nil
44
+	return "/dev/pts/" + strconv.Itoa(int(n)), nil
45 45
 }
46 46
 
47 47
 func unlockpt(f *os.File) error {
48
-	var u int
49
-	return ioctl(f.Fd(), sys_TIOCSPTLCK, &u)
50
-}
51
-
52
-func ioctl(fd uintptr, cmd uintptr, data *int) error {
53
-	_, _, e := syscall.Syscall(
54
-		syscall.SYS_IOCTL,
55
-		fd,
56
-		cmd,
57
-		uintptr(unsafe.Pointer(data)),
58
-	)
59
-	if e != 0 {
60
-		return syscall.ENOTTY
61
-	}
62
-	return nil
48
+	var u _C_int
49
+	// use TIOCSPTLCK with a zero valued arg to clear the slave pty lock
50
+	return ioctl(f.Fd(), ioctl_TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
63 51
 }
... ...
@@ -9,19 +9,3 @@ import (
9 9
 func open() (pty, tty *os.File, err error) {
10 10
 	return nil, nil, ErrUnsupported
11 11
 }
12
-
13
-func ptsname(f *os.File) (string, error) {
14
-	return "", ErrUnsupported
15
-}
16
-
17
-func grantpt(f *os.File) error {
18
-	return ErrUnsupported
19
-}
20
-
21
-func unlockpt(f *os.File) error {
22
-	return ErrUnsupported
23
-}
24
-
25
-func ioctl(fd, cmd, ptr uintptr) error {
26
-	return ErrUnsupported
27
-}
28 12
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+// +build ignore
1
+
2
+package pty
3
+
4
+import "C"
5
+
6
+type (
7
+	_C_int  C.int
8
+	_C_uint C.uint
9
+)
0 10
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+// +build ignore
1
+
2
+package pty
3
+
4
+/*
5
+#include <sys/param.h>
6
+#include <sys/filio.h>
7
+*/
8
+import "C"
9
+
10
+const (
11
+	_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
12
+)
13
+
14
+type fiodgnameArg C.struct_fiodgname_arg
0 15
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+// Created by cgo -godefs - DO NOT EDIT
1
+// cgo -godefs types.go
2
+
3
+package pty
4
+
5
+type (
6
+	_C_int  int32
7
+	_C_uint uint32
8
+)
0 9
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+// Created by cgo -godefs - DO NOT EDIT
1
+// cgo -godefs types.go
2
+
3
+package pty
4
+
5
+type (
6
+	_C_int  int32
7
+	_C_uint uint32
8
+)
0 9
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+// Created by cgo -godefs - DO NOT EDIT
1
+// cgo -godefs types.go
2
+
3
+package pty
4
+
5
+type (
6
+	_C_int  int32
7
+	_C_uint uint32
8
+)
0 9
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+// Created by cgo -godefs - DO NOT EDIT
1
+// cgo -godefs types_freebsd.go
2
+
3
+package pty
4
+
5
+const (
6
+	_C_SPECNAMELEN = 0x3f
7
+)
8
+
9
+type fiodgnameArg struct {
10
+	Len int32
11
+	Buf *byte
12
+}
0 13
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+// Created by cgo -godefs - DO NOT EDIT
1
+// cgo -godefs types_freebsd.go
2
+
3
+package pty
4
+
5
+const (
6
+	_C_SPECNAMELEN = 0x3f
7
+)
8
+
9
+type fiodgnameArg struct {
10
+	Len       int32
11
+	Pad_cgo_0 [4]byte
12
+	Buf       *byte
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+// Created by cgo -godefs - DO NOT EDIT
1
+// cgo -godefs types_freebsd.go
2
+
3
+package pty
4
+
5
+const (
6
+	_C_SPECNAMELEN = 0x3f
7
+)
8
+
9
+type fiodgnameArg struct {
10
+	Len int32
11
+	Buf *byte
12
+}