mirror of
https://github.com/LBRYFoundation/tracker.git
synced 2025-09-01 01:35:17 +00:00
godeps: refresh to get latest changes in graceful
This commit is contained in:
parent
7f2abdae4e
commit
ff5339ceb3
16 changed files with 431 additions and 417 deletions
13
Godeps/Godeps.json
generated
13
Godeps/Godeps.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/chihaya/chihaya",
|
"ImportPath": "github.com/chihaya/chihaya",
|
||||||
"GoVersion": "go1.4.1",
|
"GoVersion": "go1.4.2",
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/chihaya/bencode",
|
"ImportPath": "github.com/chihaya/bencode",
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
"ImportPath": "github.com/julienschmidt/httprouter",
|
||||||
"Rev": "00ce1c6a267162792c367acc43b1681a884e1872"
|
"Rev": "8c199fb6259ffc1af525cc3ad52ee60ba8359669"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/pushrax/bufferpool",
|
"ImportPath": "github.com/pushrax/bufferpool",
|
||||||
|
@ -28,15 +28,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/stretchr/graceful",
|
"ImportPath": "github.com/stretchr/graceful",
|
||||||
"Rev": "8e780ba3fe3d3e7ab15fc52e3d60a996587181dc"
|
"Comment": "v1-7-g0c01122",
|
||||||
},
|
"Rev": "0c011221e91b35f488b8818b00ca279929e9ed7d"
|
||||||
{
|
|
||||||
"ImportPath": "github.com/stretchr/pat/stop",
|
|
||||||
"Rev": "f7fe051f2b9bcaca162b38de4f93c9a8457160b9"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/net/netutil",
|
"ImportPath": "golang.org/x/net/netutil",
|
||||||
"Rev": "c84eff7014eba178f68bd4c05b86780efe0fbf35"
|
"Rev": "d175081df37eff8cda13f478bc11a0a65b39958b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
1
Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
1
Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml
generated
vendored
|
@ -1,3 +1,4 @@
|
||||||
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.1
|
- 1.1
|
||||||
|
|
52
Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md
generated
vendored
52
Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md
generated
vendored
|
@ -1,29 +1,17 @@
|
||||||
# HttpRouter [](https://travis-ci.org/julienschmidt/httprouter) [](http://godoc.org/github.com/julienschmidt/httprouter)
|
# HttpRouter [](https://travis-ci.org/julienschmidt/httprouter) [](http://gocover.io/github.com/julienschmidt/httprouter) [](http://godoc.org/github.com/julienschmidt/httprouter)
|
||||||
|
|
||||||
HttpRouter is a lightweight high performance HTTP request router
|
HttpRouter is a lightweight high performance HTTP request router
|
||||||
(also called *multiplexer* or just *mux* for short) for [Go](http://golang.org/).
|
(also called *multiplexer* or just *mux* for short) for [Go](http://golang.org/).
|
||||||
|
|
||||||
In contrast to the default mux of Go's net/http package, this router supports
|
In contrast to the [default mux](http://golang.org/pkg/net/http/#ServeMux) of Go's net/http package, this router supports
|
||||||
variables in the routing pattern and matches against the request method.
|
variables in the routing pattern and matches against the request method.
|
||||||
It also scales better.
|
It also scales better.
|
||||||
|
|
||||||
The router is optimized for best performance and a small memory footprint.
|
The router is optimized for high performance and a small memory footprint.
|
||||||
It scales well even with very long paths and a large number of routes.
|
It scales well even with very long paths and a large number of routes.
|
||||||
A compressing dynamic trie (radix tree) structure is used for efficient matching.
|
A compressing dynamic trie (radix tree) structure is used for efficient matching.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
**Zero Garbage:** The matching and dispatching process generates zero bytes of
|
|
||||||
garbage. In fact, the only heap allocations that are made, is by building the
|
|
||||||
slice of the key-value pairs for path parameters. If the request path contains
|
|
||||||
no parameters, not a single heap allocation is necessary.
|
|
||||||
|
|
||||||
**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark).
|
|
||||||
See below for technical details of the implementation.
|
|
||||||
|
|
||||||
**Parameters in your routing pattern:** Stop parsing the requested URL path,
|
|
||||||
just give the path segment a name and the router delivers the dynamic value to
|
|
||||||
you. Because of the design of the router, path parameters are very cheap.
|
|
||||||
|
|
||||||
**Only explicit matches:** With other routers, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux),
|
**Only explicit matches:** With other routers, like [http.ServeMux](http://golang.org/pkg/net/http/#ServeMux),
|
||||||
a requested URL path could match multiple patterns. Therefore they have some
|
a requested URL path could match multiple patterns. Therefore they have some
|
||||||
awkward pattern priority rules, like *longest match* or *first registered,
|
awkward pattern priority rules, like *longest match* or *first registered,
|
||||||
|
@ -34,7 +22,7 @@ great for SEO and improves the user experience.
|
||||||
**Stop caring about trailing slashes:** Choose the URL style you like, the
|
**Stop caring about trailing slashes:** Choose the URL style you like, the
|
||||||
router automatically redirects the client if a trailing slash is missing or if
|
router automatically redirects the client if a trailing slash is missing or if
|
||||||
there is one extra. Of course it only does so, if the new path has a handler.
|
there is one extra. Of course it only does so, if the new path has a handler.
|
||||||
If you don't like it, you can turn off this behavior.
|
If you don't like it, you can [turn off this behavior](http://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash).
|
||||||
|
|
||||||
**Path auto-correction:** Besides detecting the missing or additional trailing
|
**Path auto-correction:** Besides detecting the missing or additional trailing
|
||||||
slash at no extra cost, the router can also fix wrong cases and remove
|
slash at no extra cost, the router can also fix wrong cases and remove
|
||||||
|
@ -43,11 +31,23 @@ Is [CAPTAIN CAPS LOCK](http://www.urbandictionary.com/define.php?term=Captain+Ca
|
||||||
HttpRouter can help him by making a case-insensitive look-up and redirecting him
|
HttpRouter can help him by making a case-insensitive look-up and redirecting him
|
||||||
to the correct URL.
|
to the correct URL.
|
||||||
|
|
||||||
**No more server crashes:** You can set a PanicHandler to deal with panics
|
**Parameters in your routing pattern:** Stop parsing the requested URL path,
|
||||||
|
just give the path segment a name and the router delivers the dynamic value to
|
||||||
|
you. Because of the design of the router, path parameters are very cheap.
|
||||||
|
|
||||||
|
**Zero Garbage:** The matching and dispatching process generates zero bytes of
|
||||||
|
garbage. In fact, the only heap allocations that are made, is by building the
|
||||||
|
slice of the key-value pairs for path parameters. If the request path contains
|
||||||
|
no parameters, not a single heap allocation is necessary.
|
||||||
|
|
||||||
|
**Best Performance:** [Benchmarks speak for themselves](https://github.com/julienschmidt/go-http-routing-benchmark).
|
||||||
|
See below for technical details of the implementation.
|
||||||
|
|
||||||
|
**No more server crashes:** You can set a [Panic handler](http://godoc.org/github.com/julienschmidt/httprouter#Router.PanicHandler) to deal with panics
|
||||||
occurring during handling a HTTP request. The router then recovers and lets the
|
occurring during handling a HTTP request. The router then recovers and lets the
|
||||||
PanicHandler log what happened and deliver a nice error page.
|
PanicHandler log what happened and deliver a nice error page.
|
||||||
|
|
||||||
Of course you can also set a **custom NotFound handler** and **serve static files**.
|
Of course you can also set **custom [NotFound](http://godoc.org/github.com/julienschmidt/httprouter#Router.NotFound) and [MethodNotAllowed](http://godoc.org/github.com/julienschmidt/httprouter#Router.MethodNotAllowed) handlers** and [**serve static files**](http://godoc.org/github.com/julienschmidt/httprouter#Router.ServeFiles).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details.
|
This is just a quick introduction, view the [GoDoc](http://godoc.org/github.com/julienschmidt/httprouter) for details.
|
||||||
|
@ -189,7 +189,7 @@ for example the [Gorilla handlers](http://www.gorillatoolkit.org/pkg/handlers).
|
||||||
Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/),
|
Or you could [just write your own](http://justinas.org/writing-http-middleware-in-go/),
|
||||||
it's very easy!
|
it's very easy!
|
||||||
|
|
||||||
Alternatively, you could try [a framework building upon HttpRouter](#web-frameworks--co-based-on-httprouter).
|
Alternatively, you could try [a web framework based on HttpRouter](#web-frameworks-based-on-httprouter).
|
||||||
|
|
||||||
### Multi-domain / Sub-domains
|
### Multi-domain / Sub-domains
|
||||||
Here is a quick example: Does your server serve multiple domains / hosts?
|
Here is a quick example: Does your server serve multiple domains / hosts?
|
||||||
|
@ -256,7 +256,10 @@ func BasicAuth(h httprouter.Handle, user, pass []byte) httprouter.Handle {
|
||||||
payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
|
payload, err := base64.StdEncoding.DecodeString(auth[len(basicAuthPrefix):])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pair := bytes.SplitN(payload, []byte(":"), 2)
|
pair := bytes.SplitN(payload, []byte(":"), 2)
|
||||||
if len(pair) == 2 && bytes.Equal(pair[0], user) && bytes.Equal(pair[1], pass) {
|
if len(pair) == 2 &&
|
||||||
|
bytes.Equal(pair[0], user) &&
|
||||||
|
bytes.Equal(pair[1], pass) {
|
||||||
|
|
||||||
// Delegate request to the given handle
|
// Delegate request to the given handle
|
||||||
h(w, r, ps)
|
h(w, r, ps)
|
||||||
return
|
return
|
||||||
|
@ -305,9 +308,16 @@ router.NotFound = http.FileServer(http.Dir("public")).ServeHTTP
|
||||||
|
|
||||||
But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`.
|
But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like `/static/*filepath` or `/files/*filepath`.
|
||||||
|
|
||||||
## Web Frameworks & Co based on HttpRouter
|
## Web Frameworks based on HttpRouter
|
||||||
If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
|
If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:
|
||||||
|
* [Ace](https://github.com/plimble/ace): Blazing fast Go Web Framework
|
||||||
|
* [api2go](https://github.com/univedo/api2go): A JSON API Implementation for Go
|
||||||
* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance
|
* [Gin](https://github.com/gin-gonic/gin): Features a martini-like API with much better performance
|
||||||
|
* [Goat](https://github.com/bahlo/goat): A minimalistic REST API server in Go
|
||||||
* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine
|
* [Hikaru](https://github.com/najeira/hikaru): Supports standalone and Google AppEngine
|
||||||
* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow
|
* [Hitch](https://github.com/nbio/hitch): Hitch ties httprouter, [httpcontext](https://github.com/nbio/httpcontext), and middleware up in a bow
|
||||||
|
* [kami](https://github.com/guregu/kami): A tiny web framework using x/net/context
|
||||||
|
* [Medeina](https://github.com/imdario/medeina): Inspired by Ruby's Roda and Cuba
|
||||||
* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang
|
* [Neko](https://github.com/rocwong/neko): A lightweight web application framework for Golang
|
||||||
|
* [Roxanna](https://github.com/iamthemuffinman/Roxanna): An amalgamation of httprouter, better logging, and hot reload
|
||||||
|
* [siesta](https://github.com/VividCortex/siesta): Composable HTTP handlers with contexts
|
||||||
|
|
32
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go
generated
vendored
32
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go
generated
vendored
|
@ -142,6 +142,11 @@ type Router struct {
|
||||||
// found. If it is not set, http.NotFound is used.
|
// found. If it is not set, http.NotFound is used.
|
||||||
NotFound http.HandlerFunc
|
NotFound http.HandlerFunc
|
||||||
|
|
||||||
|
// Configurable http.HandlerFunc which is called when a request
|
||||||
|
// cannot be routed and HandleMethodNotAllowed is true.
|
||||||
|
// If it is not set, http.Error with http.StatusMethodNotAllowed is used.
|
||||||
|
MethodNotAllowed http.HandlerFunc
|
||||||
|
|
||||||
// Function to handle panics recovered from http handlers.
|
// Function to handle panics recovered from http handlers.
|
||||||
// It should be used to generate a error page and return the http error code
|
// It should be used to generate a error page and return the http error code
|
||||||
// 500 (Internal Server Error).
|
// 500 (Internal Server Error).
|
||||||
|
@ -173,6 +178,11 @@ func (r *Router) HEAD(path string, handle Handle) {
|
||||||
r.Handle("HEAD", path, handle)
|
r.Handle("HEAD", path, handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
|
||||||
|
func (r *Router) OPTIONS(path string, handle Handle) {
|
||||||
|
r.Handle("OPTIONS", path, handle)
|
||||||
|
}
|
||||||
|
|
||||||
// POST is a shortcut for router.Handle("POST", path, handle)
|
// POST is a shortcut for router.Handle("POST", path, handle)
|
||||||
func (r *Router) POST(path string, handle Handle) {
|
func (r *Router) POST(path string, handle Handle) {
|
||||||
r.Handle("POST", path, handle)
|
r.Handle("POST", path, handle)
|
||||||
|
@ -203,7 +213,7 @@ func (r *Router) DELETE(path string, handle Handle) {
|
||||||
// communication with a proxy).
|
// communication with a proxy).
|
||||||
func (r *Router) Handle(method, path string, handle Handle) {
|
func (r *Router) Handle(method, path string, handle Handle) {
|
||||||
if path[0] != '/' {
|
if path[0] != '/' {
|
||||||
panic("path must begin with '/'")
|
panic("path must begin with '/' in path '" + path + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.trees == nil {
|
if r.trees == nil {
|
||||||
|
@ -232,11 +242,7 @@ func (r *Router) Handler(method, path string, handler http.Handler) {
|
||||||
// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
|
// HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
|
||||||
// request handle.
|
// request handle.
|
||||||
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
|
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
|
||||||
r.Handle(method, path,
|
r.Handler(method, path, handler)
|
||||||
func(w http.ResponseWriter, req *http.Request, _ Params) {
|
|
||||||
handler(w, req)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeFiles serves files from the given file system root.
|
// ServeFiles serves files from the given file system root.
|
||||||
|
@ -251,7 +257,7 @@ func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
|
||||||
// router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
|
// router.ServeFiles("/src/*filepath", http.Dir("/var/www"))
|
||||||
func (r *Router) ServeFiles(path string, root http.FileSystem) {
|
func (r *Router) ServeFiles(path string, root http.FileSystem) {
|
||||||
if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
|
if len(path) < 10 || path[len(path)-10:] != "/*filepath" {
|
||||||
panic("path must end with /*filepath")
|
panic("path must end with /*filepath in path '" + path + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
fileServer := http.FileServer(root)
|
fileServer := http.FileServer(root)
|
||||||
|
@ -335,10 +341,14 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
handle, _, _ := r.trees[method].getValue(req.URL.Path)
|
handle, _, _ := r.trees[method].getValue(req.URL.Path)
|
||||||
if handle != nil {
|
if handle != nil {
|
||||||
http.Error(w,
|
if r.MethodNotAllowed != nil {
|
||||||
http.StatusText(http.StatusMethodNotAllowed),
|
r.MethodNotAllowed(w, req)
|
||||||
http.StatusMethodNotAllowed,
|
} else {
|
||||||
)
|
http.Error(w,
|
||||||
|
http.StatusText(http.StatusMethodNotAllowed),
|
||||||
|
http.StatusMethodNotAllowed,
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go
generated
vendored
27
Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go
generated
vendored
|
@ -76,7 +76,7 @@ func (h handlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouterAPI(t *testing.T) {
|
func TestRouterAPI(t *testing.T) {
|
||||||
var get, head, post, put, patch, delete, handler, handlerFunc bool
|
var get, head, options, post, put, patch, delete, handler, handlerFunc bool
|
||||||
|
|
||||||
httpHandler := handlerStruct{&handler}
|
httpHandler := handlerStruct{&handler}
|
||||||
|
|
||||||
|
@ -87,6 +87,9 @@ func TestRouterAPI(t *testing.T) {
|
||||||
router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||||
head = true
|
head = true
|
||||||
})
|
})
|
||||||
|
router.OPTIONS("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||||
|
options = true
|
||||||
|
})
|
||||||
router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
router.POST("/POST", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||||
post = true
|
post = true
|
||||||
})
|
})
|
||||||
|
@ -118,6 +121,12 @@ func TestRouterAPI(t *testing.T) {
|
||||||
t.Error("routing HEAD failed")
|
t.Error("routing HEAD failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r, _ = http.NewRequest("OPTIONS", "/GET", nil)
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
if !options {
|
||||||
|
t.Error("routing OPTIONS failed")
|
||||||
|
}
|
||||||
|
|
||||||
r, _ = http.NewRequest("POST", "/POST", nil)
|
r, _ = http.NewRequest("POST", "/POST", nil)
|
||||||
router.ServeHTTP(w, r)
|
router.ServeHTTP(w, r)
|
||||||
if !post {
|
if !post {
|
||||||
|
@ -176,7 +185,21 @@ func TestRouterNotAllowed(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
router.ServeHTTP(w, r)
|
router.ServeHTTP(w, r)
|
||||||
if !(w.Code == http.StatusMethodNotAllowed) {
|
if !(w.Code == http.StatusMethodNotAllowed) {
|
||||||
t.Errorf("NotAllowed handling route %s failed: Code=%d, Header=%v", w.Code, w.Header())
|
t.Errorf("NotAllowed handling failed: Code=%d, Header=%v", w.Code, w.Header())
|
||||||
|
}
|
||||||
|
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
responseText := "custom method"
|
||||||
|
router.MethodNotAllowed = func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusTeapot)
|
||||||
|
w.Write([]byte(responseText))
|
||||||
|
}
|
||||||
|
router.ServeHTTP(w, r)
|
||||||
|
if got := w.Body.String(); !(got == responseText) {
|
||||||
|
t.Errorf("unexpected response got %q want %q", got, responseText)
|
||||||
|
}
|
||||||
|
if w.Code != http.StatusTeapot {
|
||||||
|
t.Errorf("unexpected response code %d want %d", w.Code, http.StatusTeapot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
207
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go
generated
vendored
207
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go
generated
vendored
|
@ -43,41 +43,48 @@ type node struct {
|
||||||
wildChild bool
|
wildChild bool
|
||||||
nType nodeType
|
nType nodeType
|
||||||
maxParams uint8
|
maxParams uint8
|
||||||
indices []byte
|
indices string
|
||||||
children []*node
|
children []*node
|
||||||
handle Handle
|
handle Handle
|
||||||
priority uint32
|
priority uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// increments priority of the given child and reorders if necessary
|
// increments priority of the given child and reorders if necessary
|
||||||
func (n *node) incrementChildPrio(i int) int {
|
func (n *node) incrementChildPrio(pos int) int {
|
||||||
n.children[i].priority++
|
n.children[pos].priority++
|
||||||
prio := n.children[i].priority
|
prio := n.children[pos].priority
|
||||||
|
|
||||||
// adjust position (move to front)
|
// adjust position (move to front)
|
||||||
for j := i - 1; j >= 0 && n.children[j].priority < prio; j-- {
|
newPos := pos
|
||||||
|
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||||
// swap node positions
|
// swap node positions
|
||||||
tmpN := n.children[j]
|
tmpN := n.children[newPos-1]
|
||||||
n.children[j] = n.children[i]
|
n.children[newPos-1] = n.children[newPos]
|
||||||
n.children[i] = tmpN
|
n.children[newPos] = tmpN
|
||||||
tmpI := n.indices[j]
|
|
||||||
n.indices[j] = n.indices[i]
|
|
||||||
n.indices[i] = tmpI
|
|
||||||
|
|
||||||
i--
|
newPos--
|
||||||
}
|
}
|
||||||
return i
|
|
||||||
|
// build new index char string
|
||||||
|
if newPos != pos {
|
||||||
|
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
|
||||||
|
n.indices[pos:pos+1] + // the index char we move
|
||||||
|
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPos
|
||||||
}
|
}
|
||||||
|
|
||||||
// addRoute adds a node with the given handle to the path.
|
// addRoute adds a node with the given handle to the path.
|
||||||
// Not concurrency-safe!
|
// Not concurrency-safe!
|
||||||
func (n *node) addRoute(path string, handle Handle) {
|
func (n *node) addRoute(path string, handle Handle) {
|
||||||
|
fullPath := path
|
||||||
n.priority++
|
n.priority++
|
||||||
numParams := countParams(path)
|
numParams := countParams(path)
|
||||||
|
|
||||||
// non-empty tree
|
// non-empty tree
|
||||||
if len(n.path) > 0 || len(n.children) > 0 {
|
if len(n.path) > 0 || len(n.children) > 0 {
|
||||||
WALK:
|
walk:
|
||||||
for {
|
for {
|
||||||
// Update maxParams of the current node
|
// Update maxParams of the current node
|
||||||
if numParams > n.maxParams {
|
if numParams > n.maxParams {
|
||||||
|
@ -85,10 +92,12 @@ func (n *node) addRoute(path string, handle Handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the longest common prefix.
|
// Find the longest common prefix.
|
||||||
// This also implies that the commom prefix contains no ':' or '*'
|
// This also implies that the common prefix contains no ':' or '*'
|
||||||
// since the existing key can't contain this chars.
|
// since the existing key can't contain those chars.
|
||||||
i := 0
|
i := 0
|
||||||
for max := min(len(path), len(n.path)); i < max && path[i] == n.path[i]; i++ {
|
max := min(len(path), len(n.path))
|
||||||
|
for i < max && path[i] == n.path[i] {
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split edge
|
// Split edge
|
||||||
|
@ -110,7 +119,8 @@ func (n *node) addRoute(path string, handle Handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
n.children = []*node{&child}
|
n.children = []*node{&child}
|
||||||
n.indices = []byte{n.path[i]}
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices = string([]byte{n.path[i]})
|
||||||
n.path = path[:i]
|
n.path = path[:i]
|
||||||
n.handle = nil
|
n.handle = nil
|
||||||
n.wildChild = false
|
n.wildChild = false
|
||||||
|
@ -134,11 +144,13 @@ func (n *node) addRoute(path string, handle Handle) {
|
||||||
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
||||||
// check for longer wildcard, e.g. :name and :names
|
// check for longer wildcard, e.g. :name and :names
|
||||||
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
||||||
continue WALK
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("conflict with wildcard route")
|
panic("path segment '" + path +
|
||||||
|
"' conflicts with existing wildcard '" + n.path +
|
||||||
|
"' in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
c := path[0]
|
c := path[0]
|
||||||
|
@ -147,21 +159,22 @@ func (n *node) addRoute(path string, handle Handle) {
|
||||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
n.priority++
|
n.priority++
|
||||||
continue WALK
|
continue walk
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a child with the next path byte exists
|
// Check if a child with the next path byte exists
|
||||||
for i, index := range n.indices {
|
for i := 0; i < len(n.indices); i++ {
|
||||||
if c == index {
|
if c == n.indices[i] {
|
||||||
i = n.incrementChildPrio(i)
|
i = n.incrementChildPrio(i)
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
continue WALK
|
continue walk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise insert it
|
// Otherwise insert it
|
||||||
if c != ':' && c != '*' {
|
if c != ':' && c != '*' {
|
||||||
n.indices = append(n.indices, c)
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices += string([]byte{c})
|
||||||
child := &node{
|
child := &node{
|
||||||
maxParams: numParams,
|
maxParams: numParams,
|
||||||
}
|
}
|
||||||
|
@ -169,24 +182,24 @@ func (n *node) addRoute(path string, handle Handle) {
|
||||||
n.incrementChildPrio(len(n.indices) - 1)
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
n = child
|
n = child
|
||||||
}
|
}
|
||||||
n.insertChild(numParams, path, handle)
|
n.insertChild(numParams, path, fullPath, handle)
|
||||||
return
|
return
|
||||||
|
|
||||||
} else if i == len(path) { // Make node a (in-path) leaf
|
} else if i == len(path) { // Make node a (in-path) leaf
|
||||||
if n.handle != nil {
|
if n.handle != nil {
|
||||||
panic("a Handle is already registered for this path")
|
panic("a handle is already registered for path ''" + fullPath + "'")
|
||||||
}
|
}
|
||||||
n.handle = handle
|
n.handle = handle
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else { // Empty tree
|
} else { // Empty tree
|
||||||
n.insertChild(numParams, path, handle)
|
n.insertChild(numParams, path, fullPath, handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {
|
||||||
var offset int
|
var offset int // already handled bytes of the path
|
||||||
|
|
||||||
// find prefix until first wildcard (beginning with ':'' or '*'')
|
// find prefix until first wildcard (beginning with ':'' or '*'')
|
||||||
for i, max := 0, len(path); numParams > 0; i++ {
|
for i, max := 0, len(path); numParams > 0; i++ {
|
||||||
|
@ -195,20 +208,29 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this Node existing children which would be
|
|
||||||
// unreachable if we insert the wildcard here
|
|
||||||
if len(n.children) > 0 {
|
|
||||||
panic("wildcard route conflicts with existing children")
|
|
||||||
}
|
|
||||||
|
|
||||||
// find wildcard end (either '/' or path end)
|
// find wildcard end (either '/' or path end)
|
||||||
end := i + 1
|
end := i + 1
|
||||||
for end < max && path[end] != '/' {
|
for end < max && path[end] != '/' {
|
||||||
end++
|
switch path[end] {
|
||||||
|
// the wildcard name must not contain ':' and '*'
|
||||||
|
case ':', '*':
|
||||||
|
panic("only one wildcard per path segment is allowed, has: '" +
|
||||||
|
path[i:] + "' in path '" + fullPath + "'")
|
||||||
|
default:
|
||||||
|
end++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if this Node existing children which would be
|
||||||
|
// unreachable if we insert the wildcard here
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
panic("wildcard route '" + path[i:end] +
|
||||||
|
"' conflicts with existing children in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the wildcard has a name
|
||||||
if end-i < 2 {
|
if end-i < 2 {
|
||||||
panic("wildcards must be named with a non-empty name")
|
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == ':' { // param
|
if c == ':' { // param
|
||||||
|
@ -244,17 +266,17 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
||||||
|
|
||||||
} else { // catchAll
|
} else { // catchAll
|
||||||
if end != max || numParams > 1 {
|
if end != max || numParams > 1 {
|
||||||
panic("catch-all routes are only allowed at the end of the path")
|
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
panic("catch-all conflicts with existing handle for the path segment root")
|
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently fixed width 1 for '/'
|
// currently fixed width 1 for '/'
|
||||||
i--
|
i--
|
||||||
if path[i] != '/' {
|
if path[i] != '/' {
|
||||||
panic("no / before catch-all")
|
panic("no / before catch-all in path '" + fullPath + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
n.path = path[offset:i]
|
n.path = path[offset:i]
|
||||||
|
@ -266,7 +288,7 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
||||||
maxParams: 1,
|
maxParams: 1,
|
||||||
}
|
}
|
||||||
n.children = []*node{child}
|
n.children = []*node{child}
|
||||||
n.indices = []byte{path[i]}
|
n.indices = string(path[i])
|
||||||
n = child
|
n = child
|
||||||
n.priority++
|
n.priority++
|
||||||
|
|
||||||
|
@ -305,8 +327,8 @@ walk: // Outer loop for walking the tree
|
||||||
// to walk down the tree
|
// to walk down the tree
|
||||||
if !n.wildChild {
|
if !n.wildChild {
|
||||||
c := path[0]
|
c := path[0]
|
||||||
for i, index := range n.indices {
|
for i := 0; i < len(n.indices); i++ {
|
||||||
if c == index {
|
if c == n.indices[i] {
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
continue walk
|
continue walk
|
||||||
}
|
}
|
||||||
|
@ -379,7 +401,7 @@ walk: // Outer loop for walking the tree
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic("Invalid node type")
|
panic("invalid node type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if path == n.path {
|
} else if path == n.path {
|
||||||
|
@ -391,10 +413,10 @@ walk: // Outer loop for walking the tree
|
||||||
|
|
||||||
// No handle found. Check if a handle for this path + a
|
// No handle found. Check if a handle for this path + a
|
||||||
// trailing slash exists for trailing slash recommendation
|
// trailing slash exists for trailing slash recommendation
|
||||||
for i, index := range n.indices {
|
for i := 0; i < len(n.indices); i++ {
|
||||||
if index == '/' {
|
if n.indices[i] == '/' {
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
tsr = (n.path == "/" && n.handle != nil) ||
|
tsr = (len(n.path) == 1 && n.handle != nil) ||
|
||||||
(n.nType == catchAll && n.children[0].handle != nil)
|
(n.nType == catchAll && n.children[0].handle != nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -414,7 +436,7 @@ walk: // Outer loop for walking the tree
|
||||||
|
|
||||||
// Makes a case-insensitive lookup of the given path and tries to find a handler.
|
// Makes a case-insensitive lookup of the given path and tries to find a handler.
|
||||||
// It can optionally also fix trailing slashes.
|
// It can optionally also fix trailing slashes.
|
||||||
// It returns the case-corrected path and a bool indicating wether the lookup
|
// It returns the case-corrected path and a bool indicating whether the lookup
|
||||||
// was successful.
|
// was successful.
|
||||||
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
|
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
|
||||||
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
|
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
|
||||||
|
@ -433,7 +455,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||||
for i, index := range n.indices {
|
for i, index := range n.indices {
|
||||||
// must use recursive approach since both index and
|
// must use recursive approach since both index and
|
||||||
// ToLower(index) could exist. We must check both.
|
// ToLower(index) could exist. We must check both.
|
||||||
if r == unicode.ToLower(rune(index)) {
|
if r == unicode.ToLower(index) {
|
||||||
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
||||||
if found {
|
if found {
|
||||||
return append(ciPath, out...), true
|
return append(ciPath, out...), true
|
||||||
|
@ -445,53 +467,52 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||||
// without a trailing slash if a leaf exists for that path
|
// without a trailing slash if a leaf exists for that path
|
||||||
found = (fixTrailingSlash && path == "/" && n.handle != nil)
|
found = (fixTrailingSlash && path == "/" && n.handle != nil)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
n = n.children[0]
|
||||||
n = n.children[0]
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
|
k := 0
|
||||||
|
for k < len(path) && path[k] != '/' {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
switch n.nType {
|
// add param value to case insensitive path
|
||||||
case param:
|
ciPath = append(ciPath, path[:k]...)
|
||||||
// find param end (either '/' or path end)
|
|
||||||
k := 0
|
|
||||||
for k < len(path) && path[k] != '/' {
|
|
||||||
k++
|
|
||||||
}
|
|
||||||
|
|
||||||
// add param value to case insensitive path
|
// we need to go deeper!
|
||||||
ciPath = append(ciPath, path[:k]...)
|
if k < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
// we need to go deeper!
|
path = path[k:]
|
||||||
if k < len(path) {
|
|
||||||
if len(n.children) > 0 {
|
|
||||||
path = path[k:]
|
|
||||||
n = n.children[0]
|
|
||||||
continue
|
|
||||||
} else { // ... but we can't
|
|
||||||
if fixTrailingSlash && len(path) == k+1 {
|
|
||||||
return ciPath, true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.handle != nil {
|
|
||||||
return ciPath, true
|
|
||||||
} else if fixTrailingSlash && len(n.children) == 1 {
|
|
||||||
// No handle found. Check if a handle for this path + a
|
|
||||||
// trailing slash exists
|
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
if n.path == "/" && n.handle != nil {
|
continue
|
||||||
return append(ciPath, '/'), true
|
}
|
||||||
}
|
|
||||||
|
// ... but we can't
|
||||||
|
if fixTrailingSlash && len(path) == k+1 {
|
||||||
|
return ciPath, true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
case catchAll:
|
|
||||||
return append(ciPath, path...), true
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("Invalid node type")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if n.handle != nil {
|
||||||
|
return ciPath, true
|
||||||
|
} else if fixTrailingSlash && len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists
|
||||||
|
n = n.children[0]
|
||||||
|
if n.path == "/" && n.handle != nil {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
return append(ciPath, path...), true
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We should have reached the node containing the handle.
|
// We should have reached the node containing the handle.
|
||||||
|
@ -503,10 +524,10 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa
|
||||||
// No handle found.
|
// No handle found.
|
||||||
// Try to fix the path by adding a trailing slash
|
// Try to fix the path by adding a trailing slash
|
||||||
if fixTrailingSlash {
|
if fixTrailingSlash {
|
||||||
for i, index := range n.indices {
|
for i := 0; i < len(n.indices); i++ {
|
||||||
if index == '/' {
|
if n.indices[i] == '/' {
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
if (n.path == "/" && n.handle != nil) ||
|
if (len(n.path) == 1 && n.handle != nil) ||
|
||||||
(n.nType == catchAll && n.children[0].handle != nil) {
|
(n.nType == catchAll && n.children[0].handle != nil) {
|
||||||
return append(ciPath, '/'), true
|
return append(ciPath, '/'), true
|
||||||
}
|
}
|
||||||
|
|
35
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go
generated
vendored
35
Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go
generated
vendored
|
@ -125,6 +125,8 @@ func TestTreeAddAndGet(t *testing.T) {
|
||||||
"/doc/",
|
"/doc/",
|
||||||
"/doc/go_faq.html",
|
"/doc/go_faq.html",
|
||||||
"/doc/go1.html",
|
"/doc/go1.html",
|
||||||
|
"/α",
|
||||||
|
"/β",
|
||||||
}
|
}
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
tree.addRoute(route, fakeHandler(route))
|
tree.addRoute(route, fakeHandler(route))
|
||||||
|
@ -142,6 +144,8 @@ func TestTreeAddAndGet(t *testing.T) {
|
||||||
{"/cona", true, "", nil}, // key mismatch
|
{"/cona", true, "", nil}, // key mismatch
|
||||||
{"/no", true, "", nil}, // no matching child
|
{"/no", true, "", nil}, // no matching child
|
||||||
{"/ab", false, "/ab", nil},
|
{"/ab", false, "/ab", nil},
|
||||||
|
{"/α", false, "/α", nil},
|
||||||
|
{"/β", false, "/β", nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
checkPriorities(t, tree)
|
checkPriorities(t, tree)
|
||||||
|
@ -339,6 +343,27 @@ func TestTreeCatchAllConflictRoot(t *testing.T) {
|
||||||
testRoutes(t, routes)
|
testRoutes(t, routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTreeDoubleWildcard(t *testing.T) {
|
||||||
|
const panicMsg = "only one wildcard per path segment is allowed"
|
||||||
|
|
||||||
|
routes := [...]string{
|
||||||
|
"/:foo:bar",
|
||||||
|
"/:foo:bar/",
|
||||||
|
"/:foo*bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
tree := &node{}
|
||||||
|
recv := catchPanic(func() {
|
||||||
|
tree.addRoute(route, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
if rs, ok := recv.(string); !ok || !strings.HasPrefix(rs, panicMsg) {
|
||||||
|
t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsg, route, recv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*func TestTreeDuplicateWildcard(t *testing.T) {
|
/*func TestTreeDuplicateWildcard(t *testing.T) {
|
||||||
tree := &node{}
|
tree := &node{}
|
||||||
|
|
||||||
|
@ -559,6 +584,8 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTreeInvalidNodeType(t *testing.T) {
|
func TestTreeInvalidNodeType(t *testing.T) {
|
||||||
|
const panicMsg = "invalid node type"
|
||||||
|
|
||||||
tree := &node{}
|
tree := &node{}
|
||||||
tree.addRoute("/", fakeHandler("/"))
|
tree.addRoute("/", fakeHandler("/"))
|
||||||
tree.addRoute("/:page", fakeHandler("/:page"))
|
tree.addRoute("/:page", fakeHandler("/:page"))
|
||||||
|
@ -570,15 +597,15 @@ func TestTreeInvalidNodeType(t *testing.T) {
|
||||||
recv := catchPanic(func() {
|
recv := catchPanic(func() {
|
||||||
tree.getValue("/test")
|
tree.getValue("/test")
|
||||||
})
|
})
|
||||||
if rs, ok := recv.(string); !ok || rs != "Invalid node type" {
|
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||||
t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv)
|
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// case-insensitive lookup
|
// case-insensitive lookup
|
||||||
recv = catchPanic(func() {
|
recv = catchPanic(func() {
|
||||||
tree.findCaseInsensitivePath("/test", true)
|
tree.findCaseInsensitivePath("/test", true)
|
||||||
})
|
})
|
||||||
if rs, ok := recv.(string); !ok || rs != "Invalid node type" {
|
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||||
t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv)
|
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
Godeps/_workspace/src/github.com/stretchr/graceful/LICENSE
generated
vendored
4
Godeps/_workspace/src/github.com/stretchr/graceful/LICENSE
generated
vendored
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Stretchr, Inc.
|
Copyright (c) 2014 Tyler Bunnell
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
33
Godeps/_workspace/src/github.com/stretchr/graceful/README.md
generated
vendored
33
Godeps/_workspace/src/github.com/stretchr/graceful/README.md
generated
vendored
|
@ -1,17 +1,30 @@
|
||||||
graceful [](http://godoc.org/github.com/stretchr/graceful) [](https://app.wercker.com/project/bykey/2729ba763abf87695a17547e0f7af4a4)
|
graceful [](http://godoc.org/github.com/tylerb/graceful) [](https://drone.io/github.com/tylerb/graceful/latest) [](https://coveralls.io/r/tylerb/graceful?branch=dronedebug) [](https://gitter.im/tylerb/graceful?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
========
|
========
|
||||||
|
|
||||||
Graceful is a Go 1.3+ package enabling graceful shutdown of http.Handler servers.
|
Graceful is a Go 1.3+ package enabling graceful shutdown of http.Handler servers.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install, simply execute:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get gopkg.in/tylerb/graceful.v1
|
||||||
|
```
|
||||||
|
|
||||||
|
I am using [gopkg.in](http://http://labix.org/gopkg.in) to control releases.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Usage of Graceful is simple. Create your http.Handler and pass it to the `Run` function:
|
Using Graceful is easy. Simply create your http.Handler and pass it to the `Run` function:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/graceful"
|
"gopkg.in/tylerb/graceful.v1"
|
||||||
"net/http"
|
"net/http"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -31,9 +44,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
"github.com/stretchr/graceful"
|
"gopkg.in/tylerb/graceful.v1"
|
||||||
"net/http"
|
"net/http"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -111,4 +125,13 @@ same time and all will be signalled when stopping is complete.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Before sending a pull request, please open a new issue describing the feature/issue you wish to address so it can be discussed. The subsequent pull request should close that issue.
|
If you would like to contribute, please:
|
||||||
|
|
||||||
|
1. Create a GitHub issue regarding the contribution. Features and bugs should be discussed beforehand.
|
||||||
|
2. Fork the repository.
|
||||||
|
3. Create a pull request with your solution. This pull request should reference and close the issues (Fix #2).
|
||||||
|
|
||||||
|
All pull requests should:
|
||||||
|
|
||||||
|
1. Pass [gometalinter -t .](https://github.com/alecthomas/gometalinter) with no warnings.
|
||||||
|
2. Be `go fmt` formatted.
|
||||||
|
|
131
Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go
generated
vendored
131
Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go
generated
vendored
|
@ -11,7 +11,6 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/pat/stop"
|
|
||||||
"golang.org/x/net/netutil"
|
"golang.org/x/net/netutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,30 +40,31 @@ type Server struct {
|
||||||
// must not be set directly.
|
// must not be set directly.
|
||||||
ConnState func(net.Conn, http.ConnState)
|
ConnState func(net.Conn, http.ConnState)
|
||||||
|
|
||||||
// ShutdownInitiated is an optional callback function that is called
|
// ShutdownInitiated is an optional callback function that is called
|
||||||
// when shutdown is initiated. It can be used to notify the client
|
// when shutdown is initiated. It can be used to notify the client
|
||||||
// side of long lived connections (e.g. websockets) to reconnect.
|
// side of long lived connections (e.g. websockets) to reconnect.
|
||||||
ShutdownInitiated func()
|
ShutdownInitiated func()
|
||||||
|
|
||||||
|
// NoSignalHandling prevents graceful from automatically shutting down
|
||||||
|
// on SIGINT and SIGTERM. If set to true, you must shut down the server
|
||||||
|
// manually with Stop().
|
||||||
|
NoSignalHandling bool
|
||||||
|
|
||||||
// interrupt signals the listener to stop serving connections,
|
// interrupt signals the listener to stop serving connections,
|
||||||
// and the server to shut down.
|
// and the server to shut down.
|
||||||
interrupt chan os.Signal
|
interrupt chan os.Signal
|
||||||
|
|
||||||
// stopChan is the channel on which callers may block while waiting for
|
// stopChan is the channel on which callers may block while waiting for
|
||||||
// the server to stop.
|
// the server to stop.
|
||||||
stopChan chan stop.Signal
|
stopChan chan struct{}
|
||||||
|
|
||||||
// stopChanOnce is used to create the stop channel on demand, once, per
|
// stopLock is used to protect access to the stopChan.
|
||||||
// instance.
|
stopLock sync.RWMutex
|
||||||
stopChanOnce sync.Once
|
|
||||||
|
|
||||||
// connections holds all connections managed by graceful
|
// connections holds all connections managed by graceful
|
||||||
connections map[net.Conn]struct{}
|
connections map[net.Conn]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure Server conforms to stop.Stopper
|
|
||||||
var _ stop.Stopper = (*Server)(nil)
|
|
||||||
|
|
||||||
// Run serves the http.Handler with graceful shutdown enabled.
|
// Run serves the http.Handler with graceful shutdown enabled.
|
||||||
//
|
//
|
||||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
// timeout is the duration to wait until killing active requests and stopping the server.
|
||||||
|
@ -173,7 +173,6 @@ func (srv *Server) Serve(listener net.Listener) error {
|
||||||
case http.StateClosed, http.StateHijacked:
|
case http.StateClosed, http.StateHijacked:
|
||||||
remove <- conn
|
remove <- conn
|
||||||
}
|
}
|
||||||
|
|
||||||
if srv.ConnState != nil {
|
if srv.ConnState != nil {
|
||||||
srv.ConnState(conn, state)
|
srv.ConnState(conn, state)
|
||||||
}
|
}
|
||||||
|
@ -182,7 +181,53 @@ func (srv *Server) Serve(listener net.Listener) error {
|
||||||
// Manage open connections
|
// Manage open connections
|
||||||
shutdown := make(chan chan struct{})
|
shutdown := make(chan chan struct{})
|
||||||
kill := make(chan struct{})
|
kill := make(chan struct{})
|
||||||
go func() {
|
go srv.manageConnections(add, remove, shutdown, kill)
|
||||||
|
|
||||||
|
interrupt := srv.interruptChan()
|
||||||
|
|
||||||
|
// Set up the interrupt handler
|
||||||
|
if !srv.NoSignalHandling {
|
||||||
|
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.handleInterrupt(interrupt, listener)
|
||||||
|
|
||||||
|
// Serve with graceful listener.
|
||||||
|
// Execution blocks here until listener.Close() is called, above.
|
||||||
|
err := srv.Server.Serve(listener)
|
||||||
|
|
||||||
|
srv.shutdown(shutdown, kill)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop instructs the type to halt operations and close
|
||||||
|
// the stop channel when it is finished.
|
||||||
|
//
|
||||||
|
// timeout is grace period for which to wait before shutting
|
||||||
|
// down the server. The timeout value passed here will override the
|
||||||
|
// timeout given when constructing the server, as this is an explicit
|
||||||
|
// command to stop the server.
|
||||||
|
func (srv *Server) Stop(timeout time.Duration) {
|
||||||
|
srv.Timeout = timeout
|
||||||
|
interrupt := srv.interruptChan()
|
||||||
|
interrupt <- syscall.SIGINT
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopChan gets the stop channel which will block until
|
||||||
|
// stopping has completed, at which point it is closed.
|
||||||
|
// Callers should never close the stop channel.
|
||||||
|
func (srv *Server) StopChan() <-chan struct{} {
|
||||||
|
srv.stopLock.Lock()
|
||||||
|
if srv.stopChan == nil {
|
||||||
|
srv.stopChan = make(chan struct{})
|
||||||
|
}
|
||||||
|
srv.stopLock.Unlock()
|
||||||
|
return srv.stopChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) manageConnections(add, remove chan net.Conn, shutdown chan chan struct{}, kill chan struct{}) {
|
||||||
|
{
|
||||||
var done chan struct{}
|
var done chan struct{}
|
||||||
srv.connections = map[net.Conn]struct{}{}
|
srv.connections = map[net.Conn]struct{}{}
|
||||||
for {
|
for {
|
||||||
|
@ -202,36 +247,39 @@ func (srv *Server) Serve(listener net.Listener) error {
|
||||||
}
|
}
|
||||||
case <-kill:
|
case <-kill:
|
||||||
for k := range srv.connections {
|
for k := range srv.connections {
|
||||||
k.Close()
|
_ = k.Close() // nothing to do here if it errors
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) interruptChan() chan os.Signal {
|
||||||
|
srv.stopLock.Lock()
|
||||||
if srv.interrupt == nil {
|
if srv.interrupt == nil {
|
||||||
srv.interrupt = make(chan os.Signal, 1)
|
srv.interrupt = make(chan os.Signal, 1)
|
||||||
}
|
}
|
||||||
|
srv.stopLock.Unlock()
|
||||||
|
|
||||||
// Set up the interrupt catch
|
return srv.interrupt
|
||||||
signal.Notify(srv.interrupt, syscall.SIGINT, syscall.SIGTERM)
|
}
|
||||||
go func() {
|
|
||||||
<-srv.interrupt
|
|
||||||
srv.SetKeepAlivesEnabled(false)
|
|
||||||
listener.Close()
|
|
||||||
|
|
||||||
if srv.ShutdownInitiated != nil {
|
func (srv *Server) handleInterrupt(interrupt chan os.Signal, listener net.Listener) {
|
||||||
srv.ShutdownInitiated()
|
<-interrupt
|
||||||
}
|
|
||||||
|
|
||||||
signal.Stop(srv.interrupt)
|
srv.SetKeepAlivesEnabled(false)
|
||||||
close(srv.interrupt)
|
_ = listener.Close() // we are shutting down anyway. ignore error.
|
||||||
}()
|
|
||||||
|
|
||||||
// Serve with graceful listener.
|
if srv.ShutdownInitiated != nil {
|
||||||
// Execution blocks here until listener.Close() is called, above.
|
srv.ShutdownInitiated()
|
||||||
err := srv.Server.Serve(listener)
|
}
|
||||||
|
|
||||||
|
signal.Stop(interrupt)
|
||||||
|
close(interrupt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) shutdown(shutdown chan chan struct{}, kill chan struct{}) {
|
||||||
// Request done notification
|
// Request done notification
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
shutdown <- done
|
shutdown <- done
|
||||||
|
@ -246,32 +294,9 @@ func (srv *Server) Serve(listener net.Listener) error {
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
// Close the stopChan to wake up any blocked goroutines.
|
// Close the stopChan to wake up any blocked goroutines.
|
||||||
|
srv.stopLock.Lock()
|
||||||
if srv.stopChan != nil {
|
if srv.stopChan != nil {
|
||||||
close(srv.stopChan)
|
close(srv.stopChan)
|
||||||
}
|
}
|
||||||
return err
|
srv.stopLock.Unlock()
|
||||||
}
|
|
||||||
|
|
||||||
// Stop instructs the type to halt operations and close
|
|
||||||
// the stop channel when it is finished.
|
|
||||||
//
|
|
||||||
// timeout is grace period for which to wait before shutting
|
|
||||||
// down the server. The timeout value passed here will override the
|
|
||||||
// timeout given when constructing the server, as this is an explicit
|
|
||||||
// command to stop the server.
|
|
||||||
func (srv *Server) Stop(timeout time.Duration) {
|
|
||||||
srv.Timeout = timeout
|
|
||||||
srv.interrupt <- syscall.SIGINT
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopChan gets the stop channel which will block until
|
|
||||||
// stopping has completed, at which point it is closed.
|
|
||||||
// Callers should never close the stop channel.
|
|
||||||
func (srv *Server) StopChan() <-chan stop.Signal {
|
|
||||||
srv.stopChanOnce.Do(func() {
|
|
||||||
if srv.stopChan == nil {
|
|
||||||
srv.stopChan = stop.Make()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return srv.stopChan
|
|
||||||
}
|
}
|
||||||
|
|
131
Godeps/_workspace/src/github.com/stretchr/graceful/graceful_test.go
generated
vendored
131
Godeps/_workspace/src/github.com/stretchr/graceful/graceful_test.go
generated
vendored
|
@ -1,6 +1,7 @@
|
||||||
package graceful
|
package graceful
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,34 +14,52 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var killTime = 50 * time.Millisecond
|
var (
|
||||||
|
killTime = 500 * time.Millisecond
|
||||||
|
timeoutTime = 1000 * time.Millisecond
|
||||||
|
waitTime = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
func runQuery(t *testing.T, expected int, shouldErr bool, wg *sync.WaitGroup) {
|
func runQuery(t *testing.T, expected int, shouldErr bool, wg *sync.WaitGroup, once *sync.Once) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
r, err := client.Get("http://localhost:3000")
|
r, err := client.Get("http://localhost:3000")
|
||||||
if shouldErr && err == nil {
|
if shouldErr && err == nil {
|
||||||
t.Fatal("Expected an error but none was encountered.")
|
once.Do(func() {
|
||||||
|
t.Fatal("Expected an error but none was encountered.")
|
||||||
|
})
|
||||||
} else if shouldErr && err != nil {
|
} else if shouldErr && err != nil {
|
||||||
if err.(*url.Error).Err == io.EOF {
|
if checkErr(t, err, once) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
errno := err.(*url.Error).Err.(*net.OpError).Err.(syscall.Errno)
|
|
||||||
if errno == syscall.ECONNREFUSED {
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
t.Fatal("Error on Get:", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil && r.StatusCode != expected {
|
if r != nil && r.StatusCode != expected {
|
||||||
t.Fatalf("Incorrect status code on response. Expected %d. Got %d", expected, r.StatusCode)
|
once.Do(func() {
|
||||||
|
t.Fatalf("Incorrect status code on response. Expected %d. Got %d", expected, r.StatusCode)
|
||||||
|
})
|
||||||
} else if r == nil {
|
} else if r == nil {
|
||||||
t.Fatal("No response when a response was expected.")
|
once.Do(func() {
|
||||||
|
t.Fatal("No response when a response was expected.")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkErr(t *testing.T, err error, once *sync.Once) bool {
|
||||||
|
if err.(*url.Error).Err == io.EOF {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
errno := err.(*url.Error).Err.(*net.OpError).Err.(syscall.Errno)
|
||||||
|
if errno == syscall.ECONNREFUSED {
|
||||||
|
return true
|
||||||
|
} else if err != nil {
|
||||||
|
once.Do(func() {
|
||||||
|
t.Fatal("Error on Get:", err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func createListener(sleep time.Duration) (*http.Server, net.Listener, error) {
|
func createListener(sleep time.Duration) (*http.Server, net.Listener, error) {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -50,6 +69,9 @@ func createListener(sleep time.Duration) (*http.Server, net.Listener, error) {
|
||||||
|
|
||||||
server := &http.Server{Addr: ":3000", Handler: mux}
|
server := &http.Server{Addr: ":3000", Handler: mux}
|
||||||
l, err := net.Listen("tcp", ":3000")
|
l, err := net.Listen("tcp", ":3000")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
return server, l, err
|
return server, l, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,16 +86,17 @@ func runServer(timeout, sleep time.Duration, c chan os.Signal) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func launchTestQueries(t *testing.T, wg *sync.WaitGroup, c chan os.Signal) {
|
func launchTestQueries(t *testing.T, wg *sync.WaitGroup, c chan os.Signal) {
|
||||||
|
var once sync.Once
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
go runQuery(t, http.StatusOK, false, wg)
|
go runQuery(t, http.StatusOK, false, wg, &once)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(waitTime)
|
||||||
c <- os.Interrupt
|
c <- os.Interrupt
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(waitTime)
|
||||||
|
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
go runQuery(t, 0, true, wg)
|
go runQuery(t, 0, true, wg, &once)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
|
@ -106,16 +129,17 @@ func TestGracefulRunTimesOut(t *testing.T) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
go runQuery(t, 0, true, &wg)
|
go runQuery(t, 0, true, &wg, &once)
|
||||||
}
|
}
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(waitTime)
|
||||||
c <- os.Interrupt
|
c <- os.Interrupt
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(waitTime)
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
go runQuery(t, 0, true, &wg)
|
go runQuery(t, 0, true, &wg, &once)
|
||||||
}
|
}
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
@ -160,14 +184,23 @@ func TestGracefulRunNoRequests(t *testing.T) {
|
||||||
func TestGracefulForwardsConnState(t *testing.T) {
|
func TestGracefulForwardsConnState(t *testing.T) {
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
states := make(map[http.ConnState]int)
|
states := make(map[http.ConnState]int)
|
||||||
|
var stateLock sync.Mutex
|
||||||
|
|
||||||
connState := func(conn net.Conn, state http.ConnState) {
|
connState := func(conn net.Conn, state http.ConnState) {
|
||||||
|
stateLock.Lock()
|
||||||
states[state]++
|
states[state]++
|
||||||
|
stateLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
|
expected := map[http.ConnState]int{
|
||||||
|
http.StateNew: 8,
|
||||||
|
http.StateActive: 8,
|
||||||
|
http.StateClosed: 8,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
server, l, _ := createListener(killTime / 2)
|
server, l, _ := createListener(killTime / 2)
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
|
@ -185,15 +218,11 @@ func TestGracefulForwardsConnState(t *testing.T) {
|
||||||
go launchTestQueries(t, &wg, c)
|
go launchTestQueries(t, &wg, c)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
expected := map[http.ConnState]int{
|
stateLock.Lock()
|
||||||
http.StateNew: 8,
|
|
||||||
http.StateActive: 8,
|
|
||||||
http.StateClosed: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(states, expected) {
|
if !reflect.DeepEqual(states, expected) {
|
||||||
t.Errorf("Incorrect connection state tracking.\n actual: %v\nexpected: %v\n", states, expected)
|
t.Errorf("Incorrect connection state tracking.\n actual: %v\nexpected: %v\n", states, expected)
|
||||||
}
|
}
|
||||||
|
stateLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGracefulExplicitStop(t *testing.T) {
|
func TestGracefulExplicitStop(t *testing.T) {
|
||||||
|
@ -206,14 +235,14 @@ func TestGracefulExplicitStop(t *testing.T) {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
go srv.Serve(l)
|
go srv.Serve(l)
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(waitTime)
|
||||||
srv.Stop(killTime)
|
srv.Stop(killTime)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// block on the stopChan until the server has shut down
|
// block on the stopChan until the server has shut down
|
||||||
select {
|
select {
|
||||||
case <-srv.StopChan():
|
case <-srv.StopChan():
|
||||||
case <-time.After(100 * time.Millisecond):
|
case <-time.After(timeoutTime):
|
||||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,7 +257,7 @@ func TestGracefulExplicitStopOverride(t *testing.T) {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
go srv.Serve(l)
|
go srv.Serve(l)
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(waitTime)
|
||||||
srv.Stop(killTime / 2)
|
srv.Stop(killTime / 2)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -253,7 +282,7 @@ func TestShutdownInitiatedCallback(t *testing.T) {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
go srv.Serve(l)
|
go srv.Serve(l)
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(waitTime)
|
||||||
srv.Stop(killTime)
|
srv.Stop(killTime)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -302,12 +331,9 @@ func TestNotifyClosed(t *testing.T) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
runQuery(t, http.StatusOK, false, &wg)
|
runQuery(t, http.StatusOK, false, &wg, &once)
|
||||||
}
|
|
||||||
|
|
||||||
if len(srv.connections) > 0 {
|
|
||||||
t.Fatal("hijacked connections should not be managed")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.Stop(0)
|
srv.Stop(0)
|
||||||
|
@ -315,8 +341,39 @@ func TestNotifyClosed(t *testing.T) {
|
||||||
// block on the stopChan until the server has shut down
|
// block on the stopChan until the server has shut down
|
||||||
select {
|
select {
|
||||||
case <-srv.StopChan():
|
case <-srv.StopChan():
|
||||||
case <-time.After(100 * time.Millisecond):
|
case <-time.After(timeoutTime):
|
||||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(srv.connections) > 0 {
|
||||||
|
t.Fatal("hijacked connections should not be managed")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopDeadlock(t *testing.T) {
|
||||||
|
c := make(chan struct{})
|
||||||
|
|
||||||
|
server, l, err := createListener(1 * time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := &Server{Server: server, NoSignalHandling: true}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(waitTime)
|
||||||
|
srv.Serve(l)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
srv.Stop(0)
|
||||||
|
close(c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-c:
|
||||||
|
case <-time.After(timeoutTime):
|
||||||
|
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
2
Godeps/_workspace/src/github.com/stretchr/graceful/tests/main.go
generated
vendored
2
Godeps/_workspace/src/github.com/stretchr/graceful/tests/main.go
generated
vendored
|
@ -5,7 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
"github.com/stretchr/graceful"
|
"github.com/tylerb/graceful"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
1
Godeps/_workspace/src/github.com/stretchr/graceful/wercker.yml
generated
vendored
1
Godeps/_workspace/src/github.com/stretchr/graceful/wercker.yml
generated
vendored
|
@ -1 +0,0 @@
|
||||||
box: wercker/golang
|
|
46
Godeps/_workspace/src/github.com/stretchr/pat/stop/doc.go
generated
vendored
46
Godeps/_workspace/src/github.com/stretchr/pat/stop/doc.go
generated
vendored
|
@ -1,46 +0,0 @@
|
||||||
// Package stop represents a pattern for types that need to do some work
|
|
||||||
// when stopping. The StopChan method returns a <-chan stop.Signal which
|
|
||||||
// is closed when the operation has completed.
|
|
||||||
//
|
|
||||||
// Stopper types when implementing the stop channel pattern should use stop.Make
|
|
||||||
// to create and store a stop channel, and close the channel once stopping has completed:
|
|
||||||
// func New() Type {
|
|
||||||
// t := new(Type)
|
|
||||||
// t.stopChan = stop.Make()
|
|
||||||
// return t
|
|
||||||
// }
|
|
||||||
// func (t Type) Stop() {
|
|
||||||
// go func(){
|
|
||||||
// // TODO: tear stuff down
|
|
||||||
// close(t.stopChan)
|
|
||||||
// }()
|
|
||||||
// }
|
|
||||||
// func (t Type) StopChan() <-chan stop.Signal {
|
|
||||||
// return t.stopChan
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Stopper types can be stopped in the following ways:
|
|
||||||
// // stop and forget
|
|
||||||
// t.Stop(1 * time.Second)
|
|
||||||
//
|
|
||||||
// // stop and wait
|
|
||||||
// t.Stop(1 * time.Second)
|
|
||||||
// <-t.StopChan()
|
|
||||||
//
|
|
||||||
// // stop, do more work, then wait
|
|
||||||
// t.Stop(1 * time.Second);
|
|
||||||
// // do more work
|
|
||||||
// <-t.StopChan()
|
|
||||||
//
|
|
||||||
// // stop and timeout after 1 second
|
|
||||||
// t.Stop(1 * time.Second)
|
|
||||||
// select {
|
|
||||||
// case <-t.StopChan():
|
|
||||||
// case <-time.After(1 * time.Second):
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // stop.All is the same as calling Stop() then StopChan() so
|
|
||||||
// // all above patterns also work on many Stopper types,
|
|
||||||
// // for example; stop and wait for many things:
|
|
||||||
// <-stop.All(1 * time.Second, t1, t2, t3)
|
|
||||||
package stop
|
|
57
Godeps/_workspace/src/github.com/stretchr/pat/stop/stop.go
generated
vendored
57
Godeps/_workspace/src/github.com/stretchr/pat/stop/stop.go
generated
vendored
|
@ -1,57 +0,0 @@
|
||||||
package stop
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Signal is the type that gets sent down the stop channel.
|
|
||||||
type Signal struct{}
|
|
||||||
|
|
||||||
// NoWait represents a time.Duration with zero value.
|
|
||||||
// Logically meaning no grace wait period when stopping.
|
|
||||||
var NoWait time.Duration
|
|
||||||
|
|
||||||
// Stopper represents types that implement
|
|
||||||
// the stop channel pattern.
|
|
||||||
type Stopper interface {
|
|
||||||
// Stop instructs the type to halt operations and close
|
|
||||||
// the stop channel when it is finished.
|
|
||||||
Stop(wait time.Duration)
|
|
||||||
// StopChan gets the stop channel which will block until
|
|
||||||
// stopping has completed, at which point it is closed.
|
|
||||||
// Callers should never close the stop channel.
|
|
||||||
// The StopChan should exist from the point at which operations
|
|
||||||
// begun, not the point at which Stop was called.
|
|
||||||
StopChan() <-chan Signal
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stopped returns a channel that signals immediately. Useful for
|
|
||||||
// cases when no tear-down work is required and stopping is
|
|
||||||
// immediate.
|
|
||||||
func Stopped() <-chan Signal {
|
|
||||||
c := Make()
|
|
||||||
close(c)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make makes a new channel used to indicate when
|
|
||||||
// stopping has finished. Sends to channel will not block.
|
|
||||||
func Make() chan Signal {
|
|
||||||
return make(chan Signal, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// All stops all Stopper types and returns another channel
|
|
||||||
// which will close once all things have finished stopping.
|
|
||||||
func All(wait time.Duration, stoppers ...Stopper) <-chan Signal {
|
|
||||||
all := Make()
|
|
||||||
go func() {
|
|
||||||
var allChans []<-chan Signal
|
|
||||||
for _, stopper := range stoppers {
|
|
||||||
go stopper.Stop(wait)
|
|
||||||
allChans = append(allChans, stopper.StopChan())
|
|
||||||
}
|
|
||||||
for _, ch := range allChans {
|
|
||||||
<-ch
|
|
||||||
}
|
|
||||||
close(all)
|
|
||||||
}()
|
|
||||||
return all
|
|
||||||
}
|
|
76
Godeps/_workspace/src/github.com/stretchr/pat/stop/stop_test.go
generated
vendored
76
Godeps/_workspace/src/github.com/stretchr/pat/stop/stop_test.go
generated
vendored
|
@ -1,76 +0,0 @@
|
||||||
package stop_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/pat/stop"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testStopper struct {
|
|
||||||
stopChan chan stop.Signal
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestStopper() *testStopper {
|
|
||||||
s := new(testStopper)
|
|
||||||
s.stopChan = stop.Make()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testStopper) Stop(wait time.Duration) {
|
|
||||||
go func() {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
close(t.stopChan)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
func (t *testStopper) StopChan() <-chan stop.Signal {
|
|
||||||
return t.stopChan
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopStopper struct{}
|
|
||||||
|
|
||||||
func (t *noopStopper) Stop() {
|
|
||||||
}
|
|
||||||
func (t *noopStopper) StopChan() <-chan stop.Signal {
|
|
||||||
return stop.Stopped()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStop(t *testing.T) {
|
|
||||||
|
|
||||||
s := NewTestStopper()
|
|
||||||
s.Stop(1 * time.Second)
|
|
||||||
stopChan := s.StopChan()
|
|
||||||
select {
|
|
||||||
case <-stopChan:
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
t.Error("Stop signal was never sent (timed out)")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAll(t *testing.T) {
|
|
||||||
|
|
||||||
s1 := NewTestStopper()
|
|
||||||
s2 := NewTestStopper()
|
|
||||||
s3 := NewTestStopper()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-stop.All(1*time.Second, s1, s2, s3):
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
t.Error("All signal was never sent (timed out)")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoop(t *testing.T) {
|
|
||||||
|
|
||||||
s := new(noopStopper)
|
|
||||||
s.Stop()
|
|
||||||
stopChan := s.StopChan()
|
|
||||||
select {
|
|
||||||
case <-stopChan:
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
t.Error("Stop signal was never sent (timed out)")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue