Docker-DCO-1.1-Signed-off-by: Andrew Page <admwiggin@gmail.com> (github: tianon)
| ... | ... |
@@ -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 |
|
| ... | ... |
@@ -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 |
| ... | ... |
@@ -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) |
| 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 |
-} |
| 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 |