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",
|
||||
"GoVersion": "go1.4.1",
|
||||
"GoVersion": "go1.4.2",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/chihaya/bencode",
|
||||
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
||||
"Rev": "00ce1c6a267162792c367acc43b1681a884e1872"
|
||||
"Rev": "8c199fb6259ffc1af525cc3ad52ee60ba8359669"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pushrax/bufferpool",
|
||||
|
@ -28,15 +28,12 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/graceful",
|
||||
"Rev": "8e780ba3fe3d3e7ab15fc52e3d60a996587181dc"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/pat/stop",
|
||||
"Rev": "f7fe051f2b9bcaca162b38de4f93c9a8457160b9"
|
||||
"Comment": "v1-7-g0c01122",
|
||||
"Rev": "0c011221e91b35f488b8818b00ca279929e9ed7d"
|
||||
},
|
||||
{
|
||||
"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
|
||||
go:
|
||||
- 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 [![Build Status](https://travis-ci.org/julienschmidt/httprouter.png?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![GoDoc](http://godoc.org/github.com/julienschmidt/httprouter?status.png)](http://godoc.org/github.com/julienschmidt/httprouter)
|
||||
# HttpRouter [![Build Status](https://travis-ci.org/julienschmidt/httprouter.png?branch=master)](https://travis-ci.org/julienschmidt/httprouter) [![Coverage](http://gocover.io/_badge/github.com/julienschmidt/httprouter?0)](http://gocover.io/github.com/julienschmidt/httprouter) [![GoDoc](http://godoc.org/github.com/julienschmidt/httprouter?status.png)](http://godoc.org/github.com/julienschmidt/httprouter)
|
||||
|
||||
HttpRouter is a lightweight high performance HTTP request router
|
||||
(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.
|
||||
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.
|
||||
A compressing dynamic trie (radix tree) structure is used for efficient matching.
|
||||
|
||||
## 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),
|
||||
a requested URL path could match multiple patterns. Therefore they have some
|
||||
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
|
||||
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.
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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/),
|
||||
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
|
||||
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):])
|
||||
if err == nil {
|
||||
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
|
||||
h(w, r, ps)
|
||||
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`.
|
||||
|
||||
## 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:
|
||||
* [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
|
||||
* [Goat](https://github.com/bahlo/goat): A minimalistic REST API server in Go
|
||||
* [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
|
||||
* [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
|
||||
* [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.
|
||||
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.
|
||||
// It should be used to generate a error page and return the http error code
|
||||
// 500 (Internal Server Error).
|
||||
|
@ -173,6 +178,11 @@ func (r *Router) HEAD(path string, handle 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)
|
||||
func (r *Router) POST(path string, handle Handle) {
|
||||
r.Handle("POST", path, handle)
|
||||
|
@ -203,7 +213,7 @@ func (r *Router) DELETE(path string, handle Handle) {
|
|||
// communication with a proxy).
|
||||
func (r *Router) Handle(method, path string, handle Handle) {
|
||||
if path[0] != '/' {
|
||||
panic("path must begin with '/'")
|
||||
panic("path must begin with '/' in path '" + path + "'")
|
||||
}
|
||||
|
||||
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
|
||||
// request handle.
|
||||
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
|
||||
r.Handle(method, path,
|
||||
func(w http.ResponseWriter, req *http.Request, _ Params) {
|
||||
handler(w, req)
|
||||
},
|
||||
)
|
||||
r.Handler(method, path, handler)
|
||||
}
|
||||
|
||||
// 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"))
|
||||
func (r *Router) ServeFiles(path string, root http.FileSystem) {
|
||||
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)
|
||||
|
@ -335,10 +341,14 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
handle, _, _ := r.trees[method].getValue(req.URL.Path)
|
||||
if handle != nil {
|
||||
http.Error(w,
|
||||
http.StatusText(http.StatusMethodNotAllowed),
|
||||
http.StatusMethodNotAllowed,
|
||||
)
|
||||
if r.MethodNotAllowed != nil {
|
||||
r.MethodNotAllowed(w, req)
|
||||
} else {
|
||||
http.Error(w,
|
||||
http.StatusText(http.StatusMethodNotAllowed),
|
||||
http.StatusMethodNotAllowed,
|
||||
)
|
||||
}
|
||||
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) {
|
||||
var get, head, post, put, patch, delete, handler, handlerFunc bool
|
||||
var get, head, options, post, put, patch, delete, handler, handlerFunc bool
|
||||
|
||||
httpHandler := handlerStruct{&handler}
|
||||
|
||||
|
@ -87,6 +87,9 @@ func TestRouterAPI(t *testing.T) {
|
|||
router.HEAD("/GET", func(w http.ResponseWriter, r *http.Request, _ Params) {
|
||||
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) {
|
||||
post = true
|
||||
})
|
||||
|
@ -118,6 +121,12 @@ func TestRouterAPI(t *testing.T) {
|
|||
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)
|
||||
router.ServeHTTP(w, r)
|
||||
if !post {
|
||||
|
@ -176,7 +185,21 @@ func TestRouterNotAllowed(t *testing.T) {
|
|||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, r)
|
||||
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
|
||||
nType nodeType
|
||||
maxParams uint8
|
||||
indices []byte
|
||||
indices string
|
||||
children []*node
|
||||
handle Handle
|
||||
priority uint32
|
||||
}
|
||||
|
||||
// increments priority of the given child and reorders if necessary
|
||||
func (n *node) incrementChildPrio(i int) int {
|
||||
n.children[i].priority++
|
||||
prio := n.children[i].priority
|
||||
func (n *node) incrementChildPrio(pos int) int {
|
||||
n.children[pos].priority++
|
||||
prio := n.children[pos].priority
|
||||
|
||||
// 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
|
||||
tmpN := n.children[j]
|
||||
n.children[j] = n.children[i]
|
||||
n.children[i] = tmpN
|
||||
tmpI := n.indices[j]
|
||||
n.indices[j] = n.indices[i]
|
||||
n.indices[i] = tmpI
|
||||
tmpN := n.children[newPos-1]
|
||||
n.children[newPos-1] = n.children[newPos]
|
||||
n.children[newPos] = tmpN
|
||||
|
||||
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.
|
||||
// Not concurrency-safe!
|
||||
func (n *node) addRoute(path string, handle Handle) {
|
||||
fullPath := path
|
||||
n.priority++
|
||||
numParams := countParams(path)
|
||||
|
||||
// non-empty tree
|
||||
if len(n.path) > 0 || len(n.children) > 0 {
|
||||
WALK:
|
||||
walk:
|
||||
for {
|
||||
// Update maxParams of the current node
|
||||
if numParams > n.maxParams {
|
||||
|
@ -85,10 +92,12 @@ func (n *node) addRoute(path string, handle Handle) {
|
|||
}
|
||||
|
||||
// Find the longest common prefix.
|
||||
// This also implies that the commom prefix contains no ':' or '*'
|
||||
// since the existing key can't contain this chars.
|
||||
// This also implies that the common prefix contains no ':' or '*'
|
||||
// since the existing key can't contain those chars.
|
||||
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
|
||||
|
@ -110,7 +119,8 @@ func (n *node) addRoute(path string, handle Handle) {
|
|||
}
|
||||
|
||||
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.handle = nil
|
||||
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)] {
|
||||
// check for longer wildcard, e.g. :name and :names
|
||||
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]
|
||||
|
@ -147,21 +159,22 @@ func (n *node) addRoute(path string, handle Handle) {
|
|||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||
n = n.children[0]
|
||||
n.priority++
|
||||
continue WALK
|
||||
continue walk
|
||||
}
|
||||
|
||||
// Check if a child with the next path byte exists
|
||||
for i, index := range n.indices {
|
||||
if c == index {
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
i = n.incrementChildPrio(i)
|
||||
n = n.children[i]
|
||||
continue WALK
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise insert it
|
||||
if c != ':' && c != '*' {
|
||||
n.indices = append(n.indices, c)
|
||||
// []byte for proper unicode char conversion, see #65
|
||||
n.indices += string([]byte{c})
|
||||
child := &node{
|
||||
maxParams: numParams,
|
||||
}
|
||||
|
@ -169,24 +182,24 @@ func (n *node) addRoute(path string, handle Handle) {
|
|||
n.incrementChildPrio(len(n.indices) - 1)
|
||||
n = child
|
||||
}
|
||||
n.insertChild(numParams, path, handle)
|
||||
n.insertChild(numParams, path, fullPath, handle)
|
||||
return
|
||||
|
||||
} else if i == len(path) { // Make node a (in-path) leaf
|
||||
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
|
||||
}
|
||||
return
|
||||
}
|
||||
} else { // Empty tree
|
||||
n.insertChild(numParams, path, handle)
|
||||
n.insertChild(numParams, path, fullPath, handle)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
||||
var offset int
|
||||
func (n *node) insertChild(numParams uint8, path, fullPath string, handle Handle) {
|
||||
var offset int // already handled bytes of the path
|
||||
|
||||
// find prefix until first wildcard (beginning with ':'' or '*'')
|
||||
for i, max := 0, len(path); numParams > 0; i++ {
|
||||
|
@ -195,20 +208,29 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
|||
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)
|
||||
end := i + 1
|
||||
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 {
|
||||
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
|
||||
|
@ -244,17 +266,17 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
|||
|
||||
} else { // catchAll
|
||||
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] == '/' {
|
||||
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 '/'
|
||||
i--
|
||||
if path[i] != '/' {
|
||||
panic("no / before catch-all")
|
||||
panic("no / before catch-all in path '" + fullPath + "'")
|
||||
}
|
||||
|
||||
n.path = path[offset:i]
|
||||
|
@ -266,7 +288,7 @@ func (n *node) insertChild(numParams uint8, path string, handle Handle) {
|
|||
maxParams: 1,
|
||||
}
|
||||
n.children = []*node{child}
|
||||
n.indices = []byte{path[i]}
|
||||
n.indices = string(path[i])
|
||||
n = child
|
||||
n.priority++
|
||||
|
||||
|
@ -305,8 +327,8 @@ walk: // Outer loop for walking the tree
|
|||
// to walk down the tree
|
||||
if !n.wildChild {
|
||||
c := path[0]
|
||||
for i, index := range n.indices {
|
||||
if c == index {
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if c == n.indices[i] {
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
|
@ -379,7 +401,7 @@ walk: // Outer loop for walking the tree
|
|||
return
|
||||
|
||||
default:
|
||||
panic("Invalid node type")
|
||||
panic("invalid node type")
|
||||
}
|
||||
}
|
||||
} 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
|
||||
// trailing slash exists for trailing slash recommendation
|
||||
for i, index := range n.indices {
|
||||
if index == '/' {
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if n.indices[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)
|
||||
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.
|
||||
// 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.
|
||||
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
|
||||
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 {
|
||||
// must use recursive approach since both index and
|
||||
// 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)
|
||||
if found {
|
||||
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
|
||||
found = (fixTrailingSlash && path == "/" && n.handle != nil)
|
||||
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 {
|
||||
case param:
|
||||
// find param end (either '/' or path end)
|
||||
k := 0
|
||||
for k < len(path) && path[k] != '/' {
|
||||
k++
|
||||
}
|
||||
// add param value to case insensitive path
|
||||
ciPath = append(ciPath, path[:k]...)
|
||||
|
||||
// add param value to case insensitive path
|
||||
ciPath = append(ciPath, path[:k]...)
|
||||
|
||||
// we need to go deeper!
|
||||
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
|
||||
// we need to go deeper!
|
||||
if k < len(path) {
|
||||
if len(n.children) > 0 {
|
||||
path = path[k:]
|
||||
n = n.children[0]
|
||||
if n.path == "/" && n.handle != nil {
|
||||
return append(ciPath, '/'), true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// ... but we can't
|
||||
if fixTrailingSlash && len(path) == k+1 {
|
||||
return ciPath, true
|
||||
}
|
||||
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 {
|
||||
// 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.
|
||||
// Try to fix the path by adding a trailing slash
|
||||
if fixTrailingSlash {
|
||||
for i, index := range n.indices {
|
||||
if index == '/' {
|
||||
for i := 0; i < len(n.indices); i++ {
|
||||
if n.indices[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) {
|
||||
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/go_faq.html",
|
||||
"/doc/go1.html",
|
||||
"/α",
|
||||
"/β",
|
||||
}
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
|
@ -142,6 +144,8 @@ func TestTreeAddAndGet(t *testing.T) {
|
|||
{"/cona", true, "", nil}, // key mismatch
|
||||
{"/no", true, "", nil}, // no matching child
|
||||
{"/ab", false, "/ab", nil},
|
||||
{"/α", false, "/α", nil},
|
||||
{"/β", false, "/β", nil},
|
||||
})
|
||||
|
||||
checkPriorities(t, tree)
|
||||
|
@ -339,6 +343,27 @@ func TestTreeCatchAllConflictRoot(t *testing.T) {
|
|||
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) {
|
||||
tree := &node{}
|
||||
|
||||
|
@ -559,6 +584,8 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTreeInvalidNodeType(t *testing.T) {
|
||||
const panicMsg = "invalid node type"
|
||||
|
||||
tree := &node{}
|
||||
tree.addRoute("/", fakeHandler("/"))
|
||||
tree.addRoute("/:page", fakeHandler("/:page"))
|
||||
|
@ -570,15 +597,15 @@ func TestTreeInvalidNodeType(t *testing.T) {
|
|||
recv := catchPanic(func() {
|
||||
tree.getValue("/test")
|
||||
})
|
||||
if rs, ok := recv.(string); !ok || rs != "Invalid node type" {
|
||||
t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv)
|
||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
||||
}
|
||||
|
||||
// case-insensitive lookup
|
||||
recv = catchPanic(func() {
|
||||
tree.findCaseInsensitivePath("/test", true)
|
||||
})
|
||||
if rs, ok := recv.(string); !ok || rs != "Invalid node type" {
|
||||
t.Fatalf(`Expected panic "Invalid node type", got "%v"`, recv)
|
||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||
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)
|
||||
|
||||
Copyright (c) 2014 Stretchr, Inc.
|
||||
Copyright (c) 2014 Tyler Bunnell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
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
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
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 [![GoDoc](https://godoc.org/github.com/stretchr/graceful?status.png)](http://godoc.org/github.com/stretchr/graceful) [![wercker status](https://app.wercker.com/status/2729ba763abf87695a17547e0f7af4a4/s "wercker status")](https://app.wercker.com/project/bykey/2729ba763abf87695a17547e0f7af4a4)
|
||||
graceful [![GoDoc](https://godoc.org/github.com/tylerb/graceful?status.png)](http://godoc.org/github.com/tylerb/graceful) [![Build Status](https://drone.io/github.com/tylerb/graceful/status.png)](https://drone.io/github.com/tylerb/graceful/latest) [![Coverage Status](https://coveralls.io/repos/tylerb/graceful/badge.svg?branch=dronedebug)](https://coveralls.io/r/tylerb/graceful?branch=dronedebug) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](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.
|
||||
|
||||
## 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 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
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/stretchr/graceful"
|
||||
"gopkg.in/tylerb/graceful.v1"
|
||||
"net/http"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -31,9 +44,10 @@ package main
|
|||
|
||||
import (
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/stretchr/graceful"
|
||||
"gopkg.in/tylerb/graceful.v1"
|
||||
"net/http"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -111,4 +125,13 @@ same time and all will be signalled when stopping is complete.
|
|||
|
||||
## 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"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/pat/stop"
|
||||
"golang.org/x/net/netutil"
|
||||
)
|
||||
|
||||
|
@ -41,30 +40,31 @@ type Server struct {
|
|||
// must not be set directly.
|
||||
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
|
||||
// side of long lived connections (e.g. websockets) to reconnect.
|
||||
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,
|
||||
// and the server to shut down.
|
||||
interrupt chan os.Signal
|
||||
|
||||
// stopChan is the channel on which callers may block while waiting for
|
||||
// the server to stop.
|
||||
stopChan chan stop.Signal
|
||||
stopChan chan struct{}
|
||||
|
||||
// stopChanOnce is used to create the stop channel on demand, once, per
|
||||
// instance.
|
||||
stopChanOnce sync.Once
|
||||
// stopLock is used to protect access to the stopChan.
|
||||
stopLock sync.RWMutex
|
||||
|
||||
// connections holds all connections managed by graceful
|
||||
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.
|
||||
//
|
||||
// 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:
|
||||
remove <- conn
|
||||
}
|
||||
|
||||
if srv.ConnState != nil {
|
||||
srv.ConnState(conn, state)
|
||||
}
|
||||
|
@ -182,7 +181,53 @@ func (srv *Server) Serve(listener net.Listener) error {
|
|||
// Manage open connections
|
||||
shutdown := make(chan 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{}
|
||||
srv.connections = map[net.Conn]struct{}{}
|
||||
for {
|
||||
|
@ -202,36 +247,39 @@ func (srv *Server) Serve(listener net.Listener) error {
|
|||
}
|
||||
case <-kill:
|
||||
for k := range srv.connections {
|
||||
k.Close()
|
||||
_ = k.Close() // nothing to do here if it errors
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) interruptChan() chan os.Signal {
|
||||
srv.stopLock.Lock()
|
||||
if srv.interrupt == nil {
|
||||
srv.interrupt = make(chan os.Signal, 1)
|
||||
}
|
||||
srv.stopLock.Unlock()
|
||||
|
||||
// Set up the interrupt catch
|
||||
signal.Notify(srv.interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-srv.interrupt
|
||||
srv.SetKeepAlivesEnabled(false)
|
||||
listener.Close()
|
||||
return srv.interrupt
|
||||
}
|
||||
|
||||
if srv.ShutdownInitiated != nil {
|
||||
srv.ShutdownInitiated()
|
||||
}
|
||||
func (srv *Server) handleInterrupt(interrupt chan os.Signal, listener net.Listener) {
|
||||
<-interrupt
|
||||
|
||||
signal.Stop(srv.interrupt)
|
||||
close(srv.interrupt)
|
||||
}()
|
||||
srv.SetKeepAlivesEnabled(false)
|
||||
_ = listener.Close() // we are shutting down anyway. ignore error.
|
||||
|
||||
// Serve with graceful listener.
|
||||
// Execution blocks here until listener.Close() is called, above.
|
||||
err := srv.Server.Serve(listener)
|
||||
if srv.ShutdownInitiated != nil {
|
||||
srv.ShutdownInitiated()
|
||||
}
|
||||
|
||||
signal.Stop(interrupt)
|
||||
close(interrupt)
|
||||
}
|
||||
|
||||
func (srv *Server) shutdown(shutdown chan chan struct{}, kill chan struct{}) {
|
||||
// Request done notification
|
||||
done := make(chan struct{})
|
||||
shutdown <- done
|
||||
|
@ -246,32 +294,9 @@ func (srv *Server) Serve(listener net.Listener) error {
|
|||
<-done
|
||||
}
|
||||
// Close the stopChan to wake up any blocked goroutines.
|
||||
srv.stopLock.Lock()
|
||||
if srv.stopChan != nil {
|
||||
close(srv.stopChan)
|
||||
}
|
||||
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
|
||||
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
|
||||
srv.stopLock.Unlock()
|
||||
}
|
||||
|
|
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -13,34 +14,52 @@ import (
|
|||
"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)
|
||||
defer wg.Done()
|
||||
client := http.Client{}
|
||||
r, err := client.Get("http://localhost:3000")
|
||||
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 {
|
||||
if err.(*url.Error).Err == io.EOF {
|
||||
if checkErr(t, err, once) {
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
mux := http.NewServeMux()
|
||||
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}
|
||||
l, err := net.Listen("tcp", ":3000")
|
||||
if err != nil {
|
||||
fmt.Println(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) {
|
||||
var once sync.Once
|
||||
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
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(waitTime)
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
go runQuery(t, 0, true, wg)
|
||||
go runQuery(t, 0, true, wg, &once)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
|
@ -106,16 +129,17 @@ func TestGracefulRunTimesOut(t *testing.T) {
|
|||
wg.Done()
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
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
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(waitTime)
|
||||
for i := 0; i < 8; i++ {
|
||||
go runQuery(t, 0, true, &wg)
|
||||
go runQuery(t, 0, true, &wg, &once)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
@ -160,14 +184,23 @@ func TestGracefulRunNoRequests(t *testing.T) {
|
|||
func TestGracefulForwardsConnState(t *testing.T) {
|
||||
c := make(chan os.Signal, 1)
|
||||
states := make(map[http.ConnState]int)
|
||||
var stateLock sync.Mutex
|
||||
|
||||
connState := func(conn net.Conn, state http.ConnState) {
|
||||
stateLock.Lock()
|
||||
states[state]++
|
||||
stateLock.Unlock()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
expected := map[http.ConnState]int{
|
||||
http.StateNew: 8,
|
||||
http.StateActive: 8,
|
||||
http.StateClosed: 8,
|
||||
}
|
||||
|
||||
go func() {
|
||||
server, l, _ := createListener(killTime / 2)
|
||||
srv := &Server{
|
||||
|
@ -185,15 +218,11 @@ func TestGracefulForwardsConnState(t *testing.T) {
|
|||
go launchTestQueries(t, &wg, c)
|
||||
wg.Wait()
|
||||
|
||||
expected := map[http.ConnState]int{
|
||||
http.StateNew: 8,
|
||||
http.StateActive: 8,
|
||||
http.StateClosed: 8,
|
||||
}
|
||||
|
||||
stateLock.Lock()
|
||||
if !reflect.DeepEqual(states, expected) {
|
||||
t.Errorf("Incorrect connection state tracking.\n actual: %v\nexpected: %v\n", states, expected)
|
||||
}
|
||||
stateLock.Unlock()
|
||||
}
|
||||
|
||||
func TestGracefulExplicitStop(t *testing.T) {
|
||||
|
@ -206,14 +235,14 @@ func TestGracefulExplicitStop(t *testing.T) {
|
|||
|
||||
go func() {
|
||||
go srv.Serve(l)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(waitTime)
|
||||
srv.Stop(killTime)
|
||||
}()
|
||||
|
||||
// block on the stopChan until the server has shut down
|
||||
select {
|
||||
case <-srv.StopChan():
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-time.After(timeoutTime):
|
||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +257,7 @@ func TestGracefulExplicitStopOverride(t *testing.T) {
|
|||
|
||||
go func() {
|
||||
go srv.Serve(l)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(waitTime)
|
||||
srv.Stop(killTime / 2)
|
||||
}()
|
||||
|
||||
|
@ -253,7 +282,7 @@ func TestShutdownInitiatedCallback(t *testing.T) {
|
|||
|
||||
go func() {
|
||||
go srv.Serve(l)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(waitTime)
|
||||
srv.Stop(killTime)
|
||||
}()
|
||||
|
||||
|
@ -302,12 +331,9 @@ func TestNotifyClosed(t *testing.T) {
|
|||
wg.Done()
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
for i := 0; i < 8; i++ {
|
||||
runQuery(t, http.StatusOK, false, &wg)
|
||||
}
|
||||
|
||||
if len(srv.connections) > 0 {
|
||||
t.Fatal("hijacked connections should not be managed")
|
||||
runQuery(t, http.StatusOK, false, &wg, &once)
|
||||
}
|
||||
|
||||
srv.Stop(0)
|
||||
|
@ -315,8 +341,39 @@ func TestNotifyClosed(t *testing.T) {
|
|||
// block on the stopChan until the server has shut down
|
||||
select {
|
||||
case <-srv.StopChan():
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-time.After(timeoutTime):
|
||||
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"
|
||||
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/stretchr/graceful"
|
||||
"github.com/tylerb/graceful"
|
||||
)
|
||||
|
||||
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…
Reference in a new issue