| @ -1,33 +0,0 @@ | |||||
| language: go | |||||
| matrix: | |||||
| include: | |||||
| - go: 1.2 | |||||
| install: | |||||
| - go get golang.org/x/tools/cmd/cover | |||||
| - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go: 1.3 | |||||
| install: | |||||
| - go get golang.org/x/tools/cmd/cover | |||||
| - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go: 1.4 | |||||
| install: | |||||
| - go get golang.org/x/tools/cmd/cover | |||||
| - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go: 1.5 | |||||
| install: | |||||
| - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go: 1.6 | |||||
| install: | |||||
| - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go: tip | |||||
| install: | |||||
| - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v | |||||
| script: | |||||
| - go test -cover ./... | |||||
| @ -1,20 +0,0 @@ | |||||
| Copyright (c) 2014, 2015, 2016 Carl Jackson (carl@avtok.com) | |||||
| MIT License | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | |||||
| this software and associated documentation files (the "Software"), to deal in | |||||
| the Software without restriction, including without limitation the rights to | |||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |||||
| the Software, and to permit persons to whom the Software is furnished to do so, | |||||
| subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 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. | |||||
| @ -1,176 +0,0 @@ | |||||
| Goji | |||||
| ==== | |||||
| [](https://godoc.org/github.com/zenazn/goji/web) [](https://travis-ci.org/zenazn/goji) | |||||
| Goji is a minimalistic web framework that values composability and simplicity. | |||||
| This project has been superseded by a [new version of Goji][goji2] by the same | |||||
| author, which has very similar primitives and semantics, but has been updated to | |||||
| reflect several years of experience with this library and the surrounding Go | |||||
| ecosystem. This project is still well-loved and well-maintained, and will be for | |||||
| the foreseeable future, but new projects are encouraged to use `goji.io` | |||||
| instead. | |||||
| [goji2]: https://goji.io | |||||
| Example | |||||
| ------- | |||||
| ```go | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "net/http" | |||||
| "github.com/zenazn/goji" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| func hello(c web.C, w http.ResponseWriter, r *http.Request) { | |||||
| fmt.Fprintf(w, "Hello, %s!", c.URLParams["name"]) | |||||
| } | |||||
| func main() { | |||||
| goji.Get("/hello/:name", hello) | |||||
| goji.Serve() | |||||
| } | |||||
| ``` | |||||
| Goji also includes a [sample application][sample] in the `example` folder which | |||||
| was artificially constructed to show off all of Goji's features. Check it out! | |||||
| [sample]: https://github.com/zenazn/goji/tree/master/example | |||||
| Features | |||||
| -------- | |||||
| * Compatible with `net/http` | |||||
| * URL patterns (both Sinatra style `/foo/:bar` patterns and regular expressions, | |||||
| as well as [custom patterns][pattern]) | |||||
| * Reconfigurable middleware stack | |||||
| * Context/environment object threaded through middleware and handlers | |||||
| * Automatic support for [Einhorn][einhorn], systemd, and [more][bind] | |||||
| * [Graceful shutdown][graceful], and zero-downtime graceful reload when combined | |||||
| with Einhorn. | |||||
| * High in antioxidants | |||||
| [einhorn]: https://github.com/stripe/einhorn | |||||
| [bind]: http://godoc.org/github.com/zenazn/goji/bind | |||||
| [graceful]: http://godoc.org/github.com/zenazn/goji/graceful | |||||
| [pattern]: https://godoc.org/github.com/zenazn/goji/web#Pattern | |||||
| Stability | |||||
| --------- | |||||
| Goji's API is essentially frozen, and guarantees to never break compatibility | |||||
| with existing code (under similar rules to the Go project's | |||||
| [guidelines][compat]). Goji is suitable for use in production, and has served | |||||
| billions of requests across several companies. | |||||
| [compat]: https://golang.org/doc/go1compat | |||||
| Is it any good? | |||||
| --------------- | |||||
| Maybe! | |||||
| There are [plenty][revel] of [other][gorilla] [good][pat] [Go][martini] | |||||
| [web][gocraft] [frameworks][tiger] out there. Goji is by no means especially | |||||
| novel, nor is it uniquely good. The primary difference between Goji and other | |||||
| frameworks—and the primary reason I think Goji is any good—is its philosophy: | |||||
| Goji first of all attempts to be simple. It is of the Sinatra and Flask school | |||||
| of web framework design, and not the Rails/Django one. If you want me to tell | |||||
| you what directory you should put your models in, or if you want built-in flash | |||||
| sessions, you won't have a good time with Goji. | |||||
| Secondly, Goji attempts to be composable. It is fully composable with net/http, | |||||
| and can be used as a `http.Handler`, or can serve arbitrary `http.Handler`s. At | |||||
| least a few HTTP frameworks share this property, and is not particularly novel. | |||||
| The more interesting property in my mind is that Goji is fully composable with | |||||
| itself: it defines an interface (`web.Handler`) which is both fully compatible | |||||
| with `http.Handler` and allows Goji to perform a "protocol upgrade" of sorts | |||||
| when it detects that it is talking to itself (or another `web.Handler` | |||||
| compatible component). `web.Handler` is at the core of Goji's interfaces and is | |||||
| what allows it to share request contexts across unrelated objects. | |||||
| Third, Goji is not magic. One of my favorite existing frameworks is | |||||
| [Martini][martini], but I rejected it in favor of building Goji because I | |||||
| thought it was too magical. Goji's web package does not use reflection at all, | |||||
| which is not in itself a sign of API quality, but to me at least seems to | |||||
| suggest it. | |||||
| Finally, Goji gives you enough rope to hang yourself with. One of my other | |||||
| favorite libraries, [pat][pat], implements Sinatra-like routing in a | |||||
| particularly elegant way, but because of its reliance on net/http's interfaces, | |||||
| doesn't allow programmers to thread their own state through the request handling | |||||
| process. Implementing arbitrary context objects was one of the primary | |||||
| motivations behind abandoning pat to write Goji. | |||||
| [revel]: http://revel.github.io/ | |||||
| [gorilla]: http://www.gorillatoolkit.org/ | |||||
| [pat]: https://github.com/bmizerany/pat | |||||
| [martini]: http://martini.codegangsta.io/ | |||||
| [gocraft]: https://github.com/gocraft/web | |||||
| [tiger]: https://github.com/rcrowley/go-tigertonic | |||||
| Is it fast? | |||||
| ----------- | |||||
| [Yeah][bench1], [it is][bench2]. Goji is among the fastest HTTP routers out | |||||
| there, and is very gentle on the garbage collector. | |||||
| But that's sort of missing the point. Almost all Go routers are fast enough for | |||||
| almost all purposes. In my opinion, what matters more is how simple and flexible | |||||
| the routing semantics are. | |||||
| Goji provides results indistinguishable from naively trying routes one after | |||||
| another. This means that a route added before another route will be attempted | |||||
| before that route as well. This is perhaps the most simple and most intuitive | |||||
| interface a router can provide, and makes routes very easy to understand and | |||||
| debug. | |||||
| Goji's router is also very flexible: in addition to the standard Sinatra-style | |||||
| patterns and regular expression patterns, you can define [custom | |||||
| patterns][pattern] to perform whatever custom matching logic you desire. Custom | |||||
| patterns of course are fully compatible with the routing semantics above. | |||||
| It's easy (and quite a bit of fun!) to get carried away by microbenchmarks, but | |||||
| at the end of the day you're not going to miss those extra hundred nanoseconds | |||||
| on a request. What matters is that you aren't compromising on the API for a | |||||
| handful of CPU cycles. | |||||
| [bench1]: https://gist.github.com/zenazn/c5c8528efe1a00634096 | |||||
| [bench2]: https://github.com/julienschmidt/go-http-routing-benchmark | |||||
| Third-Party Libraries | |||||
| --------------------- | |||||
| Goji is already compatible with a great many third-party libraries that are | |||||
| themselves compatible with `net/http`, however some library authors have gone | |||||
| out of their way to include Goji compatibility specifically, perhaps by | |||||
| integrating more tightly with Goji's `web.C` or by providing a custom pattern | |||||
| type. An informal list of such libraries is maintained [on the wiki][third]; | |||||
| feel free to add to it as you see fit. | |||||
| [third]: https://github.com/zenazn/goji/wiki/Third-Party-Libraries | |||||
| Contributing | |||||
| ------------ | |||||
| Please do! I love pull requests, and I love pull requests that include tests | |||||
| even more. Goji's core packages have pretty good code coverage (yay code | |||||
| coverage gamification!), and if you have the time to write tests I'd like to | |||||
| keep it that way. | |||||
| In addition to contributing code, I'd love to know what you think about Goji. | |||||
| Please open an issue or send me an email with your thoughts; it'd mean a lot to | |||||
| me. | |||||
| @ -1,145 +0,0 @@ | |||||
| /* | |||||
| Package bind provides a convenient way to bind to sockets. It exposes a flag in | |||||
| the default flag set named "bind" which provides syntax to bind TCP and UNIX | |||||
| sockets. It also supports binding to arbitrary file descriptors passed by a | |||||
| parent (for instance, systemd), and for binding to Einhorn sockets (including | |||||
| Einhorn ACK support). | |||||
| If the value passed to bind contains a colon, as in ":8000" or "127.0.0.1:9001", | |||||
| it will be treated as a TCP address. If it begins with a "/" or a ".", it will | |||||
| be treated as a path to a UNIX socket. If it begins with the string "fd@", as in | |||||
| "fd@3", it will be treated as a file descriptor (useful for use with systemd, | |||||
| for instance). If it begins with the string "einhorn@", as in "einhorn@0", the | |||||
| corresponding einhorn socket will be used. | |||||
| If an option is not explicitly passed, the implementation will automatically | |||||
| select between using "einhorn@0", "fd@3", and ":8000", depending on whether | |||||
| Einhorn or systemd (or neither) is detected. | |||||
| This package is a teensy bit magical, and goes out of its way to Do The Right | |||||
| Thing in many situations, including in both development and production. If | |||||
| you're looking for something less magical, you'd probably be better off just | |||||
| calling net.Listen() the old-fashioned way. | |||||
| */ | |||||
| package bind | |||||
| import ( | |||||
| "flag" | |||||
| "fmt" | |||||
| "log" | |||||
| "net" | |||||
| "os" | |||||
| "strconv" | |||||
| "strings" | |||||
| "sync" | |||||
| ) | |||||
| var bind string | |||||
| func init() { | |||||
| einhornInit() | |||||
| systemdInit() | |||||
| } | |||||
| // WithFlag adds a standard flag to the global flag instance that allows | |||||
| // configuration of the default socket. Users who call Default() must call this | |||||
| // function before flags are parsed, for example in an init() block. | |||||
| // | |||||
| // When selecting the default bind string, this function will examine its | |||||
| // environment for hints about what port to bind to, selecting the GOJI_BIND | |||||
| // environment variable, Einhorn, systemd, the PORT environment variable, and | |||||
| // the port 8000, in order. In most cases, this means that the default behavior | |||||
| // of the default socket will be reasonable for use in your circumstance. | |||||
| func WithFlag() { | |||||
| defaultBind := ":8000" | |||||
| if s := Sniff(); s != "" { | |||||
| defaultBind = s | |||||
| } | |||||
| flag.StringVar(&bind, "bind", defaultBind, | |||||
| `Address to bind on. If this value has a colon, as in ":8000" or | |||||
| "127.0.0.1:9001", it will be treated as a TCP address. If it | |||||
| begins with a "/" or a ".", it will be treated as a path to a | |||||
| UNIX socket. If it begins with the string "fd@", as in "fd@3", | |||||
| it will be treated as a file descriptor (useful for use with | |||||
| systemd, for instance). If it begins with the string "einhorn@", | |||||
| as in "einhorn@0", the corresponding einhorn socket will be | |||||
| used. If an option is not explicitly passed, the implementation | |||||
| will automatically select among "einhorn@0" (Einhorn), "fd@3" | |||||
| (systemd), and ":8000" (fallback) based on its environment.`) | |||||
| } | |||||
| // Sniff attempts to select a sensible default bind string by examining its | |||||
| // environment. It examines the GOJI_BIND environment variable, Einhorn, | |||||
| // systemd, and the PORT environment variable, in that order, selecting the | |||||
| // first plausible option. It returns the empty string if no sensible default | |||||
| // could be extracted from the environment. | |||||
| func Sniff() string { | |||||
| if bind := os.Getenv("GOJI_BIND"); bind != "" { | |||||
| return bind | |||||
| } else if usingEinhorn() { | |||||
| return "einhorn@0" | |||||
| } else if usingSystemd() { | |||||
| return "fd@3" | |||||
| } else if port := os.Getenv("PORT"); port != "" { | |||||
| return ":" + port | |||||
| } | |||||
| return "" | |||||
| } | |||||
| func listenTo(bind string) (net.Listener, error) { | |||||
| if strings.Contains(bind, ":") { | |||||
| return net.Listen("tcp", bind) | |||||
| } else if strings.HasPrefix(bind, ".") || strings.HasPrefix(bind, "/") { | |||||
| return net.Listen("unix", bind) | |||||
| } else if strings.HasPrefix(bind, "fd@") { | |||||
| fd, err := strconv.Atoi(bind[3:]) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("error while parsing fd %v: %v", | |||||
| bind, err) | |||||
| } | |||||
| f := os.NewFile(uintptr(fd), bind) | |||||
| defer f.Close() | |||||
| return net.FileListener(f) | |||||
| } else if strings.HasPrefix(bind, "einhorn@") { | |||||
| fd, err := strconv.Atoi(bind[8:]) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf( | |||||
| "error while parsing einhorn %v: %v", bind, err) | |||||
| } | |||||
| return einhornBind(fd) | |||||
| } | |||||
| return nil, fmt.Errorf("error while parsing bind arg %v", bind) | |||||
| } | |||||
| // Socket parses and binds to the specified address. If Socket encounters an | |||||
| // error while parsing or binding to the given socket it will exit by calling | |||||
| // log.Fatal. | |||||
| func Socket(bind string) net.Listener { | |||||
| l, err := listenTo(bind) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| return l | |||||
| } | |||||
| // Default parses and binds to the default socket as given to us by the flag | |||||
| // module. If there was an error parsing or binding to that socket, Default will | |||||
| // exit by calling `log.Fatal`. | |||||
| func Default() net.Listener { | |||||
| return Socket(bind) | |||||
| } | |||||
| // I'm not sure why you'd ever want to call Ready() more than once, but we may | |||||
| // as well be safe against it... | |||||
| var ready sync.Once | |||||
| // Ready notifies the environment (for now, just Einhorn) that the process is | |||||
| // ready to receive traffic. Should be called at the last possible moment to | |||||
| // maximize the chances that a faulty process exits before signaling that it's | |||||
| // ready. | |||||
| func Ready() { | |||||
| ready.Do(func() { | |||||
| einhornAck() | |||||
| }) | |||||
| } | |||||
| @ -1,91 +0,0 @@ | |||||
| // +build !windows | |||||
| package bind | |||||
| import ( | |||||
| "fmt" | |||||
| "log" | |||||
| "net" | |||||
| "os" | |||||
| "strconv" | |||||
| "syscall" | |||||
| ) | |||||
| const tooBigErr = "bind: einhorn@%d not found (einhorn only passed %d fds)" | |||||
| const bindErr = "bind: could not bind einhorn@%d: not running under einhorn" | |||||
| const einhornErr = "bind: einhorn environment initialization error" | |||||
| const ackErr = "bind: error ACKing to einhorn: %v" | |||||
| var einhornNumFds int | |||||
| func envInt(val string) (int, error) { | |||||
| return strconv.Atoi(os.Getenv(val)) | |||||
| } | |||||
| // Unfortunately this can't be a normal init function, because their execution | |||||
| // order is undefined, and we need to run before the init() in bind.go. | |||||
| func einhornInit() { | |||||
| mpid, err := envInt("EINHORN_MASTER_PID") | |||||
| if err != nil || mpid != os.Getppid() { | |||||
| return | |||||
| } | |||||
| einhornNumFds, err = envInt("EINHORN_FD_COUNT") | |||||
| if err != nil { | |||||
| einhornNumFds = 0 | |||||
| return | |||||
| } | |||||
| // Prevent einhorn's fds from leaking to our children | |||||
| for i := 0; i < einhornNumFds; i++ { | |||||
| syscall.CloseOnExec(einhornFdMap(i)) | |||||
| } | |||||
| } | |||||
| func usingEinhorn() bool { | |||||
| return einhornNumFds > 0 | |||||
| } | |||||
| func einhornFdMap(n int) int { | |||||
| name := fmt.Sprintf("EINHORN_FD_%d", n) | |||||
| fno, err := envInt(name) | |||||
| if err != nil { | |||||
| log.Fatal(einhornErr) | |||||
| } | |||||
| return fno | |||||
| } | |||||
| func einhornBind(n int) (net.Listener, error) { | |||||
| if !usingEinhorn() { | |||||
| return nil, fmt.Errorf(bindErr, n) | |||||
| } | |||||
| if n >= einhornNumFds || n < 0 { | |||||
| return nil, fmt.Errorf(tooBigErr, n, einhornNumFds) | |||||
| } | |||||
| fno := einhornFdMap(n) | |||||
| f := os.NewFile(uintptr(fno), fmt.Sprintf("einhorn@%d", n)) | |||||
| defer f.Close() | |||||
| return net.FileListener(f) | |||||
| } | |||||
| // Fun story: this is actually YAML, not JSON. | |||||
| const ackMsg = `{"command": "worker:ack", "pid": %d}` + "\n" | |||||
| func einhornAck() { | |||||
| if !usingEinhorn() { | |||||
| return | |||||
| } | |||||
| log.Print("bind: ACKing to einhorn") | |||||
| ctl, err := net.Dial("unix", os.Getenv("EINHORN_SOCK_PATH")) | |||||
| if err != nil { | |||||
| log.Fatalf(ackErr, err) | |||||
| } | |||||
| defer ctl.Close() | |||||
| _, err = fmt.Fprintf(ctl, ackMsg, os.Getpid()) | |||||
| if err != nil { | |||||
| log.Fatalf(ackErr, err) | |||||
| } | |||||
| } | |||||
| @ -1,12 +0,0 @@ | |||||
| // +build windows | |||||
| package bind | |||||
| import ( | |||||
| "net" | |||||
| ) | |||||
| func einhornInit() {} | |||||
| func einhornAck() {} | |||||
| func einhornBind(fd int) (net.Listener, error) { return nil, nil } | |||||
| func usingEinhorn() bool { return false } | |||||
| @ -1,36 +0,0 @@ | |||||
| // +build !windows | |||||
| package bind | |||||
| import ( | |||||
| "os" | |||||
| "syscall" | |||||
| ) | |||||
| const systemdMinFd = 3 | |||||
| var systemdNumFds int | |||||
| // Unfortunately this can't be a normal init function, because their execution | |||||
| // order is undefined, and we need to run before the init() in bind.go. | |||||
| func systemdInit() { | |||||
| pid, err := envInt("LISTEN_PID") | |||||
| if err != nil || pid != os.Getpid() { | |||||
| return | |||||
| } | |||||
| systemdNumFds, err = envInt("LISTEN_FDS") | |||||
| if err != nil { | |||||
| systemdNumFds = 0 | |||||
| return | |||||
| } | |||||
| // Prevent fds from leaking to our children | |||||
| for i := 0; i < systemdNumFds; i++ { | |||||
| syscall.CloseOnExec(systemdMinFd + i) | |||||
| } | |||||
| } | |||||
| func usingSystemd() bool { | |||||
| return systemdNumFds > 0 | |||||
| } | |||||
| @ -1,6 +0,0 @@ | |||||
| // +build windows | |||||
| package bind | |||||
| func systemdInit() {} | |||||
| func usingSystemd() bool { return false } | |||||
| @ -1,102 +0,0 @@ | |||||
| package goji | |||||
| import ( | |||||
| "github.com/zenazn/goji/web" | |||||
| "github.com/zenazn/goji/web/middleware" | |||||
| ) | |||||
| // The default web.Mux. | |||||
| var DefaultMux *web.Mux | |||||
| func init() { | |||||
| DefaultMux = web.New() | |||||
| DefaultMux.Use(middleware.RequestID) | |||||
| DefaultMux.Use(middleware.Logger) | |||||
| DefaultMux.Use(middleware.Recoverer) | |||||
| DefaultMux.Use(middleware.AutomaticOptions) | |||||
| } | |||||
| // Use appends the given middleware to the default Mux's middleware stack. See | |||||
| // the documentation for web.Mux.Use for more information. | |||||
| func Use(middleware web.MiddlewareType) { | |||||
| DefaultMux.Use(middleware) | |||||
| } | |||||
| // Insert the given middleware into the default Mux's middleware stack. See the | |||||
| // documentation for web.Mux.Insert for more information. | |||||
| func Insert(middleware, before web.MiddlewareType) error { | |||||
| return DefaultMux.Insert(middleware, before) | |||||
| } | |||||
| // Abandon removes the given middleware from the default Mux's middleware stack. | |||||
| // See the documentation for web.Mux.Abandon for more information. | |||||
| func Abandon(middleware web.MiddlewareType) error { | |||||
| return DefaultMux.Abandon(middleware) | |||||
| } | |||||
| // Handle adds a route to the default Mux. See the documentation for web.Mux for | |||||
| // more information about what types this function accepts. | |||||
| func Handle(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Handle(pattern, handler) | |||||
| } | |||||
| // Connect adds a CONNECT route to the default Mux. See the documentation for | |||||
| // web.Mux for more information about what types this function accepts. | |||||
| func Connect(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Connect(pattern, handler) | |||||
| } | |||||
| // Delete adds a DELETE route to the default Mux. See the documentation for | |||||
| // web.Mux for more information about what types this function accepts. | |||||
| func Delete(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Delete(pattern, handler) | |||||
| } | |||||
| // Get adds a GET route to the default Mux. See the documentation for web.Mux for | |||||
| // more information about what types this function accepts. | |||||
| func Get(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Get(pattern, handler) | |||||
| } | |||||
| // Head adds a HEAD route to the default Mux. See the documentation for web.Mux | |||||
| // for more information about what types this function accepts. | |||||
| func Head(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Head(pattern, handler) | |||||
| } | |||||
| // Options adds a OPTIONS route to the default Mux. See the documentation for | |||||
| // web.Mux for more information about what types this function accepts. | |||||
| func Options(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Options(pattern, handler) | |||||
| } | |||||
| // Patch adds a PATCH route to the default Mux. See the documentation for web.Mux | |||||
| // for more information about what types this function accepts. | |||||
| func Patch(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Patch(pattern, handler) | |||||
| } | |||||
| // Post adds a POST route to the default Mux. See the documentation for web.Mux | |||||
| // for more information about what types this function accepts. | |||||
| func Post(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Post(pattern, handler) | |||||
| } | |||||
| // Put adds a PUT route to the default Mux. See the documentation for web.Mux for | |||||
| // more information about what types this function accepts. | |||||
| func Put(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Put(pattern, handler) | |||||
| } | |||||
| // Trace adds a TRACE route to the default Mux. See the documentation for | |||||
| // web.Mux for more information about what types this function accepts. | |||||
| func Trace(pattern web.PatternType, handler web.HandlerType) { | |||||
| DefaultMux.Trace(pattern, handler) | |||||
| } | |||||
| // NotFound sets the NotFound handler for the default Mux. See the documentation | |||||
| // for web.Mux.NotFound for more information. | |||||
| func NotFound(handler web.HandlerType) { | |||||
| DefaultMux.NotFound(handler) | |||||
| } | |||||
| @ -1 +0,0 @@ | |||||
| example | |||||
| @ -1,10 +0,0 @@ | |||||
| Gritter | |||||
| ======= | |||||
| Gritter is an example application built using Goji, where people who have | |||||
| nothing better to do can post short 140-character "greets." | |||||
| A good place to start is with `main.go`, which contains a well-commented | |||||
| walkthrough of Goji's features. Gritter uses a couple custom middlewares, which | |||||
| have been arbitrarily placed in `middleware.go`. Finally some uninteresting | |||||
| "database models" live in `models.go`. | |||||
| @ -1,177 +0,0 @@ | |||||
| // Command example is a sample application built with Goji. Its goal is to give | |||||
| // you a taste for what Goji looks like in the real world by artificially using | |||||
| // all of its features. | |||||
| // | |||||
| // In particular, this is a complete working site for gritter.com, a site where | |||||
| // users can post 140-character "greets". Any resemblance to real websites, | |||||
| // alive or dead, is purely coincidental. | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "io" | |||||
| "net/http" | |||||
| "regexp" | |||||
| "strconv" | |||||
| "time" | |||||
| "github.com/goji/param" | |||||
| "github.com/zenazn/goji" | |||||
| "github.com/zenazn/goji/web" | |||||
| "github.com/zenazn/goji/web/middleware" | |||||
| ) | |||||
| // Note: the code below cuts a lot of corners to make the example app simple. | |||||
| func main() { | |||||
| // Add routes to the global handler | |||||
| goji.Get("/", Root) | |||||
| // Fully backwards compatible with net/http's Handlers | |||||
| goji.Get("/greets", http.RedirectHandler("/", 301)) | |||||
| // Use your favorite HTTP verbs | |||||
| goji.Post("/greets", NewGreet) | |||||
| // Use Sinatra-style patterns in your URLs | |||||
| goji.Get("/users/:name", GetUser) | |||||
| // Goji also supports regular expressions with named capture groups. | |||||
| goji.Get(regexp.MustCompile(`^/greets/(?P<id>\d+)$`), GetGreet) | |||||
| // Middleware can be used to inject behavior into your app. The | |||||
| // middleware for this application are defined in middleware.go, but you | |||||
| // can put them wherever you like. | |||||
| goji.Use(PlainText) | |||||
| // If the patterns ends with "/*", the path is treated as a prefix, and | |||||
| // can be used to implement sub-routes. | |||||
| admin := web.New() | |||||
| goji.Handle("/admin/*", admin) | |||||
| // The standard SubRouter middleware helps make writing sub-routers | |||||
| // easy. Ordinarily, Goji does not manipulate the request's URL.Path, | |||||
| // meaning you'd have to repeat "/admin/" in each of the following | |||||
| // routes. This middleware allows you to cut down on the repetition by | |||||
| // eliminating the shared, already-matched prefix. | |||||
| admin.Use(middleware.SubRouter) | |||||
| // You can also easily attach extra middleware to sub-routers that are | |||||
| // not present on the parent router. This one, for instance, presents a | |||||
| // password prompt to users of the admin endpoints. | |||||
| admin.Use(SuperSecure) | |||||
| admin.Get("/", AdminRoot) | |||||
| admin.Get("/finances", AdminFinances) | |||||
| // Goji's routing, like Sinatra's, is exact: no effort is made to | |||||
| // normalize trailing slashes. | |||||
| goji.Get("/admin", http.RedirectHandler("/admin/", 301)) | |||||
| // Use a custom 404 handler | |||||
| goji.NotFound(NotFound) | |||||
| // Sometimes requests take a long time. | |||||
| goji.Get("/waitforit", WaitForIt) | |||||
| // Call Serve() at the bottom of your main() function, and it'll take | |||||
| // care of everything else for you, including binding to a socket (with | |||||
| // automatic support for systemd and Einhorn) and supporting graceful | |||||
| // shutdown on SIGINT. Serve() is appropriate for both development and | |||||
| // production. | |||||
| goji.Serve() | |||||
| } | |||||
| // Root route (GET "/"). Print a list of greets. | |||||
| func Root(w http.ResponseWriter, r *http.Request) { | |||||
| // In the real world you'd probably use a template or something. | |||||
| io.WriteString(w, "Gritter\n======\n\n") | |||||
| for i := len(Greets) - 1; i >= 0; i-- { | |||||
| Greets[i].Write(w) | |||||
| } | |||||
| } | |||||
| // NewGreet creates a new greet (POST "/greets"). Creates a greet and redirects | |||||
| // you to the created greet. | |||||
| // | |||||
| // To post a new greet, try this at a shell: | |||||
| // $ now=$(date +'%Y-%m-%dT%H:%M:%SZ') | |||||
| // $ curl -i -d "user=carl&message=Hello+World&time=$now" localhost:8000/greets | |||||
| func NewGreet(w http.ResponseWriter, r *http.Request) { | |||||
| var greet Greet | |||||
| // Parse the POST body into the Greet struct. The format is the same as | |||||
| // is emitted by (e.g.) jQuery.param. | |||||
| r.ParseForm() | |||||
| err := param.Parse(r.Form, &greet) | |||||
| if err != nil || len(greet.Message) > 140 { | |||||
| http.Error(w, err.Error(), http.StatusBadRequest) | |||||
| return | |||||
| } | |||||
| // We make no effort to prevent races against other insertions. | |||||
| Greets = append(Greets, greet) | |||||
| url := fmt.Sprintf("/greets/%d", len(Greets)-1) | |||||
| http.Redirect(w, r, url, http.StatusCreated) | |||||
| } | |||||
| // GetUser finds a given user and her greets (GET "/user/:name") | |||||
| func GetUser(c web.C, w http.ResponseWriter, r *http.Request) { | |||||
| io.WriteString(w, "Gritter\n======\n\n") | |||||
| handle := c.URLParams["name"] | |||||
| user, ok := Users[handle] | |||||
| if !ok { | |||||
| http.Error(w, http.StatusText(404), 404) | |||||
| return | |||||
| } | |||||
| user.Write(w, handle) | |||||
| io.WriteString(w, "\nGreets:\n") | |||||
| for i := len(Greets) - 1; i >= 0; i-- { | |||||
| if Greets[i].User == handle { | |||||
| Greets[i].Write(w) | |||||
| } | |||||
| } | |||||
| } | |||||
| // GetGreet finds a particular greet by ID (GET "/greets/\d+"). Does no bounds | |||||
| // checking, so will probably panic. | |||||
| func GetGreet(c web.C, w http.ResponseWriter, r *http.Request) { | |||||
| id, err := strconv.Atoi(c.URLParams["id"]) | |||||
| if err != nil { | |||||
| http.Error(w, http.StatusText(404), 404) | |||||
| return | |||||
| } | |||||
| // This will panic if id is too big. Try it out! | |||||
| greet := Greets[id] | |||||
| io.WriteString(w, "Gritter\n======\n\n") | |||||
| greet.Write(w) | |||||
| } | |||||
| // WaitForIt is a particularly slow handler (GET "/waitforit"). Try loading this | |||||
| // endpoint and initiating a graceful shutdown (Ctrl-C) or Einhorn reload. The | |||||
| // old server will stop accepting new connections and will attempt to kill | |||||
| // outstanding idle (keep-alive) connections, but will patiently stick around | |||||
| // for this endpoint to finish. How kind of it! | |||||
| func WaitForIt(w http.ResponseWriter, r *http.Request) { | |||||
| io.WriteString(w, "This is going to be legend... (wait for it)\n") | |||||
| if fl, ok := w.(http.Flusher); ok { | |||||
| fl.Flush() | |||||
| } | |||||
| time.Sleep(15 * time.Second) | |||||
| io.WriteString(w, "...dary! Legendary!\n") | |||||
| } | |||||
| // AdminRoot is root (GET "/admin/root"). Much secret. Very administrate. Wow. | |||||
| func AdminRoot(w http.ResponseWriter, r *http.Request) { | |||||
| io.WriteString(w, "Gritter\n======\n\nSuper secret admin page!\n") | |||||
| } | |||||
| // AdminFinances would answer the question 'How are we doing?' | |||||
| // (GET "/admin/finances") | |||||
| func AdminFinances(w http.ResponseWriter, r *http.Request) { | |||||
| io.WriteString(w, "Gritter\n======\n\nWe're broke! :(\n") | |||||
| } | |||||
| // NotFound is a 404 handler. | |||||
| func NotFound(w http.ResponseWriter, r *http.Request) { | |||||
| http.Error(w, "Umm... have you tried turning it off and on again?", 404) | |||||
| } | |||||
| @ -1,47 +0,0 @@ | |||||
| package main | |||||
| import ( | |||||
| "encoding/base64" | |||||
| "net/http" | |||||
| "strings" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| // PlainText sets the content-type of responses to text/plain. | |||||
| func PlainText(h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| w.Header().Set("Content-Type", "text/plain") | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| // Nobody will ever guess this! | |||||
| const Password = "admin:admin" | |||||
| // SuperSecure is HTTP Basic Auth middleware for super-secret admin page. Shhhh! | |||||
| func SuperSecure(c *web.C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| auth := r.Header.Get("Authorization") | |||||
| if !strings.HasPrefix(auth, "Basic ") { | |||||
| pleaseAuth(w) | |||||
| return | |||||
| } | |||||
| password, err := base64.StdEncoding.DecodeString(auth[6:]) | |||||
| if err != nil || string(password) != Password { | |||||
| pleaseAuth(w) | |||||
| return | |||||
| } | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| func pleaseAuth(w http.ResponseWriter) { | |||||
| w.Header().Set("WWW-Authenticate", `Basic realm="Gritter"`) | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| w.Write([]byte("Go away!\n")) | |||||
| } | |||||
| @ -1,49 +0,0 @@ | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "io" | |||||
| "time" | |||||
| ) | |||||
| // A Greet is a 140-character micro-blogpost that has no resemblance whatsoever | |||||
| // to the noise a bird makes. | |||||
| type Greet struct { | |||||
| User string `param:"user"` | |||||
| Message string `param:"message"` | |||||
| Time time.Time `param:"time"` | |||||
| } | |||||
| // Store all our greets in a big list in memory, because, let's be honest, who's | |||||
| // actually going to use a service that only allows you to post 140-character | |||||
| // messages? | |||||
| var Greets = []Greet{ | |||||
| {"carl", "Welcome to Gritter!", time.Now()}, | |||||
| {"alice", "Wanna know a secret?", time.Now()}, | |||||
| {"bob", "Okay!", time.Now()}, | |||||
| {"eve", "I'm listening...", time.Now()}, | |||||
| } | |||||
| // Write out a representation of the greet | |||||
| func (g Greet) Write(w io.Writer) { | |||||
| fmt.Fprintf(w, "%s\n@%s at %s\n---\n", g.Message, g.User, | |||||
| g.Time.Format(time.UnixDate)) | |||||
| } | |||||
| // A User is a person. It may even be someone you know. Or a rabbit. Hard to say | |||||
| // from here. | |||||
| type User struct { | |||||
| Name, Bio string | |||||
| } | |||||
| // All the users we know about! There aren't very many... | |||||
| var Users = map[string]User{ | |||||
| "alice": {"Alice in Wonderland", "Eating mushrooms"}, | |||||
| "bob": {"Bob the Builder", "Making children dumber"}, | |||||
| "carl": {"Carl Jackson", "Duct tape aficionado"}, | |||||
| } | |||||
| // Write out the user | |||||
| func (u User) Write(w io.Writer, handle string) { | |||||
| fmt.Fprintf(w, "%s (@%s)\n%s\n", u.Name, handle, u.Bio) | |||||
| } | |||||
| @ -1,36 +0,0 @@ | |||||
| /* | |||||
| Package goji provides an out-of-box web server with reasonable defaults. | |||||
| Example: | |||||
| package main | |||||
| import ( | |||||
| "fmt" | |||||
| "net/http" | |||||
| "github.com/zenazn/goji" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| func hello(c web.C, w http.ResponseWriter, r *http.Request) { | |||||
| fmt.Fprintf(w, "Hello, %s!", c.URLParams["name"]) | |||||
| } | |||||
| func main() { | |||||
| goji.Get("/hello/:name", hello) | |||||
| goji.Serve() | |||||
| } | |||||
| This package exists purely as a convenience to programmers who want to get | |||||
| started as quickly as possible. It draws almost all of its code from goji's | |||||
| subpackages, the most interesting of which is goji/web, and where most of the | |||||
| documentation for the web framework lives. | |||||
| A side effect of this package's ease-of-use is the fact that it is opinionated. | |||||
| If you don't like (or have outgrown) its opinions, it should be straightforward | |||||
| to use the APIs of goji's subpackages to reimplement things to your liking. Both | |||||
| methods of using this library are equally well supported. | |||||
| Goji requires Go 1.2 or newer. | |||||
| */ | |||||
| package goji | |||||
| @ -1,11 +0,0 @@ | |||||
| // +build !go1.6 | |||||
| package graceful | |||||
| import "crypto/tls" | |||||
| // see clone16.go | |||||
| func cloneTLSConfig(cfg *tls.Config) *tls.Config { | |||||
| c := *cfg | |||||
| return &c | |||||
| } | |||||
| @ -1,34 +0,0 @@ | |||||
| // +build go1.6 | |||||
| package graceful | |||||
| import "crypto/tls" | |||||
| // cloneTLSConfig was taken from the Go standard library's net/http package. We | |||||
| // need it because tls.Config objects now contain a sync.Once. | |||||
| func cloneTLSConfig(cfg *tls.Config) *tls.Config { | |||||
| if cfg == nil { | |||||
| return &tls.Config{} | |||||
| } | |||||
| return &tls.Config{ | |||||
| Rand: cfg.Rand, | |||||
| Time: cfg.Time, | |||||
| Certificates: cfg.Certificates, | |||||
| NameToCertificate: cfg.NameToCertificate, | |||||
| GetCertificate: cfg.GetCertificate, | |||||
| RootCAs: cfg.RootCAs, | |||||
| NextProtos: cfg.NextProtos, | |||||
| ServerName: cfg.ServerName, | |||||
| ClientAuth: cfg.ClientAuth, | |||||
| ClientCAs: cfg.ClientCAs, | |||||
| InsecureSkipVerify: cfg.InsecureSkipVerify, | |||||
| CipherSuites: cfg.CipherSuites, | |||||
| PreferServerCipherSuites: cfg.PreferServerCipherSuites, | |||||
| SessionTicketsDisabled: cfg.SessionTicketsDisabled, | |||||
| SessionTicketKey: cfg.SessionTicketKey, | |||||
| ClientSessionCache: cfg.ClientSessionCache, | |||||
| MinVersion: cfg.MinVersion, | |||||
| MaxVersion: cfg.MaxVersion, | |||||
| CurvePreferences: cfg.CurvePreferences, | |||||
| } | |||||
| } | |||||
| @ -1,21 +0,0 @@ | |||||
| // +build !windows | |||||
| package graceful | |||||
| import ( | |||||
| "os" | |||||
| "strconv" | |||||
| "syscall" | |||||
| ) | |||||
| func init() { | |||||
| // This is a little unfortunate: goji/bind already knows whether we're | |||||
| // running under einhorn, but we don't want to introduce a dependency | |||||
| // between the two packages. Since the check is short enough, inlining | |||||
| // it here seems "fine." | |||||
| mpid, err := strconv.Atoi(os.Getenv("EINHORN_MASTER_PID")) | |||||
| if err != nil || mpid != os.Getppid() { | |||||
| return | |||||
| } | |||||
| stdSignals = append(stdSignals, syscall.SIGUSR2) | |||||
| } | |||||
| @ -1,62 +0,0 @@ | |||||
| /* | |||||
| Package graceful implements graceful shutdown for HTTP servers by closing idle | |||||
| connections after receiving a signal. By default, this package listens for | |||||
| interrupts (i.e., SIGINT), but when it detects that it is running under Einhorn | |||||
| it will additionally listen for SIGUSR2 as well, giving your application | |||||
| automatic support for graceful restarts/code upgrades. | |||||
| */ | |||||
| package graceful | |||||
| import ( | |||||
| "net" | |||||
| "runtime" | |||||
| "sync/atomic" | |||||
| "github.com/zenazn/goji/graceful/listener" | |||||
| ) | |||||
| // WrapListener wraps an arbitrary net.Listener for use with graceful shutdowns. | |||||
| // In the background, it uses the listener sub-package to Wrap the listener in | |||||
| // Deadline mode. If another mode of operation is desired, you should call | |||||
| // listener.Wrap yourself: this function is smart enough to not double-wrap | |||||
| // listeners. | |||||
| func WrapListener(l net.Listener) net.Listener { | |||||
| if lt, ok := l.(*listener.T); ok { | |||||
| appendListener(lt) | |||||
| return lt | |||||
| } | |||||
| lt := listener.Wrap(l, listener.Deadline) | |||||
| appendListener(lt) | |||||
| return lt | |||||
| } | |||||
| func appendListener(l *listener.T) { | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| listeners = append(listeners, l) | |||||
| } | |||||
| const errClosing = "use of closed network connection" | |||||
| // During graceful shutdown, calls to Accept will start returning errors. This | |||||
| // is inconvenient, since we know these sorts of errors are peaceful, so we | |||||
| // silently swallow them. | |||||
| func peacefulError(err error) error { | |||||
| if atomic.LoadInt32(&closing) == 0 { | |||||
| return err | |||||
| } | |||||
| // Unfortunately Go doesn't really give us a better way to select errors | |||||
| // than this, so *shrug*. | |||||
| if oe, ok := err.(*net.OpError); ok { | |||||
| errOp := "accept" | |||||
| if runtime.GOOS == "windows" { | |||||
| errOp = "AcceptEx" | |||||
| } | |||||
| if oe.Op == errOp && oe.Err.Error() == errClosing { | |||||
| return nil | |||||
| } | |||||
| } | |||||
| return err | |||||
| } | |||||
| @ -1,151 +0,0 @@ | |||||
| package listener | |||||
| import ( | |||||
| "errors" | |||||
| "io" | |||||
| "net" | |||||
| "sync" | |||||
| "time" | |||||
| ) | |||||
| type conn struct { | |||||
| net.Conn | |||||
| shard *shard | |||||
| mode mode | |||||
| mu sync.Mutex // Protects the state machine below | |||||
| busy bool // connection is in use (i.e., not idle) | |||||
| closed bool // connection is closed | |||||
| disowned bool // if true, this connection is no longer under our management | |||||
| } | |||||
| // This intentionally looks a lot like the one in package net. | |||||
| var errClosing = errors.New("use of closed network connection") | |||||
| func (c *conn) init() error { | |||||
| c.shard.wg.Add(1) | |||||
| if shouldExit := c.shard.track(c); shouldExit { | |||||
| c.Close() | |||||
| return errClosing | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (c *conn) Read(b []byte) (n int, err error) { | |||||
| defer func() { | |||||
| c.mu.Lock() | |||||
| defer c.mu.Unlock() | |||||
| if c.disowned { | |||||
| return | |||||
| } | |||||
| // This protects against a Close/Read race. We're not really | |||||
| // concerned about the general case (it's fundamentally racy), | |||||
| // but are mostly trying to prevent a race between a new request | |||||
| // getting read off the wire in one thread while the connection | |||||
| // is being gracefully shut down in another. | |||||
| if c.closed && err == nil { | |||||
| n = 0 | |||||
| err = errClosing | |||||
| return | |||||
| } | |||||
| if c.mode != Manual && !c.busy && !c.closed { | |||||
| c.busy = true | |||||
| c.shard.markInUse(c) | |||||
| } | |||||
| }() | |||||
| return c.Conn.Read(b) | |||||
| } | |||||
| func (c *conn) Close() error { | |||||
| c.mu.Lock() | |||||
| defer c.mu.Unlock() | |||||
| if c.disowned { | |||||
| return c.Conn.Close() | |||||
| } else if c.closed { | |||||
| return errClosing | |||||
| } | |||||
| c.closed = true | |||||
| c.shard.disown(c) | |||||
| defer c.shard.wg.Done() | |||||
| return c.Conn.Close() | |||||
| } | |||||
| func (c *conn) SetReadDeadline(t time.Time) error { | |||||
| c.mu.Lock() | |||||
| if !c.disowned && c.mode == Deadline { | |||||
| defer c.markIdle() | |||||
| } | |||||
| c.mu.Unlock() | |||||
| return c.Conn.SetReadDeadline(t) | |||||
| } | |||||
| func (c *conn) ReadFrom(r io.Reader) (int64, error) { | |||||
| return io.Copy(c.Conn, r) | |||||
| } | |||||
| func (c *conn) markIdle() { | |||||
| c.mu.Lock() | |||||
| defer c.mu.Unlock() | |||||
| if !c.busy { | |||||
| return | |||||
| } | |||||
| c.busy = false | |||||
| if exit := c.shard.markIdle(c); exit && !c.closed && !c.disowned { | |||||
| c.closed = true | |||||
| c.shard.disown(c) | |||||
| defer c.shard.wg.Done() | |||||
| c.Conn.Close() | |||||
| return | |||||
| } | |||||
| } | |||||
| func (c *conn) markInUse() { | |||||
| c.mu.Lock() | |||||
| defer c.mu.Unlock() | |||||
| if !c.busy && !c.closed && !c.disowned { | |||||
| c.busy = true | |||||
| c.shard.markInUse(c) | |||||
| } | |||||
| } | |||||
| func (c *conn) closeIfIdle() error { | |||||
| c.mu.Lock() | |||||
| defer c.mu.Unlock() | |||||
| if !c.busy && !c.closed && !c.disowned { | |||||
| c.closed = true | |||||
| c.shard.disown(c) | |||||
| defer c.shard.wg.Done() | |||||
| return c.Conn.Close() | |||||
| } | |||||
| return nil | |||||
| } | |||||
| var errAlreadyDisowned = errors.New("listener: conn already disowned") | |||||
| func (c *conn) disown() error { | |||||
| c.mu.Lock() | |||||
| defer c.mu.Unlock() | |||||
| if c.disowned { | |||||
| return errAlreadyDisowned | |||||
| } | |||||
| c.shard.disown(c) | |||||
| c.disowned = true | |||||
| c.shard.wg.Done() | |||||
| return nil | |||||
| } | |||||
| @ -1,198 +0,0 @@ | |||||
| package listener | |||||
| import ( | |||||
| "io" | |||||
| "strings" | |||||
| "testing" | |||||
| "time" | |||||
| ) | |||||
| func TestManualRead(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Manual) | |||||
| go c.AllowRead() | |||||
| wc.Read(make([]byte, 1024)) | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if !c.Closed() { | |||||
| t.Error("Read() should not make connection not-idle") | |||||
| } | |||||
| } | |||||
| func TestAutomaticRead(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Automatic) | |||||
| go c.AllowRead() | |||||
| wc.Read(make([]byte, 1024)) | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if c.Closed() { | |||||
| t.Error("expected Read() to mark connection as in-use") | |||||
| } | |||||
| } | |||||
| func TestDeadlineRead(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Deadline) | |||||
| go c.AllowRead() | |||||
| if _, err := wc.Read(make([]byte, 1024)); err != nil { | |||||
| t.Fatalf("error reading from connection: %v", err) | |||||
| } | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if c.Closed() { | |||||
| t.Error("expected Read() to mark connection as in-use") | |||||
| } | |||||
| } | |||||
| func TestDisownedRead(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Deadline) | |||||
| if err := Disown(wc); err != nil { | |||||
| t.Fatalf("unexpected error disowning conn: %v", err) | |||||
| } | |||||
| if err := l.Close(); err != nil { | |||||
| t.Fatalf("unexpected error closing listener: %v", err) | |||||
| } | |||||
| if err := l.Drain(); err != nil { | |||||
| t.Fatalf("unexpected error draining listener: %v", err) | |||||
| } | |||||
| go c.AllowRead() | |||||
| if _, err := wc.Read(make([]byte, 1024)); err != nil { | |||||
| t.Fatalf("error reading from connection: %v", err) | |||||
| } | |||||
| } | |||||
| func TestCloseConn(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, _, wc := singleConn(t, Deadline) | |||||
| if err := MarkInUse(wc); err != nil { | |||||
| t.Fatalf("error marking conn in use: %v", err) | |||||
| } | |||||
| if err := wc.Close(); err != nil { | |||||
| t.Errorf("error closing connection: %v", err) | |||||
| } | |||||
| // This will hang if wc.Close() doesn't un-track the connection | |||||
| if err := l.Drain(); err != nil { | |||||
| t.Errorf("error draining listener: %v", err) | |||||
| } | |||||
| } | |||||
| // Regression test for issue #130. | |||||
| func TestDisownedClose(t *testing.T) { | |||||
| t.Parallel() | |||||
| _, c, wc := singleConn(t, Deadline) | |||||
| if err := Disown(wc); err != nil { | |||||
| t.Fatalf("unexpected error disowning conn: %v", err) | |||||
| } | |||||
| if err := wc.Close(); err != nil { | |||||
| t.Errorf("error closing connection: %v", err) | |||||
| } | |||||
| if !c.Closed() { | |||||
| t.Errorf("connection didn't get closed") | |||||
| } | |||||
| } | |||||
| func TestManualReadDeadline(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Manual) | |||||
| if err := MarkInUse(wc); err != nil { | |||||
| t.Fatalf("error marking connection in use: %v", err) | |||||
| } | |||||
| if err := wc.SetReadDeadline(time.Now()); err != nil { | |||||
| t.Fatalf("error setting read deadline: %v", err) | |||||
| } | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if c.Closed() { | |||||
| t.Error("SetReadDeadline() should not mark manual conn as idle") | |||||
| } | |||||
| } | |||||
| func TestAutomaticReadDeadline(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Automatic) | |||||
| if err := MarkInUse(wc); err != nil { | |||||
| t.Fatalf("error marking connection in use: %v", err) | |||||
| } | |||||
| if err := wc.SetReadDeadline(time.Now()); err != nil { | |||||
| t.Fatalf("error setting read deadline: %v", err) | |||||
| } | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if c.Closed() { | |||||
| t.Error("SetReadDeadline() should not mark automatic conn as idle") | |||||
| } | |||||
| } | |||||
| func TestDeadlineReadDeadline(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Deadline) | |||||
| if err := MarkInUse(wc); err != nil { | |||||
| t.Fatalf("error marking connection in use: %v", err) | |||||
| } | |||||
| if err := wc.SetReadDeadline(time.Now()); err != nil { | |||||
| t.Fatalf("error setting read deadline: %v", err) | |||||
| } | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if !c.Closed() { | |||||
| t.Error("SetReadDeadline() should mark deadline conn as idle") | |||||
| } | |||||
| } | |||||
| type readerConn struct { | |||||
| fakeConn | |||||
| } | |||||
| func (rc *readerConn) ReadFrom(r io.Reader) (int64, error) { | |||||
| return 123, nil | |||||
| } | |||||
| func TestReadFrom(t *testing.T) { | |||||
| t.Parallel() | |||||
| l := makeFakeListener("net.Listener") | |||||
| wl := Wrap(l, Manual) | |||||
| c := &readerConn{ | |||||
| fakeConn{ | |||||
| read: make(chan struct{}), | |||||
| write: make(chan struct{}), | |||||
| closed: make(chan struct{}), | |||||
| me: fakeAddr{"tcp", "local"}, | |||||
| you: fakeAddr{"tcp", "remote"}, | |||||
| }, | |||||
| } | |||||
| go l.Enqueue(c) | |||||
| wc, err := wl.Accept() | |||||
| if err != nil { | |||||
| t.Fatalf("error accepting connection: %v", err) | |||||
| } | |||||
| // The io.MultiReader is a convenient hack to ensure that we're using | |||||
| // our ReadFrom, not strings.Reader's WriteTo. | |||||
| r := io.MultiReader(strings.NewReader("hello world")) | |||||
| if _, err := io.Copy(wc, r); err != nil { | |||||
| t.Fatalf("error copying: %v", err) | |||||
| } | |||||
| } | |||||
| @ -1,123 +0,0 @@ | |||||
| package listener | |||||
| import ( | |||||
| "net" | |||||
| "time" | |||||
| ) | |||||
| type fakeAddr struct { | |||||
| network, addr string | |||||
| } | |||||
| func (f fakeAddr) Network() string { | |||||
| return f.network | |||||
| } | |||||
| func (f fakeAddr) String() string { | |||||
| return f.addr | |||||
| } | |||||
| type fakeListener struct { | |||||
| ch chan net.Conn | |||||
| closed chan struct{} | |||||
| addr net.Addr | |||||
| } | |||||
| func makeFakeListener(addr string) *fakeListener { | |||||
| a := fakeAddr{"tcp", addr} | |||||
| return &fakeListener{ | |||||
| ch: make(chan net.Conn), | |||||
| closed: make(chan struct{}), | |||||
| addr: a, | |||||
| } | |||||
| } | |||||
| func (f *fakeListener) Accept() (net.Conn, error) { | |||||
| select { | |||||
| case c := <-f.ch: | |||||
| return c, nil | |||||
| case <-f.closed: | |||||
| return nil, errClosing | |||||
| } | |||||
| } | |||||
| func (f *fakeListener) Close() error { | |||||
| close(f.closed) | |||||
| return nil | |||||
| } | |||||
| func (f *fakeListener) Addr() net.Addr { | |||||
| return f.addr | |||||
| } | |||||
| func (f *fakeListener) Enqueue(c net.Conn) { | |||||
| f.ch <- c | |||||
| } | |||||
| type fakeConn struct { | |||||
| read, write, closed chan struct{} | |||||
| me, you net.Addr | |||||
| } | |||||
| func makeFakeConn(me, you string) *fakeConn { | |||||
| return &fakeConn{ | |||||
| read: make(chan struct{}), | |||||
| write: make(chan struct{}), | |||||
| closed: make(chan struct{}), | |||||
| me: fakeAddr{"tcp", me}, | |||||
| you: fakeAddr{"tcp", you}, | |||||
| } | |||||
| } | |||||
| func (f *fakeConn) Read(buf []byte) (int, error) { | |||||
| select { | |||||
| case <-f.read: | |||||
| return len(buf), nil | |||||
| case <-f.closed: | |||||
| return 0, errClosing | |||||
| } | |||||
| } | |||||
| func (f *fakeConn) Write(buf []byte) (int, error) { | |||||
| select { | |||||
| case <-f.write: | |||||
| return len(buf), nil | |||||
| case <-f.closed: | |||||
| return 0, errClosing | |||||
| } | |||||
| } | |||||
| func (f *fakeConn) Close() error { | |||||
| close(f.closed) | |||||
| return nil | |||||
| } | |||||
| func (f *fakeConn) LocalAddr() net.Addr { | |||||
| return f.me | |||||
| } | |||||
| func (f *fakeConn) RemoteAddr() net.Addr { | |||||
| return f.you | |||||
| } | |||||
| func (f *fakeConn) SetDeadline(t time.Time) error { | |||||
| return nil | |||||
| } | |||||
| func (f *fakeConn) SetReadDeadline(t time.Time) error { | |||||
| return nil | |||||
| } | |||||
| func (f *fakeConn) SetWriteDeadline(t time.Time) error { | |||||
| return nil | |||||
| } | |||||
| func (f *fakeConn) Closed() bool { | |||||
| select { | |||||
| case <-f.closed: | |||||
| return true | |||||
| default: | |||||
| return false | |||||
| } | |||||
| } | |||||
| func (f *fakeConn) AllowRead() { | |||||
| f.read <- struct{}{} | |||||
| } | |||||
| func (f *fakeConn) AllowWrite() { | |||||
| f.write <- struct{}{} | |||||
| } | |||||
| @ -1,178 +0,0 @@ | |||||
| /* | |||||
| Package listener provides a way to incorporate graceful shutdown to any | |||||
| net.Listener. | |||||
| This package provides low-level primitives, not a high-level API. If you're | |||||
| looking for a package that provides graceful shutdown for HTTP servers, I | |||||
| recommend this package's parent package, github.com/zenazn/goji/graceful. | |||||
| */ | |||||
| package listener | |||||
| import ( | |||||
| "errors" | |||||
| "net" | |||||
| "runtime" | |||||
| "sync" | |||||
| "sync/atomic" | |||||
| ) | |||||
| type mode int8 | |||||
| const ( | |||||
| // Manual mode is completely manual: users must use use MarkIdle and | |||||
| // MarkInUse to indicate when connections are busy servicing requests or | |||||
| // are eligible for termination. | |||||
| Manual mode = iota | |||||
| // Automatic mode is what most users probably want: calling Read on a | |||||
| // connection will mark it as in use, but users must manually call | |||||
| // MarkIdle to indicate when connections may be safely closed. | |||||
| Automatic | |||||
| // Deadline mode is like automatic mode, except that calling | |||||
| // SetReadDeadline on a connection will also mark it as being idle. This | |||||
| // is useful for many servers like net/http, where SetReadDeadline is | |||||
| // used to implement read timeouts on new requests. | |||||
| Deadline | |||||
| ) | |||||
| // Wrap a net.Listener, returning a net.Listener which supports idle connection | |||||
| // tracking and shutdown. Listeners can be placed in to one of three modes, | |||||
| // exported as variables from this package: most users will probably want the | |||||
| // "Automatic" mode. | |||||
| func Wrap(l net.Listener, m mode) *T { | |||||
| t := &T{ | |||||
| l: l, | |||||
| mode: m, | |||||
| // To keep the expected contention rate constant we'd have to | |||||
| // grow this as numcpu**2. In practice, CPU counts don't | |||||
| // generally grow without bound, and contention is probably | |||||
| // going to be small enough that nobody cares anyways. | |||||
| shards: make([]shard, 2*runtime.NumCPU()), | |||||
| } | |||||
| for i := range t.shards { | |||||
| t.shards[i].init(t) | |||||
| } | |||||
| return t | |||||
| } | |||||
| // T is the type of this package's graceful listeners. | |||||
| type T struct { | |||||
| mu sync.Mutex | |||||
| l net.Listener | |||||
| // TODO(carl): a count of currently outstanding connections. | |||||
| connCount uint64 | |||||
| shards []shard | |||||
| mode mode | |||||
| } | |||||
| var _ net.Listener = &T{} | |||||
| // Accept waits for and returns the next connection to the listener. The | |||||
| // returned net.Conn's idleness is tracked, and idle connections can be closed | |||||
| // from the associated T. | |||||
| func (t *T) Accept() (net.Conn, error) { | |||||
| c, err := t.l.Accept() | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| connID := atomic.AddUint64(&t.connCount, 1) | |||||
| shard := &t.shards[int(connID)%len(t.shards)] | |||||
| wc := &conn{ | |||||
| Conn: c, | |||||
| shard: shard, | |||||
| mode: t.mode, | |||||
| } | |||||
| if err = wc.init(); err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return wc, nil | |||||
| } | |||||
| // Addr returns the wrapped listener's network address. | |||||
| func (t *T) Addr() net.Addr { | |||||
| return t.l.Addr() | |||||
| } | |||||
| // Close closes the wrapped listener. | |||||
| func (t *T) Close() error { | |||||
| return t.l.Close() | |||||
| } | |||||
| // CloseIdle closes all connections that are currently marked as being idle. It, | |||||
| // however, makes no attempt to wait for in-use connections to die, or to close | |||||
| // connections which become idle in the future. Call this function if you're | |||||
| // interested in shedding useless connections, but otherwise wish to continue | |||||
| // serving requests. | |||||
| func (t *T) CloseIdle() error { | |||||
| for i := range t.shards { | |||||
| t.shards[i].closeConns(false, false) | |||||
| } | |||||
| // Not sure if returning errors is actually useful here :/ | |||||
| return nil | |||||
| } | |||||
| // Drain immediately closes all idle connections, prevents new connections from | |||||
| // being accepted, and waits for all outstanding connections to finish. | |||||
| // | |||||
| // Once a listener has been drained, there is no way to re-enable it. You | |||||
| // probably want to Close the listener before draining it, otherwise new | |||||
| // connections will be accepted and immediately closed. | |||||
| func (t *T) Drain() error { | |||||
| for i := range t.shards { | |||||
| t.shards[i].closeConns(false, true) | |||||
| } | |||||
| for i := range t.shards { | |||||
| t.shards[i].wait() | |||||
| } | |||||
| return nil | |||||
| } | |||||
| // DrainAll closes all connections currently tracked by this listener (both idle | |||||
| // and in-use connections), and prevents new connections from being accepted. | |||||
| // Disowned connections are not closed. | |||||
| func (t *T) DrainAll() error { | |||||
| for i := range t.shards { | |||||
| t.shards[i].closeConns(true, true) | |||||
| } | |||||
| for i := range t.shards { | |||||
| t.shards[i].wait() | |||||
| } | |||||
| return nil | |||||
| } | |||||
| var errNotManaged = errors.New("listener: passed net.Conn is not managed by this package") | |||||
| // Disown causes a connection to no longer be tracked by the listener. The | |||||
| // passed connection must have been returned by a call to Accept from this | |||||
| // listener. | |||||
| func Disown(c net.Conn) error { | |||||
| if cn, ok := c.(*conn); ok { | |||||
| return cn.disown() | |||||
| } | |||||
| return errNotManaged | |||||
| } | |||||
| // MarkIdle marks the given connection as being idle, and therefore eligible for | |||||
| // closing at any time. The passed connection must have been returned by a call | |||||
| // to Accept from this listener. | |||||
| func MarkIdle(c net.Conn) error { | |||||
| if cn, ok := c.(*conn); ok { | |||||
| cn.markIdle() | |||||
| return nil | |||||
| } | |||||
| return errNotManaged | |||||
| } | |||||
| // MarkInUse marks this connection as being in use, removing it from the set of | |||||
| // connections which are eligible for closing. The passed connection must have | |||||
| // been returned by a call to Accept from this listener. | |||||
| func MarkInUse(c net.Conn) error { | |||||
| if cn, ok := c.(*conn); ok { | |||||
| cn.markInUse() | |||||
| return nil | |||||
| } | |||||
| return errNotManaged | |||||
| } | |||||
| @ -1,156 +0,0 @@ | |||||
| package listener | |||||
| import ( | |||||
| "net" | |||||
| "testing" | |||||
| "time" | |||||
| ) | |||||
| // Helper for tests acting on a single accepted connection | |||||
| func singleConn(t *testing.T, m mode) (*T, *fakeConn, net.Conn) { | |||||
| l := makeFakeListener("net.Listener") | |||||
| wl := Wrap(l, m) | |||||
| c := makeFakeConn("local", "remote") | |||||
| go l.Enqueue(c) | |||||
| wc, err := wl.Accept() | |||||
| if err != nil { | |||||
| t.Fatalf("error accepting connection: %v", err) | |||||
| } | |||||
| return wl, c, wc | |||||
| } | |||||
| func TestAddr(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Manual) | |||||
| if a := l.Addr(); a.String() != "net.Listener" { | |||||
| t.Errorf("addr was %v, wanted net.Listener", a) | |||||
| } | |||||
| if c.LocalAddr() != wc.LocalAddr() { | |||||
| t.Errorf("local addresses don't match: %v, %v", c.LocalAddr(), | |||||
| wc.LocalAddr()) | |||||
| } | |||||
| if c.RemoteAddr() != wc.RemoteAddr() { | |||||
| t.Errorf("remote addresses don't match: %v, %v", c.RemoteAddr(), | |||||
| wc.RemoteAddr()) | |||||
| } | |||||
| } | |||||
| func TestBasicCloseIdle(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, _ := singleConn(t, Manual) | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if !c.Closed() { | |||||
| t.Error("idle connection not closed") | |||||
| } | |||||
| } | |||||
| func TestMark(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Manual) | |||||
| if err := MarkInUse(wc); err != nil { | |||||
| t.Fatalf("error marking %v in-use: %v", wc, err) | |||||
| } | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if c.Closed() { | |||||
| t.Errorf("manually in-use connection was closed") | |||||
| } | |||||
| if err := MarkIdle(wc); err != nil { | |||||
| t.Fatalf("error marking %v idle: %v", wc, err) | |||||
| } | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if !c.Closed() { | |||||
| t.Error("manually idle connection was not closed") | |||||
| } | |||||
| } | |||||
| func TestDisown(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Manual) | |||||
| if err := Disown(wc); err != nil { | |||||
| t.Fatalf("error disowning connection: %v", err) | |||||
| } | |||||
| if err := l.CloseIdle(); err != nil { | |||||
| t.Fatalf("error closing idle connections: %v", err) | |||||
| } | |||||
| if c.Closed() { | |||||
| t.Errorf("disowned connection got closed") | |||||
| } | |||||
| } | |||||
| func TestDrain(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, _, wc := singleConn(t, Manual) | |||||
| MarkInUse(wc) | |||||
| start := time.Now() | |||||
| go func() { | |||||
| time.Sleep(50 * time.Millisecond) | |||||
| MarkIdle(wc) | |||||
| }() | |||||
| if err := l.Drain(); err != nil { | |||||
| t.Fatalf("error draining listener: %v", err) | |||||
| } | |||||
| end := time.Now() | |||||
| if dt := end.Sub(start); dt < 50*time.Millisecond { | |||||
| t.Errorf("expected at least 50ms wait, but got %v", dt) | |||||
| } | |||||
| } | |||||
| func TestDrainAll(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, wc := singleConn(t, Manual) | |||||
| MarkInUse(wc) | |||||
| if err := l.DrainAll(); err != nil { | |||||
| t.Fatalf("error draining listener: %v", err) | |||||
| } | |||||
| if !c.Closed() { | |||||
| t.Error("expected in-use connection to be closed") | |||||
| } | |||||
| } | |||||
| func TestErrors(t *testing.T) { | |||||
| t.Parallel() | |||||
| _, c, wc := singleConn(t, Manual) | |||||
| if err := Disown(c); err == nil { | |||||
| t.Error("expected error when disowning unmanaged net.Conn") | |||||
| } | |||||
| if err := MarkIdle(c); err == nil { | |||||
| t.Error("expected error when marking unmanaged net.Conn idle") | |||||
| } | |||||
| if err := MarkInUse(c); err == nil { | |||||
| t.Error("expected error when marking unmanaged net.Conn in use") | |||||
| } | |||||
| if err := Disown(wc); err != nil { | |||||
| t.Fatalf("unexpected error disowning socket: %v", err) | |||||
| } | |||||
| if err := Disown(wc); err == nil { | |||||
| t.Error("expected error disowning socket twice") | |||||
| } | |||||
| } | |||||
| func TestClose(t *testing.T) { | |||||
| t.Parallel() | |||||
| l, c, _ := singleConn(t, Manual) | |||||
| if err := l.Close(); err != nil { | |||||
| t.Fatalf("error while closing listener: %v", err) | |||||
| } | |||||
| if c.Closed() { | |||||
| t.Error("connection closed when listener was?") | |||||
| } | |||||
| } | |||||
| @ -1,103 +0,0 @@ | |||||
| package listener | |||||
| import ( | |||||
| "fmt" | |||||
| "math/rand" | |||||
| "runtime" | |||||
| "sync/atomic" | |||||
| "testing" | |||||
| "time" | |||||
| ) | |||||
| func init() { | |||||
| // Just to make sure we get some variety | |||||
| runtime.GOMAXPROCS(4 * runtime.NumCPU()) | |||||
| } | |||||
| // Chosen by random die roll | |||||
| const seed = 4611413766552969250 | |||||
| // This is mostly just fuzzing to see what happens. | |||||
| func TestRace(t *testing.T) { | |||||
| t.Parallel() | |||||
| l := makeFakeListener("net.Listener") | |||||
| wl := Wrap(l, Automatic) | |||||
| var flag int32 | |||||
| go func() { | |||||
| for i := 0; ; i++ { | |||||
| laddr := fmt.Sprintf("local%d", i) | |||||
| raddr := fmt.Sprintf("remote%d", i) | |||||
| c := makeFakeConn(laddr, raddr) | |||||
| go func() { | |||||
| defer func() { | |||||
| if r := recover(); r != nil { | |||||
| if atomic.LoadInt32(&flag) != 0 { | |||||
| return | |||||
| } | |||||
| panic(r) | |||||
| } | |||||
| }() | |||||
| l.Enqueue(c) | |||||
| }() | |||||
| wc, err := wl.Accept() | |||||
| if err != nil { | |||||
| if atomic.LoadInt32(&flag) != 0 { | |||||
| return | |||||
| } | |||||
| t.Fatalf("error accepting connection: %v", err) | |||||
| } | |||||
| go func() { | |||||
| for { | |||||
| time.Sleep(50 * time.Millisecond) | |||||
| c.AllowRead() | |||||
| } | |||||
| }() | |||||
| go func(i int64) { | |||||
| rng := rand.New(rand.NewSource(i + seed)) | |||||
| buf := make([]byte, 1024) | |||||
| for j := 0; j < 1024; j++ { | |||||
| if _, err := wc.Read(buf); err != nil { | |||||
| if atomic.LoadInt32(&flag) != 0 { | |||||
| // Peaceful; the connection has | |||||
| // probably been closed while | |||||
| // idle | |||||
| return | |||||
| } | |||||
| t.Errorf("error reading in conn %d: %v", | |||||
| i, err) | |||||
| } | |||||
| time.Sleep(time.Duration(rng.Intn(100)) * time.Millisecond) | |||||
| // This one is to make sure the connection | |||||
| // hasn't closed underneath us | |||||
| if _, err := wc.Read(buf); err != nil { | |||||
| t.Errorf("error reading in conn %d: %v", | |||||
| i, err) | |||||
| } | |||||
| MarkIdle(wc) | |||||
| time.Sleep(time.Duration(rng.Intn(100)) * time.Millisecond) | |||||
| } | |||||
| }(int64(i)) | |||||
| time.Sleep(time.Duration(i) * time.Millisecond / 2) | |||||
| } | |||||
| }() | |||||
| if testing.Short() { | |||||
| time.Sleep(2 * time.Second) | |||||
| } else { | |||||
| time.Sleep(10 * time.Second) | |||||
| } | |||||
| start := time.Now() | |||||
| atomic.StoreInt32(&flag, 1) | |||||
| wl.Close() | |||||
| wl.Drain() | |||||
| end := time.Now() | |||||
| if dt := end.Sub(start); dt > 300*time.Millisecond { | |||||
| t.Errorf("took %v to drain; expected shorter", dt) | |||||
| } | |||||
| } | |||||
| @ -1,98 +0,0 @@ | |||||
| package listener | |||||
| import "sync" | |||||
| type shard struct { | |||||
| l *T | |||||
| mu sync.Mutex | |||||
| idle map[*conn]struct{} | |||||
| all map[*conn]struct{} | |||||
| wg sync.WaitGroup | |||||
| drain bool | |||||
| } | |||||
| // We pretty aggressively preallocate set entries in the hopes that we never | |||||
| // have to allocate memory with the lock held. This is definitely a premature | |||||
| // optimization and is probably misguided, but luckily it costs us essentially | |||||
| // nothing. | |||||
| const prealloc = 2048 | |||||
| func (s *shard) init(l *T) { | |||||
| s.l = l | |||||
| s.idle = make(map[*conn]struct{}, prealloc) | |||||
| s.all = make(map[*conn]struct{}, prealloc) | |||||
| } | |||||
| func (s *shard) track(c *conn) (shouldClose bool) { | |||||
| s.mu.Lock() | |||||
| if s.drain { | |||||
| s.mu.Unlock() | |||||
| return true | |||||
| } | |||||
| s.all[c] = struct{}{} | |||||
| s.idle[c] = struct{}{} | |||||
| s.mu.Unlock() | |||||
| return false | |||||
| } | |||||
| func (s *shard) disown(c *conn) { | |||||
| s.mu.Lock() | |||||
| delete(s.all, c) | |||||
| delete(s.idle, c) | |||||
| s.mu.Unlock() | |||||
| } | |||||
| func (s *shard) markIdle(c *conn) (shouldClose bool) { | |||||
| s.mu.Lock() | |||||
| if s.drain { | |||||
| s.mu.Unlock() | |||||
| return true | |||||
| } | |||||
| s.idle[c] = struct{}{} | |||||
| s.mu.Unlock() | |||||
| return false | |||||
| } | |||||
| func (s *shard) markInUse(c *conn) { | |||||
| s.mu.Lock() | |||||
| delete(s.idle, c) | |||||
| s.mu.Unlock() | |||||
| } | |||||
| func (s *shard) closeConns(all, drain bool) { | |||||
| s.mu.Lock() | |||||
| if drain { | |||||
| s.drain = true | |||||
| } | |||||
| set := make(map[*conn]struct{}, len(s.all)) | |||||
| if all { | |||||
| for c := range s.all { | |||||
| set[c] = struct{}{} | |||||
| } | |||||
| } else { | |||||
| for c := range s.idle { | |||||
| set[c] = struct{}{} | |||||
| } | |||||
| } | |||||
| // We have to drop the shard lock here to avoid deadlock: we cannot | |||||
| // acquire the shard lock after the connection lock, and the closeIfIdle | |||||
| // call below will grab a connection lock. | |||||
| s.mu.Unlock() | |||||
| for c := range set { | |||||
| // This might return an error (from Close), but I don't think we | |||||
| // can do anything about it, so let's just pretend it didn't | |||||
| // happen. (I also expect that most errors returned in this way | |||||
| // are going to be pretty boring) | |||||
| if all { | |||||
| c.Close() | |||||
| } else { | |||||
| c.closeIfIdle() | |||||
| } | |||||
| } | |||||
| } | |||||
| func (s *shard) wait() { | |||||
| s.wg.Wait() | |||||
| } | |||||
| @ -1,103 +0,0 @@ | |||||
| // +build !go1.3 | |||||
| package graceful | |||||
| import ( | |||||
| "bufio" | |||||
| "io" | |||||
| "net" | |||||
| "net/http" | |||||
| "sync/atomic" | |||||
| "github.com/zenazn/goji/graceful/listener" | |||||
| ) | |||||
| // Middleware provides functionality similar to net/http.Server's | |||||
| // SetKeepAlivesEnabled in Go 1.3, but in Go 1.2. | |||||
| func middleware(h http.Handler) http.Handler { | |||||
| if h == nil { | |||||
| return nil | |||||
| } | |||||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| _, cn := w.(http.CloseNotifier) | |||||
| _, fl := w.(http.Flusher) | |||||
| _, hj := w.(http.Hijacker) | |||||
| _, rf := w.(io.ReaderFrom) | |||||
| bw := basicWriter{ResponseWriter: w} | |||||
| if cn && fl && hj && rf { | |||||
| h.ServeHTTP(&fancyWriter{bw}, r) | |||||
| } else { | |||||
| h.ServeHTTP(&bw, r) | |||||
| } | |||||
| if !bw.headerWritten { | |||||
| bw.maybeClose() | |||||
| } | |||||
| }) | |||||
| } | |||||
| type basicWriter struct { | |||||
| http.ResponseWriter | |||||
| headerWritten bool | |||||
| } | |||||
| func (b *basicWriter) maybeClose() { | |||||
| b.headerWritten = true | |||||
| if atomic.LoadInt32(&closing) != 0 { | |||||
| b.ResponseWriter.Header().Set("Connection", "close") | |||||
| } | |||||
| } | |||||
| func (b *basicWriter) WriteHeader(code int) { | |||||
| b.maybeClose() | |||||
| b.ResponseWriter.WriteHeader(code) | |||||
| } | |||||
| func (b *basicWriter) Write(buf []byte) (int, error) { | |||||
| if !b.headerWritten { | |||||
| b.maybeClose() | |||||
| } | |||||
| return b.ResponseWriter.Write(buf) | |||||
| } | |||||
| func (b *basicWriter) Unwrap() http.ResponseWriter { | |||||
| return b.ResponseWriter | |||||
| } | |||||
| // Optimize for the common case of a ResponseWriter that supports all three of | |||||
| // CloseNotifier, Flusher, and Hijacker. | |||||
| type fancyWriter struct { | |||||
| basicWriter | |||||
| } | |||||
| func (f *fancyWriter) CloseNotify() <-chan bool { | |||||
| cn := f.basicWriter.ResponseWriter.(http.CloseNotifier) | |||||
| return cn.CloseNotify() | |||||
| } | |||||
| func (f *fancyWriter) Flush() { | |||||
| fl := f.basicWriter.ResponseWriter.(http.Flusher) | |||||
| fl.Flush() | |||||
| } | |||||
| func (f *fancyWriter) Hijack() (c net.Conn, b *bufio.ReadWriter, e error) { | |||||
| hj := f.basicWriter.ResponseWriter.(http.Hijacker) | |||||
| c, b, e = hj.Hijack() | |||||
| if e == nil { | |||||
| e = listener.Disown(c) | |||||
| } | |||||
| return | |||||
| } | |||||
| func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) { | |||||
| rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) | |||||
| if !f.basicWriter.headerWritten { | |||||
| f.basicWriter.maybeClose() | |||||
| } | |||||
| return rf.ReadFrom(r) | |||||
| } | |||||
| var _ http.CloseNotifier = &fancyWriter{} | |||||
| var _ http.Flusher = &fancyWriter{} | |||||
| var _ http.Hijacker = &fancyWriter{} | |||||
| var _ io.ReaderFrom = &fancyWriter{} | |||||
| @ -1,71 +0,0 @@ | |||||
| // +build !go1.3 | |||||
| package graceful | |||||
| import ( | |||||
| "net/http" | |||||
| "sync/atomic" | |||||
| "testing" | |||||
| ) | |||||
| type fakeWriter http.Header | |||||
| func (f fakeWriter) Header() http.Header { | |||||
| return http.Header(f) | |||||
| } | |||||
| func (f fakeWriter) Write(buf []byte) (int, error) { | |||||
| return len(buf), nil | |||||
| } | |||||
| func (f fakeWriter) WriteHeader(status int) {} | |||||
| func testClose(t *testing.T, h http.Handler, expectClose bool) { | |||||
| m := middleware(h) | |||||
| r, _ := http.NewRequest("GET", "/", nil) | |||||
| w := make(fakeWriter) | |||||
| m.ServeHTTP(w, r) | |||||
| c, ok := w["Connection"] | |||||
| if expectClose { | |||||
| if !ok || len(c) != 1 || c[0] != "close" { | |||||
| t.Fatal("Expected 'Connection: close'") | |||||
| } | |||||
| } else { | |||||
| if ok { | |||||
| t.Fatal("Did not expect Connection header") | |||||
| } | |||||
| } | |||||
| } | |||||
| func TestNormal(t *testing.T) { | |||||
| atomic.StoreInt32(&closing, 0) | |||||
| h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| w.Write([]byte{}) | |||||
| }) | |||||
| testClose(t, h, false) | |||||
| } | |||||
| func TestClose(t *testing.T) { | |||||
| atomic.StoreInt32(&closing, 0) | |||||
| h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| atomic.StoreInt32(&closing, 1) | |||||
| }) | |||||
| testClose(t, h, true) | |||||
| } | |||||
| func TestCloseWriteHeader(t *testing.T) { | |||||
| atomic.StoreInt32(&closing, 0) | |||||
| h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| atomic.StoreInt32(&closing, 1) | |||||
| w.WriteHeader(200) | |||||
| }) | |||||
| testClose(t, h, true) | |||||
| } | |||||
| func TestCloseWrite(t *testing.T) { | |||||
| atomic.StoreInt32(&closing, 0) | |||||
| h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| atomic.StoreInt32(&closing, 1) | |||||
| w.Write([]byte{}) | |||||
| }) | |||||
| testClose(t, h, true) | |||||
| } | |||||
| @ -1,33 +0,0 @@ | |||||
| // +build !go1.3 | |||||
| package graceful | |||||
| import ( | |||||
| "net" | |||||
| "net/http" | |||||
| "time" | |||||
| "github.com/zenazn/goji/graceful/listener" | |||||
| ) | |||||
| // About 200 years, also known as "forever" | |||||
| const forever time.Duration = 200 * 365 * 24 * time.Hour | |||||
| // Serve behaves like the method on net/http.Server with the same name. | |||||
| func (srv *Server) Serve(l net.Listener) error { | |||||
| // Spawn a shadow http.Server to do the actual servering. We do this | |||||
| // because we need to sketch on some of the parameters you passed in, | |||||
| // and it's nice to keep our sketching to ourselves. | |||||
| shadow := *(*http.Server)(srv) | |||||
| if shadow.ReadTimeout == 0 { | |||||
| shadow.ReadTimeout = forever | |||||
| } | |||||
| shadow.Handler = middleware(shadow.Handler) | |||||
| wrap := listener.Wrap(l, listener.Deadline) | |||||
| appendListener(wrap) | |||||
| err := shadow.Serve(wrap) | |||||
| return peacefulError(err) | |||||
| } | |||||
| @ -1,76 +0,0 @@ | |||||
| // +build go1.3 | |||||
| package graceful | |||||
| import ( | |||||
| "log" | |||||
| "net" | |||||
| "net/http" | |||||
| "github.com/zenazn/goji/graceful/listener" | |||||
| ) | |||||
| // This is a slightly hacky shim to disable keepalives when shutting a server | |||||
| // down. We could have added extra functionality in listener or signal.go to | |||||
| // deal with this case, but this seems simpler. | |||||
| type gracefulServer struct { | |||||
| net.Listener | |||||
| s *http.Server | |||||
| } | |||||
| func (g gracefulServer) Close() error { | |||||
| g.s.SetKeepAlivesEnabled(false) | |||||
| return g.Listener.Close() | |||||
| } | |||||
| // A chaining http.ConnState wrapper | |||||
| type connState func(net.Conn, http.ConnState) | |||||
| func (c connState) Wrap(nc net.Conn, s http.ConnState) { | |||||
| // There are a few other states defined, most notably StateActive. | |||||
| // Unfortunately it doesn't look like it's possible to make use of | |||||
| // StateActive to implement graceful shutdown, since StateActive is set | |||||
| // after a complete request has been read off the wire with an intent to | |||||
| // process it. If we were to race a graceful shutdown against a | |||||
| // connection that was just read off the wire (but not yet in | |||||
| // StateActive), we would accidentally close the connection out from | |||||
| // underneath an active request. | |||||
| // | |||||
| // We already needed to work around this for Go 1.2 by shimming out a | |||||
| // full net.Conn object, so we can just fall back to the old behavior | |||||
| // there. | |||||
| // | |||||
| // I started a golang-nuts thread about this here: | |||||
| // https://groups.google.com/forum/#!topic/golang-nuts/Xi8yjBGWfCQ | |||||
| // I'd be very eager to find a better way to do this, so reach out to me | |||||
| // if you have any ideas. | |||||
| switch s { | |||||
| case http.StateIdle: | |||||
| if err := listener.MarkIdle(nc); err != nil { | |||||
| log.Printf("error marking conn as idle: %v", err) | |||||
| } | |||||
| case http.StateHijacked: | |||||
| if err := listener.Disown(nc); err != nil { | |||||
| log.Printf("error disowning hijacked conn: %v", err) | |||||
| } | |||||
| } | |||||
| if c != nil { | |||||
| c(nc, s) | |||||
| } | |||||
| } | |||||
| // Serve behaves like the method on net/http.Server with the same name. | |||||
| func (srv *Server) Serve(l net.Listener) error { | |||||
| // Spawn a shadow http.Server to do the actual servering. We do this | |||||
| // because we need to sketch on some of the parameters you passed in, | |||||
| // and it's nice to keep our sketching to ourselves. | |||||
| shadow := *(*http.Server)(srv) | |||||
| shadow.ConnState = connState(shadow.ConnState).Wrap | |||||
| l = gracefulServer{l, &shadow} | |||||
| wrap := listener.Wrap(l, listener.Automatic) | |||||
| appendListener(wrap) | |||||
| err := shadow.Serve(wrap) | |||||
| return peacefulError(err) | |||||
| } | |||||
| @ -1,108 +0,0 @@ | |||||
| package graceful | |||||
| import ( | |||||
| "crypto/tls" | |||||
| "net" | |||||
| "net/http" | |||||
| "time" | |||||
| ) | |||||
| // Most of the code here is lifted straight from net/http | |||||
| // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted | |||||
| // connections. It's used by ListenAndServe and ListenAndServeTLS so | |||||
| // dead TCP connections (e.g. closing laptop mid-download) eventually | |||||
| // go away. | |||||
| type tcpKeepAliveListener struct { | |||||
| *net.TCPListener | |||||
| } | |||||
| func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { | |||||
| tc, err := ln.AcceptTCP() | |||||
| if err != nil { | |||||
| return | |||||
| } | |||||
| tc.SetKeepAlive(true) | |||||
| tc.SetKeepAlivePeriod(3 * time.Minute) | |||||
| return tc, nil | |||||
| } | |||||
| // A Server is exactly the same as an http.Server, but provides more graceful | |||||
| // implementations of its methods. | |||||
| type Server http.Server | |||||
| // ListenAndServe behaves like the method on net/http.Server with the same name. | |||||
| func (srv *Server) ListenAndServe() error { | |||||
| addr := srv.Addr | |||||
| if addr == "" { | |||||
| addr = ":http" | |||||
| } | |||||
| ln, err := net.Listen("tcp", addr) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) | |||||
| } | |||||
| // ListenAndServeTLS behaves like the method on net/http.Server with the same | |||||
| // name. Unlike the method of the same name on http.Server, this function | |||||
| // defaults to enforcing TLS 1.0 or higher in order to address the POODLE | |||||
| // vulnerability. Users who wish to enable SSLv3 must do so by supplying a | |||||
| // TLSConfig explicitly. | |||||
| func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { | |||||
| addr := srv.Addr | |||||
| if addr == "" { | |||||
| addr = ":https" | |||||
| } | |||||
| config := &tls.Config{ | |||||
| MinVersion: tls.VersionTLS10, | |||||
| } | |||||
| if srv.TLSConfig != nil { | |||||
| config = cloneTLSConfig(srv.TLSConfig) | |||||
| } | |||||
| if config.NextProtos == nil { | |||||
| config.NextProtos = []string{"http/1.1"} | |||||
| } | |||||
| var err error | |||||
| config.Certificates = make([]tls.Certificate, 1) | |||||
| config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| ln, err := net.Listen("tcp", addr) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) | |||||
| return srv.Serve(tlsListener) | |||||
| } | |||||
| // ListenAndServe behaves exactly like the net/http function of the same name. | |||||
| func ListenAndServe(addr string, handler http.Handler) error { | |||||
| server := &Server{Addr: addr, Handler: handler} | |||||
| return server.ListenAndServe() | |||||
| } | |||||
| // ListenAndServeTLS behaves almost exactly like the net/http function of the | |||||
| // same name. Unlike net/http, however, this function defaults to enforcing TLS | |||||
| // 1.0 or higher in order to address the POODLE vulnerability. Users who wish to | |||||
| // enable SSLv3 must do so by explicitly instantiating a server with an | |||||
| // appropriately configured TLSConfig property. | |||||
| func ListenAndServeTLS(addr, certfile, keyfile string, handler http.Handler) error { | |||||
| server := &Server{Addr: addr, Handler: handler} | |||||
| return server.ListenAndServeTLS(certfile, keyfile) | |||||
| } | |||||
| // Serve mostly behaves like the net/http function of the same name, except that | |||||
| // if the passed listener is a net.TCPListener, TCP keep-alives are enabled on | |||||
| // accepted connections. | |||||
| func Serve(l net.Listener, handler http.Handler) error { | |||||
| if tl, ok := l.(*net.TCPListener); ok { | |||||
| l = tcpKeepAliveListener{tl} | |||||
| } | |||||
| server := &Server{Handler: handler} | |||||
| return server.Serve(l) | |||||
| } | |||||
| @ -1,197 +0,0 @@ | |||||
| package graceful | |||||
| import ( | |||||
| "os" | |||||
| "os/signal" | |||||
| "sync" | |||||
| "sync/atomic" | |||||
| "time" | |||||
| "github.com/zenazn/goji/graceful/listener" | |||||
| ) | |||||
| var mu sync.Mutex // protects everything that follows | |||||
| var listeners = make([]*listener.T, 0) | |||||
| var prehooks = make([]func(), 0) | |||||
| var posthooks = make([]func(), 0) | |||||
| var closing int32 | |||||
| var doubleKick, timeout time.Duration | |||||
| var wait = make(chan struct{}) | |||||
| var stdSignals = []os.Signal{os.Interrupt} | |||||
| var sigchan = make(chan os.Signal, 1) | |||||
| // HandleSignals installs signal handlers for a set of standard signals. By | |||||
| // default, this set only includes keyboard interrupts, however when the package | |||||
| // detects that it is running under Einhorn, a SIGUSR2 handler is installed as | |||||
| // well. | |||||
| func HandleSignals() { | |||||
| AddSignal(stdSignals...) | |||||
| } | |||||
| // AddSignal adds the given signal to the set of signals that trigger a graceful | |||||
| // shutdown. | |||||
| func AddSignal(sig ...os.Signal) { | |||||
| signal.Notify(sigchan, sig...) | |||||
| } | |||||
| // ResetSignals resets the list of signals that trigger a graceful shutdown. | |||||
| func ResetSignals() { | |||||
| signal.Stop(sigchan) | |||||
| } | |||||
| // PreHook registers a function to be called before any of this package's normal | |||||
| // shutdown actions. All listeners will be called in the order they were added, | |||||
| // from a single goroutine. | |||||
| func PreHook(f func()) { | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| prehooks = append(prehooks, f) | |||||
| } | |||||
| // PostHook registers a function to be called after all of this package's normal | |||||
| // shutdown actions. All listeners will be called in the order they were added, | |||||
| // from a single goroutine, and are guaranteed to be called after all listening | |||||
| // connections have been closed, but before Wait() returns. | |||||
| // | |||||
| // If you've Hijacked any connections that must be gracefully shut down in some | |||||
| // other way (since this library disowns all hijacked connections), it's | |||||
| // reasonable to use a PostHook to signal and wait for them. | |||||
| func PostHook(f func()) { | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| posthooks = append(posthooks, f) | |||||
| } | |||||
| // Shutdown manually triggers a shutdown from your application. Like Wait, | |||||
| // blocks until all connections have gracefully shut down. | |||||
| func Shutdown() { | |||||
| shutdown(false) | |||||
| } | |||||
| // ShutdownNow triggers an immediate shutdown from your application. All | |||||
| // connections (not just those that are idle) are immediately closed, even if | |||||
| // they are in the middle of serving a request. | |||||
| func ShutdownNow() { | |||||
| shutdown(true) | |||||
| } | |||||
| // DoubleKickWindow sets the length of the window during which two back-to-back | |||||
| // signals are treated as an especially urgent or forceful request to exit | |||||
| // (i.e., ShutdownNow instead of Shutdown). Signals delivered more than this | |||||
| // duration apart are treated as separate requests to exit gracefully as usual. | |||||
| // | |||||
| // Setting DoubleKickWindow to 0 disables the feature. | |||||
| func DoubleKickWindow(d time.Duration) { | |||||
| if d < 0 { | |||||
| return | |||||
| } | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| doubleKick = d | |||||
| } | |||||
| // Timeout sets the maximum amount of time package graceful will wait for | |||||
| // connections to gracefully shut down after receiving a signal. After this | |||||
| // timeout, connections will be forcefully shut down (similar to calling | |||||
| // ShutdownNow). | |||||
| // | |||||
| // Setting Timeout to 0 disables the feature. | |||||
| func Timeout(d time.Duration) { | |||||
| if d < 0 { | |||||
| return | |||||
| } | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| timeout = d | |||||
| } | |||||
| // Wait for all connections to gracefully shut down. This is commonly called at | |||||
| // the bottom of the main() function to prevent the program from exiting | |||||
| // prematurely. | |||||
| func Wait() { | |||||
| <-wait | |||||
| } | |||||
| func init() { | |||||
| go sigLoop() | |||||
| } | |||||
| func sigLoop() { | |||||
| var last time.Time | |||||
| for { | |||||
| <-sigchan | |||||
| now := time.Now() | |||||
| mu.Lock() | |||||
| force := doubleKick != 0 && now.Sub(last) < doubleKick | |||||
| if t := timeout; t != 0 && !force { | |||||
| go func() { | |||||
| time.Sleep(t) | |||||
| shutdown(true) | |||||
| }() | |||||
| } | |||||
| mu.Unlock() | |||||
| go shutdown(force) | |||||
| last = now | |||||
| } | |||||
| } | |||||
| var preOnce, closeOnce, forceOnce, postOnce, notifyOnce sync.Once | |||||
| func shutdown(force bool) { | |||||
| preOnce.Do(func() { | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| for _, f := range prehooks { | |||||
| f() | |||||
| } | |||||
| }) | |||||
| if force { | |||||
| forceOnce.Do(func() { | |||||
| closeListeners(force) | |||||
| }) | |||||
| } else { | |||||
| closeOnce.Do(func() { | |||||
| closeListeners(force) | |||||
| }) | |||||
| } | |||||
| postOnce.Do(func() { | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| for _, f := range posthooks { | |||||
| f() | |||||
| } | |||||
| }) | |||||
| notifyOnce.Do(func() { | |||||
| close(wait) | |||||
| }) | |||||
| } | |||||
| func closeListeners(force bool) { | |||||
| atomic.StoreInt32(&closing, 1) | |||||
| var wg sync.WaitGroup | |||||
| defer wg.Wait() | |||||
| mu.Lock() | |||||
| defer mu.Unlock() | |||||
| wg.Add(len(listeners)) | |||||
| for _, l := range listeners { | |||||
| go func(l *listener.T) { | |||||
| defer wg.Done() | |||||
| l.Close() | |||||
| if force { | |||||
| l.DrainAll() | |||||
| } else { | |||||
| l.Drain() | |||||
| } | |||||
| }(l) | |||||
| } | |||||
| } | |||||
| @ -1,64 +0,0 @@ | |||||
| // +build !appengine | |||||
| package goji | |||||
| import ( | |||||
| "crypto/tls" | |||||
| "flag" | |||||
| "log" | |||||
| "net" | |||||
| "net/http" | |||||
| "time" | |||||
| "github.com/zenazn/goji/bind" | |||||
| "github.com/zenazn/goji/graceful" | |||||
| ) | |||||
| func init() { | |||||
| bind.WithFlag() | |||||
| if fl := log.Flags(); fl&log.Ltime != 0 { | |||||
| log.SetFlags(fl | log.Lmicroseconds) | |||||
| } | |||||
| graceful.DoubleKickWindow(2 * time.Second) | |||||
| } | |||||
| // Serve starts Goji using reasonable defaults. | |||||
| func Serve() { | |||||
| if !flag.Parsed() { | |||||
| flag.Parse() | |||||
| } | |||||
| ServeListener(bind.Default()) | |||||
| } | |||||
| // Like Serve, but enables TLS using the given config. | |||||
| func ServeTLS(config *tls.Config) { | |||||
| if !flag.Parsed() { | |||||
| flag.Parse() | |||||
| } | |||||
| ServeListener(tls.NewListener(bind.Default(), config)) | |||||
| } | |||||
| // Like Serve, but runs Goji on top of an arbitrary net.Listener. | |||||
| func ServeListener(listener net.Listener) { | |||||
| DefaultMux.Compile() | |||||
| // Install our handler at the root of the standard net/http default mux. | |||||
| // This allows packages like expvar to continue working as expected. | |||||
| http.Handle("/", DefaultMux) | |||||
| log.Println("Starting Goji on", listener.Addr()) | |||||
| graceful.HandleSignals() | |||||
| bind.Ready() | |||||
| graceful.PreHook(func() { log.Printf("Goji received signal, gracefully stopping") }) | |||||
| graceful.PostHook(func() { log.Printf("Goji stopped") }) | |||||
| err := graceful.Serve(listener, http.DefaultServeMux) | |||||
| if err != nil { | |||||
| log.Fatal(err) | |||||
| } | |||||
| graceful.Wait() | |||||
| } | |||||
| @ -1,23 +0,0 @@ | |||||
| // +build appengine | |||||
| package goji | |||||
| import ( | |||||
| "log" | |||||
| "net/http" | |||||
| ) | |||||
| func init() { | |||||
| if fl := log.Flags(); fl&log.Ltime != 0 { | |||||
| log.SetFlags(fl | log.Lmicroseconds) | |||||
| } | |||||
| } | |||||
| // Serve starts Goji using reasonable defaults. | |||||
| func Serve() { | |||||
| DefaultMux.Compile() | |||||
| // Install our handler at the root of the standard net/http default mux. | |||||
| // This is required for App Engine, and also allows packages like expvar | |||||
| // to continue working as expected. | |||||
| http.Handle("/", DefaultMux) | |||||
| } | |||||
| @ -1,18 +0,0 @@ | |||||
| // +build !appengine | |||||
| package web | |||||
| import ( | |||||
| "sync/atomic" | |||||
| "unsafe" | |||||
| ) | |||||
| func (rt *router) getMachine() *routeMachine { | |||||
| ptr := (*unsafe.Pointer)(unsafe.Pointer(&rt.machine)) | |||||
| sm := (*routeMachine)(atomic.LoadPointer(ptr)) | |||||
| return sm | |||||
| } | |||||
| func (rt *router) setMachine(m *routeMachine) { | |||||
| ptr := (*unsafe.Pointer)(unsafe.Pointer(&rt.machine)) | |||||
| atomic.StorePointer(ptr, unsafe.Pointer(m)) | |||||
| } | |||||
| @ -1,14 +0,0 @@ | |||||
| // +build appengine | |||||
| package web | |||||
| func (rt *router) getMachine() *routeMachine { | |||||
| rt.lock.Lock() | |||||
| defer rt.lock.Unlock() | |||||
| return rt.machine | |||||
| } | |||||
| // We always hold the lock when calling setMachine. | |||||
| func (rt *router) setMachine(m *routeMachine) { | |||||
| rt.machine = m | |||||
| } | |||||
| @ -1,166 +0,0 @@ | |||||
| // +build go1.3 | |||||
| package web | |||||
| import ( | |||||
| "crypto/rand" | |||||
| "encoding/base64" | |||||
| mrand "math/rand" | |||||
| "net/http" | |||||
| "testing" | |||||
| ) | |||||
| /* | |||||
| The core benchmarks here are based on cypriss's mux benchmarks, which can be | |||||
| found here: | |||||
| https://github.com/cypriss/golang-mux-benchmark | |||||
| They happen to play very well into Goji's router's strengths. | |||||
| */ | |||||
| type nilRouter struct{} | |||||
| var helloWorld = []byte("Hello world!\n") | |||||
| func (_ nilRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| w.Write(helloWorld) | |||||
| } | |||||
| type nilResponse struct{} | |||||
| func (_ nilResponse) Write(buf []byte) (int, error) { | |||||
| return len(buf), nil | |||||
| } | |||||
| func (_ nilResponse) Header() http.Header { | |||||
| return nil | |||||
| } | |||||
| func (_ nilResponse) WriteHeader(code int) { | |||||
| } | |||||
| func trivialMiddleware(h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| var w nilResponse | |||||
| func addRoutes(m *Mux, prefix string) { | |||||
| m.Get(prefix, nilRouter{}) | |||||
| m.Post(prefix, nilRouter{}) | |||||
| m.Get(prefix+"/:id", nilRouter{}) | |||||
| m.Put(prefix+"/:id", nilRouter{}) | |||||
| m.Delete(prefix+"/:id", nilRouter{}) | |||||
| } | |||||
| func randString() string { | |||||
| var buf [6]byte | |||||
| rand.Reader.Read(buf[:]) | |||||
| return base64.URLEncoding.EncodeToString(buf[:]) | |||||
| } | |||||
| func genPrefixes(n int) []string { | |||||
| p := make([]string, n) | |||||
| for i := range p { | |||||
| p[i] = "/" + randString() | |||||
| } | |||||
| return p | |||||
| } | |||||
| func genRequests(prefixes []string) []*http.Request { | |||||
| rs := make([]*http.Request, 5*len(prefixes)) | |||||
| for i, prefix := range prefixes { | |||||
| rs[5*i+0], _ = http.NewRequest("GET", prefix, nil) | |||||
| rs[5*i+1], _ = http.NewRequest("POST", prefix, nil) | |||||
| rs[5*i+2], _ = http.NewRequest("GET", prefix+"/foo", nil) | |||||
| rs[5*i+3], _ = http.NewRequest("PUT", prefix+"/foo", nil) | |||||
| rs[5*i+4], _ = http.NewRequest("DELETE", prefix+"/foo", nil) | |||||
| } | |||||
| return rs | |||||
| } | |||||
| func permuteRequests(reqs []*http.Request) []*http.Request { | |||||
| out := make([]*http.Request, len(reqs)) | |||||
| perm := mrand.Perm(len(reqs)) | |||||
| for i, req := range reqs { | |||||
| out[perm[i]] = req | |||||
| } | |||||
| return out | |||||
| } | |||||
| func benchN(b *testing.B, n int) { | |||||
| m := New() | |||||
| prefixes := genPrefixes(n) | |||||
| for _, prefix := range prefixes { | |||||
| addRoutes(m, prefix) | |||||
| } | |||||
| m.Compile() | |||||
| reqs := permuteRequests(genRequests(prefixes)) | |||||
| b.ResetTimer() | |||||
| b.ReportAllocs() | |||||
| b.RunParallel(func(pb *testing.PB) { | |||||
| i := 0 | |||||
| for pb.Next() { | |||||
| i++ | |||||
| m.ServeHTTP(w, reqs[i%len(reqs)]) | |||||
| } | |||||
| }) | |||||
| } | |||||
| func benchM(b *testing.B, n int) { | |||||
| m := New() | |||||
| m.Get("/", nilRouter{}) | |||||
| for i := 0; i < n; i++ { | |||||
| m.Use(trivialMiddleware) | |||||
| } | |||||
| r, _ := http.NewRequest("GET", "/", nil) | |||||
| m.Compile() | |||||
| b.ResetTimer() | |||||
| b.ReportAllocs() | |||||
| b.RunParallel(func(pb *testing.PB) { | |||||
| for pb.Next() { | |||||
| m.ServeHTTP(w, r) | |||||
| } | |||||
| }) | |||||
| } | |||||
| func BenchmarkStatic(b *testing.B) { | |||||
| m := New() | |||||
| m.Get("/", nilRouter{}) | |||||
| r, _ := http.NewRequest("GET", "/", nil) | |||||
| m.Compile() | |||||
| b.ResetTimer() | |||||
| b.ReportAllocs() | |||||
| b.RunParallel(func(pb *testing.PB) { | |||||
| for pb.Next() { | |||||
| m.ServeHTTP(w, r) | |||||
| } | |||||
| }) | |||||
| } | |||||
| func BenchmarkRoute5(b *testing.B) { | |||||
| benchN(b, 1) | |||||
| } | |||||
| func BenchmarkRoute50(b *testing.B) { | |||||
| benchN(b, 10) | |||||
| } | |||||
| func BenchmarkRoute500(b *testing.B) { | |||||
| benchN(b, 100) | |||||
| } | |||||
| func BenchmarkRoute5000(b *testing.B) { | |||||
| benchN(b, 1000) | |||||
| } | |||||
| func BenchmarkMiddleware1(b *testing.B) { | |||||
| benchM(b, 1) | |||||
| } | |||||
| func BenchmarkMiddleware10(b *testing.B) { | |||||
| benchM(b, 10) | |||||
| } | |||||
| func BenchmarkMiddleware100(b *testing.B) { | |||||
| benchM(b, 100) | |||||
| } | |||||
| @ -1,265 +0,0 @@ | |||||
| package web | |||||
| /* | |||||
| This file implements a fast router by encoding a list of routes first into a | |||||
| pseudo-trie, then encoding that pseudo-trie into a state machine realized as | |||||
| a routing bytecode. | |||||
| The most interesting part of this router is not its speed (it is quite fast), | |||||
| but the guarantees it provides. In a naive router, routes are examined one after | |||||
| another until a match is found, and this is the programming model we want to | |||||
| support. For any given request ("GET /hello/carl"), there is a list of | |||||
| "plausible" routes: routes which match the method ("GET"), and which have a | |||||
| prefix that is a prefix of the requested path ("/" and "/hello/", for instance, | |||||
| but not "/foobar"). Patterns also have some amount of arbitrary code associated | |||||
| with them, which tells us whether or not the route matched. Just like the naive | |||||
| router, our goal is to call each plausible pattern, in the order they were | |||||
| added, until we find one that matches. The "fast" part here is being smart about | |||||
| which non-plausible routes we can skip. | |||||
| First, we sort routes using a pairwise comparison function: sorting occurs as | |||||
| normal on the prefixes, with the caveat that a route may not be moved past a | |||||
| route that might also match the same string. Among other things, this means | |||||
| we're forced to use particularly dumb sorting algorithms, but it only has to | |||||
| happen once, and there probably aren't even that many routes to begin with. This | |||||
| logic appears inline in the router's handle() function. | |||||
| We then build a pseudo-trie from the sorted list of routes. It's not quite a | |||||
| normal trie because there are certain routes we cannot reorder around other | |||||
| routes (since we're providing identical semantics to the naive router), but it's | |||||
| close enough and the basic idea is the same. | |||||
| Finally, we lower this psuedo-trie from its tree representation to a state | |||||
| machine bytecode. The bytecode is pretty simple: it contains up to three bytes, | |||||
| a choice of a bunch of flags, and an index. The state machine is pretty simple: | |||||
| if the bytes match the next few bytes after the cursor, the instruction matches, | |||||
| and the state machine advances to the next instruction. If it does not match, it | |||||
| jumps to the instruction at the index. Various flags modify this basic behavior, | |||||
| the documentation for which can be found below. | |||||
| The thing we're optimizing for here over pretty much everything else is memory | |||||
| locality. We make an effort to lay out both the trie child selection logic and | |||||
| the matching of long strings consecutively in memory, making both operations | |||||
| very cheap. In fact, our matching logic isn't particularly asymptotically good, | |||||
| but in practice the benefits of memory locality outweigh just about everything | |||||
| else. | |||||
| Unfortunately, the code implementing all of this is pretty bad (both inefficient | |||||
| and hard to read). Maybe someday I'll come and take a second pass at it. | |||||
| */ | |||||
| type state struct { | |||||
| mode smMode | |||||
| bs [3]byte | |||||
| i int32 | |||||
| } | |||||
| type stateMachine []state | |||||
| type smMode uint8 | |||||
| // Many combinations of smModes don't make sense, but since this is interal to | |||||
| // the library I don't feel like documenting them. | |||||
| const ( | |||||
| // The two low bits of the mode are used as a length of how many bytes | |||||
| // of bs are used. If the length is 0, the node is treated as a | |||||
| // wildcard. | |||||
| smLengthMask smMode = 3 | |||||
| ) | |||||
| const ( | |||||
| // Jump to the given index on a match. Ordinarily, the state machine | |||||
| // will jump to the state given by the index if the characters do not | |||||
| // match. | |||||
| smJumpOnMatch smMode = 4 << iota | |||||
| // The index is the index of a route to try. If running the route fails, | |||||
| // the state machine advances by one. | |||||
| smRoute | |||||
| // Reset the state machine's cursor into the input string to the state's | |||||
| // index value. | |||||
| smSetCursor | |||||
| // If this bit is set, the machine transitions into a non-accepting | |||||
| // state if it matches. | |||||
| smFail | |||||
| ) | |||||
| type trie struct { | |||||
| prefix string | |||||
| children []trieSegment | |||||
| } | |||||
| // A trie segment is a route matching this point (or -1), combined with a list | |||||
| // of trie children that follow that route. | |||||
| type trieSegment struct { | |||||
| route int | |||||
| children []trie | |||||
| } | |||||
| func buildTrie(routes []route, dp, dr int) trie { | |||||
| var t trie | |||||
| ts := trieSegment{-1, nil} | |||||
| for i, r := range routes { | |||||
| if len(r.prefix) != dp { | |||||
| continue | |||||
| } | |||||
| if i == 0 { | |||||
| ts.route = 0 | |||||
| } else { | |||||
| subroutes := routes[ts.route+1 : i] | |||||
| ts.children = buildTrieSegment(subroutes, dp, dr+ts.route+1) | |||||
| t.children = append(t.children, ts) | |||||
| ts = trieSegment{i, nil} | |||||
| } | |||||
| } | |||||
| // This could be a little DRYer... | |||||
| subroutes := routes[ts.route+1:] | |||||
| ts.children = buildTrieSegment(subroutes, dp, dr+ts.route+1) | |||||
| t.children = append(t.children, ts) | |||||
| for i := range t.children { | |||||
| if t.children[i].route != -1 { | |||||
| t.children[i].route += dr | |||||
| } | |||||
| } | |||||
| return t | |||||
| } | |||||
| func commonPrefix(s1, s2 string) string { | |||||
| if len(s1) > len(s2) { | |||||
| return commonPrefix(s2, s1) | |||||
| } | |||||
| for i := 0; i < len(s1); i++ { | |||||
| if s1[i] != s2[i] { | |||||
| return s1[:i] | |||||
| } | |||||
| } | |||||
| return s1 | |||||
| } | |||||
| func buildTrieSegment(routes []route, dp, dr int) []trie { | |||||
| if len(routes) == 0 { | |||||
| return nil | |||||
| } | |||||
| var tries []trie | |||||
| start := 0 | |||||
| p := routes[0].prefix[dp:] | |||||
| for i := 1; i < len(routes); i++ { | |||||
| ip := routes[i].prefix[dp:] | |||||
| cp := commonPrefix(p, ip) | |||||
| if len(cp) == 0 { | |||||
| t := buildTrie(routes[start:i], dp+len(p), dr+start) | |||||
| t.prefix = p | |||||
| tries = append(tries, t) | |||||
| start = i | |||||
| p = ip | |||||
| } else { | |||||
| p = cp | |||||
| } | |||||
| } | |||||
| t := buildTrie(routes[start:], dp+len(p), dr+start) | |||||
| t.prefix = p | |||||
| return append(tries, t) | |||||
| } | |||||
| // This is a bit confusing, since the encode method on a trie deals exclusively | |||||
| // with trieSegments (i.e., its children), and vice versa. | |||||
| // | |||||
| // These methods are also hideously inefficient, both in terms of memory usage | |||||
| // and algorithmic complexity. If it ever becomes a problem, maybe we can do | |||||
| // something smarter than stupid O(N^2) appends, but to be honest, I bet N is | |||||
| // small (it almost always is :P) and we only do it once at boot anyways. | |||||
| func (t trie) encode(dp, off int) stateMachine { | |||||
| ms := make([]stateMachine, len(t.children)) | |||||
| subs := make([]stateMachine, len(t.children)) | |||||
| var l, msl, subl int | |||||
| for i, ts := range t.children { | |||||
| ms[i], subs[i] = ts.encode(dp, 0) | |||||
| msl += len(ms[i]) | |||||
| l += len(ms[i]) + len(subs[i]) | |||||
| } | |||||
| l++ | |||||
| m := make(stateMachine, 0, l) | |||||
| for i, mm := range ms { | |||||
| for j := range mm { | |||||
| if mm[j].mode&(smRoute|smSetCursor) != 0 { | |||||
| continue | |||||
| } | |||||
| mm[j].i += int32(off + msl + subl + 1) | |||||
| } | |||||
| m = append(m, mm...) | |||||
| subl += len(subs[i]) | |||||
| } | |||||
| m = append(m, state{mode: smJumpOnMatch, i: -1}) | |||||
| msl = 0 | |||||
| for i, sub := range subs { | |||||
| msl += len(ms[i]) | |||||
| for j := range sub { | |||||
| if sub[j].mode&(smRoute|smSetCursor) != 0 { | |||||
| continue | |||||
| } | |||||
| if sub[j].i == -1 { | |||||
| sub[j].i = int32(off + msl) | |||||
| } else { | |||||
| sub[j].i += int32(off + len(m)) | |||||
| } | |||||
| } | |||||
| m = append(m, sub...) | |||||
| } | |||||
| return m | |||||
| } | |||||
| func (ts trieSegment) encode(dp, off int) (me stateMachine, sub stateMachine) { | |||||
| o := 1 | |||||
| if ts.route != -1 { | |||||
| o++ | |||||
| } | |||||
| me = make(stateMachine, len(ts.children)+o) | |||||
| me[0] = state{mode: smSetCursor, i: int32(dp)} | |||||
| if ts.route != -1 { | |||||
| me[1] = state{mode: smRoute, i: int32(ts.route)} | |||||
| } | |||||
| for i, t := range ts.children { | |||||
| p := t.prefix | |||||
| bc := copy(me[i+o].bs[:], p) | |||||
| me[i+o].mode = smMode(bc) | smJumpOnMatch | |||||
| me[i+o].i = int32(off + len(sub)) | |||||
| for len(p) > bc { | |||||
| var bs [3]byte | |||||
| p = p[bc:] | |||||
| bc = copy(bs[:], p) | |||||
| sub = append(sub, state{bs: bs, mode: smMode(bc), i: -1}) | |||||
| } | |||||
| sub = append(sub, t.encode(dp+len(t.prefix), off+len(sub))...) | |||||
| } | |||||
| return | |||||
| } | |||||
| func compile(routes []route) stateMachine { | |||||
| if len(routes) == 0 { | |||||
| return nil | |||||
| } | |||||
| t := buildTrie(routes, 0, 0) | |||||
| m := t.encode(0, 0) | |||||
| for i := range m { | |||||
| if m[i].i == -1 { | |||||
| m[i].mode = m[i].mode | smFail | |||||
| } | |||||
| } | |||||
| return m | |||||
| } | |||||
| @ -1,83 +0,0 @@ | |||||
| package web | |||||
| import "net/http" | |||||
| type routeMachine struct { | |||||
| sm stateMachine | |||||
| routes []route | |||||
| } | |||||
| func matchRoute(route route, m method, ms *method, r *http.Request, c *C) bool { | |||||
| if !route.pattern.Match(r, c) { | |||||
| return false | |||||
| } | |||||
| *ms |= route.method | |||||
| if route.method&m != 0 { | |||||
| route.pattern.Run(r, c) | |||||
| return true | |||||
| } | |||||
| return false | |||||
| } | |||||
| func (rm routeMachine) route(c *C, w http.ResponseWriter, r *http.Request) (method, *route) { | |||||
| m := httpMethod(r.Method) | |||||
| var methods method | |||||
| p := r.URL.Path | |||||
| if len(rm.sm) == 0 { | |||||
| return methods, nil | |||||
| } | |||||
| var i int | |||||
| for { | |||||
| sm := rm.sm[i].mode | |||||
| if sm&smSetCursor != 0 { | |||||
| si := rm.sm[i].i | |||||
| p = r.URL.Path[si:] | |||||
| i++ | |||||
| continue | |||||
| } | |||||
| length := int(sm & smLengthMask) | |||||
| match := false | |||||
| if length <= len(p) { | |||||
| bs := rm.sm[i].bs | |||||
| switch length { | |||||
| case 3: | |||||
| if p[2] != bs[2] { | |||||
| break | |||||
| } | |||||
| fallthrough | |||||
| case 2: | |||||
| if p[1] != bs[1] { | |||||
| break | |||||
| } | |||||
| fallthrough | |||||
| case 1: | |||||
| if p[0] != bs[0] { | |||||
| break | |||||
| } | |||||
| fallthrough | |||||
| case 0: | |||||
| p = p[length:] | |||||
| match = true | |||||
| } | |||||
| } | |||||
| if match && sm&smRoute != 0 { | |||||
| si := rm.sm[i].i | |||||
| if matchRoute(rm.routes[si], m, &methods, r, c) { | |||||
| return 0, &rm.routes[si] | |||||
| } | |||||
| i++ | |||||
| } else if match != (sm&smJumpOnMatch == 0) { | |||||
| if sm&smFail != 0 { | |||||
| return methods, nil | |||||
| } | |||||
| i = int(rm.sm[i].i) | |||||
| } else { | |||||
| i++ | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,31 +0,0 @@ | |||||
| // +build !go1.3 | |||||
| package web | |||||
| // This is an alternate implementation of Go 1.3's sync.Pool. | |||||
| // Maximum size of the pool of spare middleware stacks | |||||
| const cPoolSize = 32 | |||||
| type cPool chan *cStack | |||||
| func makeCPool() *cPool { | |||||
| p := make(cPool, cPoolSize) | |||||
| return &p | |||||
| } | |||||
| func (c cPool) alloc() *cStack { | |||||
| select { | |||||
| case cs := <-c: | |||||
| return cs | |||||
| default: | |||||
| return nil | |||||
| } | |||||
| } | |||||
| func (c cPool) release(cs *cStack) { | |||||
| select { | |||||
| case c <- cs: | |||||
| default: | |||||
| } | |||||
| } | |||||
| @ -1,23 +0,0 @@ | |||||
| // +build go1.3 | |||||
| package web | |||||
| import "sync" | |||||
| type cPool sync.Pool | |||||
| func makeCPool() *cPool { | |||||
| return &cPool{} | |||||
| } | |||||
| func (c *cPool) alloc() *cStack { | |||||
| cs := (*sync.Pool)(c).Get() | |||||
| if cs == nil { | |||||
| return nil | |||||
| } | |||||
| return cs.(*cStack) | |||||
| } | |||||
| func (c *cPool) release(cs *cStack) { | |||||
| (*sync.Pool)(c).Put(cs) | |||||
| } | |||||
| @ -1,69 +0,0 @@ | |||||
| package web_test | |||||
| import ( | |||||
| "fmt" | |||||
| "log" | |||||
| "net/http" | |||||
| "regexp" | |||||
| "github.com/zenazn/goji/web" | |||||
| "github.com/zenazn/goji/web/middleware" | |||||
| ) | |||||
| func Example() { | |||||
| m := web.New() | |||||
| // Use your favorite HTTP verbs and the interfaces you know and love | |||||
| // from net/http: | |||||
| m.Get("/hello", func(w http.ResponseWriter, r *http.Request) { | |||||
| fmt.Fprintf(w, "Why hello there!\n") | |||||
| }) | |||||
| m.Post("/login", func(w http.ResponseWriter, r *http.Request) { | |||||
| if r.FormValue("password") != "god" { | |||||
| http.Error(w, "Hack the planet!", 401) | |||||
| } | |||||
| }) | |||||
| // Handlers can optionally take a context parameter, which contains | |||||
| // (among other things) a set of bound parameters. | |||||
| hello := func(c web.C, w http.ResponseWriter, r *http.Request) { | |||||
| fmt.Fprintf(w, "Hello, %s!\n", c.URLParams["name"]) | |||||
| } | |||||
| // Bind parameters using pattern strings... | |||||
| m.Get("/hello/:name", hello) | |||||
| // ...or use regular expressions if you need additional power. | |||||
| bonjour := regexp.MustCompile(`^/bonjour/(?P<name>[A-Za-z]+)$`) | |||||
| m.Get(bonjour, hello) | |||||
| // Middleware are a great abstraction for performing logic on every | |||||
| // request. Some middleware use the Goji context object to set | |||||
| // request-scoped variables. | |||||
| logger := func(h http.Handler) http.Handler { | |||||
| wrap := func(w http.ResponseWriter, r *http.Request) { | |||||
| log.Println("Before request") | |||||
| h.ServeHTTP(w, r) | |||||
| log.Println("After request") | |||||
| } | |||||
| return http.HandlerFunc(wrap) | |||||
| } | |||||
| auth := func(c *web.C, h http.Handler) http.Handler { | |||||
| wrap := func(w http.ResponseWriter, r *http.Request) { | |||||
| if cookie, err := r.Cookie("user"); err == nil { | |||||
| c.Env["user"] = cookie.Value | |||||
| } | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(wrap) | |||||
| } | |||||
| // A Middleware stack is a flexible way to assemble the common | |||||
| // components of your application, like request loggers and | |||||
| // authentication. There is an ecosystem of open-source middleware for | |||||
| // Goji, so there's a chance someone has already written the middleware | |||||
| // you are looking for! | |||||
| m.Use(middleware.EnvInit) | |||||
| m.Use(logger) | |||||
| m.Use(auth) | |||||
| } | |||||
| @ -1,32 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "reflect" | |||||
| ) | |||||
| /* | |||||
| This is more than a little sketchtacular. Go's rules for function pointer | |||||
| equality are pretty restrictive: nil function pointers always compare equal, and | |||||
| all other pointer types never do. However, this is pretty limiting: it means | |||||
| that we can't let people reference the middleware they've given us since we have | |||||
| no idea which function they're referring to. | |||||
| To get better data out of Go, we sketch on the representation of interfaces. We | |||||
| happen to know that interfaces are pairs of pointers: one to the real data, one | |||||
| to data about the type. Therefore, two interfaces, including two function | |||||
| interface{}'s, point to exactly the same objects iff their interface | |||||
| representations are identical. And it turns out this is sufficient for our | |||||
| purposes. | |||||
| If you're curious, you can read more about the representation of functions here: | |||||
| http://golang.org/s/go11func | |||||
| We're in effect comparing the pointers of the indirect layer. | |||||
| This function also works on non-function values. | |||||
| */ | |||||
| func funcEqual(a, b interface{}) bool { | |||||
| av := reflect.ValueOf(&a).Elem() | |||||
| bv := reflect.ValueOf(&b).Elem() | |||||
| return av.InterfaceData() == bv.InterfaceData() | |||||
| } | |||||
| @ -1,84 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "testing" | |||||
| ) | |||||
| // To tell you the truth, I'm not actually sure how many of these cases are | |||||
| // needed. Presumably someone with more patience than I could comb through | |||||
| // http://golang.org/s/go11func and figure out what all the different cases I | |||||
| // ought to test are, but I think this test includes all the cases I care about | |||||
| // and is at least reasonably thorough. | |||||
| func a() string { | |||||
| return "A" | |||||
| } | |||||
| func b() string { | |||||
| return "B" | |||||
| } | |||||
| func mkFn(s string) func() string { | |||||
| return func() string { | |||||
| return s | |||||
| } | |||||
| } | |||||
| var c = mkFn("C") | |||||
| var d = mkFn("D") | |||||
| var e = a | |||||
| var f = c | |||||
| var g = mkFn("D") | |||||
| type Type string | |||||
| func (t *Type) String() string { | |||||
| return string(*t) | |||||
| } | |||||
| var t1 = Type("hi") | |||||
| var t2 = Type("bye") | |||||
| var t1f = t1.String | |||||
| var t2f = t2.String | |||||
| var funcEqualTests = []struct { | |||||
| a, b func() string | |||||
| result bool | |||||
| }{ | |||||
| {a, a, true}, | |||||
| {a, b, false}, | |||||
| {b, b, true}, | |||||
| {a, c, false}, | |||||
| {c, c, true}, | |||||
| {c, d, false}, | |||||
| {a, e, true}, | |||||
| {a, f, false}, | |||||
| {c, f, true}, | |||||
| {e, f, false}, | |||||
| {d, g, false}, | |||||
| {t1f, t1f, true}, | |||||
| {t1f, t2f, false}, | |||||
| } | |||||
| func TestFuncEqual(t *testing.T) { | |||||
| t.Parallel() | |||||
| for _, test := range funcEqualTests { | |||||
| r := funcEqual(test.a, test.b) | |||||
| if r != test.result { | |||||
| t.Errorf("funcEqual(%v, %v) should have been %v", | |||||
| test.a, test.b, test.result) | |||||
| } | |||||
| } | |||||
| h := mkFn("H") | |||||
| i := h | |||||
| j := mkFn("H") | |||||
| k := a | |||||
| if !funcEqual(h, i) { | |||||
| t.Errorf("h and i should have been equal") | |||||
| } | |||||
| if funcEqual(h, j) { | |||||
| t.Errorf("h and j should not have been equal") | |||||
| } | |||||
| if !funcEqual(a, k) { | |||||
| t.Errorf("a and k should have been equal") | |||||
| } | |||||
| } | |||||
| @ -1,42 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "log" | |||||
| "net/http" | |||||
| ) | |||||
| const unknownHandler = `Unknown handler type %T. See http://godoc.org/github.com/zenazn/goji/web#HandlerType for a list of acceptable types.` | |||||
| type netHTTPHandlerWrap struct{ http.Handler } | |||||
| type netHTTPHandlerFuncWrap struct { | |||||
| fn func(http.ResponseWriter, *http.Request) | |||||
| } | |||||
| type handlerFuncWrap struct { | |||||
| fn func(C, http.ResponseWriter, *http.Request) | |||||
| } | |||||
| func (h netHTTPHandlerWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||||
| h.Handler.ServeHTTP(w, r) | |||||
| } | |||||
| func (h netHTTPHandlerFuncWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||||
| h.fn(w, r) | |||||
| } | |||||
| func (h handlerFuncWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||||
| h.fn(c, w, r) | |||||
| } | |||||
| func parseHandler(h HandlerType) Handler { | |||||
| switch f := h.(type) { | |||||
| case func(c C, w http.ResponseWriter, r *http.Request): | |||||
| return handlerFuncWrap{f} | |||||
| case func(w http.ResponseWriter, r *http.Request): | |||||
| return netHTTPHandlerFuncWrap{f} | |||||
| case Handler: | |||||
| return f | |||||
| case http.Handler: | |||||
| return netHTTPHandlerWrap{f} | |||||
| default: | |||||
| log.Fatalf(unknownHandler, h) | |||||
| panic("log.Fatalf does not return") | |||||
| } | |||||
| } | |||||
| @ -1,66 +0,0 @@ | |||||
| package web | |||||
| // The key used to store route Matches in the Goji environment. If this key is | |||||
| // present in the environment and contains a value of type Match, routing will | |||||
| // not be performed, and the Match's Handler will be used instead. | |||||
| const MatchKey = "goji.web.Match" | |||||
| // Match is the type of routing matches. It is inserted into C.Env under | |||||
| // MatchKey when the Mux.Router middleware is invoked. If MatchKey is present at | |||||
| // route dispatch time, the Handler of the corresponding Match will be called | |||||
| // instead of performing routing as usual. | |||||
| // | |||||
| // By computing a Match and inserting it into the Goji environment as part of a | |||||
| // middleware stack (see Mux.Router, for instance), it is possible to customize | |||||
| // Goji's routing behavior or replace it entirely. | |||||
| type Match struct { | |||||
| // Pattern is the Pattern that matched during routing. Will be nil if no | |||||
| // route matched (Handler will be set to the Mux's NotFound handler) | |||||
| Pattern Pattern | |||||
| // The Handler corresponding to the matched pattern. | |||||
| Handler Handler | |||||
| } | |||||
| // GetMatch returns the Match stored in the Goji environment, or an empty Match | |||||
| // if none exists (valid Matches always have a Handler property). | |||||
| func GetMatch(c C) Match { | |||||
| if c.Env == nil { | |||||
| return Match{} | |||||
| } | |||||
| mi, ok := c.Env[MatchKey] | |||||
| if !ok { | |||||
| return Match{} | |||||
| } | |||||
| if m, ok := mi.(Match); ok { | |||||
| return m | |||||
| } | |||||
| return Match{} | |||||
| } | |||||
| // RawPattern returns the PatternType that was originally passed to ParsePattern | |||||
| // or any of the HTTP method functions (Get, Post, etc.). | |||||
| func (m Match) RawPattern() PatternType { | |||||
| switch v := m.Pattern.(type) { | |||||
| case regexpPattern: | |||||
| return v.re | |||||
| case stringPattern: | |||||
| return v.raw | |||||
| default: | |||||
| return v | |||||
| } | |||||
| } | |||||
| // RawHandler returns the HandlerType that was originally passed to the HTTP | |||||
| // method functions (Get, Post, etc.). | |||||
| func (m Match) RawHandler() HandlerType { | |||||
| switch v := m.Handler.(type) { | |||||
| case netHTTPHandlerWrap: | |||||
| return v.Handler | |||||
| case handlerFuncWrap: | |||||
| return v.fn | |||||
| case netHTTPHandlerFuncWrap: | |||||
| return v.fn | |||||
| default: | |||||
| return v | |||||
| } | |||||
| } | |||||
| @ -1,50 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| "regexp" | |||||
| "testing" | |||||
| ) | |||||
| var rawPatterns = []PatternType{ | |||||
| "/hello/:name", | |||||
| regexp.MustCompile("^/hello/(?P<name>[^/]+)$"), | |||||
| testPattern{}, | |||||
| } | |||||
| func TestRawPattern(t *testing.T) { | |||||
| t.Parallel() | |||||
| for _, p := range rawPatterns { | |||||
| m := Match{Pattern: ParsePattern(p)} | |||||
| if rp := m.RawPattern(); rp != p { | |||||
| t.Errorf("got %#v, expected %#v", rp, p) | |||||
| } | |||||
| } | |||||
| } | |||||
| type httpHandlerOnly struct{} | |||||
| func (httpHandlerOnly) ServeHTTP(w http.ResponseWriter, r *http.Request) {} | |||||
| type handlerOnly struct{} | |||||
| func (handlerOnly) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) {} | |||||
| var rawHandlers = []HandlerType{ | |||||
| func(w http.ResponseWriter, r *http.Request) {}, | |||||
| func(c C, w http.ResponseWriter, r *http.Request) {}, | |||||
| httpHandlerOnly{}, | |||||
| handlerOnly{}, | |||||
| } | |||||
| func TestRawHandler(t *testing.T) { | |||||
| t.Parallel() | |||||
| for _, h := range rawHandlers { | |||||
| m := Match{Handler: parseHandler(h)} | |||||
| if rh := m.RawHandler(); !funcEqual(rh, h) { | |||||
| t.Errorf("got %#v, expected %#v", rh, h) | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,154 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "fmt" | |||||
| "log" | |||||
| "net/http" | |||||
| "sync" | |||||
| ) | |||||
| // mLayer is a single middleware stack layer. It contains a canonicalized | |||||
| // middleware representation, as well as the original function as passed to us. | |||||
| type mLayer struct { | |||||
| fn func(*C, http.Handler) http.Handler | |||||
| orig interface{} | |||||
| } | |||||
| // mStack is an entire middleware stack. It contains a slice of middleware | |||||
| // layers (outermost first) protected by a mutex, a cache of pre-built stack | |||||
| // instances, and a final routing function. | |||||
| type mStack struct { | |||||
| lock sync.Mutex | |||||
| stack []mLayer | |||||
| pool *cPool | |||||
| router internalRouter | |||||
| } | |||||
| type internalRouter interface { | |||||
| route(*C, http.ResponseWriter, *http.Request) | |||||
| } | |||||
| /* | |||||
| cStack is a cached middleware stack instance. Constructing a middleware stack | |||||
| involves a lot of allocations: at the very least each layer will have to close | |||||
| over the layer after (inside) it and a stack N levels deep will incur at least N | |||||
| separate allocations. Instead of doing this on every request, we keep a pool of | |||||
| pre-built stacks around for reuse. | |||||
| */ | |||||
| type cStack struct { | |||||
| C | |||||
| m http.Handler | |||||
| pool *cPool | |||||
| } | |||||
| func (s *cStack) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| s.C = C{} | |||||
| s.m.ServeHTTP(w, r) | |||||
| } | |||||
| func (s *cStack) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||||
| s.C = c | |||||
| s.m.ServeHTTP(w, r) | |||||
| } | |||||
| const unknownMiddleware = `Unknown middleware type %T. See http://godoc.org/github.com/zenazn/goji/web#MiddlewareType for a list of acceptable types.` | |||||
| func (m *mStack) appendLayer(fn interface{}) { | |||||
| ml := mLayer{orig: fn} | |||||
| switch f := fn.(type) { | |||||
| case func(http.Handler) http.Handler: | |||||
| ml.fn = func(c *C, h http.Handler) http.Handler { | |||||
| return f(h) | |||||
| } | |||||
| case func(*C, http.Handler) http.Handler: | |||||
| ml.fn = f | |||||
| default: | |||||
| log.Fatalf(unknownMiddleware, fn) | |||||
| } | |||||
| m.stack = append(m.stack, ml) | |||||
| } | |||||
| func (m *mStack) findLayer(l interface{}) int { | |||||
| for i, middleware := range m.stack { | |||||
| if funcEqual(l, middleware.orig) { | |||||
| return i | |||||
| } | |||||
| } | |||||
| return -1 | |||||
| } | |||||
| func (m *mStack) invalidate() { | |||||
| m.pool = makeCPool() | |||||
| } | |||||
| func (m *mStack) newStack() *cStack { | |||||
| cs := cStack{} | |||||
| router := m.router | |||||
| cs.m = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| router.route(&cs.C, w, r) | |||||
| }) | |||||
| for i := len(m.stack) - 1; i >= 0; i-- { | |||||
| cs.m = m.stack[i].fn(&cs.C, cs.m) | |||||
| } | |||||
| return &cs | |||||
| } | |||||
| func (m *mStack) alloc() *cStack { | |||||
| p := m.pool | |||||
| cs := p.alloc() | |||||
| if cs == nil { | |||||
| cs = m.newStack() | |||||
| } | |||||
| cs.pool = p | |||||
| return cs | |||||
| } | |||||
| func (m *mStack) release(cs *cStack) { | |||||
| cs.C = C{} | |||||
| if cs.pool != m.pool { | |||||
| return | |||||
| } | |||||
| cs.pool.release(cs) | |||||
| cs.pool = nil | |||||
| } | |||||
| func (m *mStack) Use(middleware interface{}) { | |||||
| m.lock.Lock() | |||||
| defer m.lock.Unlock() | |||||
| m.appendLayer(middleware) | |||||
| m.invalidate() | |||||
| } | |||||
| func (m *mStack) Insert(middleware, before interface{}) error { | |||||
| m.lock.Lock() | |||||
| defer m.lock.Unlock() | |||||
| i := m.findLayer(before) | |||||
| if i < 0 { | |||||
| return fmt.Errorf("web: unknown middleware %v", before) | |||||
| } | |||||
| m.appendLayer(middleware) | |||||
| inserted := m.stack[len(m.stack)-1] | |||||
| copy(m.stack[i+1:], m.stack[i:]) | |||||
| m.stack[i] = inserted | |||||
| m.invalidate() | |||||
| return nil | |||||
| } | |||||
| func (m *mStack) Abandon(middleware interface{}) error { | |||||
| m.lock.Lock() | |||||
| defer m.lock.Unlock() | |||||
| i := m.findLayer(middleware) | |||||
| if i < 0 { | |||||
| return fmt.Errorf("web: unknown middleware %v", middleware) | |||||
| } | |||||
| copy(m.stack[i:], m.stack[i+1:]) | |||||
| m.stack = m.stack[:len(m.stack)-1 : len(m.stack)] | |||||
| m.invalidate() | |||||
| return nil | |||||
| } | |||||
| @ -1,27 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| type envInit struct { | |||||
| c *web.C | |||||
| h http.Handler | |||||
| } | |||||
| func (e envInit) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| if e.c.Env == nil { | |||||
| e.c.Env = make(map[interface{}]interface{}) | |||||
| } | |||||
| e.h.ServeHTTP(w, r) | |||||
| } | |||||
| // EnvInit is a middleware that allocates an environment map if it is nil. While | |||||
| // it's impossible in general to ensure that Env is never nil in a middleware | |||||
| // stack, in most common cases placing this middleware at the top of the stack | |||||
| // will eliminate the need for repetative nil checks. | |||||
| func EnvInit(c *web.C, h http.Handler) http.Handler { | |||||
| return envInit{c, h} | |||||
| } | |||||
| @ -1,92 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "bytes" | |||||
| "log" | |||||
| "net/http" | |||||
| "time" | |||||
| "github.com/zenazn/goji/web" | |||||
| "github.com/zenazn/goji/web/mutil" | |||||
| ) | |||||
| // Logger is a middleware that logs the start and end of each request, along | |||||
| // with some useful data about what was requested, what the response status was, | |||||
| // and how long it took to return. When standard output is a TTY, Logger will | |||||
| // print in color, otherwise it will print in black and white. | |||||
| // | |||||
| // Logger prints a request ID if one is provided. | |||||
| // | |||||
| // Logger has been designed explicitly to be Good Enough for use in small | |||||
| // applications and for people just getting started with Goji. It is expected | |||||
| // that applications will eventually outgrow this middleware and replace it with | |||||
| // a custom request logger, such as one that produces machine-parseable output, | |||||
| // outputs logs to a different service (e.g., syslog), or formats lines like | |||||
| // those printed elsewhere in the application. | |||||
| func Logger(c *web.C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| reqID := GetReqID(*c) | |||||
| printStart(reqID, r) | |||||
| lw := mutil.WrapWriter(w) | |||||
| t1 := time.Now() | |||||
| h.ServeHTTP(lw, r) | |||||
| if lw.Status() == 0 { | |||||
| lw.WriteHeader(http.StatusOK) | |||||
| } | |||||
| t2 := time.Now() | |||||
| printEnd(reqID, lw, t2.Sub(t1)) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| func printStart(reqID string, r *http.Request) { | |||||
| var buf bytes.Buffer | |||||
| if reqID != "" { | |||||
| cW(&buf, bBlack, "[%s] ", reqID) | |||||
| } | |||||
| buf.WriteString("Started ") | |||||
| cW(&buf, bMagenta, "%s ", r.Method) | |||||
| cW(&buf, nBlue, "%q ", r.URL.String()) | |||||
| buf.WriteString("from ") | |||||
| buf.WriteString(r.RemoteAddr) | |||||
| log.Print(buf.String()) | |||||
| } | |||||
| func printEnd(reqID string, w mutil.WriterProxy, dt time.Duration) { | |||||
| var buf bytes.Buffer | |||||
| if reqID != "" { | |||||
| cW(&buf, bBlack, "[%s] ", reqID) | |||||
| } | |||||
| buf.WriteString("Returning ") | |||||
| status := w.Status() | |||||
| if status < 200 { | |||||
| cW(&buf, bBlue, "%03d", status) | |||||
| } else if status < 300 { | |||||
| cW(&buf, bGreen, "%03d", status) | |||||
| } else if status < 400 { | |||||
| cW(&buf, bCyan, "%03d", status) | |||||
| } else if status < 500 { | |||||
| cW(&buf, bYellow, "%03d", status) | |||||
| } else { | |||||
| cW(&buf, bRed, "%03d", status) | |||||
| } | |||||
| buf.WriteString(" in ") | |||||
| if dt < 500*time.Millisecond { | |||||
| cW(&buf, nGreen, "%s", dt) | |||||
| } else if dt < 5*time.Second { | |||||
| cW(&buf, nYellow, "%s", dt) | |||||
| } else { | |||||
| cW(&buf, nRed, "%s", dt) | |||||
| } | |||||
| log.Print(buf.String()) | |||||
| } | |||||
| @ -1,4 +0,0 @@ | |||||
| /* | |||||
| Package middleware provides several standard middleware implementations. | |||||
| */ | |||||
| package middleware | |||||
| @ -1,55 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "time" | |||||
| ) | |||||
| // Unix epoch time | |||||
| var epoch = time.Unix(0, 0).Format(time.RFC1123) | |||||
| // Taken from https://github.com/mytrile/nocache | |||||
| var noCacheHeaders = map[string]string{ | |||||
| "Expires": epoch, | |||||
| "Cache-Control": "no-cache, private, max-age=0", | |||||
| "Pragma": "no-cache", | |||||
| "X-Accel-Expires": "0", | |||||
| } | |||||
| var etagHeaders = []string{ | |||||
| "ETag", | |||||
| "If-Modified-Since", | |||||
| "If-Match", | |||||
| "If-None-Match", | |||||
| "If-Range", | |||||
| "If-Unmodified-Since", | |||||
| } | |||||
| // NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent | |||||
| // a router (or subrouter) from being cached by an upstream proxy and/or client. | |||||
| // | |||||
| // As per http://wiki.nginx.org/HttpProxyModule - NoCache sets: | |||||
| // Expires: Thu, 01 Jan 1970 00:00:00 UTC | |||||
| // Cache-Control: no-cache, private, max-age=0 | |||||
| // X-Accel-Expires: 0 | |||||
| // Pragma: no-cache (for HTTP/1.0 proxies/clients) | |||||
| func NoCache(h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| // Delete any ETag headers that may have been set | |||||
| for _, v := range etagHeaders { | |||||
| if r.Header.Get(v) != "" { | |||||
| r.Header.Del(v) | |||||
| } | |||||
| } | |||||
| // Set our NoCache headers | |||||
| for k, v := range noCacheHeaders { | |||||
| w.Header().Set(k, v) | |||||
| } | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| @ -1,29 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "testing" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| func TestNoCache(t *testing.T) { | |||||
| rr := httptest.NewRecorder() | |||||
| s := web.New() | |||||
| s.Use(NoCache) | |||||
| r, err := http.NewRequest("GET", "/", nil) | |||||
| if err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| s.ServeHTTP(rr, r) | |||||
| for k, v := range noCacheHeaders { | |||||
| if rr.HeaderMap[k][0] != v { | |||||
| t.Errorf("%s header not set by middleware.", k) | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,97 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "strings" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| type autoOptionsState int | |||||
| const ( | |||||
| aosInit autoOptionsState = iota | |||||
| aosHeaderWritten | |||||
| aosProxying | |||||
| ) | |||||
| // I originally used an httptest.ResponseRecorder here, but package httptest | |||||
| // adds a flag which I'm not particularly eager to expose. This is essentially a | |||||
| // ResponseRecorder that has been specialized for the purpose at hand to avoid | |||||
| // the httptest dependency. | |||||
| type autoOptionsProxy struct { | |||||
| w http.ResponseWriter | |||||
| c *web.C | |||||
| state autoOptionsState | |||||
| } | |||||
| func (p *autoOptionsProxy) Header() http.Header { | |||||
| return p.w.Header() | |||||
| } | |||||
| func (p *autoOptionsProxy) Write(buf []byte) (int, error) { | |||||
| switch p.state { | |||||
| case aosInit: | |||||
| p.state = aosHeaderWritten | |||||
| case aosProxying: | |||||
| return len(buf), nil | |||||
| } | |||||
| return p.w.Write(buf) | |||||
| } | |||||
| func (p *autoOptionsProxy) WriteHeader(code int) { | |||||
| methods := getValidMethods(*p.c) | |||||
| switch p.state { | |||||
| case aosInit: | |||||
| if methods != nil && code == http.StatusNotFound { | |||||
| p.state = aosProxying | |||||
| break | |||||
| } | |||||
| p.state = aosHeaderWritten | |||||
| fallthrough | |||||
| default: | |||||
| p.w.WriteHeader(code) | |||||
| return | |||||
| } | |||||
| methods = addMethod(methods, "OPTIONS") | |||||
| p.w.Header().Set("Allow", strings.Join(methods, ", ")) | |||||
| p.w.WriteHeader(http.StatusOK) | |||||
| } | |||||
| // AutomaticOptions automatically return an appropriate "Allow" header when the | |||||
| // request method is OPTIONS and the request would have otherwise been 404'd. | |||||
| func AutomaticOptions(c *web.C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| if r.Method == "OPTIONS" { | |||||
| w = &autoOptionsProxy{c: c, w: w} | |||||
| } | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| func getValidMethods(c web.C) []string { | |||||
| if c.Env == nil { | |||||
| return nil | |||||
| } | |||||
| v, ok := c.Env[web.ValidMethodsKey] | |||||
| if !ok { | |||||
| return nil | |||||
| } | |||||
| if methods, ok := v.([]string); ok { | |||||
| return methods | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func addMethod(methods []string, method string) []string { | |||||
| for _, m := range methods { | |||||
| if m == method { | |||||
| return methods | |||||
| } | |||||
| } | |||||
| return append(methods, method) | |||||
| } | |||||
| @ -1,112 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "testing" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| func testOptions(r *http.Request, f func(*web.C, http.ResponseWriter, *http.Request)) *httptest.ResponseRecorder { | |||||
| var c web.C | |||||
| h := func(w http.ResponseWriter, r *http.Request) { | |||||
| f(&c, w, r) | |||||
| } | |||||
| m := AutomaticOptions(&c, http.HandlerFunc(h)) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| return w | |||||
| } | |||||
| var optionsTestEnv = map[interface{}]interface{}{ | |||||
| web.ValidMethodsKey: []string{ | |||||
| "hello", | |||||
| "world", | |||||
| }, | |||||
| } | |||||
| func TestAutomaticOptions(t *testing.T) { | |||||
| t.Parallel() | |||||
| // Shouldn't interfere with normal requests | |||||
| r, _ := http.NewRequest("GET", "/", nil) | |||||
| rr := testOptions(r, | |||||
| func(c *web.C, w http.ResponseWriter, r *http.Request) { | |||||
| w.Write([]byte{'h', 'i'}) | |||||
| }, | |||||
| ) | |||||
| if rr.Code != http.StatusOK { | |||||
| t.Errorf("status is %d, not 200", rr.Code) | |||||
| } | |||||
| if rr.Body.String() != "hi" { | |||||
| t.Errorf("body was %q, should be %q", rr.Body.String(), "hi") | |||||
| } | |||||
| allow := rr.HeaderMap.Get("Allow") | |||||
| if allow != "" { | |||||
| t.Errorf("Allow header was set to %q, should be empty", allow) | |||||
| } | |||||
| // If we respond non-404 to an OPTIONS request, also don't interfere | |||||
| r, _ = http.NewRequest("OPTIONS", "/", nil) | |||||
| rr = testOptions(r, | |||||
| func(c *web.C, w http.ResponseWriter, r *http.Request) { | |||||
| c.Env = optionsTestEnv | |||||
| w.Write([]byte{'h', 'i'}) | |||||
| }, | |||||
| ) | |||||
| if rr.Code != http.StatusOK { | |||||
| t.Errorf("status is %d, not 200", rr.Code) | |||||
| } | |||||
| if rr.Body.String() != "hi" { | |||||
| t.Errorf("body was %q, should be %q", rr.Body.String(), "hi") | |||||
| } | |||||
| allow = rr.HeaderMap.Get("Allow") | |||||
| if allow != "" { | |||||
| t.Errorf("Allow header was set to %q, should be empty", allow) | |||||
| } | |||||
| // Provide options if we 404. Make sure we nom the output bytes | |||||
| r, _ = http.NewRequest("OPTIONS", "/", nil) | |||||
| rr = testOptions(r, | |||||
| func(c *web.C, w http.ResponseWriter, r *http.Request) { | |||||
| c.Env = optionsTestEnv | |||||
| w.WriteHeader(http.StatusNotFound) | |||||
| w.Write([]byte{'h', 'i'}) | |||||
| }, | |||||
| ) | |||||
| if rr.Code != http.StatusOK { | |||||
| t.Errorf("status is %d, not 200", rr.Code) | |||||
| } | |||||
| if rr.Body.Len() != 0 { | |||||
| t.Errorf("body was %q, should be empty", rr.Body.String()) | |||||
| } | |||||
| allow = rr.HeaderMap.Get("Allow") | |||||
| correctHeaders := "hello, world, OPTIONS" | |||||
| if allow != "hello, world, OPTIONS" { | |||||
| t.Errorf("Allow header should be %q, was %q", correctHeaders, | |||||
| allow) | |||||
| } | |||||
| // If we somehow 404 without giving a list of valid options, don't do | |||||
| // anything | |||||
| r, _ = http.NewRequest("OPTIONS", "/", nil) | |||||
| rr = testOptions(r, | |||||
| func(c *web.C, w http.ResponseWriter, r *http.Request) { | |||||
| w.WriteHeader(http.StatusNotFound) | |||||
| w.Write([]byte{'h', 'i'}) | |||||
| }, | |||||
| ) | |||||
| if rr.Code != http.StatusNotFound { | |||||
| t.Errorf("status is %d, not 404", rr.Code) | |||||
| } | |||||
| if rr.Body.String() != "hi" { | |||||
| t.Errorf("body was %q, should be %q", rr.Body.String(), "hi") | |||||
| } | |||||
| allow = rr.HeaderMap.Get("Allow") | |||||
| if allow != "" { | |||||
| t.Errorf("Allow header was set to %q, should be empty", allow) | |||||
| } | |||||
| } | |||||
| @ -1,51 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "strings" | |||||
| ) | |||||
| var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") | |||||
| var xRealIP = http.CanonicalHeaderKey("X-Real-IP") | |||||
| // RealIP is a middleware that sets a http.Request's RemoteAddr to the results | |||||
| // of parsing either the X-Forwarded-For header or the X-Real-IP header (in that | |||||
| // order). | |||||
| // | |||||
| // This middleware should be inserted fairly early in the middleware stack to | |||||
| // ensure that subsequent layers (e.g., request loggers) which examine the | |||||
| // RemoteAddr will see the intended value. | |||||
| // | |||||
| // You should only use this middleware if you can trust the headers passed to | |||||
| // you (in particular, the two headers this middleware uses), for example | |||||
| // because you have placed a reverse proxy like HAProxy or nginx in front of | |||||
| // Goji. If your reverse proxies are configured to pass along arbitrary header | |||||
| // values from the client, or if you use this middleware without a reverse | |||||
| // proxy, malicious clients will be able to make you very sad (or, depending on | |||||
| // how you're using RemoteAddr, vulnerable to an attack of some sort). | |||||
| func RealIP(h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| if rip := realIP(r); rip != "" { | |||||
| r.RemoteAddr = rip | |||||
| } | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| func realIP(r *http.Request) string { | |||||
| var ip string | |||||
| if xff := r.Header.Get(xForwardedFor); xff != "" { | |||||
| i := strings.Index(xff, ", ") | |||||
| if i == -1 { | |||||
| i = len(xff) | |||||
| } | |||||
| ip = xff[:i] | |||||
| } else if xrip := r.Header.Get(xRealIP); xrip != "" { | |||||
| ip = xrip | |||||
| } | |||||
| return ip | |||||
| } | |||||
| @ -1,44 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "bytes" | |||||
| "log" | |||||
| "net/http" | |||||
| "runtime/debug" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| // Recoverer is a middleware that recovers from panics, logs the panic (and a | |||||
| // backtrace), and returns a HTTP 500 (Internal Server Error) status if | |||||
| // possible. | |||||
| // | |||||
| // Recoverer prints a request ID if one is provided. | |||||
| func Recoverer(c *web.C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| reqID := GetReqID(*c) | |||||
| defer func() { | |||||
| if err := recover(); err != nil { | |||||
| printPanic(reqID, err) | |||||
| debug.PrintStack() | |||||
| http.Error(w, http.StatusText(500), 500) | |||||
| } | |||||
| }() | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| func printPanic(reqID string, err interface{}) { | |||||
| var buf bytes.Buffer | |||||
| if reqID != "" { | |||||
| cW(&buf, bBlack, "[%s] ", reqID) | |||||
| } | |||||
| cW(&buf, bRed, "panic: %+v", err) | |||||
| log.Print(buf.String()) | |||||
| } | |||||
| @ -1,88 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "crypto/rand" | |||||
| "encoding/base64" | |||||
| "fmt" | |||||
| "net/http" | |||||
| "os" | |||||
| "strings" | |||||
| "sync/atomic" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| // Key to use when setting the request ID. | |||||
| const RequestIDKey = "reqID" | |||||
| var prefix string | |||||
| var reqid uint64 | |||||
| /* | |||||
| A quick note on the statistics here: we're trying to calculate the chance that | |||||
| two randomly generated base62 prefixes will collide. We use the formula from | |||||
| http://en.wikipedia.org/wiki/Birthday_problem | |||||
| P[m, n] \approx 1 - e^{-m^2/2n} | |||||
| We ballpark an upper bound for $m$ by imagining (for whatever reason) a server | |||||
| that restarts every second over 10 years, for $m = 86400 * 365 * 10 = 315360000$ | |||||
| For a $k$ character base-62 identifier, we have $n(k) = 62^k$ | |||||
| Plugging this in, we find $P[m, n(10)] \approx 5.75%$, which is good enough for | |||||
| our purposes, and is surely more than anyone would ever need in practice -- a | |||||
| process that is rebooted a handful of times a day for a hundred years has less | |||||
| than a millionth of a percent chance of generating two colliding IDs. | |||||
| */ | |||||
| func init() { | |||||
| hostname, err := os.Hostname() | |||||
| if hostname == "" || err != nil { | |||||
| hostname = "localhost" | |||||
| } | |||||
| var buf [12]byte | |||||
| var b64 string | |||||
| for len(b64) < 10 { | |||||
| rand.Read(buf[:]) | |||||
| b64 = base64.StdEncoding.EncodeToString(buf[:]) | |||||
| b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) | |||||
| } | |||||
| prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) | |||||
| } | |||||
| // RequestID is a middleware that injects a request ID into the context of each | |||||
| // request. A request ID is a string of the form "host.example.com/random-0001", | |||||
| // where "random" is a base62 random string that uniquely identifies this go | |||||
| // process, and where the last number is an atomically incremented request | |||||
| // counter. | |||||
| func RequestID(c *web.C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| if c.Env == nil { | |||||
| c.Env = make(map[interface{}]interface{}) | |||||
| } | |||||
| myid := atomic.AddUint64(&reqid, 1) | |||||
| c.Env[RequestIDKey] = fmt.Sprintf("%s-%06d", prefix, myid) | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| // GetReqID returns a request ID from the given context if one is present. | |||||
| // Returns the empty string if a request ID cannot be found. | |||||
| func GetReqID(c web.C) string { | |||||
| if c.Env == nil { | |||||
| return "" | |||||
| } | |||||
| v, ok := c.Env[RequestIDKey] | |||||
| if !ok { | |||||
| return "" | |||||
| } | |||||
| if reqID, ok := v.(string); ok { | |||||
| return reqID | |||||
| } | |||||
| return "" | |||||
| } | |||||
| @ -1,65 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| type subrouter struct { | |||||
| c *web.C | |||||
| h http.Handler | |||||
| } | |||||
| func (s subrouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| if s.c.URLParams != nil { | |||||
| path, ok := s.c.URLParams["*"] | |||||
| if !ok { | |||||
| path, ok = s.c.URLParams["_"] | |||||
| } | |||||
| if ok { | |||||
| oldpath := r.URL.Path | |||||
| oldmatch := web.GetMatch(*s.c) | |||||
| r.URL.Path = path | |||||
| if oldmatch.Handler != nil { | |||||
| delete(s.c.Env, web.MatchKey) | |||||
| } | |||||
| defer func() { | |||||
| r.URL.Path = oldpath | |||||
| if s.c.Env == nil { | |||||
| return | |||||
| } | |||||
| if oldmatch.Handler != nil { | |||||
| s.c.Env[web.MatchKey] = oldmatch | |||||
| } else { | |||||
| delete(s.c.Env, web.MatchKey) | |||||
| } | |||||
| }() | |||||
| } | |||||
| } | |||||
| s.h.ServeHTTP(w, r) | |||||
| } | |||||
| /* | |||||
| SubRouter is a helper middleware that makes writing sub-routers easier. | |||||
| If you register a sub-router under a key like "/admin/*", Goji's router will | |||||
| automatically set c.URLParams["*"] to the unmatched path suffix. This middleware | |||||
| will help you set the request URL's Path to this unmatched suffix, allowing you | |||||
| to write sub-routers with no knowledge of what routes the parent router matches. | |||||
| Since Go's regular expressions do not allow you to create a capturing group | |||||
| named "*", SubRouter also accepts the string "_". For instance, to duplicate the | |||||
| semantics of the string pattern "/foo/*", you might use the regular expression | |||||
| "^/foo(?P<_>/.*)$". | |||||
| This middleware is Match-aware: it will un-set any explicit routing information | |||||
| contained in the Goji context in order to prevent routing loops when using | |||||
| explicit routing with sub-routers. See the documentation for Mux.Router for | |||||
| more. | |||||
| */ | |||||
| func SubRouter(c *web.C, h http.Handler) http.Handler { | |||||
| return subrouter{c, h} | |||||
| } | |||||
| @ -1,28 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "testing" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| func TestSubRouterMatch(t *testing.T) { | |||||
| m := web.New() | |||||
| m.Use(m.Router) | |||||
| m2 := web.New() | |||||
| m2.Use(SubRouter) | |||||
| m2.Get("/bar", func(w http.ResponseWriter, r *http.Request) {}) | |||||
| m.Get("/foo/*", m2) | |||||
| r, err := http.NewRequest("GET", "/foo/bar", nil) | |||||
| if err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| // This function will recurse forever if SubRouter + Match didn't work. | |||||
| m.ServeHTTP(httptest.NewRecorder(), r) | |||||
| } | |||||
| @ -1,60 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "bytes" | |||||
| "fmt" | |||||
| "os" | |||||
| ) | |||||
| var ( | |||||
| // Normal colors | |||||
| nBlack = []byte{'\033', '[', '3', '0', 'm'} | |||||
| nRed = []byte{'\033', '[', '3', '1', 'm'} | |||||
| nGreen = []byte{'\033', '[', '3', '2', 'm'} | |||||
| nYellow = []byte{'\033', '[', '3', '3', 'm'} | |||||
| nBlue = []byte{'\033', '[', '3', '4', 'm'} | |||||
| nMagenta = []byte{'\033', '[', '3', '5', 'm'} | |||||
| nCyan = []byte{'\033', '[', '3', '6', 'm'} | |||||
| nWhite = []byte{'\033', '[', '3', '7', 'm'} | |||||
| // Bright colors | |||||
| bBlack = []byte{'\033', '[', '3', '0', ';', '1', 'm'} | |||||
| bRed = []byte{'\033', '[', '3', '1', ';', '1', 'm'} | |||||
| bGreen = []byte{'\033', '[', '3', '2', ';', '1', 'm'} | |||||
| bYellow = []byte{'\033', '[', '3', '3', ';', '1', 'm'} | |||||
| bBlue = []byte{'\033', '[', '3', '4', ';', '1', 'm'} | |||||
| bMagenta = []byte{'\033', '[', '3', '5', ';', '1', 'm'} | |||||
| bCyan = []byte{'\033', '[', '3', '6', ';', '1', 'm'} | |||||
| bWhite = []byte{'\033', '[', '3', '7', ';', '1', 'm'} | |||||
| reset = []byte{'\033', '[', '0', 'm'} | |||||
| ) | |||||
| var isTTY bool | |||||
| func init() { | |||||
| // This is sort of cheating: if stdout is a character device, we assume | |||||
| // that means it's a TTY. Unfortunately, there are many non-TTY | |||||
| // character devices, but fortunately stdout is rarely set to any of | |||||
| // them. | |||||
| // | |||||
| // We could solve this properly by pulling in a dependency on | |||||
| // code.google.com/p/go.crypto/ssh/terminal, for instance, but as a | |||||
| // heuristic for whether to print in color or in black-and-white, I'd | |||||
| // really rather not. | |||||
| fi, err := os.Stdout.Stat() | |||||
| if err == nil { | |||||
| m := os.ModeDevice | os.ModeCharDevice | |||||
| isTTY = fi.Mode()&m == m | |||||
| } | |||||
| } | |||||
| // colorWrite | |||||
| func cW(buf *bytes.Buffer, color []byte, s string, args ...interface{}) { | |||||
| if isTTY { | |||||
| buf.Write(color) | |||||
| } | |||||
| fmt.Fprintf(buf, s, args...) | |||||
| if isTTY { | |||||
| buf.Write(reset) | |||||
| } | |||||
| } | |||||
| @ -1,24 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "github.com/zenazn/goji/web" | |||||
| "net/http" | |||||
| ) | |||||
| // URLQueryKey is the context key for the URL Query | |||||
| const URLQueryKey string = "urlquery" | |||||
| // URLQuery is a middleware to parse the URL Query parameters just once, | |||||
| // and store the resulting url.Values in the context. | |||||
| func URLQuery(c *web.C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| if c.Env == nil { | |||||
| c.Env = make(map[interface{}]interface{}) | |||||
| } | |||||
| c.Env[URLQueryKey] = r.URL.Query() | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| @ -1,53 +0,0 @@ | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "net/url" | |||||
| "reflect" | |||||
| "testing" | |||||
| "github.com/zenazn/goji/web" | |||||
| ) | |||||
| func testURLQuery(r *http.Request, f func(*web.C, http.ResponseWriter, *http.Request)) *httptest.ResponseRecorder { | |||||
| var c web.C | |||||
| h := func(w http.ResponseWriter, r *http.Request) { | |||||
| f(&c, w, r) | |||||
| } | |||||
| m := URLQuery(&c, http.HandlerFunc(h)) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| return w | |||||
| } | |||||
| func TestURLQuery(t *testing.T) { | |||||
| type testcase struct { | |||||
| url string | |||||
| expectedParams url.Values | |||||
| } | |||||
| // we're not testing url.Query() here, but rather that the results of the query | |||||
| // appear in the context | |||||
| testcases := []testcase{ | |||||
| testcase{"/", url.Values{}}, | |||||
| testcase{"/?a=1&b=2&a=3", url.Values{"a": []string{"1", "3"}, "b": []string{"2"}}}, | |||||
| testcase{"/?x=1&y=2&z=3#freddyishere", url.Values{"x": []string{"1"}, "y": []string{"2"}, "z": []string{"3"}}}, | |||||
| } | |||||
| for _, tc := range testcases { | |||||
| r, _ := http.NewRequest("GET", tc.url, nil) | |||||
| testURLQuery(r, | |||||
| func(c *web.C, w http.ResponseWriter, r *http.Request) { | |||||
| params := c.Env[URLQueryKey].(url.Values) | |||||
| if !reflect.DeepEqual(params, tc.expectedParams) { | |||||
| t.Errorf("GET %s, URLQuery middleware found %v, should be %v", tc.url, params, tc.expectedParams) | |||||
| } | |||||
| w.Write([]byte{'h', 'i'}) | |||||
| }, | |||||
| ) | |||||
| } | |||||
| } | |||||
| @ -1,52 +0,0 @@ | |||||
| // +build !go1.3 | |||||
| package web | |||||
| import "testing" | |||||
| // These tests were pretty sketchtacular to start with, but they aren't even | |||||
| // guaranteed to pass with Go 1.3's sync.Pool. Let's keep them here for now; if | |||||
| // they start spuriously failing later we can delete them outright. | |||||
| func TestCaching(t *testing.T) { | |||||
| ch := make(chan string) | |||||
| st := makeStack(ch) | |||||
| cs1 := st.alloc() | |||||
| cs2 := st.alloc() | |||||
| if cs1 == cs2 { | |||||
| t.Fatal("cs1 and cs2 are the same") | |||||
| } | |||||
| st.release(cs2) | |||||
| cs3 := st.alloc() | |||||
| if cs2 != cs3 { | |||||
| t.Fatalf("Expected cs2 to equal cs3") | |||||
| } | |||||
| st.release(cs1) | |||||
| st.release(cs3) | |||||
| cs4 := st.alloc() | |||||
| cs5 := st.alloc() | |||||
| if cs4 != cs1 { | |||||
| t.Fatal("Expected cs4 to equal cs1") | |||||
| } | |||||
| if cs5 != cs3 { | |||||
| t.Fatal("Expected cs5 to equal cs3") | |||||
| } | |||||
| } | |||||
| func TestInvalidation(t *testing.T) { | |||||
| ch := make(chan string) | |||||
| st := makeStack(ch) | |||||
| cs1 := st.alloc() | |||||
| cs2 := st.alloc() | |||||
| st.release(cs1) | |||||
| st.invalidate() | |||||
| cs3 := st.alloc() | |||||
| if cs3 == cs1 { | |||||
| t.Fatal("Expected cs3 to be fresh, instead got cs1") | |||||
| } | |||||
| st.release(cs2) | |||||
| cs4 := st.alloc() | |||||
| if cs4 == cs2 { | |||||
| t.Fatal("Expected cs4 to be fresh, instead got cs2") | |||||
| } | |||||
| } | |||||
| @ -1,204 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "testing" | |||||
| "time" | |||||
| ) | |||||
| type iRouter func(*C, http.ResponseWriter, *http.Request) | |||||
| func (i iRouter) route(c *C, w http.ResponseWriter, r *http.Request) { | |||||
| i(c, w, r) | |||||
| } | |||||
| func makeStack(ch chan string) *mStack { | |||||
| router := func(c *C, w http.ResponseWriter, r *http.Request) { | |||||
| ch <- "router" | |||||
| } | |||||
| return &mStack{ | |||||
| stack: make([]mLayer, 0), | |||||
| pool: makeCPool(), | |||||
| router: iRouter(router), | |||||
| } | |||||
| } | |||||
| func chanWare(ch chan string, s string) func(http.Handler) http.Handler { | |||||
| return func(h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| ch <- s | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| } | |||||
| } | |||||
| func simpleRequest(ch chan string, st *mStack) { | |||||
| defer func() { | |||||
| ch <- "end" | |||||
| }() | |||||
| r, _ := http.NewRequest("GET", "/", nil) | |||||
| w := httptest.NewRecorder() | |||||
| cs := st.alloc() | |||||
| defer st.release(cs) | |||||
| cs.ServeHTTP(w, r) | |||||
| } | |||||
| func assertOrder(t *testing.T, ch chan string, strings ...string) { | |||||
| for i, s := range strings { | |||||
| var v string | |||||
| select { | |||||
| case v = <-ch: | |||||
| case <-time.After(5 * time.Millisecond): | |||||
| t.Fatalf("Expected %q as %d'th value, but timed out", s, | |||||
| i+1) | |||||
| } | |||||
| if s != v { | |||||
| t.Errorf("%d'th value was %q, expected %q", i+1, v, s) | |||||
| } | |||||
| } | |||||
| } | |||||
| func TestSimple(t *testing.T) { | |||||
| t.Parallel() | |||||
| ch := make(chan string) | |||||
| st := makeStack(ch) | |||||
| st.Use(chanWare(ch, "one")) | |||||
| st.Use(chanWare(ch, "two")) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "two", "router", "end") | |||||
| } | |||||
| func TestTypes(t *testing.T) { | |||||
| t.Parallel() | |||||
| ch := make(chan string) | |||||
| st := makeStack(ch) | |||||
| st.Use(func(h http.Handler) http.Handler { | |||||
| return h | |||||
| }) | |||||
| st.Use(func(c *C, h http.Handler) http.Handler { | |||||
| return h | |||||
| }) | |||||
| } | |||||
| func TestAddMore(t *testing.T) { | |||||
| t.Parallel() | |||||
| ch := make(chan string) | |||||
| st := makeStack(ch) | |||||
| st.Use(chanWare(ch, "one")) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "router", "end") | |||||
| st.Use(chanWare(ch, "two")) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "two", "router", "end") | |||||
| st.Use(chanWare(ch, "three")) | |||||
| st.Use(chanWare(ch, "four")) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "two", "three", "four", "router", "end") | |||||
| } | |||||
| func TestInsert(t *testing.T) { | |||||
| t.Parallel() | |||||
| ch := make(chan string) | |||||
| st := makeStack(ch) | |||||
| one := chanWare(ch, "one") | |||||
| two := chanWare(ch, "two") | |||||
| st.Use(one) | |||||
| st.Use(two) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "two", "router", "end") | |||||
| err := st.Insert(chanWare(ch, "sloth"), chanWare(ch, "squirrel")) | |||||
| if err == nil { | |||||
| t.Error("Expected error when referencing unknown middleware") | |||||
| } | |||||
| st.Insert(chanWare(ch, "middle"), two) | |||||
| err = st.Insert(chanWare(ch, "start"), one) | |||||
| if err != nil { | |||||
| t.Fatal(err) | |||||
| } | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "start", "one", "middle", "two", "router", "end") | |||||
| } | |||||
| func TestAbandon(t *testing.T) { | |||||
| t.Parallel() | |||||
| ch := make(chan string) | |||||
| st := makeStack(ch) | |||||
| one := chanWare(ch, "one") | |||||
| two := chanWare(ch, "two") | |||||
| three := chanWare(ch, "three") | |||||
| st.Use(one) | |||||
| st.Use(two) | |||||
| st.Use(three) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "two", "three", "router", "end") | |||||
| st.Abandon(two) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "three", "router", "end") | |||||
| err := st.Abandon(chanWare(ch, "panda")) | |||||
| if err == nil { | |||||
| t.Error("Expected error when deleting unknown middleware") | |||||
| } | |||||
| st.Abandon(one) | |||||
| st.Abandon(three) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "router", "end") | |||||
| st.Use(one) | |||||
| go simpleRequest(ch, st) | |||||
| assertOrder(t, ch, "one", "router", "end") | |||||
| } | |||||
| func TestContext(t *testing.T) { | |||||
| router := func(c *C, w http.ResponseWriter, r *http.Request) { | |||||
| if c.Env["reqID"].(int) != 2 { | |||||
| t.Error("Request id was not 2 :(") | |||||
| } | |||||
| } | |||||
| st := mStack{ | |||||
| stack: make([]mLayer, 0), | |||||
| pool: makeCPool(), | |||||
| router: iRouter(router), | |||||
| } | |||||
| st.Use(func(c *C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| if c.Env != nil || c.URLParams != nil { | |||||
| t.Error("Expected a clean context") | |||||
| } | |||||
| c.Env = make(map[interface{}]interface{}) | |||||
| c.Env["reqID"] = 1 | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| }) | |||||
| st.Use(func(c *C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| if c.Env == nil { | |||||
| t.Error("Expected env from last middleware") | |||||
| } | |||||
| c.Env["reqID"] = c.Env["reqID"].(int) + 1 | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| }) | |||||
| ch := make(chan string) | |||||
| go simpleRequest(ch, &st) | |||||
| assertOrder(t, ch, "end") | |||||
| } | |||||
| @ -1,3 +0,0 @@ | |||||
| // Package mutil contains various functions that are helpful when writing http | |||||
| // middleware. | |||||
| package mutil | |||||
| @ -1,139 +0,0 @@ | |||||
| package mutil | |||||
| import ( | |||||
| "bufio" | |||||
| "io" | |||||
| "net" | |||||
| "net/http" | |||||
| ) | |||||
| // WriterProxy is a proxy around an http.ResponseWriter that allows you to hook | |||||
| // into various parts of the response process. | |||||
| type WriterProxy interface { | |||||
| http.ResponseWriter | |||||
| // Status returns the HTTP status of the request, or 0 if one has not | |||||
| // yet been sent. | |||||
| Status() int | |||||
| // BytesWritten returns the total number of bytes sent to the client. | |||||
| BytesWritten() int | |||||
| // Tee causes the response body to be written to the given io.Writer in | |||||
| // addition to proxying the writes through. Only one io.Writer can be | |||||
| // tee'd to at once: setting a second one will overwrite the first. | |||||
| // Writes will be sent to the proxy before being written to this | |||||
| // io.Writer. It is illegal for the tee'd writer to be modified | |||||
| // concurrently with writes. | |||||
| Tee(io.Writer) | |||||
| // Unwrap returns the original proxied target. | |||||
| Unwrap() http.ResponseWriter | |||||
| } | |||||
| // WrapWriter wraps an http.ResponseWriter, returning a proxy that allows you to | |||||
| // hook into various parts of the response process. | |||||
| func WrapWriter(w http.ResponseWriter) WriterProxy { | |||||
| _, cn := w.(http.CloseNotifier) | |||||
| _, fl := w.(http.Flusher) | |||||
| _, hj := w.(http.Hijacker) | |||||
| _, rf := w.(io.ReaderFrom) | |||||
| bw := basicWriter{ResponseWriter: w} | |||||
| if cn && fl && hj && rf { | |||||
| return &fancyWriter{bw} | |||||
| } | |||||
| if fl { | |||||
| return &flushWriter{bw} | |||||
| } | |||||
| return &bw | |||||
| } | |||||
| // basicWriter wraps a http.ResponseWriter that implements the minimal | |||||
| // http.ResponseWriter interface. | |||||
| type basicWriter struct { | |||||
| http.ResponseWriter | |||||
| wroteHeader bool | |||||
| code int | |||||
| bytes int | |||||
| tee io.Writer | |||||
| } | |||||
| func (b *basicWriter) WriteHeader(code int) { | |||||
| if !b.wroteHeader { | |||||
| b.code = code | |||||
| b.wroteHeader = true | |||||
| b.ResponseWriter.WriteHeader(code) | |||||
| } | |||||
| } | |||||
| func (b *basicWriter) Write(buf []byte) (int, error) { | |||||
| b.WriteHeader(http.StatusOK) | |||||
| n, err := b.ResponseWriter.Write(buf) | |||||
| if b.tee != nil { | |||||
| _, err2 := b.tee.Write(buf[:n]) | |||||
| // Prefer errors generated by the proxied writer. | |||||
| if err == nil { | |||||
| err = err2 | |||||
| } | |||||
| } | |||||
| b.bytes += n | |||||
| return n, err | |||||
| } | |||||
| func (b *basicWriter) maybeWriteHeader() { | |||||
| if !b.wroteHeader { | |||||
| b.WriteHeader(http.StatusOK) | |||||
| } | |||||
| } | |||||
| func (b *basicWriter) Status() int { | |||||
| return b.code | |||||
| } | |||||
| func (b *basicWriter) BytesWritten() int { | |||||
| return b.bytes | |||||
| } | |||||
| func (b *basicWriter) Tee(w io.Writer) { | |||||
| b.tee = w | |||||
| } | |||||
| func (b *basicWriter) Unwrap() http.ResponseWriter { | |||||
| return b.ResponseWriter | |||||
| } | |||||
| // fancyWriter is a writer that additionally satisfies http.CloseNotifier, | |||||
| // http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case | |||||
| // of wrapping the http.ResponseWriter that package http gives you, in order to | |||||
| // make the proxied object support the full method set of the proxied object. | |||||
| type fancyWriter struct { | |||||
| basicWriter | |||||
| } | |||||
| func (f *fancyWriter) CloseNotify() <-chan bool { | |||||
| cn := f.basicWriter.ResponseWriter.(http.CloseNotifier) | |||||
| return cn.CloseNotify() | |||||
| } | |||||
| func (f *fancyWriter) Flush() { | |||||
| fl := f.basicWriter.ResponseWriter.(http.Flusher) | |||||
| fl.Flush() | |||||
| } | |||||
| func (f *fancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { | |||||
| hj := f.basicWriter.ResponseWriter.(http.Hijacker) | |||||
| return hj.Hijack() | |||||
| } | |||||
| func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) { | |||||
| if f.basicWriter.tee != nil { | |||||
| return io.Copy(&f.basicWriter, r) | |||||
| } | |||||
| rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) | |||||
| f.basicWriter.maybeWriteHeader() | |||||
| return rf.ReadFrom(r) | |||||
| } | |||||
| var _ http.CloseNotifier = &fancyWriter{} | |||||
| var _ http.Flusher = &fancyWriter{} | |||||
| var _ http.Hijacker = &fancyWriter{} | |||||
| var _ io.ReaderFrom = &fancyWriter{} | |||||
| type flushWriter struct { | |||||
| basicWriter | |||||
| } | |||||
| func (f *flushWriter) Flush() { | |||||
| fl := f.basicWriter.ResponseWriter.(http.Flusher) | |||||
| fl.Flush() | |||||
| } | |||||
| var _ http.Flusher = &flushWriter{} | |||||
| @ -1,213 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| ) | |||||
| /* | |||||
| Mux is an HTTP multiplexer, much like net/http's ServeMux. It functions as both | |||||
| a middleware stack and as an HTTP router. | |||||
| Middleware provide a great abstraction for actions that must be performed on | |||||
| every request, such as request logging and authentication. To append, insert, | |||||
| and remove middleware, you can call the Use, Insert, and Abandon functions | |||||
| respectively. | |||||
| Routes may be added using any of the HTTP verb functions (Get, Post, etc.), or | |||||
| through the generic Handle function. Goji's routing algorithm is very simple: | |||||
| routes are processed in the order they are added, and the first matching route | |||||
| will be executed. Routes match if their HTTP method and Pattern both match. | |||||
| */ | |||||
| type Mux struct { | |||||
| ms mStack | |||||
| rt router | |||||
| } | |||||
| // New creates a new Mux without any routes or middleware. | |||||
| func New() *Mux { | |||||
| mux := Mux{ | |||||
| ms: mStack{ | |||||
| stack: make([]mLayer, 0), | |||||
| pool: makeCPool(), | |||||
| }, | |||||
| rt: router{ | |||||
| routes: make([]route, 0), | |||||
| notFound: parseHandler(http.NotFound), | |||||
| }, | |||||
| } | |||||
| mux.ms.router = &mux.rt | |||||
| return &mux | |||||
| } | |||||
| // ServeHTTP processes HTTP requests. Satisfies net/http.Handler. | |||||
| func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| stack := m.ms.alloc() | |||||
| stack.ServeHTTP(w, r) | |||||
| m.ms.release(stack) | |||||
| } | |||||
| // ServeHTTPC creates a context dependent request with the given Mux. Satisfies | |||||
| // the Handler interface. | |||||
| func (m *Mux) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||||
| stack := m.ms.alloc() | |||||
| stack.ServeHTTPC(c, w, r) | |||||
| m.ms.release(stack) | |||||
| } | |||||
| // Middleware Stack functions | |||||
| // Use appends the given middleware to the middleware stack. | |||||
| // | |||||
| // No attempt is made to enforce the uniqueness of middlewares. It is illegal to | |||||
| // call this function concurrently with active requests. | |||||
| func (m *Mux) Use(middleware MiddlewareType) { | |||||
| m.ms.Use(middleware) | |||||
| } | |||||
| // Insert inserts the given middleware immediately before a given existing | |||||
| // middleware in the stack. Returns an error if "before" cannot be found in the | |||||
| // current stack. | |||||
| // | |||||
| // No attempt is made to enforce the uniqueness of middlewares. If the insertion | |||||
| // point is ambiguous, the first (outermost) one is chosen. It is illegal to | |||||
| // call this function concurrently with active requests. | |||||
| func (m *Mux) Insert(middleware, before MiddlewareType) error { | |||||
| return m.ms.Insert(middleware, before) | |||||
| } | |||||
| // Abandon removes the given middleware from the middleware stack. Returns an | |||||
| // error if no such middleware can be found. | |||||
| // | |||||
| // If the name of the middleware to delete is ambiguous, the first (outermost) | |||||
| // one is chosen. It is illegal to call this function concurrently with active | |||||
| // requests. | |||||
| func (m *Mux) Abandon(middleware MiddlewareType) error { | |||||
| return m.ms.Abandon(middleware) | |||||
| } | |||||
| // Router functions | |||||
| type routerMiddleware struct { | |||||
| m *Mux | |||||
| c *C | |||||
| h http.Handler | |||||
| } | |||||
| func (rm routerMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| if rm.c.Env == nil { | |||||
| rm.c.Env = make(map[interface{}]interface{}, 1) | |||||
| } | |||||
| rm.c.Env[MatchKey] = rm.m.rt.getMatch(rm.c, w, r) | |||||
| rm.h.ServeHTTP(w, r) | |||||
| } | |||||
| /* | |||||
| Router is a middleware that performs routing and stores the resulting Match in | |||||
| Goji's environment. If a routing Match is present at the end of the middleware | |||||
| stack, that Match is used instead of re-routing. | |||||
| This middleware is especially useful to create post-routing middleware, e.g. a | |||||
| request logger which prints which pattern or handler was selected, or an | |||||
| authentication middleware which only applies to certain routes. | |||||
| If you use nested Muxes with explicit routing, you should be aware that the | |||||
| explicit routing information set by an outer Mux can be picked up by an inner | |||||
| Mux, inadvertently causing an infinite routing loop. If you use both explicit | |||||
| routing and nested Muxes, you should be sure to unset MatchKey before the inner | |||||
| Mux performs routing (or attach a Router to the inner Mux as well). | |||||
| */ | |||||
| func (m *Mux) Router(c *C, h http.Handler) http.Handler { | |||||
| return routerMiddleware{m, c, h} | |||||
| } | |||||
| /* | |||||
| Handle dispatches to the given handler when the pattern matches, regardless of | |||||
| HTTP method. | |||||
| This method is commonly used to implement sub-routing: an admin application, for | |||||
| instance, can expose a single handler that is attached to the main Mux by | |||||
| calling Handle("/admin/*", adminHandler) or similar. Note that this function | |||||
| doesn't strip this prefix from the path before forwarding it on (e.g., the | |||||
| handler will see the full path, including the "/admin/" part), but this | |||||
| functionality can easily be performed by an extra middleware layer. | |||||
| */ | |||||
| func (m *Mux) Handle(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mALL, handler) | |||||
| } | |||||
| // Connect dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is CONNECT. | |||||
| func (m *Mux) Connect(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mCONNECT, handler) | |||||
| } | |||||
| // Delete dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is DELETE. | |||||
| func (m *Mux) Delete(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mDELETE, handler) | |||||
| } | |||||
| // Get dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is GET. | |||||
| // | |||||
| // All GET handlers also transparently serve HEAD requests, since net/http will | |||||
| // take care of all the fiddly bits for you. If you wish to provide an alternate | |||||
| // implementation of HEAD, you should add a handler explicitly and place it | |||||
| // above your GET handler. | |||||
| func (m *Mux) Get(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mGET|mHEAD, handler) | |||||
| } | |||||
| // Head dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is HEAD. | |||||
| func (m *Mux) Head(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mHEAD, handler) | |||||
| } | |||||
| // Options dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is OPTIONS. | |||||
| func (m *Mux) Options(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mOPTIONS, handler) | |||||
| } | |||||
| // Patch dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is PATCH. | |||||
| func (m *Mux) Patch(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mPATCH, handler) | |||||
| } | |||||
| // Post dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is POST. | |||||
| func (m *Mux) Post(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mPOST, handler) | |||||
| } | |||||
| // Put dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is PUT. | |||||
| func (m *Mux) Put(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mPUT, handler) | |||||
| } | |||||
| // Trace dispatches to the given handler when the pattern matches and the HTTP | |||||
| // method is TRACE. | |||||
| func (m *Mux) Trace(pattern PatternType, handler HandlerType) { | |||||
| m.rt.handleUntyped(pattern, mTRACE, handler) | |||||
| } | |||||
| // NotFound sets the fallback (i.e., 404) handler for this mux. | |||||
| // | |||||
| // As a convenience, the context environment variable "goji.web.validMethods" | |||||
| // (also available as the constant ValidMethodsKey) will be set to the list of | |||||
| // HTTP methods that could have been routed had they been provided on an | |||||
| // otherwise identical request. | |||||
| func (m *Mux) NotFound(handler HandlerType) { | |||||
| m.rt.notFound = parseHandler(handler) | |||||
| } | |||||
| // Compile compiles the list of routes into bytecode. This only needs to be done | |||||
| // once after all the routes have been added, and will be called automatically | |||||
| // for you (at some performance cost on the first request) if you do not call it | |||||
| // explicitly. | |||||
| func (m *Mux) Compile() { | |||||
| m.rt.compile() | |||||
| } | |||||
| @ -1,45 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "testing" | |||||
| ) | |||||
| // Sanity check types | |||||
| var _ http.Handler = &Mux{} | |||||
| var _ Handler = &Mux{} | |||||
| // There's... really not a lot to do here. | |||||
| func TestIfItWorks(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| ch := make(chan string, 1) | |||||
| m.Get("/hello/:name", func(c C, w http.ResponseWriter, r *http.Request) { | |||||
| greeting := "Hello " | |||||
| if c.Env != nil { | |||||
| if g, ok := c.Env["greeting"]; ok { | |||||
| greeting = g.(string) | |||||
| } | |||||
| } | |||||
| ch <- greeting + c.URLParams["name"] | |||||
| }) | |||||
| r, _ := http.NewRequest("GET", "/hello/carl", nil) | |||||
| m.ServeHTTP(httptest.NewRecorder(), r) | |||||
| out := <-ch | |||||
| if out != "Hello carl" { | |||||
| t.Errorf(`Unexpected response %q, expected "Hello carl"`, out) | |||||
| } | |||||
| r, _ = http.NewRequest("GET", "/hello/bob", nil) | |||||
| env := map[interface{}]interface{}{"greeting": "Yo "} | |||||
| m.ServeHTTPC(C{Env: env}, httptest.NewRecorder(), r) | |||||
| out = <-ch | |||||
| if out != "Yo bob" { | |||||
| t.Errorf(`Unexpected response %q, expected "Yo bob"`, out) | |||||
| } | |||||
| } | |||||
| @ -1,58 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "log" | |||||
| "net/http" | |||||
| "regexp" | |||||
| ) | |||||
| // A Pattern determines whether or not a given request matches some criteria. | |||||
| // They are often used in routes, which are essentially (pattern, methodSet, | |||||
| // handler) tuples. If the method and pattern match, the given handler is used. | |||||
| // | |||||
| // Built-in implementations of this interface are used to implement regular | |||||
| // expression and string matching. | |||||
| type Pattern interface { | |||||
| // In practice, most real-world routes have a string prefix that can be | |||||
| // used to quickly determine if a pattern is an eligible match. The | |||||
| // router uses the result of this function to optimize away calls to the | |||||
| // full Match function, which is likely much more expensive to compute. | |||||
| // If your Pattern does not support prefixes, this function should | |||||
| // return the empty string. | |||||
| Prefix() string | |||||
| // Returns true if the request satisfies the pattern. This function is | |||||
| // free to examine both the request and the context to make this | |||||
| // decision. Match should not modify either argument, and since it will | |||||
| // potentially be called several times over the course of matching a | |||||
| // request, it should be reasonably efficient. | |||||
| Match(r *http.Request, c *C) bool | |||||
| // Run the pattern on the request and context, modifying the context as | |||||
| // necessary to bind URL parameters or other parsed state. | |||||
| Run(r *http.Request, c *C) | |||||
| } | |||||
| const unknownPattern = `Unknown pattern type %T. See http://godoc.org/github.com/zenazn/goji/web#PatternType for a list of acceptable types.` | |||||
| /* | |||||
| ParsePattern is used internally by Goji to parse route patterns. It is exposed | |||||
| publicly to make it easier to write thin wrappers around the built-in Pattern | |||||
| implementations. | |||||
| ParsePattern fatally exits (using log.Fatalf) if it is passed a value of an | |||||
| unexpected type (see the documentation for PatternType for a list of which types | |||||
| are accepted). It is the caller's responsibility to ensure that ParsePattern is | |||||
| called in a type-safe manner. | |||||
| */ | |||||
| func ParsePattern(raw PatternType) Pattern { | |||||
| switch v := raw.(type) { | |||||
| case Pattern: | |||||
| return v | |||||
| case *regexp.Regexp: | |||||
| return parseRegexpPattern(v) | |||||
| case string: | |||||
| return parseStringPattern(v) | |||||
| default: | |||||
| log.Fatalf(unknownPattern, v) | |||||
| panic("log.Fatalf does not return") | |||||
| } | |||||
| } | |||||
| @ -1,188 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| "reflect" | |||||
| "regexp" | |||||
| "testing" | |||||
| ) | |||||
| func pt(url string, match bool, params map[string]string) patternTest { | |||||
| req, err := http.NewRequest("GET", url, nil) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| return patternTest{ | |||||
| r: req, | |||||
| match: match, | |||||
| c: &C{}, | |||||
| cout: &C{URLParams: params}, | |||||
| } | |||||
| } | |||||
| type patternTest struct { | |||||
| r *http.Request | |||||
| match bool | |||||
| c *C | |||||
| cout *C | |||||
| } | |||||
| var patternTests = []struct { | |||||
| pat Pattern | |||||
| prefix string | |||||
| tests []patternTest | |||||
| }{ | |||||
| // Regexp tests | |||||
| {parseRegexpPattern(regexp.MustCompile("^/hello$")), | |||||
| "/hello", []patternTest{ | |||||
| pt("/hello", true, nil), | |||||
| pt("/hell", false, nil), | |||||
| pt("/hello/", false, nil), | |||||
| pt("/hello/world", false, nil), | |||||
| pt("/world", false, nil), | |||||
| }}, | |||||
| {parseRegexpPattern(regexp.MustCompile("^/hello/(?P<name>[a-z]+)$")), | |||||
| "/hello/", []patternTest{ | |||||
| pt("/hello/world", true, map[string]string{ | |||||
| "name": "world", | |||||
| }), | |||||
| pt("/hello/", false, nil), | |||||
| pt("/hello/my/love", false, nil), | |||||
| }}, | |||||
| {parseRegexpPattern(regexp.MustCompile(`^/a(?P<a>\d+)/b(?P<b>\d+)/?$`)), | |||||
| "/a", []patternTest{ | |||||
| pt("/a1/b2", true, map[string]string{ | |||||
| "a": "1", | |||||
| "b": "2", | |||||
| }), | |||||
| pt("/a9001/b007/", true, map[string]string{ | |||||
| "a": "9001", | |||||
| "b": "007", | |||||
| }), | |||||
| pt("/a/b", false, nil), | |||||
| pt("/a", false, nil), | |||||
| pt("/squirrel", false, nil), | |||||
| }}, | |||||
| {parseRegexpPattern(regexp.MustCompile(`^/hello/([a-z]+)$`)), | |||||
| "/hello/", []patternTest{ | |||||
| pt("/hello/world", true, map[string]string{ | |||||
| "$1": "world", | |||||
| }), | |||||
| pt("/hello/", false, nil), | |||||
| }}, | |||||
| {parseRegexpPattern(regexp.MustCompile("/hello")), | |||||
| "/hello", []patternTest{ | |||||
| pt("/hello", true, nil), | |||||
| pt("/hell", false, nil), | |||||
| pt("/hello/", true, nil), | |||||
| pt("/hello/world", true, nil), | |||||
| pt("/world/hello", false, nil), | |||||
| }}, | |||||
| // String pattern tests | |||||
| {parseStringPattern("/hello"), | |||||
| "/hello", []patternTest{ | |||||
| pt("/hello", true, nil), | |||||
| pt("/hell", false, nil), | |||||
| pt("/hello/", false, nil), | |||||
| pt("/hello/world", false, nil), | |||||
| }}, | |||||
| {parseStringPattern("/hello/:name"), | |||||
| "/hello/", []patternTest{ | |||||
| pt("/hello/world", true, map[string]string{ | |||||
| "name": "world", | |||||
| }), | |||||
| pt("/hello/my.world;wow", true, map[string]string{ | |||||
| "name": "my.world;wow", | |||||
| }), | |||||
| pt("/hell", false, nil), | |||||
| pt("/hello/", false, nil), | |||||
| pt("/hello/my/love", false, nil), | |||||
| }}, | |||||
| {parseStringPattern("/a/:a/b/:b"), | |||||
| "/a/", []patternTest{ | |||||
| pt("/a/1/b/2", true, map[string]string{ | |||||
| "a": "1", | |||||
| "b": "2", | |||||
| }), | |||||
| pt("/a", false, nil), | |||||
| pt("/a//b/", false, nil), | |||||
| pt("/a/1/b/2/3", false, nil), | |||||
| }}, | |||||
| {parseStringPattern("/a/:b.:c"), | |||||
| "/a/", []patternTest{ | |||||
| pt("/a/cat.gif", true, map[string]string{ | |||||
| "b": "cat", | |||||
| "c": "gif", | |||||
| }), | |||||
| pt("/a/cat.tar.gz", true, map[string]string{ | |||||
| "b": "cat", | |||||
| "c": "tar.gz", | |||||
| }), | |||||
| pt("/a", false, nil), | |||||
| pt("/a/cat", false, nil), | |||||
| pt("/a/cat/gif", false, nil), | |||||
| pt("/a/cat.", false, nil), | |||||
| pt("/a/cat/dog.gif", false, nil), | |||||
| }}, | |||||
| // String prefix tests | |||||
| {parseStringPattern("/user/:user/*"), | |||||
| "/user/", []patternTest{ | |||||
| pt("/user/bob/", true, map[string]string{ | |||||
| "user": "bob", | |||||
| "*": "/", | |||||
| }), | |||||
| pt("/user/bob/friends/123", true, map[string]string{ | |||||
| "user": "bob", | |||||
| "*": "/friends/123", | |||||
| }), | |||||
| pt("/user/bob", false, nil), | |||||
| pt("/user/", false, nil), | |||||
| pt("/user//", false, nil), | |||||
| }}, | |||||
| {parseStringPattern("/user/:user/friends/*"), | |||||
| "/user/", []patternTest{ | |||||
| pt("/user/bob/friends/", true, map[string]string{ | |||||
| "user": "bob", | |||||
| "*": "/", | |||||
| }), | |||||
| pt("/user/bob/friends/123", true, map[string]string{ | |||||
| "user": "bob", | |||||
| "*": "/123", | |||||
| }), | |||||
| pt("/user/bob/enemies", false, nil), | |||||
| }}, | |||||
| } | |||||
| func TestPatterns(t *testing.T) { | |||||
| t.Parallel() | |||||
| for _, pt := range patternTests { | |||||
| p := pt.pat.Prefix() | |||||
| if p != pt.prefix { | |||||
| t.Errorf("Expected prefix %q for %v, got %q", pt.prefix, | |||||
| pt.pat, p) | |||||
| } else { | |||||
| for _, test := range pt.tests { | |||||
| runTest(t, pt.pat, test) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| func runTest(t *testing.T, p Pattern, test patternTest) { | |||||
| result := p.Match(test.r, test.c) | |||||
| if result != test.match { | |||||
| t.Errorf("Expected match(%v, %#v) to return %v", p, | |||||
| test.r.URL.Path, test.match) | |||||
| return | |||||
| } | |||||
| p.Run(test.r, test.c) | |||||
| if !reflect.DeepEqual(test.c, test.cout) { | |||||
| t.Errorf("Expected a context of %v, instead got %v", test.cout, | |||||
| test.c) | |||||
| } | |||||
| } | |||||
| @ -1,149 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "bytes" | |||||
| "fmt" | |||||
| "log" | |||||
| "net/http" | |||||
| "regexp" | |||||
| "regexp/syntax" | |||||
| ) | |||||
| type regexpPattern struct { | |||||
| re *regexp.Regexp | |||||
| prefix string | |||||
| names []string | |||||
| } | |||||
| func (p regexpPattern) Prefix() string { | |||||
| return p.prefix | |||||
| } | |||||
| func (p regexpPattern) Match(r *http.Request, c *C) bool { | |||||
| return p.match(r, c, false) | |||||
| } | |||||
| func (p regexpPattern) Run(r *http.Request, c *C) { | |||||
| p.match(r, c, false) | |||||
| } | |||||
| func (p regexpPattern) match(r *http.Request, c *C, dryrun bool) bool { | |||||
| matches := p.re.FindStringSubmatch(r.URL.Path) | |||||
| if matches == nil || len(matches) == 0 { | |||||
| return false | |||||
| } | |||||
| if c == nil || dryrun || len(matches) == 1 { | |||||
| return true | |||||
| } | |||||
| if c.URLParams == nil { | |||||
| c.URLParams = make(map[string]string, len(matches)-1) | |||||
| } | |||||
| for i := 1; i < len(matches); i++ { | |||||
| c.URLParams[p.names[i]] = matches[i] | |||||
| } | |||||
| return true | |||||
| } | |||||
| func (p regexpPattern) String() string { | |||||
| return fmt.Sprintf("regexpPattern(%v)", p.re) | |||||
| } | |||||
| func (p regexpPattern) Raw() *regexp.Regexp { | |||||
| return p.re | |||||
| } | |||||
| /* | |||||
| I'm sorry, dear reader. I really am. | |||||
| The problem here is to take an arbitrary regular expression and: | |||||
| 1. return a regular expression that is just like it, but left-anchored, | |||||
| preferring to return the original if possible. | |||||
| 2. determine a string literal prefix that all matches of this regular expression | |||||
| have, much like regexp.Regexp.Prefix(). Unfortunately, Prefix() does not work | |||||
| in the presence of anchors, so we need to write it ourselves. | |||||
| What this actually means is that we need to sketch on the internals of the | |||||
| standard regexp library to forcefully extract the information we want. | |||||
| Unfortunately, regexp.Regexp hides a lot of its state, so our abstraction is | |||||
| going to be pretty leaky. The biggest leak is that we blindly assume that all | |||||
| regular expressions are perl-style, not POSIX. This is probably Mostly True, and | |||||
| I think most users of the library probably won't be able to notice. | |||||
| */ | |||||
| func sketchOnRegex(re *regexp.Regexp) (*regexp.Regexp, string) { | |||||
| rawRe := re.String() | |||||
| sRe, err := syntax.Parse(rawRe, syntax.Perl) | |||||
| if err != nil { | |||||
| log.Printf("WARN(web): unable to parse regexp %v as perl. "+ | |||||
| "This route might behave unexpectedly.", re) | |||||
| return re, "" | |||||
| } | |||||
| sRe = sRe.Simplify() | |||||
| p, err := syntax.Compile(sRe) | |||||
| if err != nil { | |||||
| log.Printf("WARN(web): unable to compile regexp %v. This "+ | |||||
| "route might behave unexpectedly.", re) | |||||
| return re, "" | |||||
| } | |||||
| if p.StartCond()&syntax.EmptyBeginText == 0 { | |||||
| // I hope doing this is always legal... | |||||
| newRe, err := regexp.Compile(`\A` + rawRe) | |||||
| if err != nil { | |||||
| log.Printf("WARN(web): unable to create a left-"+ | |||||
| "anchored regexp from %v. This route might "+ | |||||
| "behave unexpectedly", re) | |||||
| return re, "" | |||||
| } | |||||
| re = newRe | |||||
| } | |||||
| // Run the regular expression more or less by hand :( | |||||
| pc := uint32(p.Start) | |||||
| atStart := true | |||||
| i := &p.Inst[pc] | |||||
| var buf bytes.Buffer | |||||
| Sadness: | |||||
| for { | |||||
| switch i.Op { | |||||
| case syntax.InstEmptyWidth: | |||||
| if !atStart { | |||||
| break Sadness | |||||
| } | |||||
| case syntax.InstCapture, syntax.InstNop: | |||||
| // nop! | |||||
| case syntax.InstRune, syntax.InstRune1, syntax.InstRuneAny, | |||||
| syntax.InstRuneAnyNotNL: | |||||
| atStart = false | |||||
| if len(i.Rune) != 1 || | |||||
| syntax.Flags(i.Arg)&syntax.FoldCase != 0 { | |||||
| break Sadness | |||||
| } | |||||
| buf.WriteRune(i.Rune[0]) | |||||
| default: | |||||
| break Sadness | |||||
| } | |||||
| pc = i.Out | |||||
| i = &p.Inst[pc] | |||||
| } | |||||
| return re, buf.String() | |||||
| } | |||||
| func parseRegexpPattern(re *regexp.Regexp) regexpPattern { | |||||
| re, prefix := sketchOnRegex(re) | |||||
| rnames := re.SubexpNames() | |||||
| // We have to make our own copy since package regexp forbids us | |||||
| // from scribbling over the slice returned by SubexpNames(). | |||||
| names := make([]string, len(rnames)) | |||||
| for i, rname := range rnames { | |||||
| if rname == "" { | |||||
| rname = fmt.Sprintf("$%d", i) | |||||
| } | |||||
| names[i] = rname | |||||
| } | |||||
| return regexpPattern{ | |||||
| re: re, | |||||
| prefix: prefix, | |||||
| names: names, | |||||
| } | |||||
| } | |||||
| @ -1,154 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| "sort" | |||||
| "strings" | |||||
| "sync" | |||||
| ) | |||||
| type method int | |||||
| const ( | |||||
| mCONNECT method = 1 << iota | |||||
| mDELETE | |||||
| mGET | |||||
| mHEAD | |||||
| mOPTIONS | |||||
| mPATCH | |||||
| mPOST | |||||
| mPUT | |||||
| mTRACE | |||||
| // We only natively support the methods above, but we pass through other | |||||
| // methods. This constant pretty much only exists for the sake of mALL. | |||||
| mIDK | |||||
| mALL method = mCONNECT | mDELETE | mGET | mHEAD | mOPTIONS | mPATCH | | |||||
| mPOST | mPUT | mTRACE | mIDK | |||||
| ) | |||||
| // The key used to communicate to the NotFound handler what methods would have | |||||
| // been allowed if they'd been provided. | |||||
| const ValidMethodsKey = "goji.web.ValidMethods" | |||||
| var validMethodsMap = map[string]method{ | |||||
| "CONNECT": mCONNECT, | |||||
| "DELETE": mDELETE, | |||||
| "GET": mGET, | |||||
| "HEAD": mHEAD, | |||||
| "OPTIONS": mOPTIONS, | |||||
| "PATCH": mPATCH, | |||||
| "POST": mPOST, | |||||
| "PUT": mPUT, | |||||
| "TRACE": mTRACE, | |||||
| } | |||||
| type route struct { | |||||
| prefix string | |||||
| method method | |||||
| pattern Pattern | |||||
| handler Handler | |||||
| } | |||||
| type router struct { | |||||
| lock sync.Mutex | |||||
| routes []route | |||||
| notFound Handler | |||||
| machine *routeMachine | |||||
| } | |||||
| func httpMethod(mname string) method { | |||||
| if method, ok := validMethodsMap[mname]; ok { | |||||
| return method | |||||
| } | |||||
| return mIDK | |||||
| } | |||||
| func (rt *router) compile() *routeMachine { | |||||
| rt.lock.Lock() | |||||
| defer rt.lock.Unlock() | |||||
| sm := routeMachine{ | |||||
| sm: compile(rt.routes), | |||||
| routes: rt.routes, | |||||
| } | |||||
| rt.setMachine(&sm) | |||||
| return &sm | |||||
| } | |||||
| func (rt *router) getMatch(c *C, w http.ResponseWriter, r *http.Request) Match { | |||||
| rm := rt.getMachine() | |||||
| if rm == nil { | |||||
| rm = rt.compile() | |||||
| } | |||||
| methods, route := rm.route(c, w, r) | |||||
| if route != nil { | |||||
| return Match{ | |||||
| Pattern: route.pattern, | |||||
| Handler: route.handler, | |||||
| } | |||||
| } | |||||
| if methods == 0 { | |||||
| return Match{Handler: rt.notFound} | |||||
| } | |||||
| var methodsList = make([]string, 0) | |||||
| for mname, meth := range validMethodsMap { | |||||
| if methods&meth != 0 { | |||||
| methodsList = append(methodsList, mname) | |||||
| } | |||||
| } | |||||
| sort.Strings(methodsList) | |||||
| if c.Env == nil { | |||||
| c.Env = map[interface{}]interface{}{ | |||||
| ValidMethodsKey: methodsList, | |||||
| } | |||||
| } else { | |||||
| c.Env[ValidMethodsKey] = methodsList | |||||
| } | |||||
| return Match{Handler: rt.notFound} | |||||
| } | |||||
| func (rt *router) route(c *C, w http.ResponseWriter, r *http.Request) { | |||||
| match := GetMatch(*c) | |||||
| if match.Handler == nil { | |||||
| match = rt.getMatch(c, w, r) | |||||
| } | |||||
| match.Handler.ServeHTTPC(*c, w, r) | |||||
| } | |||||
| func (rt *router) handleUntyped(p PatternType, m method, h HandlerType) { | |||||
| rt.handle(ParsePattern(p), m, parseHandler(h)) | |||||
| } | |||||
| func (rt *router) handle(p Pattern, m method, h Handler) { | |||||
| rt.lock.Lock() | |||||
| defer rt.lock.Unlock() | |||||
| // Calculate the sorted insertion point, because there's no reason to do | |||||
| // swapping hijinks if we're already making a copy. We need to use | |||||
| // bubble sort because we can only compare adjacent elements. | |||||
| pp := p.Prefix() | |||||
| var i int | |||||
| for i = len(rt.routes); i > 0; i-- { | |||||
| rip := rt.routes[i-1].prefix | |||||
| if rip <= pp || strings.HasPrefix(rip, pp) { | |||||
| break | |||||
| } | |||||
| } | |||||
| newRoutes := make([]route, len(rt.routes)+1) | |||||
| copy(newRoutes, rt.routes[:i]) | |||||
| newRoutes[i] = route{ | |||||
| prefix: pp, | |||||
| method: m, | |||||
| pattern: p, | |||||
| handler: h, | |||||
| } | |||||
| copy(newRoutes[i+1:], rt.routes[i:]) | |||||
| rt.setMachine(nil) | |||||
| rt.routes = newRoutes | |||||
| } | |||||
| @ -1,35 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "testing" | |||||
| ) | |||||
| func TestRouterMiddleware(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| ch := make(chan string, 1) | |||||
| m.Get("/a", chHandler(ch, "a")) | |||||
| m.Get("/b", chHandler(ch, "b")) | |||||
| m.Use(m.Router) | |||||
| m.Use(func(c *C, h http.Handler) http.Handler { | |||||
| fn := func(w http.ResponseWriter, r *http.Request) { | |||||
| m := GetMatch(*c) | |||||
| if rp := m.RawPattern(); rp != "/a" { | |||||
| t.Fatalf("RawPattern was not /a: %v", rp) | |||||
| } | |||||
| r.URL.Path = "/b" | |||||
| h.ServeHTTP(w, r) | |||||
| } | |||||
| return http.HandlerFunc(fn) | |||||
| }) | |||||
| r, _ := http.NewRequest("GET", "/a", nil) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| if v := <-ch; v != "a" { | |||||
| t.Errorf("Routing was not frozen! %s", v) | |||||
| } | |||||
| } | |||||
| @ -1,326 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "reflect" | |||||
| "regexp" | |||||
| "testing" | |||||
| "time" | |||||
| ) | |||||
| // These tests can probably be DRY'd up a bunch | |||||
| func chHandler(ch chan string, s string) http.Handler { | |||||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| ch <- s | |||||
| }) | |||||
| } | |||||
| var methods = []string{"CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", | |||||
| "POST", "PUT", "TRACE", "OTHER"} | |||||
| func TestMethods(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| ch := make(chan string, 1) | |||||
| m.Connect("/", chHandler(ch, "CONNECT")) | |||||
| m.Delete("/", chHandler(ch, "DELETE")) | |||||
| m.Head("/", chHandler(ch, "HEAD")) | |||||
| m.Get("/", chHandler(ch, "GET")) | |||||
| m.Options("/", chHandler(ch, "OPTIONS")) | |||||
| m.Patch("/", chHandler(ch, "PATCH")) | |||||
| m.Post("/", chHandler(ch, "POST")) | |||||
| m.Put("/", chHandler(ch, "PUT")) | |||||
| m.Trace("/", chHandler(ch, "TRACE")) | |||||
| m.Handle("/", chHandler(ch, "OTHER")) | |||||
| for _, method := range methods { | |||||
| r, _ := http.NewRequest(method, "/", nil) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| select { | |||||
| case val := <-ch: | |||||
| if val != method { | |||||
| t.Errorf("Got %q, expected %q", val, method) | |||||
| } | |||||
| case <-time.After(5 * time.Millisecond): | |||||
| t.Errorf("Timeout waiting for method %q", method) | |||||
| } | |||||
| } | |||||
| } | |||||
| type testPattern struct{} | |||||
| func (t testPattern) Prefix() string { | |||||
| return "" | |||||
| } | |||||
| func (t testPattern) Match(r *http.Request, c *C) bool { | |||||
| return true | |||||
| } | |||||
| func (t testPattern) Run(r *http.Request, c *C) { | |||||
| } | |||||
| var _ Pattern = testPattern{} | |||||
| func TestPatternTypes(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| m.Get("/hello/carl", http.NotFound) | |||||
| m.Get("/hello/:name", http.NotFound) | |||||
| m.Get(regexp.MustCompile(`^/hello/(?P<name>.+)$`), http.NotFound) | |||||
| m.Get(testPattern{}, http.NotFound) | |||||
| } | |||||
| type testHandler chan string | |||||
| func (t testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| t <- "http" | |||||
| } | |||||
| func (t testHandler) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||||
| t <- "httpc" | |||||
| } | |||||
| var testHandlerTable = map[string]string{ | |||||
| "/a": "http fn", | |||||
| "/b": "http handler", | |||||
| "/c": "web fn", | |||||
| "/d": "web handler", | |||||
| "/e": "httpc", | |||||
| } | |||||
| func TestHandlerTypes(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| ch := make(chan string, 1) | |||||
| m.Get("/a", func(w http.ResponseWriter, r *http.Request) { | |||||
| ch <- "http fn" | |||||
| }) | |||||
| m.Get("/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| ch <- "http handler" | |||||
| })) | |||||
| m.Get("/c", func(c C, w http.ResponseWriter, r *http.Request) { | |||||
| ch <- "web fn" | |||||
| }) | |||||
| m.Get("/d", HandlerFunc(func(c C, w http.ResponseWriter, r *http.Request) { | |||||
| ch <- "web handler" | |||||
| })) | |||||
| m.Get("/e", testHandler(ch)) | |||||
| for route, response := range testHandlerTable { | |||||
| r, _ := http.NewRequest("GET", route, nil) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| select { | |||||
| case resp := <-ch: | |||||
| if resp != response { | |||||
| t.Errorf("Got %q, expected %q", resp, response) | |||||
| } | |||||
| case <-time.After(5 * time.Millisecond): | |||||
| t.Errorf("Timeout waiting for path %q", route) | |||||
| } | |||||
| } | |||||
| } | |||||
| // The idea behind this test is to comprehensively test if routes are being | |||||
| // applied in the right order. We define a special pattern type that always | |||||
| // matches so long as it's greater than or equal to the global test index. By | |||||
| // incrementing this index, we can invalidate all routes up to some point, and | |||||
| // therefore test the routing guarantee that Goji provides: for any path P, if | |||||
| // both A and B match P, and if A was inserted before B, then Goji will route to | |||||
| // A before it routes to B. | |||||
| var rsRoutes = []string{ | |||||
| "/", | |||||
| "/a", | |||||
| "/a", | |||||
| "/b", | |||||
| "/ab", | |||||
| "/", | |||||
| "/ba", | |||||
| "/b", | |||||
| "/a", | |||||
| } | |||||
| var rsTests = []struct { | |||||
| key string | |||||
| results []int | |||||
| }{ | |||||
| {"/", []int{0, 5, 5, 5, 5, 5, -1, -1, -1, -1}}, | |||||
| {"/a", []int{0, 1, 2, 5, 5, 5, 8, 8, 8, -1}}, | |||||
| {"/b", []int{0, 3, 3, 3, 5, 5, 7, 7, -1, -1}}, | |||||
| {"/ab", []int{0, 1, 2, 4, 4, 5, 8, 8, 8, -1}}, | |||||
| {"/ba", []int{0, 3, 3, 3, 5, 5, 6, 7, -1, -1}}, | |||||
| {"/c", []int{0, 5, 5, 5, 5, 5, -1, -1, -1, -1}}, | |||||
| {"nope", []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, | |||||
| } | |||||
| type rsPattern struct { | |||||
| i int | |||||
| counter *int | |||||
| prefix string | |||||
| ichan chan int | |||||
| } | |||||
| func (rs rsPattern) Prefix() string { | |||||
| return rs.prefix | |||||
| } | |||||
| func (rs rsPattern) Match(_ *http.Request, _ *C) bool { | |||||
| return rs.i >= *rs.counter | |||||
| } | |||||
| func (rs rsPattern) Run(_ *http.Request, _ *C) { | |||||
| } | |||||
| func (rs rsPattern) ServeHTTP(_ http.ResponseWriter, _ *http.Request) { | |||||
| rs.ichan <- rs.i | |||||
| } | |||||
| var _ Pattern = rsPattern{} | |||||
| var _ http.Handler = rsPattern{} | |||||
| func TestRouteSelection(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| counter := 0 | |||||
| ichan := make(chan int, 1) | |||||
| m.NotFound(func(w http.ResponseWriter, r *http.Request) { | |||||
| ichan <- -1 | |||||
| }) | |||||
| for i, s := range rsRoutes { | |||||
| pat := rsPattern{ | |||||
| i: i, | |||||
| counter: &counter, | |||||
| prefix: s, | |||||
| ichan: ichan, | |||||
| } | |||||
| m.Get(pat, pat) | |||||
| } | |||||
| for _, test := range rsTests { | |||||
| var n int | |||||
| for counter, n = range test.results { | |||||
| r, _ := http.NewRequest("GET", test.key, nil) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| actual := <-ichan | |||||
| if n != actual { | |||||
| t.Errorf("Expected %q @ %d to be %d, got %d", | |||||
| test.key, counter, n, actual) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| func TestNotFound(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| r, _ := http.NewRequest("post", "/", nil) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| if w.Code != 404 { | |||||
| t.Errorf("Expected 404, got %d", w.Code) | |||||
| } | |||||
| m.NotFound(func(w http.ResponseWriter, r *http.Request) { | |||||
| http.Error(w, "I'm a teapot!", http.StatusTeapot) | |||||
| }) | |||||
| r, _ = http.NewRequest("POST", "/", nil) | |||||
| w = httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| if w.Code != http.StatusTeapot { | |||||
| t.Errorf("Expected a teapot, got %d", w.Code) | |||||
| } | |||||
| } | |||||
| func TestPrefix(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| ch := make(chan string, 1) | |||||
| m.Handle("/hello/*", func(w http.ResponseWriter, r *http.Request) { | |||||
| ch <- r.URL.Path | |||||
| }) | |||||
| r, _ := http.NewRequest("GET", "/hello/world", nil) | |||||
| w := httptest.NewRecorder() | |||||
| m.ServeHTTP(w, r) | |||||
| select { | |||||
| case val := <-ch: | |||||
| if val != "/hello/world" { | |||||
| t.Errorf("Got %q, expected /hello/world", val) | |||||
| } | |||||
| case <-time.After(5 * time.Millisecond): | |||||
| t.Errorf("Timeout waiting for hello") | |||||
| } | |||||
| } | |||||
| var validMethodsTable = map[string][]string{ | |||||
| "/hello/carl": {"DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"}, | |||||
| "/hello/bob": {"DELETE", "GET", "HEAD", "PATCH", "PUT"}, | |||||
| "/hola/carl": {"DELETE", "GET", "HEAD", "PUT"}, | |||||
| "/hola/bob": {"DELETE"}, | |||||
| "/does/not/compute": {}, | |||||
| } | |||||
| func TestValidMethods(t *testing.T) { | |||||
| t.Parallel() | |||||
| m := New() | |||||
| ch := make(chan []string, 1) | |||||
| m.NotFound(func(c C, w http.ResponseWriter, r *http.Request) { | |||||
| if c.Env == nil { | |||||
| ch <- []string{} | |||||
| return | |||||
| } | |||||
| methods, ok := c.Env[ValidMethodsKey] | |||||
| if !ok { | |||||
| ch <- []string{} | |||||
| return | |||||
| } | |||||
| ch <- methods.([]string) | |||||
| }) | |||||
| m.Get("/hello/carl", http.NotFound) | |||||
| m.Post("/hello/carl", http.NotFound) | |||||
| m.Head("/hello/bob", http.NotFound) | |||||
| m.Get("/hello/:name", http.NotFound) | |||||
| m.Put("/hello/:name", http.NotFound) | |||||
| m.Patch("/hello/:name", http.NotFound) | |||||
| m.Get("/:greet/carl", http.NotFound) | |||||
| m.Put("/:greet/carl", http.NotFound) | |||||
| m.Delete("/:greet/:anyone", http.NotFound) | |||||
| for path, eMethods := range validMethodsTable { | |||||
| r, _ := http.NewRequest("BOGUS", path, nil) | |||||
| m.ServeHTTP(httptest.NewRecorder(), r) | |||||
| aMethods := <-ch | |||||
| if !reflect.DeepEqual(eMethods, aMethods) { | |||||
| t.Errorf("For %q, expected %v, got %v", path, eMethods, | |||||
| aMethods) | |||||
| } | |||||
| } | |||||
| // This should also work when c.Env has already been initalized | |||||
| m.Use(func(c *C, h http.Handler) http.Handler { | |||||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
| c.Env = make(map[interface{}]interface{}) | |||||
| h.ServeHTTP(w, r) | |||||
| }) | |||||
| }) | |||||
| for path, eMethods := range validMethodsTable { | |||||
| r, _ := http.NewRequest("BOGUS", path, nil) | |||||
| m.ServeHTTP(httptest.NewRecorder(), r) | |||||
| aMethods := <-ch | |||||
| if !reflect.DeepEqual(eMethods, aMethods) { | |||||
| t.Errorf("For %q, expected %v, got %v", path, eMethods, | |||||
| aMethods) | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,137 +0,0 @@ | |||||
| package web | |||||
| import ( | |||||
| "fmt" | |||||
| "net/http" | |||||
| "regexp" | |||||
| "strings" | |||||
| ) | |||||
| // stringPattern is a struct describing | |||||
| type stringPattern struct { | |||||
| raw string | |||||
| pats []string | |||||
| breaks []byte | |||||
| literals []string | |||||
| wildcard bool | |||||
| } | |||||
| func (s stringPattern) Prefix() string { | |||||
| return s.literals[0] | |||||
| } | |||||
| func (s stringPattern) Match(r *http.Request, c *C) bool { | |||||
| return s.match(r, c, true) | |||||
| } | |||||
| func (s stringPattern) Run(r *http.Request, c *C) { | |||||
| s.match(r, c, false) | |||||
| } | |||||
| func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool { | |||||
| path := r.URL.Path | |||||
| var matches map[string]string | |||||
| if !dryrun { | |||||
| if s.wildcard { | |||||
| matches = make(map[string]string, len(s.pats)+1) | |||||
| } else if len(s.pats) != 0 { | |||||
| matches = make(map[string]string, len(s.pats)) | |||||
| } | |||||
| } | |||||
| for i, pat := range s.pats { | |||||
| sli := s.literals[i] | |||||
| if !strings.HasPrefix(path, sli) { | |||||
| return false | |||||
| } | |||||
| path = path[len(sli):] | |||||
| m := 0 | |||||
| bc := s.breaks[i] | |||||
| for ; m < len(path); m++ { | |||||
| if path[m] == bc || path[m] == '/' { | |||||
| break | |||||
| } | |||||
| } | |||||
| if m == 0 { | |||||
| // Empty strings are not matches, otherwise routes like | |||||
| // "/:foo" would match the path "/" | |||||
| return false | |||||
| } | |||||
| if !dryrun { | |||||
| matches[pat] = path[:m] | |||||
| } | |||||
| path = path[m:] | |||||
| } | |||||
| // There's exactly one more literal than pat. | |||||
| tail := s.literals[len(s.pats)] | |||||
| if s.wildcard { | |||||
| if !strings.HasPrefix(path, tail) { | |||||
| return false | |||||
| } | |||||
| if !dryrun { | |||||
| matches["*"] = path[len(tail)-1:] | |||||
| } | |||||
| } else if path != tail { | |||||
| return false | |||||
| } | |||||
| if c == nil || dryrun { | |||||
| return true | |||||
| } | |||||
| if c.URLParams == nil { | |||||
| c.URLParams = matches | |||||
| } else { | |||||
| for k, v := range matches { | |||||
| c.URLParams[k] = v | |||||
| } | |||||
| } | |||||
| return true | |||||
| } | |||||
| func (s stringPattern) String() string { | |||||
| return fmt.Sprintf("stringPattern(%q)", s.raw) | |||||
| } | |||||
| func (s stringPattern) Raw() string { | |||||
| return s.raw | |||||
| } | |||||
| // "Break characters" are characters that can end patterns. They are not allowed | |||||
| // to appear in pattern names. "/" was chosen because it is the standard path | |||||
| // separator, and "." was chosen because it often delimits file extensions. ";" | |||||
| // and "," were chosen because Section 3.3 of RFC 3986 suggests their use. | |||||
| const bc = "/.;," | |||||
| var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`) | |||||
| func parseStringPattern(s string) stringPattern { | |||||
| raw := s | |||||
| var wildcard bool | |||||
| if strings.HasSuffix(s, "/*") { | |||||
| s = s[:len(s)-1] | |||||
| wildcard = true | |||||
| } | |||||
| matches := patternRe.FindAllStringSubmatchIndex(s, -1) | |||||
| pats := make([]string, len(matches)) | |||||
| breaks := make([]byte, len(matches)) | |||||
| literals := make([]string, len(matches)+1) | |||||
| n := 0 | |||||
| for i, match := range matches { | |||||
| a, b := match[2], match[3] | |||||
| literals[i] = s[n : a-1] // Need to leave off the colon | |||||
| pats[i] = s[a:b] | |||||
| if b == len(s) { | |||||
| breaks[i] = '/' | |||||
| } else { | |||||
| breaks[i] = s[b] | |||||
| } | |||||
| n = b | |||||
| } | |||||
| literals[len(matches)] = s[n:] | |||||
| return stringPattern{ | |||||
| raw: raw, | |||||
| pats: pats, | |||||
| breaks: breaks, | |||||
| literals: literals, | |||||
| wildcard: wildcard, | |||||
| } | |||||
| } | |||||
| @ -1,112 +0,0 @@ | |||||
| /* | |||||
| Package web provides a fast and flexible middleware stack and mux. | |||||
| This package attempts to solve three problems that net/http does not. First, it | |||||
| allows you to specify flexible patterns, including routes with named parameters | |||||
| and regular expressions. Second, it allows you to write reconfigurable | |||||
| middleware stacks. And finally, it allows you to attach additional context to | |||||
| requests, in a manner that can be manipulated by both compliant middleware and | |||||
| handlers. | |||||
| */ | |||||
| package web | |||||
| import ( | |||||
| "net/http" | |||||
| ) | |||||
| /* | |||||
| C is a request-local context object which is threaded through all compliant | |||||
| middleware layers and given to the final request handler. | |||||
| */ | |||||
| type C struct { | |||||
| // URLParams is a map of variables extracted from the URL (typically | |||||
| // from the path portion) during routing. See the documentation for the | |||||
| // URL Pattern you are using (or the documentation for PatternType for | |||||
| // the case of standard pattern types) for more information about how | |||||
| // variables are extracted and named. | |||||
| URLParams map[string]string | |||||
| // Env is a free-form environment for storing request-local data. Keys | |||||
| // may be arbitrary types that support equality, however package-private | |||||
| // types with type-safe accessors provide a convenient way for packages | |||||
| // to mediate access to their request-local data. | |||||
| Env map[interface{}]interface{} | |||||
| } | |||||
| // Handler is similar to net/http's http.Handler, but also accepts a Goji | |||||
| // context object. | |||||
| type Handler interface { | |||||
| ServeHTTPC(C, http.ResponseWriter, *http.Request) | |||||
| } | |||||
| // HandlerFunc is similar to net/http's http.HandlerFunc, but supports a context | |||||
| // object. Implements both http.Handler and Handler. | |||||
| type HandlerFunc func(C, http.ResponseWriter, *http.Request) | |||||
| // ServeHTTP implements http.Handler, allowing HandlerFunc's to be used with | |||||
| // net/http and other compliant routers. When used in this way, the underlying | |||||
| // function will be passed an empty context. | |||||
| func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
| h(C{}, w, r) | |||||
| } | |||||
| // ServeHTTPC implements Handler. | |||||
| func (h HandlerFunc) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { | |||||
| h(c, w, r) | |||||
| } | |||||
| /* | |||||
| PatternType is the type denoting Patterns and types that Goji internally | |||||
| converts to Pattern (via the ParsePattern function). In order to provide an | |||||
| expressive API, this type is an alias for interface{} that is named for the | |||||
| purposes of documentation, however only the following concrete types are | |||||
| accepted: | |||||
| - types that implement Pattern | |||||
| - string, which is interpreted as a Sinatra-like URL pattern. In | |||||
| particular, the following syntax is recognized: | |||||
| - a path segment starting with a colon will match any | |||||
| string placed at that position. e.g., "/:name" will match | |||||
| "/carl", binding "name" to "carl". | |||||
| - a pattern ending with "/*" will match any route with that | |||||
| prefix. For instance, the pattern "/u/:name/*" will match | |||||
| "/u/carl/" and "/u/carl/projects/123", but not "/u/carl" | |||||
| (because there is no trailing slash). In addition to any names | |||||
| bound in the pattern, the special key "*" is bound to the | |||||
| unmatched tail of the match, but including the leading "/". So | |||||
| for the two matching examples above, "*" would be bound to "/" | |||||
| and "/projects/123" respectively. | |||||
| Unlike http.ServeMux's patterns, string patterns support neither the | |||||
| "rooted subtree" behavior nor Host-specific routes. Users who require | |||||
| either of these features are encouraged to compose package http's mux | |||||
| with the mux provided by this package. | |||||
| - regexp.Regexp, which is assumed to be a Perl-style regular expression | |||||
| that is anchored on the left (i.e., the beginning of the string). If | |||||
| your regular expression is not anchored on the left, a | |||||
| hopefully-identical left-anchored regular expression will be created | |||||
| and used instead. | |||||
| Capturing groups will be converted into bound URL parameters in | |||||
| URLParams. If the capturing group is named, that name will be used; | |||||
| otherwise the special identifiers "$1", "$2", etc. will be used. | |||||
| */ | |||||
| type PatternType interface{} | |||||
| /* | |||||
| HandlerType is the type of Handlers and types that Goji internally converts to | |||||
| Handler. In order to provide an expressive API, this type is an alias for | |||||
| interface{} that is named for the purposes of documentation, however only the | |||||
| following concrete types are accepted: | |||||
| - types that implement http.Handler | |||||
| - types that implement Handler | |||||
| - func(http.ResponseWriter, *http.Request) | |||||
| - func(web.C, http.ResponseWriter, *http.Request) | |||||
| */ | |||||
| type HandlerType interface{} | |||||
| /* | |||||
| MiddlewareType is the type of Goji middleware. In order to provide an expressive | |||||
| API, this type is an alias for interface{} that is named for the purposes of | |||||
| documentation, however only the following concrete types are accepted: | |||||
| - func(http.Handler) http.Handler | |||||
| - func(*web.C, http.Handler) http.Handler | |||||
| */ | |||||
| type MiddlewareType interface{} | |||||