git-subtree-dir: vendor/github.com/zenazn/goji git-subtree-mainline:f580a2651cgit-subtree-split:5a802af1de
| @ -0,0 +1,33 @@ | |||
| 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 ./... | |||
| @ -0,0 +1,20 @@ | |||
| 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. | |||
| @ -0,0 +1,176 @@ | |||
| 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. | |||
| @ -0,0 +1,145 @@ | |||
| /* | |||
| 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() | |||
| }) | |||
| } | |||
| @ -0,0 +1,91 @@ | |||
| // +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) | |||
| } | |||
| } | |||
| @ -0,0 +1,12 @@ | |||
| // +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 } | |||
| @ -0,0 +1,36 @@ | |||
| // +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 | |||
| } | |||
| @ -0,0 +1,6 @@ | |||
| // +build windows | |||
| package bind | |||
| func systemdInit() {} | |||
| func usingSystemd() bool { return false } | |||
| @ -0,0 +1,102 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1 @@ | |||
| example | |||
| @ -0,0 +1,10 @@ | |||
| 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`. | |||
| @ -0,0 +1,177 @@ | |||
| // 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) | |||
| } | |||
| @ -0,0 +1,47 @@ | |||
| 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")) | |||
| } | |||
| @ -0,0 +1,49 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1,36 @@ | |||
| /* | |||
| 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 | |||
| @ -0,0 +1,11 @@ | |||
| // +build !go1.6 | |||
| package graceful | |||
| import "crypto/tls" | |||
| // see clone16.go | |||
| func cloneTLSConfig(cfg *tls.Config) *tls.Config { | |||
| c := *cfg | |||
| return &c | |||
| } | |||
| @ -0,0 +1,34 @@ | |||
| // +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, | |||
| } | |||
| } | |||
| @ -0,0 +1,21 @@ | |||
| // +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) | |||
| } | |||
| @ -0,0 +1,62 @@ | |||
| /* | |||
| 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 | |||
| } | |||
| @ -0,0 +1,151 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,198 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,123 @@ | |||
| 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{}{} | |||
| } | |||
| @ -0,0 +1,178 @@ | |||
| /* | |||
| 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 | |||
| } | |||
| @ -0,0 +1,156 @@ | |||
| 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?") | |||
| } | |||
| } | |||
| @ -0,0 +1,103 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,98 @@ | |||
| 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() | |||
| } | |||
| @ -0,0 +1,103 @@ | |||
| // +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{} | |||
| @ -0,0 +1,71 @@ | |||
| // +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) | |||
| } | |||
| @ -0,0 +1,33 @@ | |||
| // +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) | |||
| } | |||
| @ -0,0 +1,76 @@ | |||
| // +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) | |||
| } | |||
| @ -0,0 +1,108 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1,197 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,64 @@ | |||
| // +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() | |||
| } | |||
| @ -0,0 +1,23 @@ | |||
| // +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) | |||
| } | |||
| @ -0,0 +1,18 @@ | |||
| // +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)) | |||
| } | |||
| @ -0,0 +1,14 @@ | |||
| // +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 | |||
| } | |||
| @ -0,0 +1,166 @@ | |||
| // +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) | |||
| } | |||
| @ -0,0 +1,265 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,83 @@ | |||
| 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++ | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,31 @@ | |||
| // +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: | |||
| } | |||
| } | |||
| @ -0,0 +1,23 @@ | |||
| // +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) | |||
| } | |||
| @ -0,0 +1,69 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1,32 @@ | |||
| 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() | |||
| } | |||
| @ -0,0 +1,84 @@ | |||
| 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") | |||
| } | |||
| } | |||
| @ -0,0 +1,42 @@ | |||
| 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") | |||
| } | |||
| } | |||
| @ -0,0 +1,66 @@ | |||
| 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 | |||
| } | |||
| } | |||
| @ -0,0 +1,50 @@ | |||
| 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) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,154 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,27 @@ | |||
| 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} | |||
| } | |||
| @ -0,0 +1,92 @@ | |||
| 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()) | |||
| } | |||
| @ -0,0 +1,4 @@ | |||
| /* | |||
| Package middleware provides several standard middleware implementations. | |||
| */ | |||
| package middleware | |||
| @ -0,0 +1,55 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1,29 @@ | |||
| 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) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,97 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1,112 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,51 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,44 @@ | |||
| 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()) | |||
| } | |||
| @ -0,0 +1,88 @@ | |||
| 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 "" | |||
| } | |||
| @ -0,0 +1,65 @@ | |||
| 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} | |||
| } | |||
| @ -0,0 +1,28 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1,60 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,24 @@ | |||
| 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) | |||
| } | |||
| @ -0,0 +1,53 @@ | |||
| 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'}) | |||
| }, | |||
| ) | |||
| } | |||
| } | |||
| @ -0,0 +1,52 @@ | |||
| // +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") | |||
| } | |||
| } | |||
| @ -0,0 +1,204 @@ | |||
| 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") | |||
| } | |||
| @ -0,0 +1,3 @@ | |||
| // Package mutil contains various functions that are helpful when writing http | |||
| // middleware. | |||
| package mutil | |||
| @ -0,0 +1,139 @@ | |||
| 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{} | |||
| @ -0,0 +1,213 @@ | |||
| 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() | |||
| } | |||
| @ -0,0 +1,45 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,58 @@ | |||
| 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") | |||
| } | |||
| } | |||
| @ -0,0 +1,188 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,149 @@ | |||
| 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, | |||
| } | |||
| } | |||
| @ -0,0 +1,154 @@ | |||
| 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 | |||
| } | |||
| @ -0,0 +1,35 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @ -0,0 +1,326 @@ | |||
| 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) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,137 @@ | |||
| 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, | |||
| } | |||
| } | |||
| @ -0,0 +1,112 @@ | |||
| /* | |||
| 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{} | |||