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

Docker-DCO-1.1-Signed-off-by: Andrew Page <admwiggin@gmail.com> (github: tianon)
This commit is contained in:
Tianon Gravi 2014-05-20 15:18:52 -06:00
parent a16cb394fa
commit d98af1236c
30 changed files with 661 additions and 138 deletions

View File

@ -39,11 +39,11 @@ clone() {
echo done echo done
} }
clone git github.com/kr/pty 98c7b80083 clone git github.com/kr/pty 67e2db24c8
clone git github.com/gorilla/context 708054d61e5 clone git github.com/gorilla/context b06ed15e1c
clone git github.com/gorilla/mux 9b36453141c clone git github.com/gorilla/mux 136d54f81f
clone git github.com/syndtr/gocapability 3c85049eae clone git github.com/syndtr/gocapability 3c85049eae

View File

@ -0,0 +1,7 @@
language: go
go:
- 1.0
- 1.1
- 1.2
- tip

View File

@ -1,5 +1,6 @@
context context
======= =======
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
gorilla/context is a general purpose registry for global request variables. gorilla/context is a general purpose registry for global request variables.

View File

@ -11,7 +11,7 @@ import (
) )
var ( var (
mutex sync.Mutex mutex sync.RWMutex
data = make(map[*http.Request]map[interface{}]interface{}) data = make(map[*http.Request]map[interface{}]interface{})
datat = make(map[*http.Request]int64) datat = make(map[*http.Request]int64)
) )
@ -19,42 +19,64 @@ var (
// Set stores a value for a given key in a given request. // Set stores a value for a given key in a given request.
func Set(r *http.Request, key, val interface{}) { func Set(r *http.Request, key, val interface{}) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock()
if data[r] == nil { if data[r] == nil {
data[r] = make(map[interface{}]interface{}) data[r] = make(map[interface{}]interface{})
datat[r] = time.Now().Unix() datat[r] = time.Now().Unix()
} }
data[r][key] = val data[r][key] = val
mutex.Unlock()
} }
// Get returns a value stored for a given key in a given request. // Get returns a value stored for a given key in a given request.
func Get(r *http.Request, key interface{}) interface{} { func Get(r *http.Request, key interface{}) interface{} {
mutex.Lock() mutex.RLock()
defer mutex.Unlock()
if data[r] != nil { if data[r] != nil {
mutex.RUnlock()
return data[r][key] return data[r][key]
} }
mutex.RUnlock()
return nil return nil
} }
// GetOk returns stored value and presence state like multi-value return of map access. // GetOk returns stored value and presence state like multi-value return of map access.
func GetOk(r *http.Request, key interface{}) (interface{}, bool) { func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
mutex.Lock() mutex.RLock()
defer mutex.Unlock()
if _, ok := data[r]; ok { if _, ok := data[r]; ok {
value, ok := data[r][key] value, ok := data[r][key]
mutex.RUnlock()
return value, ok return value, ok
} }
mutex.RUnlock()
return nil, false return nil, false
} }
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
func GetAll(r *http.Request) map[interface{}]interface{} {
mutex.RLock()
if context, ok := data[r]; ok {
mutex.RUnlock()
return context
}
mutex.RUnlock()
return nil
}
// GetAllOk returns all stored values for the request as a map. It returns not
// ok if the request was never registered.
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
mutex.RLock()
context, ok := data[r]
mutex.RUnlock()
return context, ok
}
// Delete removes a value stored for a given key in a given request. // Delete removes a value stored for a given key in a given request.
func Delete(r *http.Request, key interface{}) { func Delete(r *http.Request, key interface{}) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock()
if data[r] != nil { if data[r] != nil {
delete(data[r], key) delete(data[r], key)
} }
mutex.Unlock()
} }
// Clear removes all values stored for a given request. // Clear removes all values stored for a given request.
@ -63,8 +85,8 @@ func Delete(r *http.Request, key interface{}) {
// variables at the end of a request lifetime. See ClearHandler(). // variables at the end of a request lifetime. See ClearHandler().
func Clear(r *http.Request) { func Clear(r *http.Request) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock()
clear(r) clear(r)
mutex.Unlock()
} }
// clear is Clear without the lock. // clear is Clear without the lock.
@ -84,7 +106,6 @@ func clear(r *http.Request) {
// periodically until the problem is fixed. // periodically until the problem is fixed.
func Purge(maxAge int) int { func Purge(maxAge int) int {
mutex.Lock() mutex.Lock()
defer mutex.Unlock()
count := 0 count := 0
if maxAge <= 0 { if maxAge <= 0 {
count = len(data) count = len(data)
@ -92,13 +113,14 @@ func Purge(maxAge int) int {
datat = make(map[*http.Request]int64) datat = make(map[*http.Request]int64)
} else { } else {
min := time.Now().Unix() - int64(maxAge) min := time.Now().Unix() - int64(maxAge)
for r, _ := range data { for r := range data {
if datat[r] < min { if datat[r] < min {
clear(r) clear(r)
count++ count++
} }
} }
} }
mutex.Unlock()
return count return count
} }

View File

@ -24,6 +24,7 @@ func TestContext(t *testing.T) {
} }
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
// Get() // Get()
assertEqual(Get(r, key1), nil) assertEqual(Get(r, key1), nil)
@ -51,6 +52,26 @@ func TestContext(t *testing.T) {
assertEqual(value, nil) assertEqual(value, nil)
assertEqual(ok, true) 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()
Delete(r, key1) Delete(r, key1)
assertEqual(Get(r, key1), nil) assertEqual(Get(r, key1), nil)
@ -64,3 +85,77 @@ func TestContext(t *testing.T) {
Clear(r) Clear(r)
assertEqual(len(data), 0) 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++ {
Get(r, key)
}
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)
}

View File

@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
/* /*
Package gorilla/context stores values shared during a request lifetime. Package context stores values shared during a request lifetime.
For example, a router can set variables extracted from the URL and later For example, a router can set variables extracted from the URL and later
application handlers can access those values, or it can be used to store application handlers can access those values, or it can be used to store

View File

@ -0,0 +1,7 @@
language: go
go:
- 1.0
- 1.1
- 1.2
- tip

View File

@ -1,5 +1,6 @@
mux mux
=== ===
[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux)
gorilla/mux is a powerful URL router and dispatcher. gorilla/mux is a powerful URL router and dispatcher.

View File

@ -134,7 +134,7 @@ the inner routes use it as base for their paths:
// "/products/{key}/" // "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler) s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details" // "/products/{key}/details"
s.HandleFunc("/{key}/details"), ProductDetailsHandler) s.HandleFunc("/{key}/details", ProductDetailsHandler)
Now let's see how to build registered URLs. Now let's see how to build registered URLs.

View File

@ -14,7 +14,7 @@ import (
// NewRouter returns a new router instance. // NewRouter returns a new router instance.
func NewRouter() *Router { func NewRouter() *Router {
return &Router{namedRoutes: make(map[string]*Route)} return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
} }
// Router registers routes to be matched and dispatches a handler. // Router registers routes to be matched and dispatches a handler.
@ -46,6 +46,8 @@ type Router struct {
namedRoutes map[string]*Route namedRoutes map[string]*Route
// See Router.StrictSlash(). This defines the flag for new routes. // See Router.StrictSlash(). This defines the flag for new routes.
strictSlash bool strictSlash bool
// If true, do not clear the request context after handling the request
KeepContext bool
} }
// Match matches registered routes against the request. // Match matches registered routes against the request.
@ -65,6 +67,14 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Clean path to canonical form and redirect. // Clean path to canonical form and redirect.
if p := cleanPath(req.URL.Path); p != req.URL.Path { if p := cleanPath(req.URL.Path); p != req.URL.Path {
// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
// http://code.google.com/p/go/issues/detail?id=5252
url := *req.URL
url.Path = p
p = url.String()
w.Header().Set("Location", p) w.Header().Set("Location", p)
w.WriteHeader(http.StatusMovedPermanently) w.WriteHeader(http.StatusMovedPermanently)
return return
@ -82,7 +92,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
handler = r.NotFoundHandler handler = r.NotFoundHandler
} }
defer context.Clear(req) if !r.KeepContext {
defer context.Clear(req)
}
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
} }
@ -97,14 +109,20 @@ func (r *Router) GetRoute(name string) *Route {
return r.getNamedRoutes()[name] return r.getNamedRoutes()[name]
} }
// StrictSlash defines the slash behavior for new routes. // StrictSlash defines the trailing slash behavior for new routes. The initial
// value is false.
// //
// When true, if the route path is "/path/", accessing "/path" will redirect // When true, if the route path is "/path/", accessing "/path" will redirect
// to the former and vice versa. // to the former and vice versa. In other words, your application will always
// see the path as specified in the route.
// //
// Special case: when a route sets a path prefix, strict slash is // When false, if the route path is "/path", accessing "/path/" will not match
// automatically set to false for that route because the redirect behavior // this route and vice versa.
// can't be determined for prefixes. //
// Special case: when a route sets a path prefix using the PathPrefix() method,
// strict slash is ignored for that route because the redirect behavior can't
// be determined from a prefix alone. However, any subrouters created from that
// route inherit the original StrictSlash setting.
func (r *Router) StrictSlash(value bool) *Router { func (r *Router) StrictSlash(value bool) *Router {
r.strictSlash = value r.strictSlash = value
return r return r

View File

@ -8,16 +8,19 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"testing" "testing"
"github.com/gorilla/context"
) )
type routeTest struct { type routeTest struct {
title string // title of the test title string // title of the test
route *Route // the route being tested route *Route // the route being tested
request *http.Request // a request to test the route request *http.Request // a request to test the route
vars map[string]string // the expected vars of the match vars map[string]string // the expected vars of the match
host string // the expected host of the match host string // the expected host of the match
path string // the expected path of the match path string // the expected path of the match
shouldMatch bool // whether the request is expected to match the route at all 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) { func TestHost(t *testing.T) {
@ -149,6 +152,33 @@ func TestPath(t *testing.T) {
path: "/111/222/333", path: "/111/222/333",
shouldMatch: true, 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",
shouldMatch: false,
},
{
title: "Path route, do not match with trailing slash in request",
route: new(Route).Path("/111"),
request: newRequest("GET", "http://localhost/111/"),
vars: map[string]string{},
host: "",
path: "/111/",
shouldMatch: false,
},
{ {
title: "Path route, wrong path in request in request URL", title: "Path route, wrong path in request in request URL",
route: new(Route).Path("/111/222/333"), route: new(Route).Path("/111/222/333"),
@ -212,6 +242,15 @@ func TestPathPrefix(t *testing.T) {
path: "/111", path: "/111",
shouldMatch: true, 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", title: "PathPrefix route, URL prefix in request does not match",
route: new(Route).PathPrefix("/111"), route: new(Route).PathPrefix("/111"),
@ -414,6 +453,15 @@ func TestQueries(t *testing.T) {
path: "", path: "",
shouldMatch: true, shouldMatch: true,
}, },
{
title: "Queries route, match with a query string",
route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
vars: map[string]string{},
host: "",
path: "",
shouldMatch: true,
},
{ {
title: "Queries route, bad query", title: "Queries route, bad query",
route: new(Route).Queries("foo", "bar", "baz", "ding"), route: new(Route).Queries("foo", "bar", "baz", "ding"),
@ -568,26 +616,74 @@ func TestNamedRoutes(t *testing.T) {
} }
func TestStrictSlash(t *testing.T) { func TestStrictSlash(t *testing.T) {
var r *Router r := NewRouter()
var req *http.Request
var route *Route
var match *RouteMatch
var matched bool
// StrictSlash should be ignored for path prefix.
// So we register a route ending in slash but it doesn't attempt to add
// the slash for a path not ending in slash.
r = NewRouter()
r.StrictSlash(true) r.StrictSlash(true)
route = r.NewRoute().PathPrefix("/static/")
req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil) tests := []routeTest{
match = new(RouteMatch) {
matched = r.Match(req, match) title: "Redirect path without slash",
if !matched { route: r.NewRoute().Path("/111/"),
t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route)) 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,
},
} }
if match.Handler != nil {
t.Errorf("Should not redirect") for _, test := range tests {
testRoute(t, test)
} }
} }
@ -616,6 +712,7 @@ func testRoute(t *testing.T, test routeTest) {
host := test.host host := test.host
path := test.path path := test.path
url := test.host + test.path url := test.host + test.path
shouldRedirect := test.shouldRedirect
var match RouteMatch var match RouteMatch
ok := route.Match(request, &match) ok := route.Match(request, &match)
@ -653,6 +750,84 @@ func testRoute(t *testing.T, test routeTest) {
return 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
}
}
}
// 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")
} }
} }

View File

@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) {
method = "GET" method = "GET"
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
resultVars = map[bool]map[string]string{ resultVars = map[bool]map[string]string{
true: map[string]string{"var1": "www", "var2": "product", "var3": "42"}, true: {"var1": "www", "var2": "product", "var3": "42"},
false: map[string]string{}, false: {},
} }
} }
@ -110,8 +110,8 @@ func TestRouteMatchers(t *testing.T) {
method = "POST" method = "POST"
headers = map[string]string{"Content-Type": "application/json"} headers = map[string]string{"Content-Type": "application/json"}
resultVars = map[bool]map[string]string{ resultVars = map[bool]map[string]string{
true: map[string]string{"var4": "google", "var5": "product", "var6": "42"}, true: {"var4": "google", "var5": "product", "var6": "42"},
false: map[string]string{}, false: {},
} }
} }

View File

@ -98,12 +98,13 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout
} }
// Done! // Done!
return &routeRegexp{ return &routeRegexp{
template: template, template: template,
matchHost: matchHost, matchHost: matchHost,
regexp: reg, strictSlash: strictSlash,
reverse: reverse.String(), regexp: reg,
varsN: varsN, reverse: reverse.String(),
varsR: varsR, varsN: varsN,
varsR: varsR,
}, nil }, nil
} }
@ -114,6 +115,8 @@ type routeRegexp struct {
template string template string
// True for host match, false for path match. // True for host match, false for path match.
matchHost bool matchHost bool
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
strictSlash bool
// Expanded regexp. // Expanded regexp.
regexp *regexp.Regexp regexp *regexp.Regexp
// Reverse template. // Reverse template.
@ -216,7 +219,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
m.Vars[v] = pathVars[k+1] m.Vars[v] = pathVars[k+1]
} }
// Check if we should redirect. // Check if we should redirect.
if r.strictSlash { if v.path.strictSlash {
p1 := strings.HasSuffix(req.URL.Path, "/") p1 := strings.HasSuffix(req.URL.Path, "/")
p2 := strings.HasSuffix(v.path.template, "/") p2 := strings.HasSuffix(v.path.template, "/")
if p1 != p2 { if p1 != p2 {

View File

@ -259,7 +259,8 @@ func (r *Route) Methods(methods ...string) *Route {
// Path ----------------------------------------------------------------------- // Path -----------------------------------------------------------------------
// Path adds a matcher for the URL path. // Path adds a matcher for the URL path.
// It accepts a template with zero or more URL variables enclosed by {}. // It accepts a template with zero or more URL variables enclosed by {}. The
// template must start with a "/".
// Variables can define an optional regexp pattern to me matched: // Variables can define an optional regexp pattern to me matched:
// //
// - {name} matches anything until the next slash. // - {name} matches anything until the next slash.
@ -283,9 +284,16 @@ func (r *Route) Path(tpl string) *Route {
// PathPrefix ----------------------------------------------------------------- // PathPrefix -----------------------------------------------------------------
// PathPrefix adds a matcher for the URL path prefix. // PathPrefix adds a matcher for the URL path prefix. This matches if the given
// template is a prefix of the full URL path. See Route.Path() for details on
// the tpl argument.
//
// Note that it does not treat slashes specially ("/foobar/" will be matched by
// the prefix "/foo") so you may want to use a trailing slash here.
//
// Also note that the setting of Router.StrictSlash() has no effect on routes
// with a PathPrefix matcher.
func (r *Route) PathPrefix(tpl string) *Route { func (r *Route) PathPrefix(tpl string) *Route {
r.strictSlash = false
r.err = r.addRegexpMatcher(tpl, false, true) r.err = r.addRegexpMatcher(tpl, false, true)
return r return r
} }
@ -328,7 +336,7 @@ func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
} }
// Schemes adds a matcher for URL schemes. // Schemes adds a matcher for URL schemes.
// It accepts a sequence schemes to be matched, e.g.: "http", "https". // It accepts a sequence of schemes to be matched, e.g.: "http", "https".
func (r *Route) Schemes(schemes ...string) *Route { func (r *Route) Schemes(schemes ...string) *Route {
for k, v := range schemes { for k, v := range schemes {
schemes[k] = strings.ToLower(v) schemes[k] = strings.ToLower(v)

11
vendor/src/github.com/kr/pty/ioctl.go vendored Normal file
View File

@ -0,0 +1,11 @@
package pty
import "syscall"
func ioctl(fd, cmd, ptr uintptr) error {
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
if e != 0 {
return e
}
return nil
}

View File

@ -0,0 +1,39 @@
// +build darwin dragonfly freebsd netbsd openbsd
package pty
// from <sys/ioccom.h>
const (
_IOC_VOID uintptr = 0x20000000
_IOC_OUT uintptr = 0x40000000
_IOC_IN uintptr = 0x80000000
_IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
_IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
_IOC_PARAM_SHIFT = 13
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
)
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
return (ioctl >> 16) & _IOC_PARAM_MASK
}
func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
}
func _IO(group byte, ioctl_num uintptr) uintptr {
return _IOC(_IOC_VOID, group, ioctl_num, 0)
}
func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_OUT, group, ioctl_num, param_len)
}
func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_IN, group, ioctl_num, param_len)
}
func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
}

View File

@ -0,0 +1,42 @@
package pty
// from <asm-generic/ioctl.h>
const (
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
_IOC_DIRBITS = 2
_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
_IOC_NONE uint = 0
_IOC_WRITE uint = 1
_IOC_READ uint = 2
)
func _IOC(dir uint, ioctl_type byte, nr byte, size uintptr) uintptr {
return (uintptr(dir)<<_IOC_DIRSHIFT |
uintptr(ioctl_type)<<_IOC_TYPESHIFT |
uintptr(nr)<<_IOC_NRSHIFT |
size<<_IOC_SIZESHIFT)
}
func _IO(ioctl_type byte, nr byte) uintptr {
return _IOC(_IOC_NONE, ioctl_type, nr, 0)
}
func _IOR(ioctl_type byte, nr byte, size uintptr) uintptr {
return _IOC(_IOC_READ, ioctl_type, nr, size)
}
func _IOW(ioctl_type byte, nr byte, size uintptr) uintptr {
return _IOC(_IOC_WRITE, ioctl_type, nr, size)
}
func _IOWR(ioctl_type byte, nr byte, size uintptr) uintptr {
return _IOC(_IOC_READ|_IOC_WRITE, ioctl_type, nr, size)
}

19
vendor/src/github.com/kr/pty/mktypes.bash vendored Executable file
View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
GOOSARCH="${GOOS}_${GOARCH}"
case "$GOOSARCH" in
_* | *_ | _)
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
exit 1
;;
esac
GODEFS="go tool cgo -godefs"
$GODEFS types.go |gofmt > ztypes_$GOARCH.go
case $GOOS in
freebsd)
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
;;
esac

View File

@ -7,9 +7,6 @@ import (
"unsafe" "unsafe"
) )
// see ioccom.h
const sys_IOCPARM_MASK = 0x1fff
func open() (pty, tty *os.File, err error) { func open() (pty, tty *os.File, err error) {
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
if err != nil { if err != nil {
@ -39,9 +36,13 @@ func open() (pty, tty *os.File, err error) {
} }
func ptsname(f *os.File) (string, error) { func ptsname(f *os.File) (string, error) {
var n [(syscall.TIOCPTYGNAME >> 16) & sys_IOCPARM_MASK]byte n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
if err != nil {
return "", err
}
ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n)))
for i, c := range n { for i, c := range n {
if c == 0 { if c == 0 {
return string(n[:i]), nil return string(n[:i]), nil
@ -51,19 +52,9 @@ func ptsname(f *os.File) (string, error) {
} }
func grantpt(f *os.File) error { func grantpt(f *os.File) error {
var u int return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
return ioctl(f.Fd(), syscall.TIOCPTYGRANT, uintptr(unsafe.Pointer(&u)))
} }
func unlockpt(f *os.File) error { func unlockpt(f *os.File) error {
var u int return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
return ioctl(f.Fd(), syscall.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
}
func ioctl(fd, cmd, ptr uintptr) error {
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
if e != 0 {
return syscall.ENOTTY
}
return nil
} }

View File

@ -1,53 +1,73 @@
package pty package pty
import ( import (
"errors"
"os" "os"
"strconv"
"syscall" "syscall"
"unsafe" "unsafe"
) )
const ( func posix_openpt(oflag int) (fd int, err error) {
sys_TIOCGPTN = 0x4004740F r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
sys_TIOCSPTLCK = 0x40045431 fd = int(r0)
) if e1 != 0 {
err = e1
}
return
}
func open() (pty, tty *os.File, err error) { func open() (pty, tty *os.File, err error) {
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
p := os.NewFile(uintptr(fd), "/dev/pts")
sname, err := ptsname(p) sname, err := ptsname(p)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return p, t, nil return p, t, nil
} }
func isptmaster(fd uintptr) (bool, error) {
err := ioctl(fd, syscall.TIOCPTMASTER, 0)
return err == nil, err
}
var (
emptyFiodgnameArg fiodgnameArg
ioctl_FIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
)
func ptsname(f *os.File) (string, error) { func ptsname(f *os.File) (string, error) {
var n int master, err := isptmaster(f.Fd())
err := ioctl(f.Fd(), sys_TIOCGPTN, &n)
if err != nil { if err != nil {
return "", err return "", err
} }
return "/dev/pts/" + strconv.Itoa(n), nil if !master {
} return "", syscall.EINVAL
func ioctl(fd uintptr, cmd uintptr, data *int) error {
_, _, e := syscall.Syscall(
syscall.SYS_IOCTL,
fd,
cmd,
uintptr(unsafe.Pointer(data)),
)
if e != 0 {
return syscall.ENOTTY
} }
return nil
const n = _C_SPECNAMELEN + 1
var (
buf = make([]byte, n)
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
)
err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg)))
if err != nil {
return "", err
}
for i, c := range buf {
if c == 0 {
return string(buf[:i]), nil
}
}
return "", errors.New("FIODGNAME string not NUL-terminated")
} }

View File

@ -7,9 +7,9 @@ import (
"unsafe" "unsafe"
) )
const ( var (
sys_TIOCGPTN = 0x80045430 ioctl_TIOCGPTN = _IOR('T', 0x30, unsafe.Sizeof(_C_uint(0))) /* Get Pty Number (of pty-mux device) */
sys_TIOCSPTLCK = 0x40045431 ioctl_TIOCSPTLCK = _IOW('T', 0x31, unsafe.Sizeof(_C_int(0))) /* Lock/unlock Pty */
) )
func open() (pty, tty *os.File, err error) { func open() (pty, tty *os.File, err error) {
@ -36,28 +36,16 @@ func open() (pty, tty *os.File, err error) {
} }
func ptsname(f *os.File) (string, error) { func ptsname(f *os.File) (string, error) {
var n int var n _C_uint
err := ioctl(f.Fd(), sys_TIOCGPTN, &n) err := ioctl(f.Fd(), ioctl_TIOCGPTN, uintptr(unsafe.Pointer(&n)))
if err != nil { if err != nil {
return "", err return "", err
} }
return "/dev/pts/" + strconv.Itoa(n), nil return "/dev/pts/" + strconv.Itoa(int(n)), nil
} }
func unlockpt(f *os.File) error { func unlockpt(f *os.File) error {
var u int var u _C_int
return ioctl(f.Fd(), sys_TIOCSPTLCK, &u) // use TIOCSPTLCK with a zero valued arg to clear the slave pty lock
} return ioctl(f.Fd(), ioctl_TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
func ioctl(fd uintptr, cmd uintptr, data *int) error {
_, _, e := syscall.Syscall(
syscall.SYS_IOCTL,
fd,
cmd,
uintptr(unsafe.Pointer(data)),
)
if e != 0 {
return syscall.ENOTTY
}
return nil
} }

View File

@ -9,19 +9,3 @@ import (
func open() (pty, tty *os.File, err error) { func open() (pty, tty *os.File, err error) {
return nil, nil, ErrUnsupported return nil, nil, ErrUnsupported
} }
func ptsname(f *os.File) (string, error) {
return "", ErrUnsupported
}
func grantpt(f *os.File) error {
return ErrUnsupported
}
func unlockpt(f *os.File) error {
return ErrUnsupported
}
func ioctl(fd, cmd, ptr uintptr) error {
return ErrUnsupported
}

10
vendor/src/github.com/kr/pty/types.go vendored Normal file
View File

@ -0,0 +1,10 @@
// +build ignore
package pty
import "C"
type (
_C_int C.int
_C_uint C.uint
)

View File

@ -0,0 +1,15 @@
// +build ignore
package pty
/*
#include <sys/param.h>
#include <sys/filio.h>
*/
import "C"
const (
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
)
type fiodgnameArg C.struct_fiodgname_arg

View File

@ -0,0 +1,9 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go
package pty
type (
_C_int int32
_C_uint uint32
)

View File

@ -0,0 +1,9 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go
package pty
type (
_C_int int32
_C_uint uint32
)

View File

@ -0,0 +1,9 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types.go
package pty
type (
_C_int int32
_C_uint uint32
)

View File

@ -0,0 +1,13 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go
package pty
const (
_C_SPECNAMELEN = 0x3f
)
type fiodgnameArg struct {
Len int32
Buf *byte
}

View File

@ -0,0 +1,14 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go
package pty
const (
_C_SPECNAMELEN = 0x3f
)
type fiodgnameArg struct {
Len int32
Pad_cgo_0 [4]byte
Buf *byte
}

View File

@ -0,0 +1,13 @@
// Created by cgo -godefs - DO NOT EDIT
// cgo -godefs types_freebsd.go
package pty
const (
_C_SPECNAMELEN = 0x3f
)
type fiodgnameArg struct {
Len int32
Buf *byte
}