diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 72b1f3a..c781eb4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -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" } ] } diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml index 814710d..a4d6cc5 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/.travis.yml @@ -1,3 +1,4 @@ +sudo: false language: go go: - 1.1 diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md index 5a25d24..9875c70 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/README.md @@ -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 diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go index 4893950..155b871 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router.go @@ -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 } } diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go index 6292ba8..9dc6296 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/router_test.go @@ -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) } } diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go index 121d0c3..a15bc2c 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree.go @@ -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 } diff --git a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go index ed1f9a8..64f26d1 100644 --- a/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go +++ b/Godeps/_workspace/src/github.com/julienschmidt/httprouter/tree_test.go @@ -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) } } diff --git a/Godeps/_workspace/src/github.com/stretchr/graceful/LICENSE b/Godeps/_workspace/src/github.com/stretchr/graceful/LICENSE index abdb204..a4f2f28 100644 --- a/Godeps/_workspace/src/github.com/stretchr/graceful/LICENSE +++ b/Godeps/_workspace/src/github.com/stretchr/graceful/LICENSE @@ -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. \ No newline at end of file +SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/stretchr/graceful/README.md b/Godeps/_workspace/src/github.com/stretchr/graceful/README.md index 17d9560..1b0452a 100644 --- a/Godeps/_workspace/src/github.com/stretchr/graceful/README.md +++ b/Godeps/_workspace/src/github.com/stretchr/graceful/README.md @@ -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. diff --git a/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go b/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go index c43aafe..00f8205 100644 --- a/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go +++ b/Godeps/_workspace/src/github.com/stretchr/graceful/graceful.go @@ -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() } diff --git a/Godeps/_workspace/src/github.com/stretchr/graceful/graceful_test.go b/Godeps/_workspace/src/github.com/stretchr/graceful/graceful_test.go index 871df34..c738273 100644 --- a/Godeps/_workspace/src/github.com/stretchr/graceful/graceful_test.go +++ b/Godeps/_workspace/src/github.com/stretchr/graceful/graceful_test.go @@ -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") + } } diff --git a/Godeps/_workspace/src/github.com/stretchr/graceful/tests/main.go b/Godeps/_workspace/src/github.com/stretchr/graceful/tests/main.go index f9a6c4a..8c8fa20 100644 --- a/Godeps/_workspace/src/github.com/stretchr/graceful/tests/main.go +++ b/Godeps/_workspace/src/github.com/stretchr/graceful/tests/main.go @@ -5,7 +5,7 @@ import ( "sync" "github.com/codegangsta/negroni" - "github.com/stretchr/graceful" + "github.com/tylerb/graceful" ) func main() { diff --git a/Godeps/_workspace/src/github.com/stretchr/graceful/wercker.yml b/Godeps/_workspace/src/github.com/stretchr/graceful/wercker.yml deleted file mode 100644 index 41d2c52..0000000 --- a/Godeps/_workspace/src/github.com/stretchr/graceful/wercker.yml +++ /dev/null @@ -1 +0,0 @@ -box: wercker/golang diff --git a/Godeps/_workspace/src/github.com/stretchr/pat/stop/doc.go b/Godeps/_workspace/src/github.com/stretchr/pat/stop/doc.go deleted file mode 100644 index 6c6ab1b..0000000 --- a/Godeps/_workspace/src/github.com/stretchr/pat/stop/doc.go +++ /dev/null @@ -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 diff --git a/Godeps/_workspace/src/github.com/stretchr/pat/stop/stop.go b/Godeps/_workspace/src/github.com/stretchr/pat/stop/stop.go deleted file mode 100644 index 6a7792d..0000000 --- a/Godeps/_workspace/src/github.com/stretchr/pat/stop/stop.go +++ /dev/null @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/stretchr/pat/stop/stop_test.go b/Godeps/_workspace/src/github.com/stretchr/pat/stop/stop_test.go deleted file mode 100644 index d47443f..0000000 --- a/Godeps/_workspace/src/github.com/stretchr/pat/stop/stop_test.go +++ /dev/null @@ -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)") - } - -}