godeps: refresh to get latest changes in graceful

This commit is contained in:
Jimmy Zelinskie 2015-04-22 22:54:46 -04:00
parent 7f2abdae4e
commit ff5339ceb3
16 changed files with 431 additions and 417 deletions

13
Godeps/Godeps.json generated
View file

@ -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"
}
]
}

View file

@ -1,3 +1,4 @@
sudo: false
language: go
go:
- 1.1

View file

@ -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

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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.

View file

@ -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()
}

View file

@ -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")
}
}

View file

@ -5,7 +5,7 @@ import (
"sync"
"github.com/codegangsta/negroni"
"github.com/stretchr/graceful"
"github.com/tylerb/graceful"
)
func main() {

View file

@ -1 +0,0 @@
box: wercker/golang

View file

@ -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

View file

@ -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
}

View file

@ -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)")
}
}