mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-30 21:30:16 +00:00 
			
		
		
		
	Add dependencies for k8petstore to Godeps
Cleanup k8petstore to use Godeps dependencies, add to hack/build-go.sh Remove trailing spaces, README
This commit is contained in:
		
				
					committed by
					
						 jayunit100
						jayunit100
					
				
			
			
				
	
			
			
			
						parent
						
							abf3bb226e
						
					
				
				
					commit
					404b20e6b8
				
			
							
								
								
									
										28
									
								
								Godeps/Godeps.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								Godeps/Godeps.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
| 	"ImportPath": "github.com/GoogleCloudPlatform/kubernetes", | ||||
| 	"GoVersion": "go1.4.1", | ||||
| 	"GoVersion": "go1.3", | ||||
| 	"Packages": [ | ||||
| 		"./..." | ||||
| 	], | ||||
| @@ -62,6 +62,11 @@ | ||||
| 			"ImportPath": "github.com/beorn7/perks/quantile", | ||||
| 			"Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/codegangsta/negroni", | ||||
| 			"Comment": "v0.1-62-g8d75e11", | ||||
| 			"Rev": "8d75e11374a1928608c906fe745b538483e7aeb2" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/coreos/go-etcd/etcd", | ||||
| 			"Comment": "v0.2.0-rc1-120-g23142f6", | ||||
| @@ -162,6 +167,14 @@ | ||||
| 			"ImportPath": "github.com/fsouza/go-dockerclient", | ||||
| 			"Rev": "d19717788084716e4adff0515be6289aa04bec46" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/garyburd/redigo/internal", | ||||
| 			"Rev": "535138d7bcd717d6531c701ef5933d98b1866257" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/garyburd/redigo/redis", | ||||
| 			"Rev": "535138d7bcd717d6531c701ef5933d98b1866257" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/ghodss/yaml", | ||||
| 			"Rev": "588cb435e59ee8b6c2795482887755841ad67207" | ||||
| @@ -267,6 +280,14 @@ | ||||
| 			"ImportPath": "github.com/google/gofuzz", | ||||
| 			"Rev": "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/gorilla/context", | ||||
| 			"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/gorilla/mux", | ||||
| 			"Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/imdario/mergo", | ||||
| 			"Comment": "0.1.3-8-g6633656", | ||||
| @@ -383,6 +404,11 @@ | ||||
| 			"ImportPath": "github.com/vaughan0/go-ini", | ||||
| 			"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/xyproto/simpleredis", | ||||
| 			"Comment": "v1.0-13-g5292687", | ||||
| 			"Rev": "5292687f5379e01054407da44d7c4590a61fd3de" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "golang.org/x/net/context", | ||||
| 			"Rev": "cbcac7bb8415db9b6cb4d1ebab1dc9afbd688b97" | ||||
|   | ||||
							
								
								
									
										21
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2014 Jeremy Saenz | ||||
|  | ||||
| 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. | ||||
							
								
								
									
										181
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| # Negroni [](http://godoc.org/github.com/codegangsta/negroni) [](https://app.wercker.com/project/bykey/13688a4a94b82d84a0b8d038c4965b61) | ||||
|  | ||||
| Negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of `net/http` Handlers. | ||||
|  | ||||
| If you like the idea of [Martini](http://github.com/go-martini/martini), but you think it contains too much magic, then Negroni is a great fit. | ||||
|  | ||||
|  | ||||
| Language Translations: | ||||
| * [Português Brasileiro (pt_BR)](translations/README_pt_br.md) | ||||
|  | ||||
| ## Getting Started | ||||
|  | ||||
| After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. | ||||
|  | ||||
| ~~~ go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
|   "github.com/codegangsta/negroni" | ||||
|   "net/http" | ||||
|   "fmt" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|   mux := http.NewServeMux() | ||||
|   mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { | ||||
|     fmt.Fprintf(w, "Welcome to the home page!") | ||||
|   }) | ||||
|  | ||||
|   n := negroni.Classic() | ||||
|   n.UseHandler(mux) | ||||
|   n.Run(":3000") | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| Then install the Negroni package (**go 1.1** and greater is required): | ||||
| ~~~ | ||||
| go get github.com/codegangsta/negroni | ||||
| ~~~ | ||||
|  | ||||
| Then run your server: | ||||
| ~~~ | ||||
| go run server.go | ||||
| ~~~ | ||||
|  | ||||
| You will now have a Go net/http webserver running on `localhost:3000`. | ||||
|  | ||||
| ## Need Help? | ||||
| If you have a question or feature request, [go ask the mailing list](https://groups.google.com/forum/#!forum/negroni-users). The GitHub issues for Negroni will be used exclusively for bug reports and pull requests. | ||||
|  | ||||
| ## Is Negroni a Framework? | ||||
| Negroni is **not** a framework. It is a library that is designed to work directly with net/http. | ||||
|  | ||||
| ## Routing? | ||||
| Negroni is BYOR (Bring your own Router). The Go community already has a number of great http routers available, Negroni tries to play well with all of them by fully supporting `net/http`. For instance, integrating with [Gorilla Mux](http://github.com/gorilla/mux) looks like so: | ||||
|  | ||||
| ~~~ go | ||||
| router := mux.NewRouter() | ||||
| router.HandleFunc("/", HomeHandler) | ||||
|  | ||||
| n := negroni.New(Middleware1, Middleware2) | ||||
| // Or use a middleware with the Use() function | ||||
| n.Use(Middleware3) | ||||
| // router goes last | ||||
| n.UseHandler(router) | ||||
|  | ||||
| n.Run(":3000") | ||||
| ~~~ | ||||
|  | ||||
| ## `negroni.Classic()` | ||||
| `negroni.Classic()` provides some default middleware that is useful for most applications: | ||||
|  | ||||
| * `negroni.Recovery` - Panic Recovery Middleware. | ||||
| * `negroni.Logging` - Request/Response Logging Middleware. | ||||
| * `negroni.Static` - Static File serving under the "public" directory. | ||||
|  | ||||
| This makes it really easy to get started with some useful features from Negroni. | ||||
|  | ||||
| ## Handlers | ||||
| Negroni provides a bidirectional middleware flow. This is done through the `negroni.Handler` interface: | ||||
|  | ||||
| ~~~ go | ||||
| type Handler interface { | ||||
|   ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| If a middleware hasn't already written to the ResponseWriter, it should call the next `http.HandlerFunc` in the chain to yield to the next middleware handler. This can be used for great good: | ||||
|  | ||||
| ~~~ go | ||||
| func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
|   // do some stuff before | ||||
|   next(rw, r) | ||||
|   // do some stuff after | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| And you can map it to the handler chain with the `Use` function: | ||||
|  | ||||
| ~~~ go | ||||
| n := negroni.New() | ||||
| n.Use(negroni.HandlerFunc(MyMiddleware)) | ||||
| ~~~ | ||||
|  | ||||
| You can also map plain old `http.Handler`s: | ||||
|  | ||||
| ~~~ go | ||||
| n := negroni.New() | ||||
|  | ||||
| mux := http.NewServeMux() | ||||
| // map your routes | ||||
|  | ||||
| n.UseHandler(mux) | ||||
|  | ||||
| n.Run(":3000") | ||||
| ~~~ | ||||
|  | ||||
| ## `Run()` | ||||
| Negroni has a convenience function called `Run`. `Run` takes an addr string identical to [http.ListenAndServe](http://golang.org/pkg/net/http#ListenAndServe). | ||||
|  | ||||
| ~~~ go | ||||
| n := negroni.Classic() | ||||
| // ... | ||||
| log.Fatal(http.ListenAndServe(":8080", n)) | ||||
| ~~~ | ||||
|  | ||||
| ## Route Specific Middleware | ||||
| If you have a route group of routes that need specific middleware to be executed, you can simply create a new Negroni instance and use it as your route handler. | ||||
|  | ||||
| ~~~ go | ||||
| router := mux.NewRouter() | ||||
| adminRoutes := mux.NewRouter() | ||||
| // add admin routes here | ||||
|  | ||||
| // Create a new negroni for the admin middleware | ||||
| router.Handle("/admin", negroni.New( | ||||
|   Middleware1, | ||||
|   Middleware2, | ||||
|   negroni.Wrap(adminRoutes), | ||||
| )) | ||||
| ~~~ | ||||
|  | ||||
| ## Third Party Middleware | ||||
|  | ||||
| Here is a current list of Negroni compatible middlware. Feel free to put up a PR linking your middleware if you have built one: | ||||
|  | ||||
|  | ||||
| | Middleware | Author | Description | | ||||
| | -----------|--------|-------------| | ||||
| | [RestGate](https://github.com/pjebs/restgate) | [Prasanga Siripala](https://github.com/pjebs) | Secure authentication for REST API endpoints | | ||||
| | [Graceful](https://github.com/stretchr/graceful) | [Tyler Bunnell](https://github.com/tylerb) | Graceful HTTP Shutdown | | ||||
| | [secure](https://github.com/unrolled/secure) | [Cory Jacobsen](https://github.com/unrolled) | Middleware that implements a few quick security wins | | ||||
| | [JWT Middleware](https://github.com/auth0/go-jwt-middleware) | [Auth0](https://github.com/auth0) | Middleware checks for a JWT on the `Authorization` header on incoming requests and decodes it| | ||||
| | [binding](https://github.com/mholt/binding) | [Matt Holt](https://github.com/mholt) | Data binding from HTTP requests into structs | | ||||
| | [logrus](https://github.com/meatballhat/negroni-logrus) | [Dan Buch](https://github.com/meatballhat) | Logrus-based logger | | ||||
| | [render](https://github.com/unrolled/render) | [Cory Jacobsen](https://github.com/unrolled) | Render JSON, XML and HTML templates | | ||||
| | [gorelic](https://github.com/jingweno/negroni-gorelic) | [Jingwen Owen Ou](https://github.com/jingweno) | New Relic agent for Go runtime | | ||||
| | [gzip](https://github.com/phyber/negroni-gzip) | [phyber](https://github.com/phyber) | GZIP response compression | | ||||
| | [oauth2](https://github.com/goincremental/negroni-oauth2) | [David Bochenski](https://github.com/bochenski) | oAuth2 middleware | | ||||
| | [sessions](https://github.com/goincremental/negroni-sessions) | [David Bochenski](https://github.com/bochenski) | Session Management | | ||||
| | [permissions2](https://github.com/xyproto/permissions2) | [Alexander Rødseth](https://github.com/xyproto) | Cookies, users and permissions | | ||||
| | [onthefly](https://github.com/xyproto/onthefly) | [Alexander Rødseth](https://github.com/xyproto) | Generate TinySVG, HTML and CSS on the fly | | ||||
| | [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support | | ||||
| | [xrequestid](https://github.com/pilu/xrequestid) | [Andrea Franz](https://github.com/pilu) | Middleware that assigns a random X-Request-Id header to each request | | ||||
| | [VanGoH](https://github.com/auroratechnologies/vangoh) | [Taylor Wrobel](https://github.com/twrobel3) | Configurable [AWS-Style](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html) HMAC authentication middleware | | ||||
| | [stats](https://github.com/thoas/stats) | [Florent Messa](https://github.com/thoas) | Store information about your web application (response time, etc.) | | ||||
|  | ||||
| ## Examples | ||||
| [Alexander Rødseth](https://github.com/xyproto) created [mooseware](https://github.com/xyproto/mooseware), a skeleton for writing a Negroni middleware handler. | ||||
|  | ||||
| ## Live code reload? | ||||
| [gin](https://github.com/codegangsta/gin) and [fresh](https://github.com/pilu/fresh) both live reload negroni apps. | ||||
|  | ||||
| ## Essential Reading for Beginners of Go & Negroni | ||||
|  | ||||
| * [Using a Context to pass information from middleware to end handler](http://elithrar.github.io/article/map-string-interface/) | ||||
| * [Understanding middleware](http://mattstauffer.co/blog/laravel-5.0-middleware-replacing-filters) | ||||
|  | ||||
| ## About | ||||
|  | ||||
| Negroni is obsessively designed by none other than the [Code Gangsta](http://codegangsta.io/) | ||||
							
								
								
									
										25
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // Package negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of net/http Handlers. | ||||
| // | ||||
| // If you like the idea of Martini, but you think it contains too much magic, then Negroni is a great fit. | ||||
| // | ||||
| // For a full guide visit http://github.com/codegangsta/negroni | ||||
| // | ||||
| //  package main | ||||
| // | ||||
| //  import ( | ||||
| //    "github.com/codegangsta/negroni" | ||||
| //    "net/http" | ||||
| //    "fmt" | ||||
| //  ) | ||||
| // | ||||
| //  func main() { | ||||
| //    mux := http.NewServeMux() | ||||
| //    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { | ||||
| //      fmt.Fprintf(w, "Welcome to the home page!") | ||||
| //    }) | ||||
| // | ||||
| //    n := negroni.Classic() | ||||
| //    n.UseHandler(mux) | ||||
| //    n.Run(":3000") | ||||
| //  } | ||||
| package negroni | ||||
							
								
								
									
										29
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/logger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/logger.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // Logger is a middleware handler that logs the request as it goes in and the response as it goes out. | ||||
| type Logger struct { | ||||
| 	// Logger inherits from log.Logger used to log messages with the Logger middleware | ||||
| 	*log.Logger | ||||
| } | ||||
|  | ||||
| // NewLogger returns a new Logger instance | ||||
| func NewLogger() *Logger { | ||||
| 	return &Logger{log.New(os.Stdout, "[negroni] ", 0)} | ||||
| } | ||||
|  | ||||
| func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 	start := time.Now() | ||||
| 	l.Printf("Started %s %s", r.Method, r.URL.Path) | ||||
|  | ||||
| 	next(rw, r) | ||||
|  | ||||
| 	res := rw.(ResponseWriter) | ||||
| 	l.Printf("Completed %v %s in %v", res.Status(), http.StatusText(res.Status()), time.Since(start)) | ||||
| } | ||||
							
								
								
									
										33
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/logger_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/logger_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func Test_Logger(t *testing.T) { | ||||
| 	buff := bytes.NewBufferString("") | ||||
| 	recorder := httptest.NewRecorder() | ||||
|  | ||||
| 	l := NewLogger() | ||||
| 	l.Logger = log.New(buff, "[negroni] ", 0) | ||||
|  | ||||
| 	n := New() | ||||
| 	// replace log for testing | ||||
| 	n.Use(l) | ||||
| 	n.UseHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||
| 		rw.WriteHeader(http.StatusNotFound) | ||||
| 	})) | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://localhost:3000/foobar", nil) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	n.ServeHTTP(recorder, req) | ||||
| 	expect(t, recorder.Code, http.StatusNotFound) | ||||
| 	refute(t, len(buff.String()), 0) | ||||
| } | ||||
							
								
								
									
										119
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/negroni.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/negroni.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // Handler handler is an interface that objects can implement to be registered to serve as middleware | ||||
| // in the Negroni middleware stack. | ||||
| // ServeHTTP should yield to the next middleware in the chain by invoking the next http.HandlerFunc | ||||
| // passed in. | ||||
| // | ||||
| // If the Handler writes to the ResponseWriter, the next http.HandlerFunc should not be invoked. | ||||
| type Handler interface { | ||||
| 	ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) | ||||
| } | ||||
|  | ||||
| // HandlerFunc is an adapter to allow the use of ordinary functions as Negroni handlers. | ||||
| // If f is a function with the appropriate signature, HandlerFunc(f) is a Handler object that calls f. | ||||
| type HandlerFunc func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) | ||||
|  | ||||
| func (h HandlerFunc) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 	h(rw, r, next) | ||||
| } | ||||
|  | ||||
| type middleware struct { | ||||
| 	handler Handler | ||||
| 	next    *middleware | ||||
| } | ||||
|  | ||||
| func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) { | ||||
| 	m.handler.ServeHTTP(rw, r, m.next.ServeHTTP) | ||||
| } | ||||
|  | ||||
| // Wrap converts a http.Handler into a negroni.Handler so it can be used as a Negroni | ||||
| // middleware. The next http.HandlerFunc is automatically called after the Handler | ||||
| // is executed. | ||||
| func Wrap(handler http.Handler) Handler { | ||||
| 	return HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 		handler.ServeHTTP(rw, r) | ||||
| 		next(rw, r) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // Negroni is a stack of Middleware Handlers that can be invoked as an http.Handler. | ||||
| // Negroni middleware is evaluated in the order that they are added to the stack using | ||||
| // the Use and UseHandler methods. | ||||
| type Negroni struct { | ||||
| 	middleware middleware | ||||
| 	handlers   []Handler | ||||
| } | ||||
|  | ||||
| // New returns a new Negroni instance with no middleware preconfigured. | ||||
| func New(handlers ...Handler) *Negroni { | ||||
| 	return &Negroni{ | ||||
| 		handlers:   handlers, | ||||
| 		middleware: build(handlers), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Classic returns a new Negroni instance with the default middleware already | ||||
| // in the stack. | ||||
| // | ||||
| // Recovery - Panic Recovery Middleware | ||||
| // Logger - Request/Response Logging | ||||
| // Static - Static File Serving | ||||
| func Classic() *Negroni { | ||||
| 	return New(NewRecovery(), NewLogger(), NewStatic(http.Dir("public"))) | ||||
| } | ||||
|  | ||||
| func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) { | ||||
| 	n.middleware.ServeHTTP(NewResponseWriter(rw), r) | ||||
| } | ||||
|  | ||||
| // Use adds a Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. | ||||
| func (n *Negroni) Use(handler Handler) { | ||||
| 	n.handlers = append(n.handlers, handler) | ||||
| 	n.middleware = build(n.handlers) | ||||
| } | ||||
|  | ||||
| // UseHandler adds a http.Handler onto the middleware stack. Handlers are invoked in the order they are added to a Negroni. | ||||
| func (n *Negroni) UseHandler(handler http.Handler) { | ||||
| 	n.Use(Wrap(handler)) | ||||
| } | ||||
|  | ||||
| // Run is a convenience function that runs the negroni stack as an HTTP | ||||
| // server. The addr string takes the same format as http.ListenAndServe. | ||||
| func (n *Negroni) Run(addr string) { | ||||
| 	l := log.New(os.Stdout, "[negroni] ", 0) | ||||
| 	l.Printf("listening on %s", addr) | ||||
| 	l.Fatal(http.ListenAndServe(addr, n)) | ||||
| } | ||||
|  | ||||
| // Returns a list of all the handlers in the current Negroni middleware chain. | ||||
| func (n *Negroni) Handlers() ([]Handler) { | ||||
| 	return n.handlers | ||||
| } | ||||
|  | ||||
| func build(handlers []Handler) middleware { | ||||
| 	var next middleware | ||||
|  | ||||
| 	if len(handlers) == 0 { | ||||
| 		return voidMiddleware() | ||||
| 	} else if len(handlers) > 1 { | ||||
| 		next = build(handlers[1:]) | ||||
| 	} else { | ||||
| 		next = voidMiddleware() | ||||
| 	} | ||||
|  | ||||
| 	return middleware{handlers[0], &next} | ||||
| } | ||||
|  | ||||
| func voidMiddleware() middleware { | ||||
| 	return middleware{ | ||||
| 		HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}), | ||||
| 		&middleware{}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										75
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/negroni_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/negroni_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| /* Test Helpers */ | ||||
| func expect(t *testing.T, a interface{}, b interface{}) { | ||||
| 	if a != b { | ||||
| 		t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func refute(t *testing.T, a interface{}, b interface{}) { | ||||
| 	if a == b { | ||||
| 		t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestNegroniRun(t *testing.T) { | ||||
| 	// just test that Run doesn't bomb | ||||
| 	go New().Run(":3000") | ||||
| } | ||||
|  | ||||
| func TestNegroniServeHTTP(t *testing.T) { | ||||
| 	result := "" | ||||
| 	response := httptest.NewRecorder() | ||||
|  | ||||
| 	n := New() | ||||
| 	n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 		result += "foo" | ||||
| 		next(rw, r) | ||||
| 		result += "ban" | ||||
| 	})) | ||||
| 	n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 		result += "bar" | ||||
| 		next(rw, r) | ||||
| 		result += "baz" | ||||
| 	})) | ||||
| 	n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 		result += "bat" | ||||
| 		rw.WriteHeader(http.StatusBadRequest) | ||||
| 	})) | ||||
|  | ||||
| 	n.ServeHTTP(response, (*http.Request)(nil)) | ||||
|  | ||||
| 	expect(t, result, "foobarbatbazban") | ||||
| 	expect(t, response.Code, http.StatusBadRequest) | ||||
| } | ||||
|  | ||||
| // Ensures that a Negroni middleware chain  | ||||
| // can correctly return all of its handlers. | ||||
| func TestHandlers(t *testing.T) { | ||||
| 	response := httptest.NewRecorder() | ||||
| 	n := New() | ||||
| 	handlers := n.Handlers() | ||||
| 	expect(t, 0, len(handlers)) | ||||
|  | ||||
| 	n.Use(HandlerFunc(func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 		rw.WriteHeader(http.StatusOK) | ||||
| 	})) | ||||
|  | ||||
| 	// Expects the length of handlers to be exactly 1  | ||||
| 	// after adding exactly one handler to the middleware chain | ||||
| 	handlers = n.Handlers() | ||||
| 	expect(t, 1, len(handlers)) | ||||
|  | ||||
| 	// Ensures that the first handler that is in sequence behaves | ||||
| 	// exactly the same as the one that was registered earlier | ||||
| 	handlers[0].ServeHTTP(response, (*http.Request)(nil), nil) | ||||
| 	expect(t, response.Code, http.StatusOK) | ||||
| } | ||||
							
								
								
									
										46
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/recovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/recovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| ) | ||||
|  | ||||
| // Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one. | ||||
| type Recovery struct { | ||||
| 	Logger     *log.Logger | ||||
| 	PrintStack bool | ||||
| 	StackAll   bool | ||||
| 	StackSize  int | ||||
| } | ||||
|  | ||||
| // NewRecovery returns a new instance of Recovery | ||||
| func NewRecovery() *Recovery { | ||||
| 	return &Recovery{ | ||||
| 		Logger:     log.New(os.Stdout, "[negroni] ", 0), | ||||
| 		PrintStack: true, | ||||
| 		StackAll:   false, | ||||
| 		StackSize:  1024 * 8, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			rw.WriteHeader(http.StatusInternalServerError) | ||||
| 			stack := make([]byte, rec.StackSize) | ||||
| 			stack = stack[:runtime.Stack(stack, rec.StackAll)] | ||||
|  | ||||
| 			f := "PANIC: %s\n%s" | ||||
| 			rec.Logger.Printf(f, err, stack) | ||||
|  | ||||
| 			if rec.PrintStack { | ||||
| 				fmt.Fprintf(rw, f, err, stack) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	next(rw, r) | ||||
| } | ||||
							
								
								
									
										28
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/recovery_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/recovery_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestRecovery(t *testing.T) { | ||||
| 	buff := bytes.NewBufferString("") | ||||
| 	recorder := httptest.NewRecorder() | ||||
|  | ||||
| 	rec := NewRecovery() | ||||
| 	rec.Logger = log.New(buff, "[negroni] ", 0) | ||||
|  | ||||
| 	n := New() | ||||
| 	// replace log for testing | ||||
| 	n.Use(rec) | ||||
| 	n.UseHandler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { | ||||
| 		panic("here is a panic!") | ||||
| 	})) | ||||
| 	n.ServeHTTP(recorder, (*http.Request)(nil)) | ||||
| 	expect(t, recorder.Code, http.StatusInternalServerError) | ||||
| 	refute(t, recorder.Body.Len(), 0) | ||||
| 	refute(t, len(buff.String()), 0) | ||||
| } | ||||
							
								
								
									
										96
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about | ||||
| // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter | ||||
| // if the functionality calls for it. | ||||
| type ResponseWriter interface { | ||||
| 	http.ResponseWriter | ||||
| 	http.Flusher | ||||
| 	// Status returns the status code of the response or 0 if the response has not been written. | ||||
| 	Status() int | ||||
| 	// Written returns whether or not the ResponseWriter has been written. | ||||
| 	Written() bool | ||||
| 	// Size returns the size of the response body. | ||||
| 	Size() int | ||||
| 	// Before allows for a function to be called before the ResponseWriter has been written to. This is | ||||
| 	// useful for setting headers or any other operations that must happen before a response has been written. | ||||
| 	Before(func(ResponseWriter)) | ||||
| } | ||||
|  | ||||
| type beforeFunc func(ResponseWriter) | ||||
|  | ||||
| // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter | ||||
| func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { | ||||
| 	return &responseWriter{rw, 0, 0, nil} | ||||
| } | ||||
|  | ||||
| type responseWriter struct { | ||||
| 	http.ResponseWriter | ||||
| 	status      int | ||||
| 	size        int | ||||
| 	beforeFuncs []beforeFunc | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) WriteHeader(s int) { | ||||
| 	rw.status = s | ||||
| 	rw.callBefore() | ||||
| 	rw.ResponseWriter.WriteHeader(s) | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) Write(b []byte) (int, error) { | ||||
| 	if !rw.Written() { | ||||
| 		// The status will be StatusOK if WriteHeader has not been called yet | ||||
| 		rw.WriteHeader(http.StatusOK) | ||||
| 	} | ||||
| 	size, err := rw.ResponseWriter.Write(b) | ||||
| 	rw.size += size | ||||
| 	return size, err | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) Status() int { | ||||
| 	return rw.status | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) Size() int { | ||||
| 	return rw.size | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) Written() bool { | ||||
| 	return rw.status != 0 | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) Before(before func(ResponseWriter)) { | ||||
| 	rw.beforeFuncs = append(rw.beforeFuncs, before) | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	hijacker, ok := rw.ResponseWriter.(http.Hijacker) | ||||
| 	if !ok { | ||||
| 		return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") | ||||
| 	} | ||||
| 	return hijacker.Hijack() | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) CloseNotify() <-chan bool { | ||||
| 	return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) callBefore() { | ||||
| 	for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { | ||||
| 		rw.beforeFuncs[i](rw) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (rw *responseWriter) Flush() { | ||||
| 	flusher, ok := rw.ResponseWriter.(http.Flusher) | ||||
| 	if ok { | ||||
| 		flusher.Flush() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										150
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/response_writer_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type closeNotifyingRecorder struct { | ||||
| 	*httptest.ResponseRecorder | ||||
| 	closed chan bool | ||||
| } | ||||
|  | ||||
| func newCloseNotifyingRecorder() *closeNotifyingRecorder { | ||||
| 	return &closeNotifyingRecorder{ | ||||
| 		httptest.NewRecorder(), | ||||
| 		make(chan bool, 1), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *closeNotifyingRecorder) close() { | ||||
| 	c.closed <- true | ||||
| } | ||||
|  | ||||
| func (c *closeNotifyingRecorder) CloseNotify() <-chan bool { | ||||
| 	return c.closed | ||||
| } | ||||
|  | ||||
| type hijackableResponse struct { | ||||
| 	Hijacked bool | ||||
| } | ||||
|  | ||||
| func newHijackableResponse() *hijackableResponse { | ||||
| 	return &hijackableResponse{} | ||||
| } | ||||
|  | ||||
| func (h *hijackableResponse) Header() http.Header           { return nil } | ||||
| func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil } | ||||
| func (h *hijackableResponse) WriteHeader(code int)          {} | ||||
| func (h *hijackableResponse) Flush()                        {} | ||||
| func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||||
| 	h.Hijacked = true | ||||
| 	return nil, nil, nil | ||||
| } | ||||
|  | ||||
| func TestResponseWriterWritingString(t *testing.T) { | ||||
| 	rec := httptest.NewRecorder() | ||||
| 	rw := NewResponseWriter(rec) | ||||
|  | ||||
| 	rw.Write([]byte("Hello world")) | ||||
|  | ||||
| 	expect(t, rec.Code, rw.Status()) | ||||
| 	expect(t, rec.Body.String(), "Hello world") | ||||
| 	expect(t, rw.Status(), http.StatusOK) | ||||
| 	expect(t, rw.Size(), 11) | ||||
| 	expect(t, rw.Written(), true) | ||||
| } | ||||
|  | ||||
| func TestResponseWriterWritingStrings(t *testing.T) { | ||||
| 	rec := httptest.NewRecorder() | ||||
| 	rw := NewResponseWriter(rec) | ||||
|  | ||||
| 	rw.Write([]byte("Hello world")) | ||||
| 	rw.Write([]byte("foo bar bat baz")) | ||||
|  | ||||
| 	expect(t, rec.Code, rw.Status()) | ||||
| 	expect(t, rec.Body.String(), "Hello worldfoo bar bat baz") | ||||
| 	expect(t, rw.Status(), http.StatusOK) | ||||
| 	expect(t, rw.Size(), 26) | ||||
| } | ||||
|  | ||||
| func TestResponseWriterWritingHeader(t *testing.T) { | ||||
| 	rec := httptest.NewRecorder() | ||||
| 	rw := NewResponseWriter(rec) | ||||
|  | ||||
| 	rw.WriteHeader(http.StatusNotFound) | ||||
|  | ||||
| 	expect(t, rec.Code, rw.Status()) | ||||
| 	expect(t, rec.Body.String(), "") | ||||
| 	expect(t, rw.Status(), http.StatusNotFound) | ||||
| 	expect(t, rw.Size(), 0) | ||||
| } | ||||
|  | ||||
| func TestResponseWriterBefore(t *testing.T) { | ||||
| 	rec := httptest.NewRecorder() | ||||
| 	rw := NewResponseWriter(rec) | ||||
| 	result := "" | ||||
|  | ||||
| 	rw.Before(func(ResponseWriter) { | ||||
| 		result += "foo" | ||||
| 	}) | ||||
| 	rw.Before(func(ResponseWriter) { | ||||
| 		result += "bar" | ||||
| 	}) | ||||
|  | ||||
| 	rw.WriteHeader(http.StatusNotFound) | ||||
|  | ||||
| 	expect(t, rec.Code, rw.Status()) | ||||
| 	expect(t, rec.Body.String(), "") | ||||
| 	expect(t, rw.Status(), http.StatusNotFound) | ||||
| 	expect(t, rw.Size(), 0) | ||||
| 	expect(t, result, "barfoo") | ||||
| } | ||||
|  | ||||
| func TestResponseWriterHijack(t *testing.T) { | ||||
| 	hijackable := newHijackableResponse() | ||||
| 	rw := NewResponseWriter(hijackable) | ||||
| 	hijacker, ok := rw.(http.Hijacker) | ||||
| 	expect(t, ok, true) | ||||
| 	_, _, err := hijacker.Hijack() | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	expect(t, hijackable.Hijacked, true) | ||||
| } | ||||
|  | ||||
| func TestResponseWriteHijackNotOK(t *testing.T) { | ||||
| 	hijackable := new(http.ResponseWriter) | ||||
| 	rw := NewResponseWriter(*hijackable) | ||||
| 	hijacker, ok := rw.(http.Hijacker) | ||||
| 	expect(t, ok, true) | ||||
| 	_, _, err := hijacker.Hijack() | ||||
|  | ||||
| 	refute(t, err, nil) | ||||
| } | ||||
|  | ||||
| func TestResponseWriterCloseNotify(t *testing.T) { | ||||
| 	rec := newCloseNotifyingRecorder() | ||||
| 	rw := NewResponseWriter(rec) | ||||
| 	closed := false | ||||
| 	notifier := rw.(http.CloseNotifier).CloseNotify() | ||||
| 	rec.close() | ||||
| 	select { | ||||
| 	case <-notifier: | ||||
| 		closed = true | ||||
| 	case <-time.After(time.Second): | ||||
| 	} | ||||
| 	expect(t, closed, true) | ||||
| } | ||||
|  | ||||
| func TestResponseWriterFlusher(t *testing.T) { | ||||
| 	rec := httptest.NewRecorder() | ||||
| 	rw := NewResponseWriter(rec) | ||||
|  | ||||
| 	_, ok := rw.(http.Flusher) | ||||
| 	expect(t, ok, true) | ||||
| } | ||||
							
								
								
									
										84
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/static.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/static.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Static is a middleware handler that serves static files in the given directory/filesystem. | ||||
| type Static struct { | ||||
| 	// Dir is the directory to serve static files from | ||||
| 	Dir http.FileSystem | ||||
| 	// Prefix is the optional prefix used to serve the static directory content | ||||
| 	Prefix string | ||||
| 	// IndexFile defines which file to serve as index if it exists. | ||||
| 	IndexFile string | ||||
| } | ||||
|  | ||||
| // NewStatic returns a new instance of Static | ||||
| func NewStatic(directory http.FileSystem) *Static { | ||||
| 	return &Static{ | ||||
| 		Dir:       directory, | ||||
| 		Prefix:    "", | ||||
| 		IndexFile: "index.html", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *Static) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
| 	if r.Method != "GET" && r.Method != "HEAD" { | ||||
| 		next(rw, r) | ||||
| 		return | ||||
| 	} | ||||
| 	file := r.URL.Path | ||||
| 	// if we have a prefix, filter requests by stripping the prefix | ||||
| 	if s.Prefix != "" { | ||||
| 		if !strings.HasPrefix(file, s.Prefix) { | ||||
| 			next(rw, r) | ||||
| 			return | ||||
| 		} | ||||
| 		file = file[len(s.Prefix):] | ||||
| 		if file != "" && file[0] != '/' { | ||||
| 			next(rw, r) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	f, err := s.Dir.Open(file) | ||||
| 	if err != nil { | ||||
| 		// discard the error? | ||||
| 		next(rw, r) | ||||
| 		return | ||||
| 	} | ||||
| 	defer f.Close() | ||||
|  | ||||
| 	fi, err := f.Stat() | ||||
| 	if err != nil { | ||||
| 		next(rw, r) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// try to serve index file | ||||
| 	if fi.IsDir() { | ||||
| 		// redirect if missing trailing slash | ||||
| 		if !strings.HasSuffix(r.URL.Path, "/") { | ||||
| 			http.Redirect(rw, r, r.URL.Path+"/", http.StatusFound) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		file = path.Join(file, s.IndexFile) | ||||
| 		f, err = s.Dir.Open(file) | ||||
| 		if err != nil { | ||||
| 			next(rw, r) | ||||
| 			return | ||||
| 		} | ||||
| 		defer f.Close() | ||||
|  | ||||
| 		fi, err = f.Stat() | ||||
| 		if err != nil || fi.IsDir() { | ||||
| 			next(rw, r) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	http.ServeContent(rw, r, file, fi.ModTime(), f) | ||||
| } | ||||
							
								
								
									
										113
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/static_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/static_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| package negroni | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestStatic(t *testing.T) { | ||||
| 	response := httptest.NewRecorder() | ||||
| 	response.Body = new(bytes.Buffer) | ||||
|  | ||||
| 	n := New() | ||||
| 	n.Use(NewStatic(http.Dir("."))) | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://localhost:3000/negroni.go", nil) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	n.ServeHTTP(response, req) | ||||
| 	expect(t, response.Code, http.StatusOK) | ||||
| 	expect(t, response.Header().Get("Expires"), "") | ||||
| 	if response.Body.Len() == 0 { | ||||
| 		t.Errorf("Got empty body for GET request") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestStaticHead(t *testing.T) { | ||||
| 	response := httptest.NewRecorder() | ||||
| 	response.Body = new(bytes.Buffer) | ||||
|  | ||||
| 	n := New() | ||||
| 	n.Use(NewStatic(http.Dir("."))) | ||||
| 	n.UseHandler(http.NotFoundHandler()) | ||||
|  | ||||
| 	req, err := http.NewRequest("HEAD", "http://localhost:3000/negroni.go", nil) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	n.ServeHTTP(response, req) | ||||
| 	expect(t, response.Code, http.StatusOK) | ||||
| 	if response.Body.Len() != 0 { | ||||
| 		t.Errorf("Got non-empty body for HEAD request") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestStaticAsPost(t *testing.T) { | ||||
| 	response := httptest.NewRecorder() | ||||
|  | ||||
| 	n := New() | ||||
| 	n.Use(NewStatic(http.Dir("."))) | ||||
| 	n.UseHandler(http.NotFoundHandler()) | ||||
|  | ||||
| 	req, err := http.NewRequest("POST", "http://localhost:3000/negroni.go", nil) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	n.ServeHTTP(response, req) | ||||
| 	expect(t, response.Code, http.StatusNotFound) | ||||
| } | ||||
|  | ||||
| func TestStaticBadDir(t *testing.T) { | ||||
| 	response := httptest.NewRecorder() | ||||
|  | ||||
| 	n := Classic() | ||||
| 	n.UseHandler(http.NotFoundHandler()) | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://localhost:3000/negroni.go", nil) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	n.ServeHTTP(response, req) | ||||
| 	refute(t, response.Code, http.StatusOK) | ||||
| } | ||||
|  | ||||
| func TestStaticOptionsServeIndex(t *testing.T) { | ||||
| 	response := httptest.NewRecorder() | ||||
|  | ||||
| 	n := New() | ||||
| 	s := NewStatic(http.Dir(".")) | ||||
| 	s.IndexFile = "negroni.go" | ||||
| 	n.Use(s) | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://localhost:3000/", nil) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	n.ServeHTTP(response, req) | ||||
| 	expect(t, response.Code, http.StatusOK) | ||||
| } | ||||
|  | ||||
| func TestStaticOptionsPrefix(t *testing.T) { | ||||
| 	response := httptest.NewRecorder() | ||||
|  | ||||
| 	n := New() | ||||
| 	s := NewStatic(http.Dir(".")) | ||||
| 	s.Prefix = "/public" | ||||
| 	n.Use(s) | ||||
|  | ||||
| 	// Check file content behaviour | ||||
| 	req, err := http.NewRequest("GET", "http://localhost:3000/public/negroni.go", nil) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	n.ServeHTTP(response, req) | ||||
| 	expect(t, response.Code, http.StatusOK) | ||||
| } | ||||
							
								
								
									
										165
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/translations/README_pt_br.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								Godeps/_workspace/src/github.com/codegangsta/negroni/translations/README_pt_br.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| # Negroni [](http://godoc.org/github.com/codegangsta/negroni) [](https://app.wercker.com/project/bykey/13688a4a94b82d84a0b8d038c4965b61) | ||||
|  | ||||
| Negroni é uma abordagem idiomática para middleware web em Go. É pequeno, não intrusivo, e incentiva uso da biblioteca `net/http`. | ||||
|  | ||||
| Se gosta da idéia do [Martini](http://github.com/go-martini/martini), mas acha que contém muita mágica, então Negroni é ideal. | ||||
|  | ||||
| ## Começando | ||||
|  | ||||
| Depois de instalar Go e definir seu [GOPATH](http://golang.org/doc/code.html#GOPATH), criar seu primeirto arquivo `.go`. Iremos chamá-lo `server.go`. | ||||
|  | ||||
| ~~~ go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
|   "github.com/codegangsta/negroni" | ||||
|   "net/http" | ||||
|   "fmt" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
|   mux := http.NewServeMux() | ||||
|   mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { | ||||
|     fmt.Fprintf(w, "Welcome to the home page!") | ||||
|   }) | ||||
|  | ||||
|   n := negroni.Classic() | ||||
|   n.UseHandler(mux) | ||||
|   n.Run(":3000") | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| Depois instale o pacote Negroni (**go 1.1** ou superior) | ||||
| ~~~ | ||||
| go get github.com/codegangsta/negroni | ||||
| ~~~ | ||||
|  | ||||
| Depois execute seu servidor: | ||||
| ~~~ | ||||
| go run server.go | ||||
| ~~~ | ||||
|  | ||||
| Agora terá um servidor web Go net/http rodando em `localhost:3000`. | ||||
|  | ||||
| ## Precisa de Ajuda? | ||||
| Se você tem uma pergunta ou pedido de recurso,[go ask the mailing list](https://groups.google.com/forum/#!forum/negroni-users). O Github issuses para o Negroni será usado exclusivamente para Reportar bugs e pull requests. | ||||
|  | ||||
| ## Negroni é um Framework? | ||||
| Negroni **não** é a framework. É uma biblioteca que é desenhada para trabalhar diretamente com net/http. | ||||
|  | ||||
| ## Roteamento? | ||||
| Negroni é TSPR(Traga seu próprio Roteamento). A comunidade Go já tem um grande número de roteadores http disponíveis, Negroni tenta rodar bem com todos eles pelo suporte total `net/http`/ Por exemplo, a integração com [Gorilla Mux](http://github.com/gorilla/mux) se parece com isso: | ||||
|  | ||||
| ~~~ go | ||||
| router := mux.NewRouter() | ||||
| router.HandleFunc("/", HomeHandler) | ||||
|  | ||||
| n := negroni.New(Middleware1, Middleware2) | ||||
| // Or use a middleware with the Use() function | ||||
| n.Use(Middleware3) | ||||
| // router goes last | ||||
| n.UseHandler(router) | ||||
|  | ||||
| n.Run(":3000") | ||||
| ~~~ | ||||
|  | ||||
| ## `negroni.Classic()` | ||||
| `negroni.Classic()`  fornece alguns middlewares padrão que são útil para maioria das aplicações: | ||||
|  | ||||
| * `negroni.Recovery` - Panic Recovery Middleware. | ||||
| * `negroni.Logging` - Request/Response Logging Middleware. | ||||
| * `negroni.Static` - Static File serving under the "public" directory. | ||||
|  | ||||
| Isso torna muito fácil começar com alguns recursos úteis do Negroni. | ||||
|  | ||||
| ## Handlers | ||||
| Negroni fornece um middleware de fluxo bidirecional. Isso é feito através da interface `negroni.Handler`: | ||||
|  | ||||
| ~~~ go | ||||
| type Handler interface { | ||||
|   ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| Se um middleware não tenha escrito o ResponseWriter, ele deve chamar a próxima `http.HandlerFunc` na cadeia para produzir o próximo handler middleware. Isso pode ser usado muito bem: | ||||
|  | ||||
| ~~~ go | ||||
| func MyMiddleware(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { | ||||
|   // do some stuff before | ||||
|   next(rw, r) | ||||
|   // do some stuff after | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| E pode mapear isso para a cadeia de handler com a função `Use`: | ||||
|  | ||||
| ~~~ go | ||||
| n := negroni.New() | ||||
| n.Use(negroni.HandlerFunc(MyMiddleware)) | ||||
| ~~~ | ||||
|  | ||||
| Você também pode mapear `http.Handler` antigos: | ||||
|  | ||||
| ~~~ go | ||||
| n := negroni.New() | ||||
|  | ||||
| mux := http.NewServeMux() | ||||
| // map your routes | ||||
|  | ||||
| n.UseHandler(mux) | ||||
|  | ||||
| n.Run(":3000") | ||||
| ~~~ | ||||
|  | ||||
| ## `Run()` | ||||
| Negroni tem uma função de conveniência chamada `Run`. `Run` pega um endereço de string idêntico para [http.ListenAndServe](http://golang.org/pkg/net/http#ListenAndServe). | ||||
|  | ||||
| ~~~ go | ||||
| n := negroni.Classic() | ||||
| // ... | ||||
| log.Fatal(http.ListenAndServe(":8080", n)) | ||||
| ~~~ | ||||
|  | ||||
| ## Middleware para Rotas Específicas | ||||
| Se você tem um grupo de rota com rotas que precisam ser executadas por um middleware específico, pode simplesmente criar uma nova instância de Negroni e usar no seu Manipular de rota. | ||||
|  | ||||
| ~~~ go | ||||
| router := mux.NewRouter() | ||||
| adminRoutes := mux.NewRouter() | ||||
| // add admin routes here | ||||
|  | ||||
| // Criar um middleware negroni para admin | ||||
| router.Handle("/admin", negroni.New( | ||||
|   Middleware1, | ||||
|   Middleware2, | ||||
|   negroni.Wrap(adminRoutes), | ||||
| )) | ||||
| ~~~ | ||||
|  | ||||
| ## Middleware de Terceiros | ||||
|  | ||||
| Aqui está uma lista atual de Middleware Compatíveis com Negroni. Sinta se livre para mandar um PR vinculando seu middleware if construiu um: | ||||
|  | ||||
|  | ||||
| | Middleware | Autor | Descrição | | ||||
| | -----------|--------|-------------| | ||||
| | [Graceful](https://github.com/stretchr/graceful) | [Tyler Bunnell](https://github.com/tylerb) | Graceful HTTP Shutdown | | ||||
| | [secure](https://github.com/unrolled/secure) | [Cory Jacobsen](https://github.com/unrolled) |  Implementa rapidamente itens de segurança.| | ||||
| | [binding](https://github.com/mholt/binding) | [Matt Holt](https://github.com/mholt) | Handler para mapeamento/validação de um request a estrutura. | | ||||
| | [logrus](https://github.com/meatballhat/negroni-logrus) | [Dan Buch](https://github.com/meatballhat) | Logrus-based logger | | ||||
| | [render](https://github.com/unrolled/render) | [Cory Jacobsen](https://github.com/unrolled) | Pacote para renderizar JSON, XML, e templates HTML. | | ||||
| | [gorelic](https://github.com/jingweno/negroni-gorelic) | [Jingwen Owen Ou](https://github.com/jingweno) | New Relic agent for Go runtime | | ||||
| | [gzip](https://github.com/phyber/negroni-gzip) | [phyber](https://github.com/phyber) | Handler para adicionar compreção gzip para as requisições | | ||||
| | [oauth2](https://github.com/goincremental/negroni-oauth2) | [David Bochenski](https://github.com/bochenski) | Handler que prove sistema de login OAuth 2.0 para aplicações Martini. Google Sign-in, Facebook Connect e Github login são suportados. | | ||||
| | [sessions](https://github.com/goincremental/negroni-sessions) | [David Bochenski](https://github.com/bochenski) | Handler que provê o serviço de sessão. | | ||||
| | [permissions](https://github.com/xyproto/permissions) | [Alexander Rødseth](https://github.com/xyproto) | Cookies, usuários e permissões. | | ||||
| | [onthefly](https://github.com/xyproto/onthefly) | [Alexander Rødseth](https://github.com/xyproto) | Pacote para gerar TinySVG, HTML e CSS em tempo real. | | ||||
|  | ||||
| ## Exemplos | ||||
| [Alexander Rødseth](https://github.com/xyproto) criou [mooseware](https://github.com/xyproto/mooseware), uma estrutura para escrever um handler middleware Negroni. | ||||
|  | ||||
| ## Servidor com autoreload? | ||||
| [gin](https://github.com/codegangsta/gin) e [fresh](https://github.com/pilu/fresh) são aplicativos para autoreload do Negroni. | ||||
|  | ||||
| ## Sobre | ||||
| Negroni é obsessivamente desenhado por ninguém menos que  [Code Gangsta](http://codegangsta.io/) | ||||
							
								
								
									
										45
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| // Copyright 2014 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package internal // import "github.com/garyburd/redigo/internal" | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	WatchState = 1 << iota | ||||
| 	MultiState | ||||
| 	SubscribeState | ||||
| 	MonitorState | ||||
| ) | ||||
|  | ||||
| type CommandInfo struct { | ||||
| 	Set, Clear int | ||||
| } | ||||
|  | ||||
| var commandInfos = map[string]CommandInfo{ | ||||
| 	"WATCH":      {Set: WatchState}, | ||||
| 	"UNWATCH":    {Clear: WatchState}, | ||||
| 	"MULTI":      {Set: MultiState}, | ||||
| 	"EXEC":       {Clear: WatchState | MultiState}, | ||||
| 	"DISCARD":    {Clear: WatchState | MultiState}, | ||||
| 	"PSUBSCRIBE": {Set: SubscribeState}, | ||||
| 	"SUBSCRIBE":  {Set: SubscribeState}, | ||||
| 	"MONITOR":    {Set: MonitorState}, | ||||
| } | ||||
|  | ||||
| func LookupCommandInfo(commandName string) CommandInfo { | ||||
| 	return commandInfos[strings.ToUpper(commandName)] | ||||
| } | ||||
							
								
								
									
										65
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| // Copyright 2014 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| // Package redistest contains utilities for writing Redigo tests. | ||||
| package redistest | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| type testConn struct { | ||||
| 	redis.Conn | ||||
| } | ||||
|  | ||||
| func (t testConn) Close() error { | ||||
| 	_, err := t.Conn.Do("SELECT", "9") | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	_, err = t.Conn.Do("FLUSHDB") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return t.Conn.Close() | ||||
| } | ||||
|  | ||||
| // Dial dials the local Redis server and selects database 9. To prevent | ||||
| // stomping on real data, DialTestDB fails if database 9 contains data. The | ||||
| // returned connection flushes database 9 on close. | ||||
| func Dial() (redis.Conn, error) { | ||||
| 	c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	_, err = c.Do("SELECT", "9") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	n, err := redis.Int(c.Do("DBSIZE")) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if n != 0 { | ||||
| 		return nil, errors.New("database #9 is not empty, test can not continue") | ||||
| 	} | ||||
|  | ||||
| 	return testConn{c}, nil | ||||
| } | ||||
							
								
								
									
										455
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										455
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,455 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // conn is the low-level implementation of Conn | ||||
| type conn struct { | ||||
|  | ||||
| 	// Shared | ||||
| 	mu      sync.Mutex | ||||
| 	pending int | ||||
| 	err     error | ||||
| 	conn    net.Conn | ||||
|  | ||||
| 	// Read | ||||
| 	readTimeout time.Duration | ||||
| 	br          *bufio.Reader | ||||
|  | ||||
| 	// Write | ||||
| 	writeTimeout time.Duration | ||||
| 	bw           *bufio.Writer | ||||
|  | ||||
| 	// Scratch space for formatting argument length. | ||||
| 	// '*' or '$', length, "\r\n" | ||||
| 	lenScratch [32]byte | ||||
|  | ||||
| 	// Scratch space for formatting integers and floats. | ||||
| 	numScratch [40]byte | ||||
| } | ||||
|  | ||||
| // Dial connects to the Redis server at the given network and address. | ||||
| func Dial(network, address string) (Conn, error) { | ||||
| 	dialer := xDialer{} | ||||
| 	return dialer.Dial(network, address) | ||||
| } | ||||
|  | ||||
| // DialTimeout acts like Dial but takes timeouts for establishing the | ||||
| // connection to the server, writing a command and reading a reply. | ||||
| func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { | ||||
| 	netDialer := net.Dialer{Timeout: connectTimeout} | ||||
| 	dialer := xDialer{ | ||||
| 		NetDial:      netDialer.Dial, | ||||
| 		ReadTimeout:  readTimeout, | ||||
| 		WriteTimeout: writeTimeout, | ||||
| 	} | ||||
| 	return dialer.Dial(network, address) | ||||
| } | ||||
|  | ||||
| // A Dialer specifies options for connecting to a Redis server. | ||||
| type xDialer struct { | ||||
| 	// NetDial specifies the dial function for creating TCP connections. If | ||||
| 	// NetDial is nil, then net.Dial is used. | ||||
| 	NetDial func(network, addr string) (net.Conn, error) | ||||
|  | ||||
| 	// ReadTimeout specifies the timeout for reading a single command | ||||
| 	// reply. If ReadTimeout is zero, then no timeout is used. | ||||
| 	ReadTimeout time.Duration | ||||
|  | ||||
| 	// WriteTimeout specifies the timeout for writing a single command.  If | ||||
| 	// WriteTimeout is zero, then no timeout is used. | ||||
| 	WriteTimeout time.Duration | ||||
| } | ||||
|  | ||||
| // Dial connects to the Redis server at address on the named network. | ||||
| func (d *xDialer) Dial(network, address string) (Conn, error) { | ||||
| 	dial := d.NetDial | ||||
| 	if dial == nil { | ||||
| 		dial = net.Dial | ||||
| 	} | ||||
| 	netConn, err := dial(network, address) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &conn{ | ||||
| 		conn:         netConn, | ||||
| 		bw:           bufio.NewWriter(netConn), | ||||
| 		br:           bufio.NewReader(netConn), | ||||
| 		readTimeout:  d.ReadTimeout, | ||||
| 		writeTimeout: d.WriteTimeout, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // NewConn returns a new Redigo connection for the given net connection. | ||||
| func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { | ||||
| 	return &conn{ | ||||
| 		conn:         netConn, | ||||
| 		bw:           bufio.NewWriter(netConn), | ||||
| 		br:           bufio.NewReader(netConn), | ||||
| 		readTimeout:  readTimeout, | ||||
| 		writeTimeout: writeTimeout, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *conn) Close() error { | ||||
| 	c.mu.Lock() | ||||
| 	err := c.err | ||||
| 	if c.err == nil { | ||||
| 		c.err = errors.New("redigo: closed") | ||||
| 		err = c.conn.Close() | ||||
| 	} | ||||
| 	c.mu.Unlock() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *conn) fatal(err error) error { | ||||
| 	c.mu.Lock() | ||||
| 	if c.err == nil { | ||||
| 		c.err = err | ||||
| 		// Close connection to force errors on subsequent calls and to unblock | ||||
| 		// other reader or writer. | ||||
| 		c.conn.Close() | ||||
| 	} | ||||
| 	c.mu.Unlock() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *conn) Err() error { | ||||
| 	c.mu.Lock() | ||||
| 	err := c.err | ||||
| 	c.mu.Unlock() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *conn) writeLen(prefix byte, n int) error { | ||||
| 	c.lenScratch[len(c.lenScratch)-1] = '\n' | ||||
| 	c.lenScratch[len(c.lenScratch)-2] = '\r' | ||||
| 	i := len(c.lenScratch) - 3 | ||||
| 	for { | ||||
| 		c.lenScratch[i] = byte('0' + n%10) | ||||
| 		i -= 1 | ||||
| 		n = n / 10 | ||||
| 		if n == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	c.lenScratch[i] = prefix | ||||
| 	_, err := c.bw.Write(c.lenScratch[i:]) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *conn) writeString(s string) error { | ||||
| 	c.writeLen('$', len(s)) | ||||
| 	c.bw.WriteString(s) | ||||
| 	_, err := c.bw.WriteString("\r\n") | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *conn) writeBytes(p []byte) error { | ||||
| 	c.writeLen('$', len(p)) | ||||
| 	c.bw.Write(p) | ||||
| 	_, err := c.bw.WriteString("\r\n") | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *conn) writeInt64(n int64) error { | ||||
| 	return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) | ||||
| } | ||||
|  | ||||
| func (c *conn) writeFloat64(n float64) error { | ||||
| 	return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) | ||||
| } | ||||
|  | ||||
| func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { | ||||
| 	c.writeLen('*', 1+len(args)) | ||||
| 	err = c.writeString(cmd) | ||||
| 	for _, arg := range args { | ||||
| 		if err != nil { | ||||
| 			break | ||||
| 		} | ||||
| 		switch arg := arg.(type) { | ||||
| 		case string: | ||||
| 			err = c.writeString(arg) | ||||
| 		case []byte: | ||||
| 			err = c.writeBytes(arg) | ||||
| 		case int: | ||||
| 			err = c.writeInt64(int64(arg)) | ||||
| 		case int64: | ||||
| 			err = c.writeInt64(arg) | ||||
| 		case float64: | ||||
| 			err = c.writeFloat64(arg) | ||||
| 		case bool: | ||||
| 			if arg { | ||||
| 				err = c.writeString("1") | ||||
| 			} else { | ||||
| 				err = c.writeString("0") | ||||
| 			} | ||||
| 		case nil: | ||||
| 			err = c.writeString("") | ||||
| 		default: | ||||
| 			var buf bytes.Buffer | ||||
| 			fmt.Fprint(&buf, arg) | ||||
| 			err = c.writeBytes(buf.Bytes()) | ||||
| 		} | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| type protocolError string | ||||
|  | ||||
| func (pe protocolError) Error() string { | ||||
| 	return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) | ||||
| } | ||||
|  | ||||
| func (c *conn) readLine() ([]byte, error) { | ||||
| 	p, err := c.br.ReadSlice('\n') | ||||
| 	if err == bufio.ErrBufferFull { | ||||
| 		return nil, protocolError("long response line") | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	i := len(p) - 2 | ||||
| 	if i < 0 || p[i] != '\r' { | ||||
| 		return nil, protocolError("bad response line terminator") | ||||
| 	} | ||||
| 	return p[:i], nil | ||||
| } | ||||
|  | ||||
| // parseLen parses bulk string and array lengths. | ||||
| func parseLen(p []byte) (int, error) { | ||||
| 	if len(p) == 0 { | ||||
| 		return -1, protocolError("malformed length") | ||||
| 	} | ||||
|  | ||||
| 	if p[0] == '-' && len(p) == 2 && p[1] == '1' { | ||||
| 		// handle $-1 and $-1 null replies. | ||||
| 		return -1, nil | ||||
| 	} | ||||
|  | ||||
| 	var n int | ||||
| 	for _, b := range p { | ||||
| 		n *= 10 | ||||
| 		if b < '0' || b > '9' { | ||||
| 			return -1, protocolError("illegal bytes in length") | ||||
| 		} | ||||
| 		n += int(b - '0') | ||||
| 	} | ||||
|  | ||||
| 	return n, nil | ||||
| } | ||||
|  | ||||
| // parseInt parses an integer reply. | ||||
| func parseInt(p []byte) (interface{}, error) { | ||||
| 	if len(p) == 0 { | ||||
| 		return 0, protocolError("malformed integer") | ||||
| 	} | ||||
|  | ||||
| 	var negate bool | ||||
| 	if p[0] == '-' { | ||||
| 		negate = true | ||||
| 		p = p[1:] | ||||
| 		if len(p) == 0 { | ||||
| 			return 0, protocolError("malformed integer") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var n int64 | ||||
| 	for _, b := range p { | ||||
| 		n *= 10 | ||||
| 		if b < '0' || b > '9' { | ||||
| 			return 0, protocolError("illegal bytes in length") | ||||
| 		} | ||||
| 		n += int64(b - '0') | ||||
| 	} | ||||
|  | ||||
| 	if negate { | ||||
| 		n = -n | ||||
| 	} | ||||
| 	return n, nil | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	okReply   interface{} = "OK" | ||||
| 	pongReply interface{} = "PONG" | ||||
| ) | ||||
|  | ||||
| func (c *conn) readReply() (interface{}, error) { | ||||
| 	line, err := c.readLine() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if len(line) == 0 { | ||||
| 		return nil, protocolError("short response line") | ||||
| 	} | ||||
| 	switch line[0] { | ||||
| 	case '+': | ||||
| 		switch { | ||||
| 		case len(line) == 3 && line[1] == 'O' && line[2] == 'K': | ||||
| 			// Avoid allocation for frequent "+OK" response. | ||||
| 			return okReply, nil | ||||
| 		case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': | ||||
| 			// Avoid allocation in PING command benchmarks :) | ||||
| 			return pongReply, nil | ||||
| 		default: | ||||
| 			return string(line[1:]), nil | ||||
| 		} | ||||
| 	case '-': | ||||
| 		return Error(string(line[1:])), nil | ||||
| 	case ':': | ||||
| 		return parseInt(line[1:]) | ||||
| 	case '$': | ||||
| 		n, err := parseLen(line[1:]) | ||||
| 		if n < 0 || err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		p := make([]byte, n) | ||||
| 		_, err = io.ReadFull(c.br, p) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if line, err := c.readLine(); err != nil { | ||||
| 			return nil, err | ||||
| 		} else if len(line) != 0 { | ||||
| 			return nil, protocolError("bad bulk string format") | ||||
| 		} | ||||
| 		return p, nil | ||||
| 	case '*': | ||||
| 		n, err := parseLen(line[1:]) | ||||
| 		if n < 0 || err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		r := make([]interface{}, n) | ||||
| 		for i := range r { | ||||
| 			r[i], err = c.readReply() | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 		return r, nil | ||||
| 	} | ||||
| 	return nil, protocolError("unexpected response line") | ||||
| } | ||||
|  | ||||
| func (c *conn) Send(cmd string, args ...interface{}) error { | ||||
| 	c.mu.Lock() | ||||
| 	c.pending += 1 | ||||
| 	c.mu.Unlock() | ||||
| 	if c.writeTimeout != 0 { | ||||
| 		c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) | ||||
| 	} | ||||
| 	if err := c.writeCommand(cmd, args); err != nil { | ||||
| 		return c.fatal(err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *conn) Flush() error { | ||||
| 	if c.writeTimeout != 0 { | ||||
| 		c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) | ||||
| 	} | ||||
| 	if err := c.bw.Flush(); err != nil { | ||||
| 		return c.fatal(err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *conn) Receive() (reply interface{}, err error) { | ||||
| 	if c.readTimeout != 0 { | ||||
| 		c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) | ||||
| 	} | ||||
| 	if reply, err = c.readReply(); err != nil { | ||||
| 		return nil, c.fatal(err) | ||||
| 	} | ||||
| 	// When using pub/sub, the number of receives can be greater than the | ||||
| 	// number of sends. To enable normal use of the connection after | ||||
| 	// unsubscribing from all channels, we do not decrement pending to a | ||||
| 	// negative value. | ||||
| 	// | ||||
| 	// The pending field is decremented after the reply is read to handle the | ||||
| 	// case where Receive is called before Send. | ||||
| 	c.mu.Lock() | ||||
| 	if c.pending > 0 { | ||||
| 		c.pending -= 1 | ||||
| 	} | ||||
| 	c.mu.Unlock() | ||||
| 	if err, ok := reply.(Error); ok { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { | ||||
| 	c.mu.Lock() | ||||
| 	pending := c.pending | ||||
| 	c.pending = 0 | ||||
| 	c.mu.Unlock() | ||||
|  | ||||
| 	if cmd == "" && pending == 0 { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	if c.writeTimeout != 0 { | ||||
| 		c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) | ||||
| 	} | ||||
|  | ||||
| 	if cmd != "" { | ||||
| 		c.writeCommand(cmd, args) | ||||
| 	} | ||||
|  | ||||
| 	if err := c.bw.Flush(); err != nil { | ||||
| 		return nil, c.fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if c.readTimeout != 0 { | ||||
| 		c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) | ||||
| 	} | ||||
|  | ||||
| 	if cmd == "" { | ||||
| 		reply := make([]interface{}, pending) | ||||
| 		for i := range reply { | ||||
| 			r, e := c.readReply() | ||||
| 			if e != nil { | ||||
| 				return nil, c.fatal(e) | ||||
| 			} | ||||
| 			reply[i] = r | ||||
| 		} | ||||
| 		return reply, nil | ||||
| 	} | ||||
|  | ||||
| 	var err error | ||||
| 	var reply interface{} | ||||
| 	for i := 0; i <= pending; i++ { | ||||
| 		var e error | ||||
| 		if reply, e = c.readReply(); e != nil { | ||||
| 			return nil, c.fatal(e) | ||||
| 		} | ||||
| 		if e, ok := reply.(Error); ok && err == nil { | ||||
| 			err = e | ||||
| 		} | ||||
| 	} | ||||
| 	return reply, err | ||||
| } | ||||
							
								
								
									
										542
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										542
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,542 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis_test | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"math" | ||||
| 	"net" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/internal/redistest" | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| var writeTests = []struct { | ||||
| 	args     []interface{} | ||||
| 	expected string | ||||
| }{ | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", "value"}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", "value"}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", byte(100)}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", 100}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", int64(math.MinInt64)}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", float64(1349673917.939762)}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", ""}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "key", nil}, | ||||
| 		"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"ECHO", true, false}, | ||||
| 		"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n", | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestWrite(t *testing.T) { | ||||
| 	for _, tt := range writeTests { | ||||
| 		var buf bytes.Buffer | ||||
| 		rw := bufio.ReadWriter{Writer: bufio.NewWriter(&buf)} | ||||
| 		c := redis.NewConnBufio(rw) | ||||
| 		err := c.Send(tt.args[0].(string), tt.args[1:]...) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Send(%v) returned error %v", tt.args, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		rw.Flush() | ||||
| 		actual := buf.String() | ||||
| 		if actual != tt.expected { | ||||
| 			t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var errorSentinel = &struct{}{} | ||||
|  | ||||
| var readTests = []struct { | ||||
| 	reply    string | ||||
| 	expected interface{} | ||||
| }{ | ||||
| 	{ | ||||
| 		"+OK\r\n", | ||||
| 		"OK", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"+PONG\r\n", | ||||
| 		"PONG", | ||||
| 	}, | ||||
| 	{ | ||||
| 		"@OK\r\n", | ||||
| 		errorSentinel, | ||||
| 	}, | ||||
| 	{ | ||||
| 		"$6\r\nfoobar\r\n", | ||||
| 		[]byte("foobar"), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"$-1\r\n", | ||||
| 		nil, | ||||
| 	}, | ||||
| 	{ | ||||
| 		":1\r\n", | ||||
| 		int64(1), | ||||
| 	}, | ||||
| 	{ | ||||
| 		":-2\r\n", | ||||
| 		int64(-2), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"*0\r\n", | ||||
| 		[]interface{}{}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		"*-1\r\n", | ||||
| 		nil, | ||||
| 	}, | ||||
| 	{ | ||||
| 		"*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n", | ||||
| 		[]interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		"*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n", | ||||
| 		[]interface{}{[]byte("foo"), nil, []byte("bar")}, | ||||
| 	}, | ||||
|  | ||||
| 	{ | ||||
| 		// "x" is not a valid length | ||||
| 		"$x\r\nfoobar\r\n", | ||||
| 		errorSentinel, | ||||
| 	}, | ||||
| 	{ | ||||
| 		// -2 is not a valid length | ||||
| 		"$-2\r\n", | ||||
| 		errorSentinel, | ||||
| 	}, | ||||
| 	{ | ||||
| 		// "x"  is not a valid integer | ||||
| 		":x\r\n", | ||||
| 		errorSentinel, | ||||
| 	}, | ||||
| 	{ | ||||
| 		// missing \r\n following value | ||||
| 		"$6\r\nfoobar", | ||||
| 		errorSentinel, | ||||
| 	}, | ||||
| 	{ | ||||
| 		// short value | ||||
| 		"$6\r\nxx", | ||||
| 		errorSentinel, | ||||
| 	}, | ||||
| 	{ | ||||
| 		// long value | ||||
| 		"$6\r\nfoobarx\r\n", | ||||
| 		errorSentinel, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestRead(t *testing.T) { | ||||
| 	for _, tt := range readTests { | ||||
| 		rw := bufio.ReadWriter{ | ||||
| 			Reader: bufio.NewReader(strings.NewReader(tt.reply)), | ||||
| 			Writer: bufio.NewWriter(nil), // writer need to support Flush | ||||
| 		} | ||||
| 		c := redis.NewConnBufio(rw) | ||||
| 		actual, err := c.Receive() | ||||
| 		if tt.expected == errorSentinel { | ||||
| 			if err == nil { | ||||
| 				t.Errorf("Receive(%q) did not return expected error", tt.reply) | ||||
| 			} | ||||
| 		} else { | ||||
| 			if err != nil { | ||||
| 				t.Errorf("Receive(%q) returned error %v", tt.reply, err) | ||||
| 				continue | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual, tt.expected) { | ||||
| 				t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var testCommands = []struct { | ||||
| 	args     []interface{} | ||||
| 	expected interface{} | ||||
| }{ | ||||
| 	{ | ||||
| 		[]interface{}{"PING"}, | ||||
| 		"PONG", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"SET", "foo", "bar"}, | ||||
| 		"OK", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"GET", "foo"}, | ||||
| 		[]byte("bar"), | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"GET", "nokey"}, | ||||
| 		nil, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"MGET", "nokey", "foo"}, | ||||
| 		[]interface{}{nil, []byte("bar")}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"INCR", "mycounter"}, | ||||
| 		int64(1), | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"LPUSH", "mylist", "foo"}, | ||||
| 		int64(1), | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"LPUSH", "mylist", "bar"}, | ||||
| 		int64(2), | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"LRANGE", "mylist", 0, -1}, | ||||
| 		[]interface{}{[]byte("bar"), []byte("foo")}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"MULTI"}, | ||||
| 		"OK", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"LRANGE", "mylist", 0, -1}, | ||||
| 		"QUEUED", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"PING"}, | ||||
| 		"QUEUED", | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{"EXEC"}, | ||||
| 		[]interface{}{ | ||||
| 			[]interface{}{[]byte("bar"), []byte("foo")}, | ||||
| 			"PONG", | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestDoCommands(t *testing.T) { | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	for _, cmd := range testCommands { | ||||
| 		actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Do(%v) returned error %v", cmd.args, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(actual, cmd.expected) { | ||||
| 			t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPipelineCommands(t *testing.T) { | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	for _, cmd := range testCommands { | ||||
| 		if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { | ||||
| 			t.Fatalf("Send(%v) returned error %v", cmd.args, err) | ||||
| 		} | ||||
| 	} | ||||
| 	if err := c.Flush(); err != nil { | ||||
| 		t.Errorf("Flush() returned error %v", err) | ||||
| 	} | ||||
| 	for _, cmd := range testCommands { | ||||
| 		actual, err := c.Receive() | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Receive(%v) returned error %v", cmd.args, err) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(actual, cmd.expected) { | ||||
| 			t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestBlankCommmand(t *testing.T) { | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	for _, cmd := range testCommands { | ||||
| 		if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { | ||||
| 			t.Fatalf("Send(%v) returned error %v", cmd.args, err) | ||||
| 		} | ||||
| 	} | ||||
| 	reply, err := redis.Values(c.Do("")) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Do() returned error %v", err) | ||||
| 	} | ||||
| 	if len(reply) != len(testCommands) { | ||||
| 		t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands)) | ||||
| 	} | ||||
| 	for i, cmd := range testCommands { | ||||
| 		actual := reply[i] | ||||
| 		if !reflect.DeepEqual(actual, cmd.expected) { | ||||
| 			t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRecvBeforeSend(t *testing.T) { | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| 	done := make(chan struct{}) | ||||
| 	go func() { | ||||
| 		c.Receive() | ||||
| 		close(done) | ||||
| 	}() | ||||
| 	time.Sleep(time.Millisecond) | ||||
| 	c.Send("PING") | ||||
| 	c.Flush() | ||||
| 	<-done | ||||
| 	_, err = c.Do("") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error=%v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestError(t *testing.T) { | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	c.Do("SET", "key", "val") | ||||
| 	_, err = c.Do("HSET", "key", "fld", "val") | ||||
| 	if err == nil { | ||||
| 		t.Errorf("Expected err for HSET on string key.") | ||||
| 	} | ||||
| 	if c.Err() != nil { | ||||
| 		t.Errorf("Conn has Err()=%v, expect nil", c.Err()) | ||||
| 	} | ||||
| 	_, err = c.Do("SET", "key", "val") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReadDeadline(t *testing.T) { | ||||
| 	l, err := net.Listen("tcp", "127.0.0.1:0") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("net.Listen returned %v", err) | ||||
| 	} | ||||
| 	defer l.Close() | ||||
|  | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			c, err := l.Accept() | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			go func() { | ||||
| 				time.Sleep(time.Second) | ||||
| 				c.Write([]byte("+OK\r\n")) | ||||
| 				c.Close() | ||||
| 			}() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	c1, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("redis.Dial returned %v", err) | ||||
| 	} | ||||
| 	defer c1.Close() | ||||
|  | ||||
| 	_, err = c1.Do("PING") | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("c1.Do() returned nil, expect error") | ||||
| 	} | ||||
| 	if c1.Err() == nil { | ||||
| 		t.Fatalf("c1.Err() = nil, expect error") | ||||
| 	} | ||||
|  | ||||
| 	c2, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("redis.Dial returned %v", err) | ||||
| 	} | ||||
| 	defer c2.Close() | ||||
|  | ||||
| 	c2.Send("PING") | ||||
| 	c2.Flush() | ||||
| 	_, err = c2.Receive() | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("c2.Receive() returned nil, expect error") | ||||
| 	} | ||||
| 	if c2.Err() == nil { | ||||
| 		t.Fatalf("c2.Err() = nil, expect error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Connect to local instance of Redis running on the default port. | ||||
| func ExampleDial(x int) { | ||||
| 	c, err := redis.Dial("tcp", ":6379") | ||||
| 	if err != nil { | ||||
| 		// handle error | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| } | ||||
|  | ||||
| // TextExecError tests handling of errors in a transaction. See | ||||
| // http://redis.io/topics/transactions for information on how Redis handles | ||||
| // errors in a transaction. | ||||
| func TestExecError(t *testing.T) { | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	// Execute commands that fail before EXEC is called. | ||||
|  | ||||
| 	c.Do("ZADD", "k0", 0, 0) | ||||
| 	c.Send("MULTI") | ||||
| 	c.Send("NOTACOMMAND", "k0", 0, 0) | ||||
| 	c.Send("ZINCRBY", "k0", 0, 0) | ||||
| 	v, err := c.Do("EXEC") | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("EXEC returned values %v, expected error", v) | ||||
| 	} | ||||
|  | ||||
| 	// Execute commands that fail after EXEC is called. The first command | ||||
| 	// returns an error. | ||||
|  | ||||
| 	c.Do("ZADD", "k1", 0, 0) | ||||
| 	c.Send("MULTI") | ||||
| 	c.Send("HSET", "k1", 0, 0) | ||||
| 	c.Send("ZINCRBY", "k1", 0, 0) | ||||
| 	v, err = c.Do("EXEC") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("EXEC returned error %v", err) | ||||
| 	} | ||||
|  | ||||
| 	vs, err := redis.Values(v, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Values(v) returned error %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if len(vs) != 2 { | ||||
| 		t.Fatalf("len(vs) == %d, want 2", len(vs)) | ||||
| 	} | ||||
|  | ||||
| 	if _, ok := vs[0].(error); !ok { | ||||
| 		t.Fatalf("first result is type %T, expected error", vs[0]) | ||||
| 	} | ||||
|  | ||||
| 	if _, ok := vs[1].([]byte); !ok { | ||||
| 		t.Fatalf("second result is type %T, expected []byte", vs[2]) | ||||
| 	} | ||||
|  | ||||
| 	// Execute commands that fail after EXEC is called. The second command | ||||
| 	// returns an error. | ||||
|  | ||||
| 	c.Do("ZADD", "k2", 0, 0) | ||||
| 	c.Send("MULTI") | ||||
| 	c.Send("ZINCRBY", "k2", 0, 0) | ||||
| 	c.Send("HSET", "k2", 0, 0) | ||||
| 	v, err = c.Do("EXEC") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("EXEC returned error %v", err) | ||||
| 	} | ||||
|  | ||||
| 	vs, err = redis.Values(v, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Values(v) returned error %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if len(vs) != 2 { | ||||
| 		t.Fatalf("len(vs) == %d, want 2", len(vs)) | ||||
| 	} | ||||
|  | ||||
| 	if _, ok := vs[0].([]byte); !ok { | ||||
| 		t.Fatalf("first result is type %T, expected []byte", vs[0]) | ||||
| 	} | ||||
|  | ||||
| 	if _, ok := vs[1].(error); !ok { | ||||
| 		t.Fatalf("second result is type %T, expected error", vs[2]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkDoEmpty(b *testing.B) { | ||||
| 	b.StopTimer() | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| 	b.StartTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		if _, err := c.Do(""); err != nil { | ||||
| 			b.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkDoPing(b *testing.B) { | ||||
| 	b.StopTimer() | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| 	b.StartTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		if _, err := c.Do("PING"); err != nil { | ||||
| 			b.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										169
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| // Package redis is a client for the Redis database. | ||||
| // | ||||
| // The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more | ||||
| // documentation about this package. | ||||
| // | ||||
| // Connections | ||||
| // | ||||
| // The Conn interface is the primary interface for working with Redis. | ||||
| // Applications create connections by calling the Dial, DialWithTimeout or | ||||
| // NewConn functions. In the future, functions will be added for creating | ||||
| // sharded and other types of connections. | ||||
| // | ||||
| // The application must call the connection Close method when the application | ||||
| // is done with the connection. | ||||
| // | ||||
| // Executing Commands | ||||
| // | ||||
| // The Conn interface has a generic method for executing Redis commands: | ||||
| // | ||||
| //  Do(commandName string, args ...interface{}) (reply interface{}, err error) | ||||
| // | ||||
| // The Redis command reference (http://redis.io/commands) lists the available | ||||
| // commands. An example of using the Redis APPEND command is: | ||||
| // | ||||
| //  n, err := conn.Do("APPEND", "key", "value") | ||||
| // | ||||
| // The Do method converts command arguments to binary strings for transmission | ||||
| // to the server as follows: | ||||
| // | ||||
| //  Go Type                 Conversion | ||||
| //  []byte                  Sent as is | ||||
| //  string                  Sent as is | ||||
| //  int, int64              strconv.FormatInt(v) | ||||
| //  float64                 strconv.FormatFloat(v, 'g', -1, 64) | ||||
| //  bool                    true -> "1", false -> "0" | ||||
| //  nil                     "" | ||||
| //  all other types         fmt.Print(v) | ||||
| // | ||||
| // Redis command reply types are represented using the following Go types: | ||||
| // | ||||
| //  Redis type              Go type | ||||
| //  error                   redis.Error | ||||
| //  integer                 int64 | ||||
| //  simple string           string | ||||
| //  bulk string             []byte or nil if value not present. | ||||
| //  array                   []interface{} or nil if value not present. | ||||
| // | ||||
| // Use type assertions or the reply helper functions to convert from | ||||
| // interface{} to the specific Go type for the command result. | ||||
| // | ||||
| // Pipelining | ||||
| // | ||||
| // Connections support pipelining using the Send, Flush and Receive methods. | ||||
| // | ||||
| //  Send(commandName string, args ...interface{}) error | ||||
| //  Flush() error | ||||
| //  Receive() (reply interface{}, err error) | ||||
| // | ||||
| // Send writes the command to the connection's output buffer. Flush flushes the | ||||
| // connection's output buffer to the server. Receive reads a single reply from | ||||
| // the server. The following example shows a simple pipeline. | ||||
| // | ||||
| //  c.Send("SET", "foo", "bar") | ||||
| //  c.Send("GET", "foo") | ||||
| //  c.Flush() | ||||
| //  c.Receive() // reply from SET | ||||
| //  v, err = c.Receive() // reply from GET | ||||
| // | ||||
| // The Do method combines the functionality of the Send, Flush and Receive | ||||
| // methods. The Do method starts by writing the command and flushing the output | ||||
| // buffer. Next, the Do method receives all pending replies including the reply | ||||
| // for the command just sent by Do. If any of the received replies is an error, | ||||
| // then Do returns the error. If there are no errors, then Do returns the last | ||||
| // reply. If the command argument to the Do method is "", then the Do method | ||||
| // will flush the output buffer and receive pending replies without sending a | ||||
| // command. | ||||
| // | ||||
| // Use the Send and Do methods to implement pipelined transactions. | ||||
| // | ||||
| //  c.Send("MULTI") | ||||
| //  c.Send("INCR", "foo") | ||||
| //  c.Send("INCR", "bar") | ||||
| //  r, err := c.Do("EXEC") | ||||
| //  fmt.Println(r) // prints [1, 1] | ||||
| // | ||||
| // Concurrency | ||||
| // | ||||
| // Connections do not support concurrent calls to the write methods (Send, | ||||
| // Flush) or concurrent calls to the read method (Receive). Connections do | ||||
| // allow a concurrent reader and writer. | ||||
| // | ||||
| // Because the Do method combines the functionality of Send, Flush and Receive, | ||||
| // the Do method cannot be called concurrently with the other methods. | ||||
| // | ||||
| // For full concurrent access to Redis, use the thread-safe Pool to get and | ||||
| // release connections from within a goroutine. | ||||
| // | ||||
| // Publish and Subscribe | ||||
| // | ||||
| // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. | ||||
| // | ||||
| //  c.Send("SUBSCRIBE", "example") | ||||
| //  c.Flush() | ||||
| //  for { | ||||
| //      reply, err := c.Receive() | ||||
| //      if err != nil { | ||||
| //          return err | ||||
| //      } | ||||
| //      // process pushed message | ||||
| //  } | ||||
| // | ||||
| // The PubSubConn type wraps a Conn with convenience methods for implementing | ||||
| // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods | ||||
| // send and flush a subscription management command. The receive method | ||||
| // converts a pushed message to convenient types for use in a type switch. | ||||
| // | ||||
| //  psc := redis.PubSubConn{c} | ||||
| //  psc.Subscribe("example") | ||||
| //  for { | ||||
| //      switch v := psc.Receive().(type) { | ||||
| //      case redis.Message: | ||||
| //          fmt.Printf("%s: message: %s\n", v.Channel, v.Data) | ||||
| //      case redis.Subscription: | ||||
| //          fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) | ||||
| //      case error: | ||||
| //          return v | ||||
| //      } | ||||
| //  } | ||||
| // | ||||
| // Reply Helpers | ||||
| // | ||||
| // The Bool, Int, Bytes, String, Strings and Values functions convert a reply | ||||
| // to a value of a specific type. To allow convenient wrapping of calls to the | ||||
| // connection Do and Receive methods, the functions take a second argument of | ||||
| // type error.  If the error is non-nil, then the helper function returns the | ||||
| // error. If the error is nil, the function converts the reply to the specified | ||||
| // type: | ||||
| // | ||||
| //  exists, err := redis.Bool(c.Do("EXISTS", "foo")) | ||||
| //  if err != nil { | ||||
| //      // handle error return from c.Do or type conversion error. | ||||
| //  } | ||||
| // | ||||
| // The Scan function converts elements of a array reply to Go types: | ||||
| // | ||||
| //  var value1 int | ||||
| //  var value2 string | ||||
| //  reply, err := redis.Values(c.Do("MGET", "key1", "key2")) | ||||
| //  if err != nil { | ||||
| //      // handle error | ||||
| //  } | ||||
| //   if _, err := redis.Scan(reply, &value1, &value2); err != nil { | ||||
| //      // handle error | ||||
| //  } | ||||
| package redis // import "github.com/garyburd/redigo/redis" | ||||
							
								
								
									
										117
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| ) | ||||
|  | ||||
| // NewLoggingConn returns a logging wrapper around a connection. | ||||
| func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { | ||||
| 	if prefix != "" { | ||||
| 		prefix = prefix + "." | ||||
| 	} | ||||
| 	return &loggingConn{conn, logger, prefix} | ||||
| } | ||||
|  | ||||
| type loggingConn struct { | ||||
| 	Conn | ||||
| 	logger *log.Logger | ||||
| 	prefix string | ||||
| } | ||||
|  | ||||
| func (c *loggingConn) Close() error { | ||||
| 	err := c.Conn.Close() | ||||
| 	var buf bytes.Buffer | ||||
| 	fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) | ||||
| 	c.logger.Output(2, buf.String()) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { | ||||
| 	const chop = 32 | ||||
| 	switch v := v.(type) { | ||||
| 	case []byte: | ||||
| 		if len(v) > chop { | ||||
| 			fmt.Fprintf(buf, "%q...", v[:chop]) | ||||
| 		} else { | ||||
| 			fmt.Fprintf(buf, "%q", v) | ||||
| 		} | ||||
| 	case string: | ||||
| 		if len(v) > chop { | ||||
| 			fmt.Fprintf(buf, "%q...", v[:chop]) | ||||
| 		} else { | ||||
| 			fmt.Fprintf(buf, "%q", v) | ||||
| 		} | ||||
| 	case []interface{}: | ||||
| 		if len(v) == 0 { | ||||
| 			buf.WriteString("[]") | ||||
| 		} else { | ||||
| 			sep := "[" | ||||
| 			fin := "]" | ||||
| 			if len(v) > chop { | ||||
| 				v = v[:chop] | ||||
| 				fin = "...]" | ||||
| 			} | ||||
| 			for _, vv := range v { | ||||
| 				buf.WriteString(sep) | ||||
| 				c.printValue(buf, vv) | ||||
| 				sep = ", " | ||||
| 			} | ||||
| 			buf.WriteString(fin) | ||||
| 		} | ||||
| 	default: | ||||
| 		fmt.Fprint(buf, v) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { | ||||
| 	var buf bytes.Buffer | ||||
| 	fmt.Fprintf(&buf, "%s%s(", c.prefix, method) | ||||
| 	if method != "Receive" { | ||||
| 		buf.WriteString(commandName) | ||||
| 		for _, arg := range args { | ||||
| 			buf.WriteString(", ") | ||||
| 			c.printValue(&buf, arg) | ||||
| 		} | ||||
| 	} | ||||
| 	buf.WriteString(") -> (") | ||||
| 	if method != "Send" { | ||||
| 		c.printValue(&buf, reply) | ||||
| 		buf.WriteString(", ") | ||||
| 	} | ||||
| 	fmt.Fprintf(&buf, "%v)", err) | ||||
| 	c.logger.Output(3, buf.String()) | ||||
| } | ||||
|  | ||||
| func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { | ||||
| 	reply, err := c.Conn.Do(commandName, args...) | ||||
| 	c.print("Do", commandName, args, reply, err) | ||||
| 	return reply, err | ||||
| } | ||||
|  | ||||
| func (c *loggingConn) Send(commandName string, args ...interface{}) error { | ||||
| 	err := c.Conn.Send(commandName, args...) | ||||
| 	c.print("Send", commandName, args, nil, err) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *loggingConn) Receive() (interface{}, error) { | ||||
| 	reply, err := c.Conn.Receive() | ||||
| 	c.print("Receive", "", nil, reply, err) | ||||
| 	return reply, err | ||||
| } | ||||
							
								
								
									
										389
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										389
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,389 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"container/list" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/sha1" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/internal" | ||||
| ) | ||||
|  | ||||
| var nowFunc = time.Now // for testing | ||||
|  | ||||
| // ErrPoolExhausted is returned from a pool connection method (Do, Send, | ||||
| // Receive, Flush, Err) when the maximum number of database connections in the | ||||
| // pool has been reached. | ||||
| var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") | ||||
|  | ||||
| var ( | ||||
| 	errPoolClosed = errors.New("redigo: connection pool closed") | ||||
| 	errConnClosed = errors.New("redigo: connection closed") | ||||
| ) | ||||
|  | ||||
| // Pool maintains a pool of connections. The application calls the Get method | ||||
| // to get a connection from the pool and the connection's Close method to | ||||
| // return the connection's resources to the pool. | ||||
| // | ||||
| // The following example shows how to use a pool in a web application. The | ||||
| // application creates a pool at application startup and makes it available to | ||||
| // request handlers using a global variable. | ||||
| // | ||||
| //  func newPool(server, password string) *redis.Pool { | ||||
| //      return &redis.Pool{ | ||||
| //          MaxIdle: 3, | ||||
| //          IdleTimeout: 240 * time.Second, | ||||
| //          Dial: func () (redis.Conn, error) { | ||||
| //              c, err := redis.Dial("tcp", server) | ||||
| //              if err != nil { | ||||
| //                  return nil, err | ||||
| //              } | ||||
| //              if _, err := c.Do("AUTH", password); err != nil { | ||||
| //                  c.Close() | ||||
| //                  return nil, err | ||||
| //              } | ||||
| //              return c, err | ||||
| //          }, | ||||
| //          TestOnBorrow: func(c redis.Conn, t time.Time) error { | ||||
| //              _, err := c.Do("PING") | ||||
| //              return err | ||||
| //          }, | ||||
| //      } | ||||
| //  } | ||||
| // | ||||
| //  var ( | ||||
| //      pool *redis.Pool | ||||
| //      redisServer = flag.String("redisServer", ":6379", "") | ||||
| //      redisPassword = flag.String("redisPassword", "", "") | ||||
| //  ) | ||||
| // | ||||
| //  func main() { | ||||
| //      flag.Parse() | ||||
| //      pool = newPool(*redisServer, *redisPassword) | ||||
| //      ... | ||||
| //  } | ||||
| // | ||||
| // A request handler gets a connection from the pool and closes the connection | ||||
| // when the handler is done: | ||||
| // | ||||
| //  func serveHome(w http.ResponseWriter, r *http.Request) { | ||||
| //      conn := pool.Get() | ||||
| //      defer conn.Close() | ||||
| //      .... | ||||
| //  } | ||||
| // | ||||
| type Pool struct { | ||||
|  | ||||
| 	// Dial is an application supplied function for creating and configuring a | ||||
| 	// connection | ||||
| 	Dial func() (Conn, error) | ||||
|  | ||||
| 	// TestOnBorrow is an optional application supplied function for checking | ||||
| 	// the health of an idle connection before the connection is used again by | ||||
| 	// the application. Argument t is the time that the connection was returned | ||||
| 	// to the pool. If the function returns an error, then the connection is | ||||
| 	// closed. | ||||
| 	TestOnBorrow func(c Conn, t time.Time) error | ||||
|  | ||||
| 	// Maximum number of idle connections in the pool. | ||||
| 	MaxIdle int | ||||
|  | ||||
| 	// Maximum number of connections allocated by the pool at a given time. | ||||
| 	// When zero, there is no limit on the number of connections in the pool. | ||||
| 	MaxActive int | ||||
|  | ||||
| 	// Close connections after remaining idle for this duration. If the value | ||||
| 	// is zero, then idle connections are not closed. Applications should set | ||||
| 	// the timeout to a value less than the server's timeout. | ||||
| 	IdleTimeout time.Duration | ||||
|  | ||||
| 	// If Wait is true and the pool is at the MaxIdle limit, then Get() waits | ||||
| 	// for a connection to be returned to the pool before returning. | ||||
| 	Wait bool | ||||
|  | ||||
| 	// mu protects fields defined below. | ||||
| 	mu     sync.Mutex | ||||
| 	cond   *sync.Cond | ||||
| 	closed bool | ||||
| 	active int | ||||
|  | ||||
| 	// Stack of idleConn with most recently used at the front. | ||||
| 	idle list.List | ||||
| } | ||||
|  | ||||
| type idleConn struct { | ||||
| 	c Conn | ||||
| 	t time.Time | ||||
| } | ||||
|  | ||||
| // NewPool creates a new pool. This function is deprecated. Applications should | ||||
| // initialize the Pool fields directly as shown in example. | ||||
| func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { | ||||
| 	return &Pool{Dial: newFn, MaxIdle: maxIdle} | ||||
| } | ||||
|  | ||||
| // Get gets a connection. The application must close the returned connection. | ||||
| // This method always returns a valid connection so that applications can defer | ||||
| // error handling to the first use of the connection. If there is an error | ||||
| // getting an underlying connection, then the connection Err, Do, Send, Flush | ||||
| // and Receive methods return that error. | ||||
| func (p *Pool) Get() Conn { | ||||
| 	c, err := p.get() | ||||
| 	if err != nil { | ||||
| 		return errorConnection{err} | ||||
| 	} | ||||
| 	return &pooledConnection{p: p, c: c} | ||||
| } | ||||
|  | ||||
| // ActiveCount returns the number of active connections in the pool. | ||||
| func (p *Pool) ActiveCount() int { | ||||
| 	p.mu.Lock() | ||||
| 	active := p.active | ||||
| 	p.mu.Unlock() | ||||
| 	return active | ||||
| } | ||||
|  | ||||
| // Close releases the resources used by the pool. | ||||
| func (p *Pool) Close() error { | ||||
| 	p.mu.Lock() | ||||
| 	idle := p.idle | ||||
| 	p.idle.Init() | ||||
| 	p.closed = true | ||||
| 	p.active -= idle.Len() | ||||
| 	if p.cond != nil { | ||||
| 		p.cond.Broadcast() | ||||
| 	} | ||||
| 	p.mu.Unlock() | ||||
| 	for e := idle.Front(); e != nil; e = e.Next() { | ||||
| 		e.Value.(idleConn).c.Close() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // release decrements the active count and signals waiters. The caller must | ||||
| // hold p.mu during the call. | ||||
| func (p *Pool) release() { | ||||
| 	p.active -= 1 | ||||
| 	if p.cond != nil { | ||||
| 		p.cond.Signal() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // get prunes stale connections and returns a connection from the idle list or | ||||
| // creates a new connection. | ||||
| func (p *Pool) get() (Conn, error) { | ||||
| 	p.mu.Lock() | ||||
|  | ||||
| 	// Prune stale connections. | ||||
|  | ||||
| 	if timeout := p.IdleTimeout; timeout > 0 { | ||||
| 		for i, n := 0, p.idle.Len(); i < n; i++ { | ||||
| 			e := p.idle.Back() | ||||
| 			if e == nil { | ||||
| 				break | ||||
| 			} | ||||
| 			ic := e.Value.(idleConn) | ||||
| 			if ic.t.Add(timeout).After(nowFunc()) { | ||||
| 				break | ||||
| 			} | ||||
| 			p.idle.Remove(e) | ||||
| 			p.release() | ||||
| 			p.mu.Unlock() | ||||
| 			ic.c.Close() | ||||
| 			p.mu.Lock() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for { | ||||
|  | ||||
| 		// Get idle connection. | ||||
|  | ||||
| 		for i, n := 0, p.idle.Len(); i < n; i++ { | ||||
| 			e := p.idle.Front() | ||||
| 			if e == nil { | ||||
| 				break | ||||
| 			} | ||||
| 			ic := e.Value.(idleConn) | ||||
| 			p.idle.Remove(e) | ||||
| 			test := p.TestOnBorrow | ||||
| 			p.mu.Unlock() | ||||
| 			if test == nil || test(ic.c, ic.t) == nil { | ||||
| 				return ic.c, nil | ||||
| 			} | ||||
| 			ic.c.Close() | ||||
| 			p.mu.Lock() | ||||
| 			p.release() | ||||
| 		} | ||||
|  | ||||
| 		// Check for pool closed before dialing a new connection. | ||||
|  | ||||
| 		if p.closed { | ||||
| 			p.mu.Unlock() | ||||
| 			return nil, errors.New("redigo: get on closed pool") | ||||
| 		} | ||||
|  | ||||
| 		// Dial new connection if under limit. | ||||
|  | ||||
| 		if p.MaxActive == 0 || p.active < p.MaxActive { | ||||
| 			dial := p.Dial | ||||
| 			p.active += 1 | ||||
| 			p.mu.Unlock() | ||||
| 			c, err := dial() | ||||
| 			if err != nil { | ||||
| 				p.mu.Lock() | ||||
| 				p.release() | ||||
| 				p.mu.Unlock() | ||||
| 				c = nil | ||||
| 			} | ||||
| 			return c, err | ||||
| 		} | ||||
|  | ||||
| 		if !p.Wait { | ||||
| 			p.mu.Unlock() | ||||
| 			return nil, ErrPoolExhausted | ||||
| 		} | ||||
|  | ||||
| 		if p.cond == nil { | ||||
| 			p.cond = sync.NewCond(&p.mu) | ||||
| 		} | ||||
| 		p.cond.Wait() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *Pool) put(c Conn, forceClose bool) error { | ||||
| 	err := c.Err() | ||||
| 	p.mu.Lock() | ||||
| 	if !p.closed && err == nil && !forceClose { | ||||
| 		p.idle.PushFront(idleConn{t: nowFunc(), c: c}) | ||||
| 		if p.idle.Len() > p.MaxIdle { | ||||
| 			c = p.idle.Remove(p.idle.Back()).(idleConn).c | ||||
| 		} else { | ||||
| 			c = nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if c == nil { | ||||
| 		if p.cond != nil { | ||||
| 			p.cond.Signal() | ||||
| 		} | ||||
| 		p.mu.Unlock() | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	p.release() | ||||
| 	p.mu.Unlock() | ||||
| 	return c.Close() | ||||
| } | ||||
|  | ||||
| type pooledConnection struct { | ||||
| 	p     *Pool | ||||
| 	c     Conn | ||||
| 	state int | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	sentinel     []byte | ||||
| 	sentinelOnce sync.Once | ||||
| ) | ||||
|  | ||||
| func initSentinel() { | ||||
| 	p := make([]byte, 64) | ||||
| 	if _, err := rand.Read(p); err == nil { | ||||
| 		sentinel = p | ||||
| 	} else { | ||||
| 		h := sha1.New() | ||||
| 		io.WriteString(h, "Oops, rand failed. Use time instead.") | ||||
| 		io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) | ||||
| 		sentinel = h.Sum(nil) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pc *pooledConnection) Close() error { | ||||
| 	c := pc.c | ||||
| 	if _, ok := c.(errorConnection); ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 	pc.c = errorConnection{errConnClosed} | ||||
|  | ||||
| 	if pc.state&internal.MultiState != 0 { | ||||
| 		c.Send("DISCARD") | ||||
| 		pc.state &^= (internal.MultiState | internal.WatchState) | ||||
| 	} else if pc.state&internal.WatchState != 0 { | ||||
| 		c.Send("UNWATCH") | ||||
| 		pc.state &^= internal.WatchState | ||||
| 	} | ||||
| 	if pc.state&internal.SubscribeState != 0 { | ||||
| 		c.Send("UNSUBSCRIBE") | ||||
| 		c.Send("PUNSUBSCRIBE") | ||||
| 		// To detect the end of the message stream, ask the server to echo | ||||
| 		// a sentinel value and read until we see that value. | ||||
| 		sentinelOnce.Do(initSentinel) | ||||
| 		c.Send("ECHO", sentinel) | ||||
| 		c.Flush() | ||||
| 		for { | ||||
| 			p, err := c.Receive() | ||||
| 			if err != nil { | ||||
| 				break | ||||
| 			} | ||||
| 			if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { | ||||
| 				pc.state &^= internal.SubscribeState | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	c.Do("") | ||||
| 	pc.p.put(c, pc.state != 0) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (pc *pooledConnection) Err() error { | ||||
| 	return pc.c.Err() | ||||
| } | ||||
|  | ||||
| func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { | ||||
| 	ci := internal.LookupCommandInfo(commandName) | ||||
| 	pc.state = (pc.state | ci.Set) &^ ci.Clear | ||||
| 	return pc.c.Do(commandName, args...) | ||||
| } | ||||
|  | ||||
| func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { | ||||
| 	ci := internal.LookupCommandInfo(commandName) | ||||
| 	pc.state = (pc.state | ci.Set) &^ ci.Clear | ||||
| 	return pc.c.Send(commandName, args...) | ||||
| } | ||||
|  | ||||
| func (pc *pooledConnection) Flush() error { | ||||
| 	return pc.c.Flush() | ||||
| } | ||||
|  | ||||
| func (pc *pooledConnection) Receive() (reply interface{}, err error) { | ||||
| 	return pc.c.Receive() | ||||
| } | ||||
|  | ||||
| type errorConnection struct{ err error } | ||||
|  | ||||
| func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } | ||||
| func (ec errorConnection) Send(string, ...interface{}) error              { return ec.err } | ||||
| func (ec errorConnection) Err() error                                     { return ec.err } | ||||
| func (ec errorConnection) Close() error                                   { return ec.err } | ||||
| func (ec errorConnection) Flush() error                                   { return ec.err } | ||||
| func (ec errorConnection) Receive() (interface{}, error)                  { return nil, ec.err } | ||||
							
								
								
									
										674
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										674
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,674 @@ | ||||
| // Copyright 2011 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis_test | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/internal/redistest" | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| type poolTestConn struct { | ||||
| 	d   *poolDialer | ||||
| 	err error | ||||
| 	redis.Conn | ||||
| } | ||||
|  | ||||
| func (c *poolTestConn) Close() error { c.d.open -= 1; return nil } | ||||
| func (c *poolTestConn) Err() error   { return c.err } | ||||
|  | ||||
| func (c *poolTestConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { | ||||
| 	if commandName == "ERR" { | ||||
| 		c.err = args[0].(error) | ||||
| 		commandName = "PING" | ||||
| 	} | ||||
| 	if commandName != "" { | ||||
| 		c.d.commands = append(c.d.commands, commandName) | ||||
| 	} | ||||
| 	return c.Conn.Do(commandName, args...) | ||||
| } | ||||
|  | ||||
| func (c *poolTestConn) Send(commandName string, args ...interface{}) error { | ||||
| 	c.d.commands = append(c.d.commands, commandName) | ||||
| 	return c.Conn.Send(commandName, args...) | ||||
| } | ||||
|  | ||||
| type poolDialer struct { | ||||
| 	t        *testing.T | ||||
| 	dialed   int | ||||
| 	open     int | ||||
| 	commands []string | ||||
| 	dialErr  error | ||||
| } | ||||
|  | ||||
| func (d *poolDialer) dial() (redis.Conn, error) { | ||||
| 	d.dialed += 1 | ||||
| 	if d.dialErr != nil { | ||||
| 		return nil, d.dialErr | ||||
| 	} | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	d.open += 1 | ||||
| 	return &poolTestConn{d: d, Conn: c}, nil | ||||
| } | ||||
|  | ||||
| func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { | ||||
| 	if d.dialed != dialed { | ||||
| 		d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) | ||||
| 	} | ||||
| 	if d.open != open { | ||||
| 		d.t.Errorf("%s: open=%d, want %d", message, d.open, open) | ||||
| 	} | ||||
| 	if active := p.ActiveCount(); active != open { | ||||
| 		d.t.Errorf("%s: active=%d, want %d", message, active, open) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPoolReuse(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle: 2, | ||||
| 		Dial:    d.dial, | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		c1 := p.Get() | ||||
| 		c1.Do("PING") | ||||
| 		c2 := p.Get() | ||||
| 		c2.Do("PING") | ||||
| 		c1.Close() | ||||
| 		c2.Close() | ||||
| 	} | ||||
|  | ||||
| 	d.check("before close", p, 2, 2) | ||||
| 	p.Close() | ||||
| 	d.check("after close", p, 2, 0) | ||||
| } | ||||
|  | ||||
| func TestPoolMaxIdle(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle: 2, | ||||
| 		Dial:    d.dial, | ||||
| 	} | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		c1 := p.Get() | ||||
| 		c1.Do("PING") | ||||
| 		c2 := p.Get() | ||||
| 		c2.Do("PING") | ||||
| 		c3 := p.Get() | ||||
| 		c3.Do("PING") | ||||
| 		c1.Close() | ||||
| 		c2.Close() | ||||
| 		c3.Close() | ||||
| 	} | ||||
| 	d.check("before close", p, 12, 2) | ||||
| 	p.Close() | ||||
| 	d.check("after close", p, 12, 0) | ||||
| } | ||||
|  | ||||
| func TestPoolError(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle: 2, | ||||
| 		Dial:    d.dial, | ||||
| 	} | ||||
|  | ||||
| 	c := p.Get() | ||||
| 	c.Do("ERR", io.EOF) | ||||
| 	if c.Err() == nil { | ||||
| 		t.Errorf("expected c.Err() != nil") | ||||
| 	} | ||||
| 	c.Close() | ||||
|  | ||||
| 	c = p.Get() | ||||
| 	c.Do("ERR", io.EOF) | ||||
| 	c.Close() | ||||
|  | ||||
| 	d.check(".", p, 2, 0) | ||||
| } | ||||
|  | ||||
| func TestPoolClose(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle: 2, | ||||
| 		Dial:    d.dial, | ||||
| 	} | ||||
|  | ||||
| 	c1 := p.Get() | ||||
| 	c1.Do("PING") | ||||
| 	c2 := p.Get() | ||||
| 	c2.Do("PING") | ||||
| 	c3 := p.Get() | ||||
| 	c3.Do("PING") | ||||
|  | ||||
| 	c1.Close() | ||||
| 	if _, err := c1.Do("PING"); err == nil { | ||||
| 		t.Errorf("expected error after connection closed") | ||||
| 	} | ||||
|  | ||||
| 	c2.Close() | ||||
| 	c2.Close() | ||||
|  | ||||
| 	p.Close() | ||||
|  | ||||
| 	d.check("after pool close", p, 3, 1) | ||||
|  | ||||
| 	if _, err := c1.Do("PING"); err == nil { | ||||
| 		t.Errorf("expected error after connection and pool closed") | ||||
| 	} | ||||
|  | ||||
| 	c3.Close() | ||||
|  | ||||
| 	d.check("after conn close", p, 3, 0) | ||||
|  | ||||
| 	c1 = p.Get() | ||||
| 	if _, err := c1.Do("PING"); err == nil { | ||||
| 		t.Errorf("expected error after pool closed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPoolTimeout(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:     2, | ||||
| 		IdleTimeout: 300 * time.Second, | ||||
| 		Dial:        d.dial, | ||||
| 	} | ||||
|  | ||||
| 	now := time.Now() | ||||
| 	redis.SetNowFunc(func() time.Time { return now }) | ||||
| 	defer redis.SetNowFunc(time.Now) | ||||
|  | ||||
| 	c := p.Get() | ||||
| 	c.Do("PING") | ||||
| 	c.Close() | ||||
|  | ||||
| 	d.check("1", p, 1, 1) | ||||
|  | ||||
| 	now = now.Add(p.IdleTimeout) | ||||
|  | ||||
| 	c = p.Get() | ||||
| 	c.Do("PING") | ||||
| 	c.Close() | ||||
|  | ||||
| 	d.check("2", p, 2, 1) | ||||
|  | ||||
| 	p.Close() | ||||
| } | ||||
|  | ||||
| func TestPoolConcurrenSendReceive(t *testing.T) { | ||||
| 	p := &redis.Pool{ | ||||
| 		Dial: redistest.Dial, | ||||
| 	} | ||||
| 	c := p.Get() | ||||
| 	done := make(chan error, 1) | ||||
| 	go func() { | ||||
| 		_, err := c.Receive() | ||||
| 		done <- err | ||||
| 	}() | ||||
| 	c.Send("PING") | ||||
| 	c.Flush() | ||||
| 	err := <-done | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Receive() returned error %v", err) | ||||
| 	} | ||||
| 	_, err = c.Do("") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Do() returned error %v", err) | ||||
| 	} | ||||
| 	c.Close() | ||||
| 	p.Close() | ||||
| } | ||||
|  | ||||
| func TestPoolBorrowCheck(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:      2, | ||||
| 		Dial:         d.dial, | ||||
| 		TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") }, | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		c := p.Get() | ||||
| 		c.Do("PING") | ||||
| 		c.Close() | ||||
| 	} | ||||
| 	d.check("1", p, 10, 1) | ||||
| 	p.Close() | ||||
| } | ||||
|  | ||||
| func TestPoolMaxActive(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   2, | ||||
| 		MaxActive: 2, | ||||
| 		Dial:      d.dial, | ||||
| 	} | ||||
| 	c1 := p.Get() | ||||
| 	c1.Do("PING") | ||||
| 	c2 := p.Get() | ||||
| 	c2.Do("PING") | ||||
|  | ||||
| 	d.check("1", p, 2, 2) | ||||
|  | ||||
| 	c3 := p.Get() | ||||
| 	if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted { | ||||
| 		t.Errorf("expected pool exhausted") | ||||
| 	} | ||||
|  | ||||
| 	c3.Close() | ||||
| 	d.check("2", p, 2, 2) | ||||
| 	c2.Close() | ||||
| 	d.check("3", p, 2, 2) | ||||
|  | ||||
| 	c3 = p.Get() | ||||
| 	if _, err := c3.Do("PING"); err != nil { | ||||
| 		t.Errorf("expected good channel, err=%v", err) | ||||
| 	} | ||||
| 	c3.Close() | ||||
|  | ||||
| 	d.check("4", p, 2, 2) | ||||
| 	p.Close() | ||||
| } | ||||
|  | ||||
| func TestPoolMonitorCleanup(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   2, | ||||
| 		MaxActive: 2, | ||||
| 		Dial:      d.dial, | ||||
| 	} | ||||
| 	c := p.Get() | ||||
| 	c.Send("MONITOR") | ||||
| 	c.Close() | ||||
|  | ||||
| 	d.check("", p, 1, 0) | ||||
| 	p.Close() | ||||
| } | ||||
|  | ||||
| func TestPoolPubSubCleanup(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   2, | ||||
| 		MaxActive: 2, | ||||
| 		Dial:      d.dial, | ||||
| 	} | ||||
|  | ||||
| 	c := p.Get() | ||||
| 	c.Send("SUBSCRIBE", "x") | ||||
| 	c.Close() | ||||
|  | ||||
| 	want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} | ||||
| 	if !reflect.DeepEqual(d.commands, want) { | ||||
| 		t.Errorf("got commands %v, want %v", d.commands, want) | ||||
| 	} | ||||
| 	d.commands = nil | ||||
|  | ||||
| 	c = p.Get() | ||||
| 	c.Send("PSUBSCRIBE", "x*") | ||||
| 	c.Close() | ||||
|  | ||||
| 	want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} | ||||
| 	if !reflect.DeepEqual(d.commands, want) { | ||||
| 		t.Errorf("got commands %v, want %v", d.commands, want) | ||||
| 	} | ||||
| 	d.commands = nil | ||||
|  | ||||
| 	p.Close() | ||||
| } | ||||
|  | ||||
| func TestPoolTransactionCleanup(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   2, | ||||
| 		MaxActive: 2, | ||||
| 		Dial:      d.dial, | ||||
| 	} | ||||
|  | ||||
| 	c := p.Get() | ||||
| 	c.Do("WATCH", "key") | ||||
| 	c.Do("PING") | ||||
| 	c.Close() | ||||
|  | ||||
| 	want := []string{"WATCH", "PING", "UNWATCH"} | ||||
| 	if !reflect.DeepEqual(d.commands, want) { | ||||
| 		t.Errorf("got commands %v, want %v", d.commands, want) | ||||
| 	} | ||||
| 	d.commands = nil | ||||
|  | ||||
| 	c = p.Get() | ||||
| 	c.Do("WATCH", "key") | ||||
| 	c.Do("UNWATCH") | ||||
| 	c.Do("PING") | ||||
| 	c.Close() | ||||
|  | ||||
| 	want = []string{"WATCH", "UNWATCH", "PING"} | ||||
| 	if !reflect.DeepEqual(d.commands, want) { | ||||
| 		t.Errorf("got commands %v, want %v", d.commands, want) | ||||
| 	} | ||||
| 	d.commands = nil | ||||
|  | ||||
| 	c = p.Get() | ||||
| 	c.Do("WATCH", "key") | ||||
| 	c.Do("MULTI") | ||||
| 	c.Do("PING") | ||||
| 	c.Close() | ||||
|  | ||||
| 	want = []string{"WATCH", "MULTI", "PING", "DISCARD"} | ||||
| 	if !reflect.DeepEqual(d.commands, want) { | ||||
| 		t.Errorf("got commands %v, want %v", d.commands, want) | ||||
| 	} | ||||
| 	d.commands = nil | ||||
|  | ||||
| 	c = p.Get() | ||||
| 	c.Do("WATCH", "key") | ||||
| 	c.Do("MULTI") | ||||
| 	c.Do("DISCARD") | ||||
| 	c.Do("PING") | ||||
| 	c.Close() | ||||
|  | ||||
| 	want = []string{"WATCH", "MULTI", "DISCARD", "PING"} | ||||
| 	if !reflect.DeepEqual(d.commands, want) { | ||||
| 		t.Errorf("got commands %v, want %v", d.commands, want) | ||||
| 	} | ||||
| 	d.commands = nil | ||||
|  | ||||
| 	c = p.Get() | ||||
| 	c.Do("WATCH", "key") | ||||
| 	c.Do("MULTI") | ||||
| 	c.Do("EXEC") | ||||
| 	c.Do("PING") | ||||
| 	c.Close() | ||||
|  | ||||
| 	want = []string{"WATCH", "MULTI", "EXEC", "PING"} | ||||
| 	if !reflect.DeepEqual(d.commands, want) { | ||||
| 		t.Errorf("got commands %v, want %v", d.commands, want) | ||||
| 	} | ||||
| 	d.commands = nil | ||||
|  | ||||
| 	p.Close() | ||||
| } | ||||
|  | ||||
| func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error { | ||||
| 	errs := make(chan error, 10) | ||||
| 	for i := 0; i < cap(errs); i++ { | ||||
| 		go func() { | ||||
| 			c := p.Get() | ||||
| 			_, err := c.Do(cmd, args...) | ||||
| 			errs <- err | ||||
| 			c.Close() | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	// Wait for goroutines to block. | ||||
| 	time.Sleep(time.Second / 4) | ||||
|  | ||||
| 	return errs | ||||
| } | ||||
|  | ||||
| func TestWaitPool(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   1, | ||||
| 		MaxActive: 1, | ||||
| 		Dial:      d.dial, | ||||
| 		Wait:      true, | ||||
| 	} | ||||
| 	defer p.Close() | ||||
| 	c := p.Get() | ||||
| 	errs := startGoroutines(p, "PING") | ||||
| 	d.check("before close", p, 1, 1) | ||||
| 	c.Close() | ||||
| 	timeout := time.After(2 * time.Second) | ||||
| 	for i := 0; i < cap(errs); i++ { | ||||
| 		select { | ||||
| 		case err := <-errs: | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 		case <-timeout: | ||||
| 			t.Fatalf("timeout waiting for blocked goroutine %d", i) | ||||
| 		} | ||||
| 	} | ||||
| 	d.check("done", p, 1, 1) | ||||
| } | ||||
|  | ||||
| func TestWaitPoolClose(t *testing.T) { | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   1, | ||||
| 		MaxActive: 1, | ||||
| 		Dial:      d.dial, | ||||
| 		Wait:      true, | ||||
| 	} | ||||
| 	c := p.Get() | ||||
| 	if _, err := c.Do("PING"); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	errs := startGoroutines(p, "PING") | ||||
| 	d.check("before close", p, 1, 1) | ||||
| 	p.Close() | ||||
| 	timeout := time.After(2 * time.Second) | ||||
| 	for i := 0; i < cap(errs); i++ { | ||||
| 		select { | ||||
| 		case err := <-errs: | ||||
| 			switch err { | ||||
| 			case nil: | ||||
| 				t.Fatal("blocked goroutine did not get error") | ||||
| 			case redis.ErrPoolExhausted: | ||||
| 				t.Fatal("blocked goroutine got pool exhausted error") | ||||
| 			} | ||||
| 		case <-timeout: | ||||
| 			t.Fatal("timeout waiting for blocked goroutine") | ||||
| 		} | ||||
| 	} | ||||
| 	c.Close() | ||||
| 	d.check("done", p, 1, 0) | ||||
| } | ||||
|  | ||||
| func TestWaitPoolCommandError(t *testing.T) { | ||||
| 	testErr := errors.New("test") | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   1, | ||||
| 		MaxActive: 1, | ||||
| 		Dial:      d.dial, | ||||
| 		Wait:      true, | ||||
| 	} | ||||
| 	defer p.Close() | ||||
| 	c := p.Get() | ||||
| 	errs := startGoroutines(p, "ERR", testErr) | ||||
| 	d.check("before close", p, 1, 1) | ||||
| 	c.Close() | ||||
| 	timeout := time.After(2 * time.Second) | ||||
| 	for i := 0; i < cap(errs); i++ { | ||||
| 		select { | ||||
| 		case err := <-errs: | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 		case <-timeout: | ||||
| 			t.Fatalf("timeout waiting for blocked goroutine %d", i) | ||||
| 		} | ||||
| 	} | ||||
| 	d.check("done", p, cap(errs), 0) | ||||
| } | ||||
|  | ||||
| func TestWaitPoolDialError(t *testing.T) { | ||||
| 	testErr := errors.New("test") | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   1, | ||||
| 		MaxActive: 1, | ||||
| 		Dial:      d.dial, | ||||
| 		Wait:      true, | ||||
| 	} | ||||
| 	defer p.Close() | ||||
| 	c := p.Get() | ||||
| 	errs := startGoroutines(p, "ERR", testErr) | ||||
| 	d.check("before close", p, 1, 1) | ||||
|  | ||||
| 	d.dialErr = errors.New("dial") | ||||
| 	c.Close() | ||||
|  | ||||
| 	nilCount := 0 | ||||
| 	errCount := 0 | ||||
| 	timeout := time.After(2 * time.Second) | ||||
| 	for i := 0; i < cap(errs); i++ { | ||||
| 		select { | ||||
| 		case err := <-errs: | ||||
| 			switch err { | ||||
| 			case nil: | ||||
| 				nilCount++ | ||||
| 			case d.dialErr: | ||||
| 				errCount++ | ||||
| 			default: | ||||
| 				t.Fatalf("expected dial error or nil, got %v", err) | ||||
| 			} | ||||
| 		case <-timeout: | ||||
| 			t.Fatalf("timeout waiting for blocked goroutine %d", i) | ||||
| 		} | ||||
| 	} | ||||
| 	if nilCount != 1 { | ||||
| 		t.Errorf("expected one nil error, got %d", nilCount) | ||||
| 	} | ||||
| 	if errCount != cap(errs)-1 { | ||||
| 		t.Errorf("expected %d dial erors, got %d", cap(errs)-1, errCount) | ||||
| 	} | ||||
| 	d.check("done", p, cap(errs), 0) | ||||
| } | ||||
|  | ||||
| // Borrowing requires us to iterate over the idle connections, unlock the pool, | ||||
| // and perform a blocking operation to check the connection still works. If | ||||
| // TestOnBorrow fails, we must reacquire the lock and continue iteration. This | ||||
| // test ensures that iteration will work correctly if multiple threads are | ||||
| // iterating simultaneously. | ||||
| func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { | ||||
| 	count := 100 | ||||
|  | ||||
| 	// First we'll Create a pool where the pilfering of idle connections fails. | ||||
| 	d := poolDialer{t: t} | ||||
| 	p := &redis.Pool{ | ||||
| 		MaxIdle:   count, | ||||
| 		MaxActive: count, | ||||
| 		Dial:      d.dial, | ||||
| 		TestOnBorrow: func(c redis.Conn, t time.Time) error { | ||||
| 			return errors.New("No way back into the real world.") | ||||
| 		}, | ||||
| 	} | ||||
| 	defer p.Close() | ||||
|  | ||||
| 	// Fill the pool with idle connections. | ||||
| 	b1 := sync.WaitGroup{} | ||||
| 	b1.Add(count) | ||||
| 	b2 := sync.WaitGroup{} | ||||
| 	b2.Add(count) | ||||
| 	for i := 0; i < count; i++ { | ||||
| 		go func() { | ||||
| 			c := p.Get() | ||||
| 			if c.Err() != nil { | ||||
| 				t.Errorf("pool get failed: %v", c.Err()) | ||||
| 			} | ||||
| 			b1.Done() | ||||
| 			b1.Wait() | ||||
| 			c.Close() | ||||
| 			b2.Done() | ||||
| 		}() | ||||
| 	} | ||||
| 	b2.Wait() | ||||
| 	if d.dialed != count { | ||||
| 		t.Errorf("Expected %d dials, got %d", count, d.dialed) | ||||
| 	} | ||||
|  | ||||
| 	// Spawn a bunch of goroutines to thrash the pool. | ||||
| 	b2.Add(count) | ||||
| 	for i := 0; i < count; i++ { | ||||
| 		go func() { | ||||
| 			c := p.Get() | ||||
| 			if c.Err() != nil { | ||||
| 				t.Errorf("pool get failed: %v", c.Err()) | ||||
| 			} | ||||
| 			c.Close() | ||||
| 			b2.Done() | ||||
| 		}() | ||||
| 	} | ||||
| 	b2.Wait() | ||||
| 	if d.dialed != count*2 { | ||||
| 		t.Errorf("Expected %d dials, got %d", count*2, d.dialed) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkPoolGet(b *testing.B) { | ||||
| 	b.StopTimer() | ||||
| 	p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} | ||||
| 	c := p.Get() | ||||
| 	if err := c.Err(); err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 	c.Close() | ||||
| 	defer p.Close() | ||||
| 	b.StartTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		c = p.Get() | ||||
| 		c.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkPoolGetErr(b *testing.B) { | ||||
| 	b.StopTimer() | ||||
| 	p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} | ||||
| 	c := p.Get() | ||||
| 	if err := c.Err(); err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 	c.Close() | ||||
| 	defer p.Close() | ||||
| 	b.StartTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		c = p.Get() | ||||
| 		if err := c.Err(); err != nil { | ||||
| 			b.Fatal(err) | ||||
| 		} | ||||
| 		c.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkPoolGetPing(b *testing.B) { | ||||
| 	b.StopTimer() | ||||
| 	p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} | ||||
| 	c := p.Get() | ||||
| 	if err := c.Err(); err != nil { | ||||
| 		b.Fatal(err) | ||||
| 	} | ||||
| 	c.Close() | ||||
| 	defer p.Close() | ||||
| 	b.StartTimer() | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		c = p.Get() | ||||
| 		if _, err := c.Do("PING"); err != nil { | ||||
| 			b.Fatal(err) | ||||
| 		} | ||||
| 		c.Close() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										129
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
|  | ||||
| // Subscription represents a subscribe or unsubscribe notification. | ||||
| type Subscription struct { | ||||
|  | ||||
| 	// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" | ||||
| 	Kind string | ||||
|  | ||||
| 	// The channel that was changed. | ||||
| 	Channel string | ||||
|  | ||||
| 	// The current number of subscriptions for connection. | ||||
| 	Count int | ||||
| } | ||||
|  | ||||
| // Message represents a message notification. | ||||
| type Message struct { | ||||
|  | ||||
| 	// The originating channel. | ||||
| 	Channel string | ||||
|  | ||||
| 	// The message data. | ||||
| 	Data []byte | ||||
| } | ||||
|  | ||||
| // PMessage represents a pmessage notification. | ||||
| type PMessage struct { | ||||
|  | ||||
| 	// The matched pattern. | ||||
| 	Pattern string | ||||
|  | ||||
| 	// The originating channel. | ||||
| 	Channel string | ||||
|  | ||||
| 	// The message data. | ||||
| 	Data []byte | ||||
| } | ||||
|  | ||||
| // PubSubConn wraps a Conn with convenience methods for subscribers. | ||||
| type PubSubConn struct { | ||||
| 	Conn Conn | ||||
| } | ||||
|  | ||||
| // Close closes the connection. | ||||
| func (c PubSubConn) Close() error { | ||||
| 	return c.Conn.Close() | ||||
| } | ||||
|  | ||||
| // Subscribe subscribes the connection to the specified channels. | ||||
| func (c PubSubConn) Subscribe(channel ...interface{}) error { | ||||
| 	c.Conn.Send("SUBSCRIBE", channel...) | ||||
| 	return c.Conn.Flush() | ||||
| } | ||||
|  | ||||
| // PSubscribe subscribes the connection to the given patterns. | ||||
| func (c PubSubConn) PSubscribe(channel ...interface{}) error { | ||||
| 	c.Conn.Send("PSUBSCRIBE", channel...) | ||||
| 	return c.Conn.Flush() | ||||
| } | ||||
|  | ||||
| // Unsubscribe unsubscribes the connection from the given channels, or from all | ||||
| // of them if none is given. | ||||
| func (c PubSubConn) Unsubscribe(channel ...interface{}) error { | ||||
| 	c.Conn.Send("UNSUBSCRIBE", channel...) | ||||
| 	return c.Conn.Flush() | ||||
| } | ||||
|  | ||||
| // PUnsubscribe unsubscribes the connection from the given patterns, or from all | ||||
| // of them if none is given. | ||||
| func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { | ||||
| 	c.Conn.Send("PUNSUBSCRIBE", channel...) | ||||
| 	return c.Conn.Flush() | ||||
| } | ||||
|  | ||||
| // Receive returns a pushed message as a Subscription, Message, PMessage or | ||||
| // error. The return value is intended to be used directly in a type switch as | ||||
| // illustrated in the PubSubConn example. | ||||
| func (c PubSubConn) Receive() interface{} { | ||||
| 	reply, err := Values(c.Conn.Receive()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var kind string | ||||
| 	reply, err = Scan(reply, &kind) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	switch kind { | ||||
| 	case "message": | ||||
| 		var m Message | ||||
| 		if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return m | ||||
| 	case "pmessage": | ||||
| 		var pm PMessage | ||||
| 		if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return pm | ||||
| 	case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": | ||||
| 		s := Subscription{Kind: kind} | ||||
| 		if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return s | ||||
| 	} | ||||
| 	return errors.New("redigo: unknown pubsub notification") | ||||
| } | ||||
							
								
								
									
										143
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis_test | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/internal/redistest" | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| func publish(channel, value interface{}) { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| 	c.Do("PUBLISH", channel, value) | ||||
| } | ||||
|  | ||||
| // Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine. | ||||
| func ExamplePubSubConn() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| 	var wg sync.WaitGroup | ||||
| 	wg.Add(2) | ||||
|  | ||||
| 	psc := redis.PubSubConn{Conn: c} | ||||
|  | ||||
| 	// This goroutine receives and prints pushed notifications from the server. | ||||
| 	// The goroutine exits when the connection is unsubscribed from all | ||||
| 	// channels or there is an error. | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
| 		for { | ||||
| 			switch n := psc.Receive().(type) { | ||||
| 			case redis.Message: | ||||
| 				fmt.Printf("Message: %s %s\n", n.Channel, n.Data) | ||||
| 			case redis.PMessage: | ||||
| 				fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data) | ||||
| 			case redis.Subscription: | ||||
| 				fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count) | ||||
| 				if n.Count == 0 { | ||||
| 					return | ||||
| 				} | ||||
| 			case error: | ||||
| 				fmt.Printf("error: %v\n", n) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// This goroutine manages subscriptions for the connection. | ||||
| 	go func() { | ||||
| 		defer wg.Done() | ||||
|  | ||||
| 		psc.Subscribe("example") | ||||
| 		psc.PSubscribe("p*") | ||||
|  | ||||
| 		// The following function calls publish a message using another | ||||
| 		// connection to the Redis server. | ||||
| 		publish("example", "hello") | ||||
| 		publish("example", "world") | ||||
| 		publish("pexample", "foo") | ||||
| 		publish("pexample", "bar") | ||||
|  | ||||
| 		// Unsubscribe from all connections. This will cause the receiving | ||||
| 		// goroutine to exit. | ||||
| 		psc.Unsubscribe() | ||||
| 		psc.PUnsubscribe() | ||||
| 	}() | ||||
|  | ||||
| 	wg.Wait() | ||||
|  | ||||
| 	// Output: | ||||
| 	// Subscription: subscribe example 1 | ||||
| 	// Subscription: psubscribe p* 2 | ||||
| 	// Message: example hello | ||||
| 	// Message: example world | ||||
| 	// PMessage: p* pexample foo | ||||
| 	// PMessage: p* pexample bar | ||||
| 	// Subscription: unsubscribe example 1 | ||||
| 	// Subscription: punsubscribe p* 0 | ||||
| } | ||||
|  | ||||
| func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) { | ||||
| 	actual := c.Receive() | ||||
| 	if !reflect.DeepEqual(actual, expected) { | ||||
| 		t.Errorf("%s = %v, want %v", message, actual, expected) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPushed(t *testing.T) { | ||||
| 	pc, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer pc.Close() | ||||
|  | ||||
| 	nc, err := net.Dial("tcp", ":6379") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer nc.Close() | ||||
| 	nc.SetReadDeadline(time.Now().Add(4 * time.Second)) | ||||
|  | ||||
| 	c := redis.PubSubConn{Conn: redis.NewConn(nc, 0, 0)} | ||||
|  | ||||
| 	c.Subscribe("c1") | ||||
| 	expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}) | ||||
| 	c.Subscribe("c2") | ||||
| 	expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2}) | ||||
| 	c.PSubscribe("p1") | ||||
| 	expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3}) | ||||
| 	c.PSubscribe("p2") | ||||
| 	expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4}) | ||||
| 	c.PUnsubscribe() | ||||
| 	expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3}) | ||||
| 	expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2}) | ||||
|  | ||||
| 	pc.Do("PUBLISH", "c1", "hello") | ||||
| 	expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")}) | ||||
| } | ||||
							
								
								
									
										44
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| // Error represents an error returned in a command reply. | ||||
| type Error string | ||||
|  | ||||
| func (err Error) Error() string { return string(err) } | ||||
|  | ||||
| // Conn represents a connection to a Redis server. | ||||
| type Conn interface { | ||||
| 	// Close closes the connection. | ||||
| 	Close() error | ||||
|  | ||||
| 	// Err returns a non-nil value if the connection is broken. The returned | ||||
| 	// value is either the first non-nil value returned from the underlying | ||||
| 	// network connection or a protocol parsing error. Applications should | ||||
| 	// close broken connections. | ||||
| 	Err() error | ||||
|  | ||||
| 	// Do sends a command to the server and returns the received reply. | ||||
| 	Do(commandName string, args ...interface{}) (reply interface{}, err error) | ||||
|  | ||||
| 	// Send writes the command to the client's output buffer. | ||||
| 	Send(commandName string, args ...interface{}) error | ||||
|  | ||||
| 	// Flush flushes the output buffer to the Redis server. | ||||
| 	Flush() error | ||||
|  | ||||
| 	// Receive receives a single reply from the Redis server | ||||
| 	Receive() (reply interface{}, err error) | ||||
| } | ||||
							
								
								
									
										312
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,312 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // ErrNil indicates that a reply value is nil. | ||||
| var ErrNil = errors.New("redigo: nil returned") | ||||
|  | ||||
| // Int is a helper that converts a command reply to an integer. If err is not | ||||
| // equal to nil, then Int returns 0, err. Otherwise, Int converts the | ||||
| // reply to an int as follows: | ||||
| // | ||||
| //  Reply type    Result | ||||
| //  integer       int(reply), nil | ||||
| //  bulk string   parsed reply, nil | ||||
| //  nil           0, ErrNil | ||||
| //  other         0, error | ||||
| func Int(reply interface{}, err error) (int, error) { | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case int64: | ||||
| 		x := int(reply) | ||||
| 		if int64(x) != reply { | ||||
| 			return 0, strconv.ErrRange | ||||
| 		} | ||||
| 		return x, nil | ||||
| 	case []byte: | ||||
| 		n, err := strconv.ParseInt(string(reply), 10, 0) | ||||
| 		return int(n), err | ||||
| 	case nil: | ||||
| 		return 0, ErrNil | ||||
| 	case Error: | ||||
| 		return 0, reply | ||||
| 	} | ||||
| 	return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // Int64 is a helper that converts a command reply to 64 bit integer. If err is | ||||
| // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the | ||||
| // reply to an int64 as follows: | ||||
| // | ||||
| //  Reply type    Result | ||||
| //  integer       reply, nil | ||||
| //  bulk string   parsed reply, nil | ||||
| //  nil           0, ErrNil | ||||
| //  other         0, error | ||||
| func Int64(reply interface{}, err error) (int64, error) { | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case int64: | ||||
| 		return reply, nil | ||||
| 	case []byte: | ||||
| 		n, err := strconv.ParseInt(string(reply), 10, 64) | ||||
| 		return n, err | ||||
| 	case nil: | ||||
| 		return 0, ErrNil | ||||
| 	case Error: | ||||
| 		return 0, reply | ||||
| 	} | ||||
| 	return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) | ||||
| } | ||||
|  | ||||
| var errNegativeInt = errors.New("redigo: unexpected value for Uint64") | ||||
|  | ||||
| // Uint64 is a helper that converts a command reply to 64 bit integer. If err is | ||||
| // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the | ||||
| // reply to an int64 as follows: | ||||
| // | ||||
| //  Reply type    Result | ||||
| //  integer       reply, nil | ||||
| //  bulk string   parsed reply, nil | ||||
| //  nil           0, ErrNil | ||||
| //  other         0, error | ||||
| func Uint64(reply interface{}, err error) (uint64, error) { | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case int64: | ||||
| 		if reply < 0 { | ||||
| 			return 0, errNegativeInt | ||||
| 		} | ||||
| 		return uint64(reply), nil | ||||
| 	case []byte: | ||||
| 		n, err := strconv.ParseUint(string(reply), 10, 64) | ||||
| 		return n, err | ||||
| 	case nil: | ||||
| 		return 0, ErrNil | ||||
| 	case Error: | ||||
| 		return 0, reply | ||||
| 	} | ||||
| 	return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // Float64 is a helper that converts a command reply to 64 bit float. If err is | ||||
| // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts | ||||
| // the reply to an int as follows: | ||||
| // | ||||
| //  Reply type    Result | ||||
| //  bulk string   parsed reply, nil | ||||
| //  nil           0, ErrNil | ||||
| //  other         0, error | ||||
| func Float64(reply interface{}, err error) (float64, error) { | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case []byte: | ||||
| 		n, err := strconv.ParseFloat(string(reply), 64) | ||||
| 		return n, err | ||||
| 	case nil: | ||||
| 		return 0, ErrNil | ||||
| 	case Error: | ||||
| 		return 0, reply | ||||
| 	} | ||||
| 	return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // String is a helper that converts a command reply to a string. If err is not | ||||
| // equal to nil, then String returns "", err. Otherwise String converts the | ||||
| // reply to a string as follows: | ||||
| // | ||||
| //  Reply type      Result | ||||
| //  bulk string     string(reply), nil | ||||
| //  simple string   reply, nil | ||||
| //  nil             "",  ErrNil | ||||
| //  other           "",  error | ||||
| func String(reply interface{}, err error) (string, error) { | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case []byte: | ||||
| 		return string(reply), nil | ||||
| 	case string: | ||||
| 		return reply, nil | ||||
| 	case nil: | ||||
| 		return "", ErrNil | ||||
| 	case Error: | ||||
| 		return "", reply | ||||
| 	} | ||||
| 	return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // Bytes is a helper that converts a command reply to a slice of bytes. If err | ||||
| // is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts | ||||
| // the reply to a slice of bytes as follows: | ||||
| // | ||||
| //  Reply type      Result | ||||
| //  bulk string     reply, nil | ||||
| //  simple string   []byte(reply), nil | ||||
| //  nil             nil, ErrNil | ||||
| //  other           nil, error | ||||
| func Bytes(reply interface{}, err error) ([]byte, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case []byte: | ||||
| 		return reply, nil | ||||
| 	case string: | ||||
| 		return []byte(reply), nil | ||||
| 	case nil: | ||||
| 		return nil, ErrNil | ||||
| 	case Error: | ||||
| 		return nil, reply | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // Bool is a helper that converts a command reply to a boolean. If err is not | ||||
| // equal to nil, then Bool returns false, err. Otherwise Bool converts the | ||||
| // reply to boolean as follows: | ||||
| // | ||||
| //  Reply type      Result | ||||
| //  integer         value != 0, nil | ||||
| //  bulk string     strconv.ParseBool(reply) | ||||
| //  nil             false, ErrNil | ||||
| //  other           false, error | ||||
| func Bool(reply interface{}, err error) (bool, error) { | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case int64: | ||||
| 		return reply != 0, nil | ||||
| 	case []byte: | ||||
| 		return strconv.ParseBool(string(reply)) | ||||
| 	case nil: | ||||
| 		return false, ErrNil | ||||
| 	case Error: | ||||
| 		return false, reply | ||||
| 	} | ||||
| 	return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // MultiBulk is deprecated. Use Values. | ||||
| func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } | ||||
|  | ||||
| // Values is a helper that converts an array command reply to a []interface{}. | ||||
| // If err is not equal to nil, then Values returns nil, err. Otherwise, Values | ||||
| // converts the reply as follows: | ||||
| // | ||||
| //  Reply type      Result | ||||
| //  array           reply, nil | ||||
| //  nil             nil, ErrNil | ||||
| //  other           nil, error | ||||
| func Values(reply interface{}, err error) ([]interface{}, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case []interface{}: | ||||
| 		return reply, nil | ||||
| 	case nil: | ||||
| 		return nil, ErrNil | ||||
| 	case Error: | ||||
| 		return nil, reply | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // Strings is a helper that converts an array command reply to a []string. If | ||||
| // err is not equal to nil, then Strings returns nil, err. Nil array items are | ||||
| // converted to "" in the output slice. Strings returns an error if an array | ||||
| // item is not a bulk string or nil. | ||||
| func Strings(reply interface{}, err error) ([]string, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	switch reply := reply.(type) { | ||||
| 	case []interface{}: | ||||
| 		result := make([]string, len(reply)) | ||||
| 		for i := range reply { | ||||
| 			if reply[i] == nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			p, ok := reply[i].([]byte) | ||||
| 			if !ok { | ||||
| 				return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) | ||||
| 			} | ||||
| 			result[i] = string(p) | ||||
| 		} | ||||
| 		return result, nil | ||||
| 	case nil: | ||||
| 		return nil, ErrNil | ||||
| 	case Error: | ||||
| 		return nil, reply | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) | ||||
| } | ||||
|  | ||||
| // Ints is a helper that converts an array command reply to a []int. If | ||||
| // err is not equal to nil, then Ints returns nil, err. | ||||
| func Ints(reply interface{}, err error) ([]int, error) { | ||||
| 	var ints []int | ||||
| 	if reply == nil { | ||||
| 		return ints, ErrNil | ||||
| 	} | ||||
| 	values, err := Values(reply, err) | ||||
| 	if err != nil { | ||||
| 		return ints, err | ||||
| 	} | ||||
| 	if err := ScanSlice(values, &ints); err != nil { | ||||
| 		return ints, err | ||||
| 	} | ||||
| 	return ints, nil | ||||
| } | ||||
|  | ||||
| // StringMap is a helper that converts an array of strings (alternating key, value) | ||||
| // into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. | ||||
| // Requires an even number of values in result. | ||||
| func StringMap(result interface{}, err error) (map[string]string, error) { | ||||
| 	values, err := Values(result, err) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if len(values)%2 != 0 { | ||||
| 		return nil, errors.New("redigo: StringMap expects even number of values result") | ||||
| 	} | ||||
| 	m := make(map[string]string, len(values)/2) | ||||
| 	for i := 0; i < len(values); i += 2 { | ||||
| 		key, okKey := values[i].([]byte) | ||||
| 		value, okValue := values[i+1].([]byte) | ||||
| 		if !okKey || !okValue { | ||||
| 			return nil, errors.New("redigo: ScanMap key not a bulk string value") | ||||
| 		} | ||||
| 		m[string(key)] = string(value) | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
							
								
								
									
										166
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis_test | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/internal/redistest" | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| type valueError struct { | ||||
| 	v   interface{} | ||||
| 	err error | ||||
| } | ||||
|  | ||||
| func ve(v interface{}, err error) valueError { | ||||
| 	return valueError{v, err} | ||||
| } | ||||
|  | ||||
| var replyTests = []struct { | ||||
| 	name     interface{} | ||||
| 	actual   valueError | ||||
| 	expected valueError | ||||
| }{ | ||||
| 	{ | ||||
| 		"ints([v1, v2])", | ||||
| 		ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)), | ||||
| 		ve([]int{4, 5}, nil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"ints(nil)", | ||||
| 		ve(redis.Ints(nil, nil)), | ||||
| 		ve([]int(nil), redis.ErrNil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"strings([v1, v2])", | ||||
| 		ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)), | ||||
| 		ve([]string{"v1", "v2"}, nil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"strings(nil)", | ||||
| 		ve(redis.Strings(nil, nil)), | ||||
| 		ve([]string(nil), redis.ErrNil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"values([v1, v2])", | ||||
| 		ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)), | ||||
| 		ve([]interface{}{[]byte("v1"), []byte("v2")}, nil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"values(nil)", | ||||
| 		ve(redis.Values(nil, nil)), | ||||
| 		ve([]interface{}(nil), redis.ErrNil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"float64(1.0)", | ||||
| 		ve(redis.Float64([]byte("1.0"), nil)), | ||||
| 		ve(float64(1.0), nil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"float64(nil)", | ||||
| 		ve(redis.Float64(nil, nil)), | ||||
| 		ve(float64(0.0), redis.ErrNil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"uint64(1)", | ||||
| 		ve(redis.Uint64(int64(1), nil)), | ||||
| 		ve(uint64(1), nil), | ||||
| 	}, | ||||
| 	{ | ||||
| 		"uint64(-1)", | ||||
| 		ve(redis.Uint64(int64(-1), nil)), | ||||
| 		ve(uint64(0), redis.ErrNegativeInt), | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestReply(t *testing.T) { | ||||
| 	for _, rt := range replyTests { | ||||
| 		if rt.actual.err != rt.expected.err { | ||||
| 			t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(rt.actual.v, rt.expected.v) { | ||||
| 			t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // dial wraps DialTestDB() with a more suitable function name for examples. | ||||
| func dial() (redis.Conn, error) { | ||||
| 	return redistest.Dial() | ||||
| } | ||||
|  | ||||
| func ExampleBool() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	c.Do("SET", "foo", 1) | ||||
| 	exists, _ := redis.Bool(c.Do("EXISTS", "foo")) | ||||
| 	fmt.Printf("%#v\n", exists) | ||||
| 	// Output: | ||||
| 	// true | ||||
| } | ||||
|  | ||||
| func ExampleInt() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	c.Do("SET", "k1", 1) | ||||
| 	n, _ := redis.Int(c.Do("GET", "k1")) | ||||
| 	fmt.Printf("%#v\n", n) | ||||
| 	n, _ = redis.Int(c.Do("INCR", "k1")) | ||||
| 	fmt.Printf("%#v\n", n) | ||||
| 	// Output: | ||||
| 	// 1 | ||||
| 	// 2 | ||||
| } | ||||
|  | ||||
| func ExampleInts() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	c.Do("SADD", "set_with_integers", 4, 5, 6) | ||||
| 	ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers")) | ||||
| 	fmt.Printf("%#v\n", ints) | ||||
| 	// Output: | ||||
| 	// []int{4, 5, 6} | ||||
| } | ||||
|  | ||||
| func ExampleString() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	c.Do("SET", "hello", "world") | ||||
| 	s, err := redis.String(c.Do("GET", "hello")) | ||||
| 	fmt.Printf("%#v\n", s) | ||||
| 	// Output: | ||||
| 	// "world" | ||||
| } | ||||
							
								
								
									
										513
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,513 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| func ensureLen(d reflect.Value, n int) { | ||||
| 	if n > d.Cap() { | ||||
| 		d.Set(reflect.MakeSlice(d.Type(), n, n)) | ||||
| 	} else { | ||||
| 		d.SetLen(n) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func cannotConvert(d reflect.Value, s interface{}) error { | ||||
| 	return fmt.Errorf("redigo: Scan cannot convert from %s to %s", | ||||
| 		reflect.TypeOf(s), d.Type()) | ||||
| } | ||||
|  | ||||
| func convertAssignBytes(d reflect.Value, s []byte) (err error) { | ||||
| 	switch d.Type().Kind() { | ||||
| 	case reflect.Float32, reflect.Float64: | ||||
| 		var x float64 | ||||
| 		x, err = strconv.ParseFloat(string(s), d.Type().Bits()) | ||||
| 		d.SetFloat(x) | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||||
| 		var x int64 | ||||
| 		x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) | ||||
| 		d.SetInt(x) | ||||
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | ||||
| 		var x uint64 | ||||
| 		x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) | ||||
| 		d.SetUint(x) | ||||
| 	case reflect.Bool: | ||||
| 		var x bool | ||||
| 		x, err = strconv.ParseBool(string(s)) | ||||
| 		d.SetBool(x) | ||||
| 	case reflect.String: | ||||
| 		d.SetString(string(s)) | ||||
| 	case reflect.Slice: | ||||
| 		if d.Type().Elem().Kind() != reflect.Uint8 { | ||||
| 			err = cannotConvert(d, s) | ||||
| 		} else { | ||||
| 			d.SetBytes(s) | ||||
| 		} | ||||
| 	default: | ||||
| 		err = cannotConvert(d, s) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func convertAssignInt(d reflect.Value, s int64) (err error) { | ||||
| 	switch d.Type().Kind() { | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||||
| 		d.SetInt(s) | ||||
| 		if d.Int() != s { | ||||
| 			err = strconv.ErrRange | ||||
| 			d.SetInt(0) | ||||
| 		} | ||||
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | ||||
| 		if s < 0 { | ||||
| 			err = strconv.ErrRange | ||||
| 		} else { | ||||
| 			x := uint64(s) | ||||
| 			d.SetUint(x) | ||||
| 			if d.Uint() != x { | ||||
| 				err = strconv.ErrRange | ||||
| 				d.SetUint(0) | ||||
| 			} | ||||
| 		} | ||||
| 	case reflect.Bool: | ||||
| 		d.SetBool(s != 0) | ||||
| 	default: | ||||
| 		err = cannotConvert(d, s) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func convertAssignValue(d reflect.Value, s interface{}) (err error) { | ||||
| 	switch s := s.(type) { | ||||
| 	case []byte: | ||||
| 		err = convertAssignBytes(d, s) | ||||
| 	case int64: | ||||
| 		err = convertAssignInt(d, s) | ||||
| 	default: | ||||
| 		err = cannotConvert(d, s) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func convertAssignValues(d reflect.Value, s []interface{}) error { | ||||
| 	if d.Type().Kind() != reflect.Slice { | ||||
| 		return cannotConvert(d, s) | ||||
| 	} | ||||
| 	ensureLen(d, len(s)) | ||||
| 	for i := 0; i < len(s); i++ { | ||||
| 		if err := convertAssignValue(d.Index(i), s[i]); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func convertAssign(d interface{}, s interface{}) (err error) { | ||||
| 	// Handle the most common destination types using type switches and | ||||
| 	// fall back to reflection for all other types. | ||||
| 	switch s := s.(type) { | ||||
| 	case nil: | ||||
| 		// ingore | ||||
| 	case []byte: | ||||
| 		switch d := d.(type) { | ||||
| 		case *string: | ||||
| 			*d = string(s) | ||||
| 		case *int: | ||||
| 			*d, err = strconv.Atoi(string(s)) | ||||
| 		case *bool: | ||||
| 			*d, err = strconv.ParseBool(string(s)) | ||||
| 		case *[]byte: | ||||
| 			*d = s | ||||
| 		case *interface{}: | ||||
| 			*d = s | ||||
| 		case nil: | ||||
| 			// skip value | ||||
| 		default: | ||||
| 			if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { | ||||
| 				err = cannotConvert(d, s) | ||||
| 			} else { | ||||
| 				err = convertAssignBytes(d.Elem(), s) | ||||
| 			} | ||||
| 		} | ||||
| 	case int64: | ||||
| 		switch d := d.(type) { | ||||
| 		case *int: | ||||
| 			x := int(s) | ||||
| 			if int64(x) != s { | ||||
| 				err = strconv.ErrRange | ||||
| 				x = 0 | ||||
| 			} | ||||
| 			*d = x | ||||
| 		case *bool: | ||||
| 			*d = s != 0 | ||||
| 		case *interface{}: | ||||
| 			*d = s | ||||
| 		case nil: | ||||
| 			// skip value | ||||
| 		default: | ||||
| 			if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { | ||||
| 				err = cannotConvert(d, s) | ||||
| 			} else { | ||||
| 				err = convertAssignInt(d.Elem(), s) | ||||
| 			} | ||||
| 		} | ||||
| 	case []interface{}: | ||||
| 		switch d := d.(type) { | ||||
| 		case *[]interface{}: | ||||
| 			*d = s | ||||
| 		case *interface{}: | ||||
| 			*d = s | ||||
| 		case nil: | ||||
| 			// skip value | ||||
| 		default: | ||||
| 			if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { | ||||
| 				err = cannotConvert(d, s) | ||||
| 			} else { | ||||
| 				err = convertAssignValues(d.Elem(), s) | ||||
| 			} | ||||
| 		} | ||||
| 	case Error: | ||||
| 		err = s | ||||
| 	default: | ||||
| 		err = cannotConvert(reflect.ValueOf(d), s) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Scan copies from src to the values pointed at by dest. | ||||
| // | ||||
| // The values pointed at by dest must be an integer, float, boolean, string, | ||||
| // []byte, interface{} or slices of these types. Scan uses the standard strconv | ||||
| // package to convert bulk strings to numeric and boolean types. | ||||
| // | ||||
| // If a dest value is nil, then the corresponding src value is skipped. | ||||
| // | ||||
| // If a src element is nil, then the corresponding dest value is not modified. | ||||
| // | ||||
| // To enable easy use of Scan in a loop, Scan returns the slice of src | ||||
| // following the copied values. | ||||
| func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { | ||||
| 	if len(src) < len(dest) { | ||||
| 		return nil, errors.New("redigo: Scan array short") | ||||
| 	} | ||||
| 	var err error | ||||
| 	for i, d := range dest { | ||||
| 		err = convertAssign(d, src[i]) | ||||
| 		if err != nil { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return src[len(dest):], err | ||||
| } | ||||
|  | ||||
| type fieldSpec struct { | ||||
| 	name  string | ||||
| 	index []int | ||||
| 	//omitEmpty bool | ||||
| } | ||||
|  | ||||
| type structSpec struct { | ||||
| 	m map[string]*fieldSpec | ||||
| 	l []*fieldSpec | ||||
| } | ||||
|  | ||||
| func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { | ||||
| 	return ss.m[string(name)] | ||||
| } | ||||
|  | ||||
| func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { | ||||
| 	for i := 0; i < t.NumField(); i++ { | ||||
| 		f := t.Field(i) | ||||
| 		switch { | ||||
| 		case f.PkgPath != "": | ||||
| 			// Ignore unexported fields. | ||||
| 		case f.Anonymous: | ||||
| 			// TODO: Handle pointers. Requires change to decoder and | ||||
| 			// protection against infinite recursion. | ||||
| 			if f.Type.Kind() == reflect.Struct { | ||||
| 				compileStructSpec(f.Type, depth, append(index, i), ss) | ||||
| 			} | ||||
| 		default: | ||||
| 			fs := &fieldSpec{name: f.Name} | ||||
| 			tag := f.Tag.Get("redis") | ||||
| 			p := strings.Split(tag, ",") | ||||
| 			if len(p) > 0 { | ||||
| 				if p[0] == "-" { | ||||
| 					continue | ||||
| 				} | ||||
| 				if len(p[0]) > 0 { | ||||
| 					fs.name = p[0] | ||||
| 				} | ||||
| 				for _, s := range p[1:] { | ||||
| 					switch s { | ||||
| 					//case "omitempty": | ||||
| 					//  fs.omitempty = true | ||||
| 					default: | ||||
| 						panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name())) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			d, found := depth[fs.name] | ||||
| 			if !found { | ||||
| 				d = 1 << 30 | ||||
| 			} | ||||
| 			switch { | ||||
| 			case len(index) == d: | ||||
| 				// At same depth, remove from result. | ||||
| 				delete(ss.m, fs.name) | ||||
| 				j := 0 | ||||
| 				for i := 0; i < len(ss.l); i++ { | ||||
| 					if fs.name != ss.l[i].name { | ||||
| 						ss.l[j] = ss.l[i] | ||||
| 						j += 1 | ||||
| 					} | ||||
| 				} | ||||
| 				ss.l = ss.l[:j] | ||||
| 			case len(index) < d: | ||||
| 				fs.index = make([]int, len(index)+1) | ||||
| 				copy(fs.index, index) | ||||
| 				fs.index[len(index)] = i | ||||
| 				depth[fs.name] = len(index) | ||||
| 				ss.m[fs.name] = fs | ||||
| 				ss.l = append(ss.l, fs) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	structSpecMutex  sync.RWMutex | ||||
| 	structSpecCache  = make(map[reflect.Type]*structSpec) | ||||
| 	defaultFieldSpec = &fieldSpec{} | ||||
| ) | ||||
|  | ||||
| func structSpecForType(t reflect.Type) *structSpec { | ||||
|  | ||||
| 	structSpecMutex.RLock() | ||||
| 	ss, found := structSpecCache[t] | ||||
| 	structSpecMutex.RUnlock() | ||||
| 	if found { | ||||
| 		return ss | ||||
| 	} | ||||
|  | ||||
| 	structSpecMutex.Lock() | ||||
| 	defer structSpecMutex.Unlock() | ||||
| 	ss, found = structSpecCache[t] | ||||
| 	if found { | ||||
| 		return ss | ||||
| 	} | ||||
|  | ||||
| 	ss = &structSpec{m: make(map[string]*fieldSpec)} | ||||
| 	compileStructSpec(t, make(map[string]int), nil, ss) | ||||
| 	structSpecCache[t] = ss | ||||
| 	return ss | ||||
| } | ||||
|  | ||||
| var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct") | ||||
|  | ||||
| // ScanStruct scans alternating names and values from src to a struct. The | ||||
| // HGETALL and CONFIG GET commands return replies in this format. | ||||
| // | ||||
| // ScanStruct uses exported field names to match values in the response. Use | ||||
| // 'redis' field tag to override the name: | ||||
| // | ||||
| //      Field int `redis:"myName"` | ||||
| // | ||||
| // Fields with the tag redis:"-" are ignored. | ||||
| // | ||||
| // Integer, float, boolean, string and []byte fields are supported. Scan uses the | ||||
| // standard strconv package to convert bulk string values to numeric and | ||||
| // boolean types. | ||||
| // | ||||
| // If a src element is nil, then the corresponding field is not modified. | ||||
| func ScanStruct(src []interface{}, dest interface{}) error { | ||||
| 	d := reflect.ValueOf(dest) | ||||
| 	if d.Kind() != reflect.Ptr || d.IsNil() { | ||||
| 		return errScanStructValue | ||||
| 	} | ||||
| 	d = d.Elem() | ||||
| 	if d.Kind() != reflect.Struct { | ||||
| 		return errScanStructValue | ||||
| 	} | ||||
| 	ss := structSpecForType(d.Type()) | ||||
|  | ||||
| 	if len(src)%2 != 0 { | ||||
| 		return errors.New("redigo: ScanStruct expects even number of values in values") | ||||
| 	} | ||||
|  | ||||
| 	for i := 0; i < len(src); i += 2 { | ||||
| 		s := src[i+1] | ||||
| 		if s == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		name, ok := src[i].([]byte) | ||||
| 		if !ok { | ||||
| 			return errors.New("redigo: ScanStruct key not a bulk string value") | ||||
| 		} | ||||
| 		fs := ss.fieldSpec(name) | ||||
| 		if fs == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct") | ||||
| ) | ||||
|  | ||||
| // ScanSlice scans src to the slice pointed to by dest. The elements the dest | ||||
| // slice must be integer, float, boolean, string, struct or pointer to struct | ||||
| // values. | ||||
| // | ||||
| // Struct fields must be integer, float, boolean or string values. All struct | ||||
| // fields are used unless a subset is specified using fieldNames. | ||||
| func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { | ||||
| 	d := reflect.ValueOf(dest) | ||||
| 	if d.Kind() != reflect.Ptr || d.IsNil() { | ||||
| 		return errScanSliceValue | ||||
| 	} | ||||
| 	d = d.Elem() | ||||
| 	if d.Kind() != reflect.Slice { | ||||
| 		return errScanSliceValue | ||||
| 	} | ||||
|  | ||||
| 	isPtr := false | ||||
| 	t := d.Type().Elem() | ||||
| 	if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { | ||||
| 		isPtr = true | ||||
| 		t = t.Elem() | ||||
| 	} | ||||
|  | ||||
| 	if t.Kind() != reflect.Struct { | ||||
| 		ensureLen(d, len(src)) | ||||
| 		for i, s := range src { | ||||
| 			if s == nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			if err := convertAssignValue(d.Index(i), s); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	ss := structSpecForType(t) | ||||
| 	fss := ss.l | ||||
| 	if len(fieldNames) > 0 { | ||||
| 		fss = make([]*fieldSpec, len(fieldNames)) | ||||
| 		for i, name := range fieldNames { | ||||
| 			fss[i] = ss.m[name] | ||||
| 			if fss[i] == nil { | ||||
| 				return errors.New("redigo: ScanSlice bad field name " + name) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(fss) == 0 { | ||||
| 		return errors.New("redigo: ScanSlice no struct fields") | ||||
| 	} | ||||
|  | ||||
| 	n := len(src) / len(fss) | ||||
| 	if n*len(fss) != len(src) { | ||||
| 		return errors.New("redigo: ScanSlice length not a multiple of struct field count") | ||||
| 	} | ||||
|  | ||||
| 	ensureLen(d, n) | ||||
| 	for i := 0; i < n; i++ { | ||||
| 		d := d.Index(i) | ||||
| 		if isPtr { | ||||
| 			if d.IsNil() { | ||||
| 				d.Set(reflect.New(t)) | ||||
| 			} | ||||
| 			d = d.Elem() | ||||
| 		} | ||||
| 		for j, fs := range fss { | ||||
| 			s := src[i*len(fss)+j] | ||||
| 			if s == nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Args is a helper for constructing command arguments from structured values. | ||||
| type Args []interface{} | ||||
|  | ||||
| // Add returns the result of appending value to args. | ||||
| func (args Args) Add(value ...interface{}) Args { | ||||
| 	return append(args, value...) | ||||
| } | ||||
|  | ||||
| // AddFlat returns the result of appending the flattened value of v to args. | ||||
| // | ||||
| // Maps are flattened by appending the alternating keys and map values to args. | ||||
| // | ||||
| // Slices are flattened by appending the slice elements to args. | ||||
| // | ||||
| // Structs are flattened by appending the alternating names and values of | ||||
| // exported fields to args. If v is a nil struct pointer, then nothing is | ||||
| // appended. The 'redis' field tag overrides struct field names. See ScanStruct | ||||
| // for more information on the use of the 'redis' field tag. | ||||
| // | ||||
| // Other types are appended to args as is. | ||||
| func (args Args) AddFlat(v interface{}) Args { | ||||
| 	rv := reflect.ValueOf(v) | ||||
| 	switch rv.Kind() { | ||||
| 	case reflect.Struct: | ||||
| 		args = flattenStruct(args, rv) | ||||
| 	case reflect.Slice: | ||||
| 		for i := 0; i < rv.Len(); i++ { | ||||
| 			args = append(args, rv.Index(i).Interface()) | ||||
| 		} | ||||
| 	case reflect.Map: | ||||
| 		for _, k := range rv.MapKeys() { | ||||
| 			args = append(args, k.Interface(), rv.MapIndex(k).Interface()) | ||||
| 		} | ||||
| 	case reflect.Ptr: | ||||
| 		if rv.Type().Elem().Kind() == reflect.Struct { | ||||
| 			if !rv.IsNil() { | ||||
| 				args = flattenStruct(args, rv.Elem()) | ||||
| 			} | ||||
| 		} else { | ||||
| 			args = append(args, v) | ||||
| 		} | ||||
| 	default: | ||||
| 		args = append(args, v) | ||||
| 	} | ||||
| 	return args | ||||
| } | ||||
|  | ||||
| func flattenStruct(args Args, v reflect.Value) Args { | ||||
| 	ss := structSpecForType(v.Type()) | ||||
| 	for _, fs := range ss.l { | ||||
| 		fv := v.FieldByIndex(fs.index) | ||||
| 		args = append(args, fs.name, fv.Interface()) | ||||
| 	} | ||||
| 	return args | ||||
| } | ||||
							
								
								
									
										412
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,412 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis_test | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| var scanConversionTests = []struct { | ||||
| 	src  interface{} | ||||
| 	dest interface{} | ||||
| }{ | ||||
| 	{[]byte("-inf"), math.Inf(-1)}, | ||||
| 	{[]byte("+inf"), math.Inf(1)}, | ||||
| 	{[]byte("0"), float64(0)}, | ||||
| 	{[]byte("3.14159"), float64(3.14159)}, | ||||
| 	{[]byte("3.14"), float32(3.14)}, | ||||
| 	{[]byte("-100"), int(-100)}, | ||||
| 	{[]byte("101"), int(101)}, | ||||
| 	{int64(102), int(102)}, | ||||
| 	{[]byte("103"), uint(103)}, | ||||
| 	{int64(104), uint(104)}, | ||||
| 	{[]byte("105"), int8(105)}, | ||||
| 	{int64(106), int8(106)}, | ||||
| 	{[]byte("107"), uint8(107)}, | ||||
| 	{int64(108), uint8(108)}, | ||||
| 	{[]byte("0"), false}, | ||||
| 	{int64(0), false}, | ||||
| 	{[]byte("f"), false}, | ||||
| 	{[]byte("1"), true}, | ||||
| 	{int64(1), true}, | ||||
| 	{[]byte("t"), true}, | ||||
| 	{[]byte("hello"), "hello"}, | ||||
| 	{[]byte("world"), []byte("world")}, | ||||
| 	{[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}}, | ||||
| 	{[]interface{}{[]byte("foo")}, []string{"foo"}}, | ||||
| 	{[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}}, | ||||
| 	{[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}}, | ||||
| 	{[]interface{}{[]byte("1")}, []int{1}}, | ||||
| 	{[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}}, | ||||
| 	{[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}}, | ||||
| 	{[]interface{}{[]byte("1")}, []byte{1}}, | ||||
| 	{[]interface{}{[]byte("1")}, []bool{true}}, | ||||
| } | ||||
|  | ||||
| func TestScanConversion(t *testing.T) { | ||||
| 	for _, tt := range scanConversionTests { | ||||
| 		values := []interface{}{tt.src} | ||||
| 		dest := reflect.New(reflect.TypeOf(tt.dest)) | ||||
| 		values, err := redis.Scan(values, dest.Interface()) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Scan(%v) returned error %v", tt, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) { | ||||
| 			t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var scanConversionErrorTests = []struct { | ||||
| 	src  interface{} | ||||
| 	dest interface{} | ||||
| }{ | ||||
| 	{[]byte("1234"), byte(0)}, | ||||
| 	{int64(1234), byte(0)}, | ||||
| 	{[]byte("-1"), byte(0)}, | ||||
| 	{int64(-1), byte(0)}, | ||||
| 	{[]byte("junk"), false}, | ||||
| 	{redis.Error("blah"), false}, | ||||
| } | ||||
|  | ||||
| func TestScanConversionError(t *testing.T) { | ||||
| 	for _, tt := range scanConversionErrorTests { | ||||
| 		values := []interface{}{tt.src} | ||||
| 		dest := reflect.New(reflect.TypeOf(tt.dest)) | ||||
| 		values, err := redis.Scan(values, dest.Interface()) | ||||
| 		if err == nil { | ||||
| 			t.Errorf("Scan(%v) did not return error", tt) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ExampleScan() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	c.Send("HMSET", "album:1", "title", "Red", "rating", 5) | ||||
| 	c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) | ||||
| 	c.Send("HMSET", "album:3", "title", "Beat") | ||||
| 	c.Send("LPUSH", "albums", "1") | ||||
| 	c.Send("LPUSH", "albums", "2") | ||||
| 	c.Send("LPUSH", "albums", "3") | ||||
| 	values, err := redis.Values(c.Do("SORT", "albums", | ||||
| 		"BY", "album:*->rating", | ||||
| 		"GET", "album:*->title", | ||||
| 		"GET", "album:*->rating")) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	for len(values) > 0 { | ||||
| 		var title string | ||||
| 		rating := -1 // initialize to illegal value to detect nil. | ||||
| 		values, err = redis.Scan(values, &title, &rating) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		if rating == -1 { | ||||
| 			fmt.Println(title, "not-rated") | ||||
| 		} else { | ||||
| 			fmt.Println(title, rating) | ||||
| 		} | ||||
| 	} | ||||
| 	// Output: | ||||
| 	// Beat not-rated | ||||
| 	// Earthbound 1 | ||||
| 	// Red 5 | ||||
| } | ||||
|  | ||||
| type s0 struct { | ||||
| 	X  int | ||||
| 	Y  int `redis:"y"` | ||||
| 	Bt bool | ||||
| } | ||||
|  | ||||
| type s1 struct { | ||||
| 	X  int    `redis:"-"` | ||||
| 	I  int    `redis:"i"` | ||||
| 	U  uint   `redis:"u"` | ||||
| 	S  string `redis:"s"` | ||||
| 	P  []byte `redis:"p"` | ||||
| 	B  bool   `redis:"b"` | ||||
| 	Bt bool | ||||
| 	Bf bool | ||||
| 	s0 | ||||
| } | ||||
|  | ||||
| var scanStructTests = []struct { | ||||
| 	title string | ||||
| 	reply []string | ||||
| 	value interface{} | ||||
| }{ | ||||
| 	{"basic", | ||||
| 		[]string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"}, | ||||
| 		&s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}}, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestScanStruct(t *testing.T) { | ||||
| 	for _, tt := range scanStructTests { | ||||
|  | ||||
| 		var reply []interface{} | ||||
| 		for _, v := range tt.reply { | ||||
| 			reply = append(reply, []byte(v)) | ||||
| 		} | ||||
|  | ||||
| 		value := reflect.New(reflect.ValueOf(tt.value).Type().Elem()) | ||||
|  | ||||
| 		if err := redis.ScanStruct(reply, value.Interface()); err != nil { | ||||
| 			t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err) | ||||
| 		} | ||||
|  | ||||
| 		if !reflect.DeepEqual(value.Interface(), tt.value) { | ||||
| 			t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestBadScanStructArgs(t *testing.T) { | ||||
| 	x := []interface{}{"A", "b"} | ||||
| 	test := func(v interface{}) { | ||||
| 		if err := redis.ScanStruct(x, v); err == nil { | ||||
| 			t.Errorf("Expect error for ScanStruct(%T, %T)", x, v) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	test(nil) | ||||
|  | ||||
| 	var v0 *struct{} | ||||
| 	test(v0) | ||||
|  | ||||
| 	var v1 int | ||||
| 	test(&v1) | ||||
|  | ||||
| 	x = x[:1] | ||||
| 	v2 := struct{ A string }{} | ||||
| 	test(&v2) | ||||
| } | ||||
|  | ||||
| var scanSliceTests = []struct { | ||||
| 	src        []interface{} | ||||
| 	fieldNames []string | ||||
| 	ok         bool | ||||
| 	dest       interface{} | ||||
| }{ | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("1"), nil, []byte("-1")}, | ||||
| 		nil, | ||||
| 		true, | ||||
| 		[]int{1, 0, -1}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("1"), nil, []byte("2")}, | ||||
| 		nil, | ||||
| 		true, | ||||
| 		[]uint{1, 0, 2}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("-1")}, | ||||
| 		nil, | ||||
| 		false, | ||||
| 		[]uint{1}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("hello"), nil, []byte("world")}, | ||||
| 		nil, | ||||
| 		true, | ||||
| 		[][]byte{[]byte("hello"), nil, []byte("world")}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("hello"), nil, []byte("world")}, | ||||
| 		nil, | ||||
| 		true, | ||||
| 		[]string{"hello", "", "world"}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | ||||
| 		nil, | ||||
| 		true, | ||||
| 		[]struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("a1"), []byte("b1")}, | ||||
| 		nil, | ||||
| 		false, | ||||
| 		[]struct{ A, B, C string }{{"a1", "b1", ""}}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | ||||
| 		nil, | ||||
| 		true, | ||||
| 		[]*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | ||||
| 		[]string{"A", "B"}, | ||||
| 		true, | ||||
| 		[]struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}}, | ||||
| 	}, | ||||
| 	{ | ||||
| 		[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | ||||
| 		nil, | ||||
| 		false, | ||||
| 		[]struct{}{}, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestScanSlice(t *testing.T) { | ||||
| 	for _, tt := range scanSliceTests { | ||||
|  | ||||
| 		typ := reflect.ValueOf(tt.dest).Type() | ||||
| 		dest := reflect.New(typ) | ||||
|  | ||||
| 		err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...) | ||||
| 		if tt.ok != (err == nil) { | ||||
| 			t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) { | ||||
| 			t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ExampleScanSlice() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	c.Send("HMSET", "album:1", "title", "Red", "rating", 5) | ||||
| 	c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) | ||||
| 	c.Send("HMSET", "album:3", "title", "Beat", "rating", 4) | ||||
| 	c.Send("LPUSH", "albums", "1") | ||||
| 	c.Send("LPUSH", "albums", "2") | ||||
| 	c.Send("LPUSH", "albums", "3") | ||||
| 	values, err := redis.Values(c.Do("SORT", "albums", | ||||
| 		"BY", "album:*->rating", | ||||
| 		"GET", "album:*->title", | ||||
| 		"GET", "album:*->rating")) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	var albums []struct { | ||||
| 		Title  string | ||||
| 		Rating int | ||||
| 	} | ||||
| 	if err := redis.ScanSlice(values, &albums); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	fmt.Printf("%v\n", albums) | ||||
| 	// Output: | ||||
| 	// [{Earthbound 1} {Beat 4} {Red 5}] | ||||
| } | ||||
|  | ||||
| var argsTests = []struct { | ||||
| 	title    string | ||||
| 	actual   redis.Args | ||||
| 	expected redis.Args | ||||
| }{ | ||||
| 	{"struct ptr", | ||||
| 		redis.Args{}.AddFlat(&struct { | ||||
| 			I  int    `redis:"i"` | ||||
| 			U  uint   `redis:"u"` | ||||
| 			S  string `redis:"s"` | ||||
| 			P  []byte `redis:"p"` | ||||
| 			Bt bool | ||||
| 			Bf bool | ||||
| 		}{ | ||||
| 			-1234, 5678, "hello", []byte("world"), true, false, | ||||
| 		}), | ||||
| 		redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "Bt", true, "Bf", false}, | ||||
| 	}, | ||||
| 	{"struct", | ||||
| 		redis.Args{}.AddFlat(struct{ I int }{123}), | ||||
| 		redis.Args{"I", 123}, | ||||
| 	}, | ||||
| 	{"slice", | ||||
| 		redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2), | ||||
| 		redis.Args{1, "a", "b", "c", 2}, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestArgs(t *testing.T) { | ||||
| 	for _, tt := range argsTests { | ||||
| 		if !reflect.DeepEqual(tt.actual, tt.expected) { | ||||
| 			t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ExampleArgs() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	var p1, p2 struct { | ||||
| 		Title  string `redis:"title"` | ||||
| 		Author string `redis:"author"` | ||||
| 		Body   string `redis:"body"` | ||||
| 	} | ||||
|  | ||||
| 	p1.Title = "Example" | ||||
| 	p1.Author = "Gary" | ||||
| 	p1.Body = "Hello" | ||||
|  | ||||
| 	if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	m := map[string]string{ | ||||
| 		"title":  "Example2", | ||||
| 		"author": "Steve", | ||||
| 		"body":   "Map", | ||||
| 	} | ||||
|  | ||||
| 	if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	for _, id := range []string{"id1", "id2"} { | ||||
|  | ||||
| 		v, err := redis.Values(c.Do("HGETALL", id)) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | ||||
| 		if err := redis.ScanStruct(v, &p2); err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
|  | ||||
| 		fmt.Printf("%+v\n", p2) | ||||
| 	} | ||||
|  | ||||
| 	// Output: | ||||
| 	// {Title:Example Author:Gary Body:Hello} | ||||
| 	// {Title:Example2 Author:Steve Body:Map} | ||||
| } | ||||
							
								
								
									
										86
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"crypto/sha1" | ||||
| 	"encoding/hex" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Script encapsulates the source, hash and key count for a Lua script. See | ||||
| // http://redis.io/commands/eval for information on scripts in Redis. | ||||
| type Script struct { | ||||
| 	keyCount int | ||||
| 	src      string | ||||
| 	hash     string | ||||
| } | ||||
|  | ||||
| // NewScript returns a new script object. If keyCount is greater than or equal | ||||
| // to zero, then the count is automatically inserted in the EVAL command | ||||
| // argument list. If keyCount is less than zero, then the application supplies | ||||
| // the count as the first value in the keysAndArgs argument to the Do, Send and | ||||
| // SendHash methods. | ||||
| func NewScript(keyCount int, src string) *Script { | ||||
| 	h := sha1.New() | ||||
| 	io.WriteString(h, src) | ||||
| 	return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} | ||||
| } | ||||
|  | ||||
| func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { | ||||
| 	var args []interface{} | ||||
| 	if s.keyCount < 0 { | ||||
| 		args = make([]interface{}, 1+len(keysAndArgs)) | ||||
| 		args[0] = spec | ||||
| 		copy(args[1:], keysAndArgs) | ||||
| 	} else { | ||||
| 		args = make([]interface{}, 2+len(keysAndArgs)) | ||||
| 		args[0] = spec | ||||
| 		args[1] = s.keyCount | ||||
| 		copy(args[2:], keysAndArgs) | ||||
| 	} | ||||
| 	return args | ||||
| } | ||||
|  | ||||
| // Do evaluates the script. Under the covers, Do optimistically evaluates the | ||||
| // script using the EVALSHA command. If the command fails because the script is | ||||
| // not loaded, then Do evaluates the script using the EVAL command (thus | ||||
| // causing the script to load). | ||||
| func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { | ||||
| 	v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) | ||||
| 	if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { | ||||
| 		v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) | ||||
| 	} | ||||
| 	return v, err | ||||
| } | ||||
|  | ||||
| // SendHash evaluates the script without waiting for the reply. The script is | ||||
| // evaluated with the EVALSHA command. The application must ensure that the | ||||
| // script is loaded by a previous call to Send, Do or Load methods. | ||||
| func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { | ||||
| 	return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) | ||||
| } | ||||
|  | ||||
| // Send evaluates the script without waiting for the reply. | ||||
| func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { | ||||
| 	return c.Send("EVAL", s.args(s.src, keysAndArgs)...) | ||||
| } | ||||
|  | ||||
| // Load loads the script without evaluating it. | ||||
| func (s *Script) Load(c Conn) error { | ||||
| 	_, err := c.Do("SCRIPT", "LOAD", s.src) | ||||
| 	return err | ||||
| } | ||||
							
								
								
									
										93
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis_test | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/internal/redistest" | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| func ExampleScript(c redis.Conn, reply interface{}, err error) { | ||||
| 	// Initialize a package-level variable with a script. | ||||
| 	var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`) | ||||
|  | ||||
| 	// In a function, use the script Do method to evaluate the script. The Do | ||||
| 	// method optimistically uses the EVALSHA command. If the script is not | ||||
| 	// loaded, then the Do method falls back to the EVAL command. | ||||
| 	reply, err = getScript.Do(c, "foo") | ||||
| } | ||||
|  | ||||
| func TestScript(t *testing.T) { | ||||
| 	c, err := redistest.Dial() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("error connection to database, %v", err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	// To test fall back in Do, we make script unique by adding comment with current time. | ||||
| 	script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano()) | ||||
| 	s := redis.NewScript(2, script) | ||||
| 	reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")} | ||||
|  | ||||
| 	v, err := s.Do(c, "key1", "key2", "arg1", "arg2") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("s.Do(c, ...) returned %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if !reflect.DeepEqual(v, reply) { | ||||
| 		t.Errorf("s.Do(c, ..); = %v, want %v", v, reply) | ||||
| 	} | ||||
|  | ||||
| 	err = s.Load(c) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("s.Load(c) returned %v", err) | ||||
| 	} | ||||
|  | ||||
| 	err = s.SendHash(c, "key1", "key2", "arg1", "arg2") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("s.SendHash(c, ...) returned %v", err) | ||||
| 	} | ||||
|  | ||||
| 	err = c.Flush() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("c.Flush() returned %v", err) | ||||
| 	} | ||||
|  | ||||
| 	v, err = c.Receive() | ||||
| 	if !reflect.DeepEqual(v, reply) { | ||||
| 		t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply) | ||||
| 	} | ||||
|  | ||||
| 	err = s.Send(c, "key1", "key2", "arg1", "arg2") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("s.Send(c, ...) returned %v", err) | ||||
| 	} | ||||
|  | ||||
| 	err = c.Flush() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("c.Flush() returned %v", err) | ||||
| 	} | ||||
|  | ||||
| 	v, err = c.Receive() | ||||
| 	if !reflect.DeepEqual(v, reply) { | ||||
| 		t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply) | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										38
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| // Copyright 2012 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"net" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func SetNowFunc(f func() time.Time) { | ||||
| 	nowFunc = f | ||||
| } | ||||
|  | ||||
| type nopCloser struct{ net.Conn } | ||||
|  | ||||
| func (nopCloser) Close() error { return nil } | ||||
|  | ||||
| // NewConnBufio is a hook for tests. | ||||
| func NewConnBufio(rw bufio.ReadWriter) Conn { | ||||
| 	return &conn{br: rw.Reader, bw: rw.Writer, conn: nopCloser{}} | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	ErrNegativeInt = errNegativeInt | ||||
| ) | ||||
							
								
								
									
										113
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| // Copyright 2013 Gary Burd | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | ||||
| // not use this file except in compliance with the License. You may obtain | ||||
| // a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| // License for the specific language governing permissions and limitations | ||||
| // under the License. | ||||
|  | ||||
| package redis_test | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| // zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. | ||||
| func zpop(c redis.Conn, key string) (result string, err error) { | ||||
|  | ||||
| 	defer func() { | ||||
| 		// Return connection to normal state on error. | ||||
| 		if err != nil { | ||||
| 			c.Do("DISCARD") | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// Loop until transaction is successful. | ||||
| 	for { | ||||
| 		if _, err := c.Do("WATCH", key); err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
|  | ||||
| 		members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0)) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if len(members) != 1 { | ||||
| 			return "", redis.ErrNil | ||||
| 		} | ||||
|  | ||||
| 		c.Send("MULTI") | ||||
| 		c.Send("ZREM", key, members[0]) | ||||
| 		queued, err := c.Do("EXEC") | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
|  | ||||
| 		if queued != nil { | ||||
| 			result = members[0] | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // zpopScript pops a value from a ZSET. | ||||
| var zpopScript = redis.NewScript(1, ` | ||||
|     local r = redis.call('ZRANGE', KEYS[1], 0, 0) | ||||
|     if r ~= nil then | ||||
|         r = r[1] | ||||
|         redis.call('ZREM', KEYS[1], r) | ||||
|     end | ||||
|     return r | ||||
| `) | ||||
|  | ||||
| // This example implements ZPOP as described at | ||||
| // http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting. | ||||
| func Example_zpop() { | ||||
| 	c, err := dial() | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer c.Close() | ||||
|  | ||||
| 	// Add test data using a pipeline. | ||||
|  | ||||
| 	for i, member := range []string{"red", "blue", "green"} { | ||||
| 		c.Send("ZADD", "zset", i, member) | ||||
| 	} | ||||
| 	if _, err := c.Do(""); err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Pop using WATCH/MULTI/EXEC | ||||
|  | ||||
| 	v, err := zpop(c, "zset") | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Println(v) | ||||
|  | ||||
| 	// Pop using a script. | ||||
|  | ||||
| 	v, err = redis.String(zpopScript.Do(c, "zset")) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Println(v) | ||||
|  | ||||
| 	// Output: | ||||
| 	// red | ||||
| 	// blue | ||||
| } | ||||
							
								
								
									
										9
									
								
								Godeps/_workspace/src/github.com/gorilla/context/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Godeps/_workspace/src/github.com/gorilla/context/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| language: go | ||||
|  | ||||
| go: | ||||
|   - 1.0 | ||||
|   - 1.1 | ||||
|   - 1.2 | ||||
|   - 1.3 | ||||
|   - 1.4 | ||||
|   - tip | ||||
							
								
								
									
										27
									
								
								Godeps/_workspace/src/github.com/gorilla/context/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Godeps/_workspace/src/github.com/gorilla/context/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| Copyright (c) 2012 Rodrigo Moraes. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
| 	 * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
| 	 * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
| 	 * Neither the name of Google Inc. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										7
									
								
								Godeps/_workspace/src/github.com/gorilla/context/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Godeps/_workspace/src/github.com/gorilla/context/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| context | ||||
| ======= | ||||
| [](https://travis-ci.org/gorilla/context) | ||||
|  | ||||
| gorilla/context is a general purpose registry for global request variables. | ||||
|  | ||||
| Read the full documentation here: http://www.gorillatoolkit.org/pkg/context | ||||
							
								
								
									
										143
									
								
								Godeps/_workspace/src/github.com/gorilla/context/context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								Godeps/_workspace/src/github.com/gorilla/context/context.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package context | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	mutex sync.RWMutex | ||||
| 	data  = make(map[*http.Request]map[interface{}]interface{}) | ||||
| 	datat = make(map[*http.Request]int64) | ||||
| ) | ||||
|  | ||||
| // Set stores a value for a given key in a given request. | ||||
| func Set(r *http.Request, key, val interface{}) { | ||||
| 	mutex.Lock() | ||||
| 	if data[r] == nil { | ||||
| 		data[r] = make(map[interface{}]interface{}) | ||||
| 		datat[r] = time.Now().Unix() | ||||
| 	} | ||||
| 	data[r][key] = val | ||||
| 	mutex.Unlock() | ||||
| } | ||||
|  | ||||
| // Get returns a value stored for a given key in a given request. | ||||
| func Get(r *http.Request, key interface{}) interface{} { | ||||
| 	mutex.RLock() | ||||
| 	if ctx := data[r]; ctx != nil { | ||||
| 		value := ctx[key] | ||||
| 		mutex.RUnlock() | ||||
| 		return value | ||||
| 	} | ||||
| 	mutex.RUnlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetOk returns stored value and presence state like multi-value return of map access. | ||||
| func GetOk(r *http.Request, key interface{}) (interface{}, bool) { | ||||
| 	mutex.RLock() | ||||
| 	if _, ok := data[r]; ok { | ||||
| 		value, ok := data[r][key] | ||||
| 		mutex.RUnlock() | ||||
| 		return value, ok | ||||
| 	} | ||||
| 	mutex.RUnlock() | ||||
| 	return nil, false | ||||
| } | ||||
|  | ||||
| // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. | ||||
| func GetAll(r *http.Request) map[interface{}]interface{} { | ||||
| 	mutex.RLock() | ||||
| 	if context, ok := data[r]; ok { | ||||
| 		result := make(map[interface{}]interface{}, len(context)) | ||||
| 		for k, v := range context { | ||||
| 			result[k] = v | ||||
| 		} | ||||
| 		mutex.RUnlock() | ||||
| 		return result | ||||
| 	} | ||||
| 	mutex.RUnlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetAllOk returns all stored values for the request as a map and a boolean value that indicates if | ||||
| // the request was registered. | ||||
| func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { | ||||
| 	mutex.RLock() | ||||
| 	context, ok := data[r] | ||||
| 	result := make(map[interface{}]interface{}, len(context)) | ||||
| 	for k, v := range context { | ||||
| 		result[k] = v | ||||
| 	} | ||||
| 	mutex.RUnlock() | ||||
| 	return result, ok | ||||
| } | ||||
|  | ||||
| // Delete removes a value stored for a given key in a given request. | ||||
| func Delete(r *http.Request, key interface{}) { | ||||
| 	mutex.Lock() | ||||
| 	if data[r] != nil { | ||||
| 		delete(data[r], key) | ||||
| 	} | ||||
| 	mutex.Unlock() | ||||
| } | ||||
|  | ||||
| // Clear removes all values stored for a given request. | ||||
| // | ||||
| // This is usually called by a handler wrapper to clean up request | ||||
| // variables at the end of a request lifetime. See ClearHandler(). | ||||
| func Clear(r *http.Request) { | ||||
| 	mutex.Lock() | ||||
| 	clear(r) | ||||
| 	mutex.Unlock() | ||||
| } | ||||
|  | ||||
| // clear is Clear without the lock. | ||||
| func clear(r *http.Request) { | ||||
| 	delete(data, r) | ||||
| 	delete(datat, r) | ||||
| } | ||||
|  | ||||
| // Purge removes request data stored for longer than maxAge, in seconds. | ||||
| // It returns the amount of requests removed. | ||||
| // | ||||
| // If maxAge <= 0, all request data is removed. | ||||
| // | ||||
| // This is only used for sanity check: in case context cleaning was not | ||||
| // properly set some request data can be kept forever, consuming an increasing | ||||
| // amount of memory. In case this is detected, Purge() must be called | ||||
| // periodically until the problem is fixed. | ||||
| func Purge(maxAge int) int { | ||||
| 	mutex.Lock() | ||||
| 	count := 0 | ||||
| 	if maxAge <= 0 { | ||||
| 		count = len(data) | ||||
| 		data = make(map[*http.Request]map[interface{}]interface{}) | ||||
| 		datat = make(map[*http.Request]int64) | ||||
| 	} else { | ||||
| 		min := time.Now().Unix() - int64(maxAge) | ||||
| 		for r := range data { | ||||
| 			if datat[r] < min { | ||||
| 				clear(r) | ||||
| 				count++ | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	mutex.Unlock() | ||||
| 	return count | ||||
| } | ||||
|  | ||||
| // ClearHandler wraps an http.Handler and clears request values at the end | ||||
| // of a request lifetime. | ||||
| func ClearHandler(h http.Handler) http.Handler { | ||||
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		defer Clear(r) | ||||
| 		h.ServeHTTP(w, r) | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										161
									
								
								Godeps/_workspace/src/github.com/gorilla/context/context_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								Godeps/_workspace/src/github.com/gorilla/context/context_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package context | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| type keyType int | ||||
|  | ||||
| const ( | ||||
| 	key1 keyType = iota | ||||
| 	key2 | ||||
| ) | ||||
|  | ||||
| func TestContext(t *testing.T) { | ||||
| 	assertEqual := func(val interface{}, exp interface{}) { | ||||
| 		if val != exp { | ||||
| 			t.Errorf("Expected %v, got %v.", exp, val) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) | ||||
| 	emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil) | ||||
|  | ||||
| 	// Get() | ||||
| 	assertEqual(Get(r, key1), nil) | ||||
|  | ||||
| 	// Set() | ||||
| 	Set(r, key1, "1") | ||||
| 	assertEqual(Get(r, key1), "1") | ||||
| 	assertEqual(len(data[r]), 1) | ||||
|  | ||||
| 	Set(r, key2, "2") | ||||
| 	assertEqual(Get(r, key2), "2") | ||||
| 	assertEqual(len(data[r]), 2) | ||||
|  | ||||
| 	//GetOk | ||||
| 	value, ok := GetOk(r, key1) | ||||
| 	assertEqual(value, "1") | ||||
| 	assertEqual(ok, true) | ||||
|  | ||||
| 	value, ok = GetOk(r, "not exists") | ||||
| 	assertEqual(value, nil) | ||||
| 	assertEqual(ok, false) | ||||
|  | ||||
| 	Set(r, "nil value", nil) | ||||
| 	value, ok = GetOk(r, "nil value") | ||||
| 	assertEqual(value, nil) | ||||
| 	assertEqual(ok, true) | ||||
|  | ||||
| 	// GetAll() | ||||
| 	values := GetAll(r) | ||||
| 	assertEqual(len(values), 3) | ||||
|  | ||||
| 	// GetAll() for empty request | ||||
| 	values = GetAll(emptyR) | ||||
| 	if values != nil { | ||||
| 		t.Error("GetAll didn't return nil value for invalid request") | ||||
| 	} | ||||
|  | ||||
| 	// GetAllOk() | ||||
| 	values, ok = GetAllOk(r) | ||||
| 	assertEqual(len(values), 3) | ||||
| 	assertEqual(ok, true) | ||||
|  | ||||
| 	// GetAllOk() for empty request | ||||
| 	values, ok = GetAllOk(emptyR) | ||||
| 	assertEqual(value, nil) | ||||
| 	assertEqual(ok, false) | ||||
|  | ||||
| 	// Delete() | ||||
| 	Delete(r, key1) | ||||
| 	assertEqual(Get(r, key1), nil) | ||||
| 	assertEqual(len(data[r]), 2) | ||||
|  | ||||
| 	Delete(r, key2) | ||||
| 	assertEqual(Get(r, key2), nil) | ||||
| 	assertEqual(len(data[r]), 1) | ||||
|  | ||||
| 	// Clear() | ||||
| 	Clear(r) | ||||
| 	assertEqual(len(data), 0) | ||||
| } | ||||
|  | ||||
| func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { | ||||
| 	<-wait | ||||
| 	for i := 0; i < iterations; i++ { | ||||
| 		Get(r, key) | ||||
| 	} | ||||
| 	done <- struct{}{} | ||||
|  | ||||
| } | ||||
|  | ||||
| func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { | ||||
| 	<-wait | ||||
| 	for i := 0; i < iterations; i++ { | ||||
| 		Set(r, key, value) | ||||
| 	} | ||||
| 	done <- struct{}{} | ||||
|  | ||||
| } | ||||
|  | ||||
| func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { | ||||
|  | ||||
| 	b.StopTimer() | ||||
| 	r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) | ||||
| 	done := make(chan struct{}) | ||||
| 	b.StartTimer() | ||||
|  | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		wait := make(chan struct{}) | ||||
|  | ||||
| 		for i := 0; i < numReaders; i++ { | ||||
| 			go parallelReader(r, "test", iterations, wait, done) | ||||
| 		} | ||||
|  | ||||
| 		for i := 0; i < numWriters; i++ { | ||||
| 			go parallelWriter(r, "test", "123", iterations, wait, done) | ||||
| 		} | ||||
|  | ||||
| 		close(wait) | ||||
|  | ||||
| 		for i := 0; i < numReaders+numWriters; i++ { | ||||
| 			<-done | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func BenchmarkMutexSameReadWrite1(b *testing.B) { | ||||
| 	benchmarkMutex(b, 1, 1, 32) | ||||
| } | ||||
| func BenchmarkMutexSameReadWrite2(b *testing.B) { | ||||
| 	benchmarkMutex(b, 2, 2, 32) | ||||
| } | ||||
| func BenchmarkMutexSameReadWrite4(b *testing.B) { | ||||
| 	benchmarkMutex(b, 4, 4, 32) | ||||
| } | ||||
| func BenchmarkMutex1(b *testing.B) { | ||||
| 	benchmarkMutex(b, 2, 8, 32) | ||||
| } | ||||
| func BenchmarkMutex2(b *testing.B) { | ||||
| 	benchmarkMutex(b, 16, 4, 64) | ||||
| } | ||||
| func BenchmarkMutex3(b *testing.B) { | ||||
| 	benchmarkMutex(b, 1, 2, 128) | ||||
| } | ||||
| func BenchmarkMutex4(b *testing.B) { | ||||
| 	benchmarkMutex(b, 128, 32, 256) | ||||
| } | ||||
| func BenchmarkMutex5(b *testing.B) { | ||||
| 	benchmarkMutex(b, 1024, 2048, 64) | ||||
| } | ||||
| func BenchmarkMutex6(b *testing.B) { | ||||
| 	benchmarkMutex(b, 2048, 1024, 512) | ||||
| } | ||||
							
								
								
									
										82
									
								
								Godeps/_workspace/src/github.com/gorilla/context/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								Godeps/_workspace/src/github.com/gorilla/context/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| /* | ||||
| Package context stores values shared during a request lifetime. | ||||
|  | ||||
| For example, a router can set variables extracted from the URL and later | ||||
| application handlers can access those values, or it can be used to store | ||||
| sessions values to be saved at the end of a request. There are several | ||||
| others common uses. | ||||
|  | ||||
| The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: | ||||
|  | ||||
| 	http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 | ||||
|  | ||||
| Here's the basic usage: first define the keys that you will need. The key | ||||
| type is interface{} so a key can be of any type that supports equality. | ||||
| Here we define a key using a custom int type to avoid name collisions: | ||||
|  | ||||
| 	package foo | ||||
|  | ||||
| 	import ( | ||||
| 		"github.com/gorilla/context" | ||||
| 	) | ||||
|  | ||||
| 	type key int | ||||
|  | ||||
| 	const MyKey key = 0 | ||||
|  | ||||
| Then set a variable. Variables are bound to an http.Request object, so you | ||||
| need a request instance to set a value: | ||||
|  | ||||
| 	context.Set(r, MyKey, "bar") | ||||
|  | ||||
| The application can later access the variable using the same key you provided: | ||||
|  | ||||
| 	func MyHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 		// val is "bar". | ||||
| 		val := context.Get(r, foo.MyKey) | ||||
|  | ||||
| 		// returns ("bar", true) | ||||
| 		val, ok := context.GetOk(r, foo.MyKey) | ||||
| 		// ... | ||||
| 	} | ||||
|  | ||||
| And that's all about the basic usage. We discuss some other ideas below. | ||||
|  | ||||
| Any type can be stored in the context. To enforce a given type, make the key | ||||
| private and wrap Get() and Set() to accept and return values of a specific | ||||
| type: | ||||
|  | ||||
| 	type key int | ||||
|  | ||||
| 	const mykey key = 0 | ||||
|  | ||||
| 	// GetMyKey returns a value for this package from the request values. | ||||
| 	func GetMyKey(r *http.Request) SomeType { | ||||
| 		if rv := context.Get(r, mykey); rv != nil { | ||||
| 			return rv.(SomeType) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// SetMyKey sets a value for this package in the request values. | ||||
| 	func SetMyKey(r *http.Request, val SomeType) { | ||||
| 		context.Set(r, mykey, val) | ||||
| 	} | ||||
|  | ||||
| Variables must be cleared at the end of a request, to remove all values | ||||
| that were stored. This can be done in an http.Handler, after a request was | ||||
| served. Just call Clear() passing the request: | ||||
|  | ||||
| 	context.Clear(r) | ||||
|  | ||||
| ...or use ClearHandler(), which conveniently wraps an http.Handler to clear | ||||
| variables at the end of a request lifetime. | ||||
|  | ||||
| The Routers from the packages gorilla/mux and gorilla/pat call Clear() | ||||
| so if you are using either of them you don't need to clear the context manually. | ||||
| */ | ||||
| package context | ||||
							
								
								
									
										7
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| language: go | ||||
|  | ||||
| go: | ||||
|   - 1.0 | ||||
|   - 1.1 | ||||
|   - 1.2 | ||||
|   - tip | ||||
							
								
								
									
										27
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| Copyright (c) 2012 Rodrigo Moraes. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
| 	 * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
| 	 * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
| 	 * Neither the name of Google Inc. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										7
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| mux | ||||
| === | ||||
| [](https://travis-ci.org/gorilla/mux) | ||||
|  | ||||
| gorilla/mux is a powerful URL router and dispatcher. | ||||
|  | ||||
| Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux | ||||
							
								
								
									
										21
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package mux | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func BenchmarkMux(b *testing.B) { | ||||
| 	router := new(Router) | ||||
| 	handler := func(w http.ResponseWriter, r *http.Request) {} | ||||
| 	router.HandleFunc("/v1/{v1}", handler) | ||||
|  | ||||
| 	request, _ := http.NewRequest("GET", "/v1/anything", nil) | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		router.ServeHTTP(nil, request) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										199
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,199 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| /* | ||||
| Package gorilla/mux implements a request router and dispatcher. | ||||
|  | ||||
| The name mux stands for "HTTP request multiplexer". Like the standard | ||||
| http.ServeMux, mux.Router matches incoming requests against a list of | ||||
| registered routes and calls a handler for the route that matches the URL | ||||
| or other conditions. The main features are: | ||||
|  | ||||
| 	* Requests can be matched based on URL host, path, path prefix, schemes, | ||||
| 	  header and query values, HTTP methods or using custom matchers. | ||||
| 	* URL hosts and paths can have variables with an optional regular | ||||
| 	  expression. | ||||
| 	* Registered URLs can be built, or "reversed", which helps maintaining | ||||
| 	  references to resources. | ||||
| 	* Routes can be used as subrouters: nested routes are only tested if the | ||||
| 	  parent route matches. This is useful to define groups of routes that | ||||
| 	  share common conditions like a host, a path prefix or other repeated | ||||
| 	  attributes. As a bonus, this optimizes request matching. | ||||
| 	* It implements the http.Handler interface so it is compatible with the | ||||
| 	  standard http.ServeMux. | ||||
|  | ||||
| Let's start registering a couple of URL paths and handlers: | ||||
|  | ||||
| 	func main() { | ||||
| 		r := mux.NewRouter() | ||||
| 		r.HandleFunc("/", HomeHandler) | ||||
| 		r.HandleFunc("/products", ProductsHandler) | ||||
| 		r.HandleFunc("/articles", ArticlesHandler) | ||||
| 		http.Handle("/", r) | ||||
| 	} | ||||
|  | ||||
| Here we register three routes mapping URL paths to handlers. This is | ||||
| equivalent to how http.HandleFunc() works: if an incoming request URL matches | ||||
| one of the paths, the corresponding handler is called passing | ||||
| (http.ResponseWriter, *http.Request) as parameters. | ||||
|  | ||||
| Paths can have variables. They are defined using the format {name} or | ||||
| {name:pattern}. If a regular expression pattern is not defined, the matched | ||||
| variable will be anything until the next slash. For example: | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	r.HandleFunc("/products/{key}", ProductHandler) | ||||
| 	r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) | ||||
| 	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) | ||||
|  | ||||
| The names are used to create a map of route variables which can be retrieved | ||||
| calling mux.Vars(): | ||||
|  | ||||
| 	vars := mux.Vars(request) | ||||
| 	category := vars["category"] | ||||
|  | ||||
| And this is all you need to know about the basic usage. More advanced options | ||||
| are explained below. | ||||
|  | ||||
| Routes can also be restricted to a domain or subdomain. Just define a host | ||||
| pattern to be matched. They can also have variables: | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	// Only matches if domain is "www.domain.com". | ||||
| 	r.Host("www.domain.com") | ||||
| 	// Matches a dynamic subdomain. | ||||
| 	r.Host("{subdomain:[a-z]+}.domain.com") | ||||
|  | ||||
| There are several other matchers that can be added. To match path prefixes: | ||||
|  | ||||
| 	r.PathPrefix("/products/") | ||||
|  | ||||
| ...or HTTP methods: | ||||
|  | ||||
| 	r.Methods("GET", "POST") | ||||
|  | ||||
| ...or URL schemes: | ||||
|  | ||||
| 	r.Schemes("https") | ||||
|  | ||||
| ...or header values: | ||||
|  | ||||
| 	r.Headers("X-Requested-With", "XMLHttpRequest") | ||||
|  | ||||
| ...or query values: | ||||
|  | ||||
| 	r.Queries("key", "value") | ||||
|  | ||||
| ...or to use a custom matcher function: | ||||
|  | ||||
| 	r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { | ||||
| 		return r.ProtoMajor == 0 | ||||
|     }) | ||||
|  | ||||
| ...and finally, it is possible to combine several matchers in a single route: | ||||
|  | ||||
| 	r.HandleFunc("/products", ProductsHandler). | ||||
| 	  Host("www.domain.com"). | ||||
| 	  Methods("GET"). | ||||
| 	  Schemes("http") | ||||
|  | ||||
| Setting the same matching conditions again and again can be boring, so we have | ||||
| a way to group several routes that share the same requirements. | ||||
| We call it "subrouting". | ||||
|  | ||||
| For example, let's say we have several URLs that should only match when the | ||||
| host is "www.domain.com". Create a route for that host and get a "subrouter" | ||||
| from it: | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	s := r.Host("www.domain.com").Subrouter() | ||||
|  | ||||
| Then register routes in the subrouter: | ||||
|  | ||||
| 	s.HandleFunc("/products/", ProductsHandler) | ||||
| 	s.HandleFunc("/products/{key}", ProductHandler) | ||||
| 	s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) | ||||
|  | ||||
| The three URL paths we registered above will only be tested if the domain is | ||||
| "www.domain.com", because the subrouter is tested first. This is not | ||||
| only convenient, but also optimizes request matching. You can create | ||||
| subrouters combining any attribute matchers accepted by a route. | ||||
|  | ||||
| Subrouters can be used to create domain or path "namespaces": you define | ||||
| subrouters in a central place and then parts of the app can register its | ||||
| paths relatively to a given subrouter. | ||||
|  | ||||
| There's one more thing about subroutes. When a subrouter has a path prefix, | ||||
| the inner routes use it as base for their paths: | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	s := r.PathPrefix("/products").Subrouter() | ||||
| 	// "/products/" | ||||
| 	s.HandleFunc("/", ProductsHandler) | ||||
| 	// "/products/{key}/" | ||||
| 	s.HandleFunc("/{key}/", ProductHandler) | ||||
| 	// "/products/{key}/details" | ||||
| 	s.HandleFunc("/{key}/details", ProductDetailsHandler) | ||||
|  | ||||
| Now let's see how to build registered URLs. | ||||
|  | ||||
| Routes can be named. All routes that define a name can have their URLs built, | ||||
| or "reversed". We define a name calling Name() on a route. For example: | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). | ||||
| 	  Name("article") | ||||
|  | ||||
| To build a URL, get the route and call the URL() method, passing a sequence of | ||||
| key/value pairs for the route variables. For the previous route, we would do: | ||||
|  | ||||
| 	url, err := r.Get("article").URL("category", "technology", "id", "42") | ||||
|  | ||||
| ...and the result will be a url.URL with the following path: | ||||
|  | ||||
| 	"/articles/technology/42" | ||||
|  | ||||
| This also works for host variables: | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	r.Host("{subdomain}.domain.com"). | ||||
| 	  Path("/articles/{category}/{id:[0-9]+}"). | ||||
| 	  HandlerFunc(ArticleHandler). | ||||
| 	  Name("article") | ||||
|  | ||||
| 	// url.String() will be "http://news.domain.com/articles/technology/42" | ||||
| 	url, err := r.Get("article").URL("subdomain", "news", | ||||
| 									 "category", "technology", | ||||
| 									 "id", "42") | ||||
|  | ||||
| All variables defined in the route are required, and their values must | ||||
| conform to the corresponding patterns. These requirements guarantee that a | ||||
| generated URL will always match a registered route -- the only exception is | ||||
| for explicitly defined "build-only" routes which never match. | ||||
|  | ||||
| There's also a way to build only the URL host or path for a route: | ||||
| use the methods URLHost() or URLPath() instead. For the previous route, | ||||
| we would do: | ||||
|  | ||||
| 	// "http://news.domain.com/" | ||||
| 	host, err := r.Get("article").URLHost("subdomain", "news") | ||||
|  | ||||
| 	// "/articles/technology/42" | ||||
| 	path, err := r.Get("article").URLPath("category", "technology", "id", "42") | ||||
|  | ||||
| And if you use subrouters, host and path defined separately can be built | ||||
| as well: | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	s := r.Host("{subdomain}.domain.com").Subrouter() | ||||
| 	s.Path("/articles/{category}/{id:[0-9]+}"). | ||||
| 	  HandlerFunc(ArticleHandler). | ||||
| 	  Name("article") | ||||
|  | ||||
| 	// "http://news.domain.com/articles/technology/42" | ||||
| 	url, err := r.Get("article").URL("subdomain", "news", | ||||
| 									 "category", "technology", | ||||
| 									 "id", "42") | ||||
| */ | ||||
| package mux | ||||
							
								
								
									
										366
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/mux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										366
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/mux.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,366 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package mux | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"path" | ||||
|  | ||||
| 	"github.com/gorilla/context" | ||||
| ) | ||||
|  | ||||
| // NewRouter returns a new router instance. | ||||
| func NewRouter() *Router { | ||||
| 	return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} | ||||
| } | ||||
|  | ||||
| // Router registers routes to be matched and dispatches a handler. | ||||
| // | ||||
| // It implements the http.Handler interface, so it can be registered to serve | ||||
| // requests: | ||||
| // | ||||
| //     var router = mux.NewRouter() | ||||
| // | ||||
| //     func main() { | ||||
| //         http.Handle("/", router) | ||||
| //     } | ||||
| // | ||||
| // Or, for Google App Engine, register it in a init() function: | ||||
| // | ||||
| //     func init() { | ||||
| //         http.Handle("/", router) | ||||
| //     } | ||||
| // | ||||
| // This will send all incoming requests to the router. | ||||
| type Router struct { | ||||
| 	// Configurable Handler to be used when no route matches. | ||||
| 	NotFoundHandler http.Handler | ||||
| 	// Parent route, if this is a subrouter. | ||||
| 	parent parentRoute | ||||
| 	// Routes to be matched, in order. | ||||
| 	routes []*Route | ||||
| 	// Routes by name for URL building. | ||||
| 	namedRoutes map[string]*Route | ||||
| 	// See Router.StrictSlash(). This defines the flag for new routes. | ||||
| 	strictSlash bool | ||||
| 	// If true, do not clear the request context after handling the request | ||||
| 	KeepContext bool | ||||
| } | ||||
|  | ||||
| // Match matches registered routes against the request. | ||||
| func (r *Router) Match(req *http.Request, match *RouteMatch) bool { | ||||
| 	for _, route := range r.routes { | ||||
| 		if route.Match(req, match) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // ServeHTTP dispatches the handler registered in the matched route. | ||||
| // | ||||
| // When there is a match, the route variables can be retrieved calling | ||||
| // mux.Vars(request). | ||||
| func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { | ||||
| 	// Clean path to canonical form and redirect. | ||||
| 	if p := cleanPath(req.URL.Path); p != req.URL.Path { | ||||
|  | ||||
| 		// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query. | ||||
| 		// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue: | ||||
| 		// http://code.google.com/p/go/issues/detail?id=5252 | ||||
| 		url := *req.URL | ||||
| 		url.Path = p | ||||
| 		p = url.String() | ||||
|  | ||||
| 		w.Header().Set("Location", p) | ||||
| 		w.WriteHeader(http.StatusMovedPermanently) | ||||
| 		return | ||||
| 	} | ||||
| 	var match RouteMatch | ||||
| 	var handler http.Handler | ||||
| 	if r.Match(req, &match) { | ||||
| 		handler = match.Handler | ||||
| 		setVars(req, match.Vars) | ||||
| 		setCurrentRoute(req, match.Route) | ||||
| 	} | ||||
| 	if handler == nil { | ||||
| 		handler = r.NotFoundHandler | ||||
| 		if handler == nil { | ||||
| 			handler = http.NotFoundHandler() | ||||
| 		} | ||||
| 	} | ||||
| 	if !r.KeepContext { | ||||
| 		defer context.Clear(req) | ||||
| 	} | ||||
| 	handler.ServeHTTP(w, req) | ||||
| } | ||||
|  | ||||
| // Get returns a route registered with the given name. | ||||
| func (r *Router) Get(name string) *Route { | ||||
| 	return r.getNamedRoutes()[name] | ||||
| } | ||||
|  | ||||
| // GetRoute returns a route registered with the given name. This method | ||||
| // was renamed to Get() and remains here for backwards compatibility. | ||||
| func (r *Router) GetRoute(name string) *Route { | ||||
| 	return r.getNamedRoutes()[name] | ||||
| } | ||||
|  | ||||
| // StrictSlash defines the trailing slash behavior for new routes. The initial | ||||
| // value is false. | ||||
| // | ||||
| // When true, if the route path is "/path/", accessing "/path" will redirect | ||||
| // to the former and vice versa. In other words, your application will always | ||||
| // see the path as specified in the route. | ||||
| // | ||||
| // When false, if the route path is "/path", accessing "/path/" will not match | ||||
| // this route and vice versa. | ||||
| // | ||||
| // Special case: when a route sets a path prefix using the PathPrefix() method, | ||||
| // strict slash is ignored for that route because the redirect behavior can't | ||||
| // be determined from a prefix alone. However, any subrouters created from that | ||||
| // route inherit the original StrictSlash setting. | ||||
| func (r *Router) StrictSlash(value bool) *Router { | ||||
| 	r.strictSlash = value | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // parentRoute | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // getNamedRoutes returns the map where named routes are registered. | ||||
| func (r *Router) getNamedRoutes() map[string]*Route { | ||||
| 	if r.namedRoutes == nil { | ||||
| 		if r.parent != nil { | ||||
| 			r.namedRoutes = r.parent.getNamedRoutes() | ||||
| 		} else { | ||||
| 			r.namedRoutes = make(map[string]*Route) | ||||
| 		} | ||||
| 	} | ||||
| 	return r.namedRoutes | ||||
| } | ||||
|  | ||||
| // getRegexpGroup returns regexp definitions from the parent route, if any. | ||||
| func (r *Router) getRegexpGroup() *routeRegexpGroup { | ||||
| 	if r.parent != nil { | ||||
| 		return r.parent.getRegexpGroup() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (r *Router) buildVars(m map[string]string) map[string]string { | ||||
| 	if r.parent != nil { | ||||
| 		m = r.parent.buildVars(m) | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // Route factories | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // NewRoute registers an empty route. | ||||
| func (r *Router) NewRoute() *Route { | ||||
| 	route := &Route{parent: r, strictSlash: r.strictSlash} | ||||
| 	r.routes = append(r.routes, route) | ||||
| 	return route | ||||
| } | ||||
|  | ||||
| // Handle registers a new route with a matcher for the URL path. | ||||
| // See Route.Path() and Route.Handler(). | ||||
| func (r *Router) Handle(path string, handler http.Handler) *Route { | ||||
| 	return r.NewRoute().Path(path).Handler(handler) | ||||
| } | ||||
|  | ||||
| // HandleFunc registers a new route with a matcher for the URL path. | ||||
| // See Route.Path() and Route.HandlerFunc(). | ||||
| func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, | ||||
| 	*http.Request)) *Route { | ||||
| 	return r.NewRoute().Path(path).HandlerFunc(f) | ||||
| } | ||||
|  | ||||
| // Headers registers a new route with a matcher for request header values. | ||||
| // See Route.Headers(). | ||||
| func (r *Router) Headers(pairs ...string) *Route { | ||||
| 	return r.NewRoute().Headers(pairs...) | ||||
| } | ||||
|  | ||||
| // Host registers a new route with a matcher for the URL host. | ||||
| // See Route.Host(). | ||||
| func (r *Router) Host(tpl string) *Route { | ||||
| 	return r.NewRoute().Host(tpl) | ||||
| } | ||||
|  | ||||
| // MatcherFunc registers a new route with a custom matcher function. | ||||
| // See Route.MatcherFunc(). | ||||
| func (r *Router) MatcherFunc(f MatcherFunc) *Route { | ||||
| 	return r.NewRoute().MatcherFunc(f) | ||||
| } | ||||
|  | ||||
| // Methods registers a new route with a matcher for HTTP methods. | ||||
| // See Route.Methods(). | ||||
| func (r *Router) Methods(methods ...string) *Route { | ||||
| 	return r.NewRoute().Methods(methods...) | ||||
| } | ||||
|  | ||||
| // Path registers a new route with a matcher for the URL path. | ||||
| // See Route.Path(). | ||||
| func (r *Router) Path(tpl string) *Route { | ||||
| 	return r.NewRoute().Path(tpl) | ||||
| } | ||||
|  | ||||
| // PathPrefix registers a new route with a matcher for the URL path prefix. | ||||
| // See Route.PathPrefix(). | ||||
| func (r *Router) PathPrefix(tpl string) *Route { | ||||
| 	return r.NewRoute().PathPrefix(tpl) | ||||
| } | ||||
|  | ||||
| // Queries registers a new route with a matcher for URL query values. | ||||
| // See Route.Queries(). | ||||
| func (r *Router) Queries(pairs ...string) *Route { | ||||
| 	return r.NewRoute().Queries(pairs...) | ||||
| } | ||||
|  | ||||
| // Schemes registers a new route with a matcher for URL schemes. | ||||
| // See Route.Schemes(). | ||||
| func (r *Router) Schemes(schemes ...string) *Route { | ||||
| 	return r.NewRoute().Schemes(schemes...) | ||||
| } | ||||
|  | ||||
| // BuildVars registers a new route with a custom function for modifying | ||||
| // route variables before building a URL. | ||||
| func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { | ||||
| 	return r.NewRoute().BuildVarsFunc(f) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // Context | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // RouteMatch stores information about a matched route. | ||||
| type RouteMatch struct { | ||||
| 	Route   *Route | ||||
| 	Handler http.Handler | ||||
| 	Vars    map[string]string | ||||
| } | ||||
|  | ||||
| type contextKey int | ||||
|  | ||||
| const ( | ||||
| 	varsKey contextKey = iota | ||||
| 	routeKey | ||||
| ) | ||||
|  | ||||
| // Vars returns the route variables for the current request, if any. | ||||
| func Vars(r *http.Request) map[string]string { | ||||
| 	if rv := context.Get(r, varsKey); rv != nil { | ||||
| 		return rv.(map[string]string) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CurrentRoute returns the matched route for the current request, if any. | ||||
| func CurrentRoute(r *http.Request) *Route { | ||||
| 	if rv := context.Get(r, routeKey); rv != nil { | ||||
| 		return rv.(*Route) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func setVars(r *http.Request, val interface{}) { | ||||
| 	context.Set(r, varsKey, val) | ||||
| } | ||||
|  | ||||
| func setCurrentRoute(r *http.Request, val interface{}) { | ||||
| 	context.Set(r, routeKey, val) | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // Helpers | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // cleanPath returns the canonical path for p, eliminating . and .. elements. | ||||
| // Borrowed from the net/http package. | ||||
| func cleanPath(p string) string { | ||||
| 	if p == "" { | ||||
| 		return "/" | ||||
| 	} | ||||
| 	if p[0] != '/' { | ||||
| 		p = "/" + p | ||||
| 	} | ||||
| 	np := path.Clean(p) | ||||
| 	// path.Clean removes trailing slash except for root; | ||||
| 	// put the trailing slash back if necessary. | ||||
| 	if p[len(p)-1] == '/' && np != "/" { | ||||
| 		np += "/" | ||||
| 	} | ||||
| 	return np | ||||
| } | ||||
|  | ||||
| // uniqueVars returns an error if two slices contain duplicated strings. | ||||
| func uniqueVars(s1, s2 []string) error { | ||||
| 	for _, v1 := range s1 { | ||||
| 		for _, v2 := range s2 { | ||||
| 			if v1 == v2 { | ||||
| 				return fmt.Errorf("mux: duplicated route variable %q", v2) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // mapFromPairs converts variadic string parameters to a string map. | ||||
| func mapFromPairs(pairs ...string) (map[string]string, error) { | ||||
| 	length := len(pairs) | ||||
| 	if length%2 != 0 { | ||||
| 		return nil, fmt.Errorf( | ||||
| 			"mux: number of parameters must be multiple of 2, got %v", pairs) | ||||
| 	} | ||||
| 	m := make(map[string]string, length/2) | ||||
| 	for i := 0; i < length; i += 2 { | ||||
| 		m[pairs[i]] = pairs[i+1] | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| // matchInArray returns true if the given string value is in the array. | ||||
| func matchInArray(arr []string, value string) bool { | ||||
| 	for _, v := range arr { | ||||
| 		if v == value { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // matchMap returns true if the given key/value pairs exist in a given map. | ||||
| func matchMap(toCheck map[string]string, toMatch map[string][]string, | ||||
| 	canonicalKey bool) bool { | ||||
| 	for k, v := range toCheck { | ||||
| 		// Check if key exists. | ||||
| 		if canonicalKey { | ||||
| 			k = http.CanonicalHeaderKey(k) | ||||
| 		} | ||||
| 		if values := toMatch[k]; values == nil { | ||||
| 			return false | ||||
| 		} else if v != "" { | ||||
| 			// If value was defined as an empty string we only check that the | ||||
| 			// key exists. Otherwise we also check for equality. | ||||
| 			valueExists := false | ||||
| 			for _, value := range values { | ||||
| 				if v == value { | ||||
| 					valueExists = true | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 			if !valueExists { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
							
								
								
									
										1003
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1003
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										714
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/old_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										714
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/old_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,714 @@ | ||||
| // Old tests ported to Go1. This is a mess. Want to drop it one day. | ||||
|  | ||||
| // Copyright 2011 Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package mux | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // ResponseRecorder | ||||
| // ---------------------------------------------------------------------------- | ||||
| // Copyright 2009 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // ResponseRecorder is an implementation of http.ResponseWriter that | ||||
| // records its mutations for later inspection in tests. | ||||
| type ResponseRecorder struct { | ||||
| 	Code      int           // the HTTP response code from WriteHeader | ||||
| 	HeaderMap http.Header   // the HTTP response headers | ||||
| 	Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to | ||||
| 	Flushed   bool | ||||
| } | ||||
|  | ||||
| // NewRecorder returns an initialized ResponseRecorder. | ||||
| func NewRecorder() *ResponseRecorder { | ||||
| 	return &ResponseRecorder{ | ||||
| 		HeaderMap: make(http.Header), | ||||
| 		Body:      new(bytes.Buffer), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DefaultRemoteAddr is the default remote address to return in RemoteAddr if | ||||
| // an explicit DefaultRemoteAddr isn't set on ResponseRecorder. | ||||
| const DefaultRemoteAddr = "1.2.3.4" | ||||
|  | ||||
| // Header returns the response headers. | ||||
| func (rw *ResponseRecorder) Header() http.Header { | ||||
| 	return rw.HeaderMap | ||||
| } | ||||
|  | ||||
| // Write always succeeds and writes to rw.Body, if not nil. | ||||
| func (rw *ResponseRecorder) Write(buf []byte) (int, error) { | ||||
| 	if rw.Body != nil { | ||||
| 		rw.Body.Write(buf) | ||||
| 	} | ||||
| 	if rw.Code == 0 { | ||||
| 		rw.Code = http.StatusOK | ||||
| 	} | ||||
| 	return len(buf), nil | ||||
| } | ||||
|  | ||||
| // WriteHeader sets rw.Code. | ||||
| func (rw *ResponseRecorder) WriteHeader(code int) { | ||||
| 	rw.Code = code | ||||
| } | ||||
|  | ||||
| // Flush sets rw.Flushed to true. | ||||
| func (rw *ResponseRecorder) Flush() { | ||||
| 	rw.Flushed = true | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| func TestRouteMatchers(t *testing.T) { | ||||
| 	var scheme, host, path, query, method string | ||||
| 	var headers map[string]string | ||||
| 	var resultVars map[bool]map[string]string | ||||
|  | ||||
| 	router := NewRouter() | ||||
| 	router.NewRoute().Host("{var1}.google.com"). | ||||
| 		Path("/{var2:[a-z]+}/{var3:[0-9]+}"). | ||||
| 		Queries("foo", "bar"). | ||||
| 		Methods("GET"). | ||||
| 		Schemes("https"). | ||||
| 		Headers("x-requested-with", "XMLHttpRequest") | ||||
| 	router.NewRoute().Host("www.{var4}.com"). | ||||
| 		PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}"). | ||||
| 		Queries("baz", "ding"). | ||||
| 		Methods("POST"). | ||||
| 		Schemes("http"). | ||||
| 		Headers("Content-Type", "application/json") | ||||
|  | ||||
| 	reset := func() { | ||||
| 		// Everything match. | ||||
| 		scheme = "https" | ||||
| 		host = "www.google.com" | ||||
| 		path = "/product/42" | ||||
| 		query = "?foo=bar" | ||||
| 		method = "GET" | ||||
| 		headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} | ||||
| 		resultVars = map[bool]map[string]string{ | ||||
| 			true:  {"var1": "www", "var2": "product", "var3": "42"}, | ||||
| 			false: {}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	reset2 := func() { | ||||
| 		// Everything match. | ||||
| 		scheme = "http" | ||||
| 		host = "www.google.com" | ||||
| 		path = "/foo/product/42/path/that/is/ignored" | ||||
| 		query = "?baz=ding" | ||||
| 		method = "POST" | ||||
| 		headers = map[string]string{"Content-Type": "application/json"} | ||||
| 		resultVars = map[bool]map[string]string{ | ||||
| 			true:  {"var4": "google", "var5": "product", "var6": "42"}, | ||||
| 			false: {}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	match := func(shouldMatch bool) { | ||||
| 		url := scheme + "://" + host + path + query | ||||
| 		request, _ := http.NewRequest(method, url, nil) | ||||
| 		for key, value := range headers { | ||||
| 			request.Header.Add(key, value) | ||||
| 		} | ||||
|  | ||||
| 		var routeMatch RouteMatch | ||||
| 		matched := router.Match(request, &routeMatch) | ||||
| 		if matched != shouldMatch { | ||||
| 			// Need better messages. :) | ||||
| 			if matched { | ||||
| 				t.Errorf("Should match.") | ||||
| 			} else { | ||||
| 				t.Errorf("Should not match.") | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if matched { | ||||
| 			currentRoute := routeMatch.Route | ||||
| 			if currentRoute == nil { | ||||
| 				t.Errorf("Expected a current route.") | ||||
| 			} | ||||
| 			vars := routeMatch.Vars | ||||
| 			expectedVars := resultVars[shouldMatch] | ||||
| 			if len(vars) != len(expectedVars) { | ||||
| 				t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) | ||||
| 			} | ||||
| 			for name, value := range vars { | ||||
| 				if expectedVars[name] != value { | ||||
| 					t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 1st route -------------------------------------------------------------- | ||||
|  | ||||
| 	// Everything match. | ||||
| 	reset() | ||||
| 	match(true) | ||||
|  | ||||
| 	// Scheme doesn't match. | ||||
| 	reset() | ||||
| 	scheme = "http" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Host doesn't match. | ||||
| 	reset() | ||||
| 	host = "www.mygoogle.com" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Path doesn't match. | ||||
| 	reset() | ||||
| 	path = "/product/notdigits" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Query doesn't match. | ||||
| 	reset() | ||||
| 	query = "?foo=baz" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Method doesn't match. | ||||
| 	reset() | ||||
| 	method = "POST" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Header doesn't match. | ||||
| 	reset() | ||||
| 	headers = map[string]string{} | ||||
| 	match(false) | ||||
|  | ||||
| 	// Everything match, again. | ||||
| 	reset() | ||||
| 	match(true) | ||||
|  | ||||
| 	// 2nd route -------------------------------------------------------------- | ||||
|  | ||||
| 	// Everything match. | ||||
| 	reset2() | ||||
| 	match(true) | ||||
|  | ||||
| 	// Scheme doesn't match. | ||||
| 	reset2() | ||||
| 	scheme = "https" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Host doesn't match. | ||||
| 	reset2() | ||||
| 	host = "sub.google.com" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Path doesn't match. | ||||
| 	reset2() | ||||
| 	path = "/bar/product/42" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Query doesn't match. | ||||
| 	reset2() | ||||
| 	query = "?foo=baz" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Method doesn't match. | ||||
| 	reset2() | ||||
| 	method = "GET" | ||||
| 	match(false) | ||||
|  | ||||
| 	// Header doesn't match. | ||||
| 	reset2() | ||||
| 	headers = map[string]string{} | ||||
| 	match(false) | ||||
|  | ||||
| 	// Everything match, again. | ||||
| 	reset2() | ||||
| 	match(true) | ||||
| } | ||||
|  | ||||
| type headerMatcherTest struct { | ||||
| 	matcher headerMatcher | ||||
| 	headers map[string]string | ||||
| 	result  bool | ||||
| } | ||||
|  | ||||
| var headerMatcherTests = []headerMatcherTest{ | ||||
| 	{ | ||||
| 		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), | ||||
| 		headers: map[string]string{"X-Requested-With": "XMLHttpRequest"}, | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: headerMatcher(map[string]string{"x-requested-with": ""}), | ||||
| 		headers: map[string]string{"X-Requested-With": "anything"}, | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}), | ||||
| 		headers: map[string]string{}, | ||||
| 		result:  false, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| type hostMatcherTest struct { | ||||
| 	matcher *Route | ||||
| 	url     string | ||||
| 	vars    map[string]string | ||||
| 	result  bool | ||||
| } | ||||
|  | ||||
| var hostMatcherTests = []hostMatcherTest{ | ||||
| 	{ | ||||
| 		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), | ||||
| 		url:     "http://abc.def.ghi/", | ||||
| 		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"), | ||||
| 		url:     "http://a.b.c/", | ||||
| 		vars:    map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"}, | ||||
| 		result:  false, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| type methodMatcherTest struct { | ||||
| 	matcher methodMatcher | ||||
| 	method  string | ||||
| 	result  bool | ||||
| } | ||||
|  | ||||
| var methodMatcherTests = []methodMatcherTest{ | ||||
| 	{ | ||||
| 		matcher: methodMatcher([]string{"GET", "POST", "PUT"}), | ||||
| 		method:  "GET", | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: methodMatcher([]string{"GET", "POST", "PUT"}), | ||||
| 		method:  "POST", | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: methodMatcher([]string{"GET", "POST", "PUT"}), | ||||
| 		method:  "PUT", | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: methodMatcher([]string{"GET", "POST", "PUT"}), | ||||
| 		method:  "DELETE", | ||||
| 		result:  false, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| type pathMatcherTest struct { | ||||
| 	matcher *Route | ||||
| 	url     string | ||||
| 	vars    map[string]string | ||||
| 	result  bool | ||||
| } | ||||
|  | ||||
| var pathMatcherTests = []pathMatcherTest{ | ||||
| 	{ | ||||
| 		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), | ||||
| 		url:     "http://localhost:8080/123/456/789", | ||||
| 		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"}, | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"), | ||||
| 		url:     "http://localhost:8080/1/2/3", | ||||
| 		vars:    map[string]string{"foo": "123", "bar": "456", "baz": "789"}, | ||||
| 		result:  false, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| type schemeMatcherTest struct { | ||||
| 	matcher schemeMatcher | ||||
| 	url     string | ||||
| 	result  bool | ||||
| } | ||||
|  | ||||
| var schemeMatcherTests = []schemeMatcherTest{ | ||||
| 	{ | ||||
| 		matcher: schemeMatcher([]string{"http", "https"}), | ||||
| 		url:     "http://localhost:8080/", | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: schemeMatcher([]string{"http", "https"}), | ||||
| 		url:     "https://localhost:8080/", | ||||
| 		result:  true, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: schemeMatcher([]string{"https"}), | ||||
| 		url:     "http://localhost:8080/", | ||||
| 		result:  false, | ||||
| 	}, | ||||
| 	{ | ||||
| 		matcher: schemeMatcher([]string{"http"}), | ||||
| 		url:     "https://localhost:8080/", | ||||
| 		result:  false, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| type urlBuildingTest struct { | ||||
| 	route *Route | ||||
| 	vars  []string | ||||
| 	url   string | ||||
| } | ||||
|  | ||||
| var urlBuildingTests = []urlBuildingTest{ | ||||
| 	{ | ||||
| 		route: new(Route).Host("foo.domain.com"), | ||||
| 		vars:  []string{}, | ||||
| 		url:   "http://foo.domain.com", | ||||
| 	}, | ||||
| 	{ | ||||
| 		route: new(Route).Host("{subdomain}.domain.com"), | ||||
| 		vars:  []string{"subdomain", "bar"}, | ||||
| 		url:   "http://bar.domain.com", | ||||
| 	}, | ||||
| 	{ | ||||
| 		route: new(Route).Host("foo.domain.com").Path("/articles"), | ||||
| 		vars:  []string{}, | ||||
| 		url:   "http://foo.domain.com/articles", | ||||
| 	}, | ||||
| 	{ | ||||
| 		route: new(Route).Path("/articles"), | ||||
| 		vars:  []string{}, | ||||
| 		url:   "/articles", | ||||
| 	}, | ||||
| 	{ | ||||
| 		route: new(Route).Path("/articles/{category}/{id:[0-9]+}"), | ||||
| 		vars:  []string{"category", "technology", "id", "42"}, | ||||
| 		url:   "/articles/technology/42", | ||||
| 	}, | ||||
| 	{ | ||||
| 		route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"), | ||||
| 		vars:  []string{"subdomain", "foo", "category", "technology", "id", "42"}, | ||||
| 		url:   "http://foo.domain.com/articles/technology/42", | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestHeaderMatcher(t *testing.T) { | ||||
| 	for _, v := range headerMatcherTests { | ||||
| 		request, _ := http.NewRequest("GET", "http://localhost:8080/", nil) | ||||
| 		for key, value := range v.headers { | ||||
| 			request.Header.Add(key, value) | ||||
| 		} | ||||
| 		var routeMatch RouteMatch | ||||
| 		result := v.matcher.Match(request, &routeMatch) | ||||
| 		if result != v.result { | ||||
| 			if v.result { | ||||
| 				t.Errorf("%#v: should match %v.", v.matcher, request.Header) | ||||
| 			} else { | ||||
| 				t.Errorf("%#v: should not match %v.", v.matcher, request.Header) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestHostMatcher(t *testing.T) { | ||||
| 	for _, v := range hostMatcherTests { | ||||
| 		request, _ := http.NewRequest("GET", v.url, nil) | ||||
| 		var routeMatch RouteMatch | ||||
| 		result := v.matcher.Match(request, &routeMatch) | ||||
| 		vars := routeMatch.Vars | ||||
| 		if result != v.result { | ||||
| 			if v.result { | ||||
| 				t.Errorf("%#v: should match %v.", v.matcher, v.url) | ||||
| 			} else { | ||||
| 				t.Errorf("%#v: should not match %v.", v.matcher, v.url) | ||||
| 			} | ||||
| 		} | ||||
| 		if result { | ||||
| 			if len(vars) != len(v.vars) { | ||||
| 				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) | ||||
| 			} | ||||
| 			for name, value := range vars { | ||||
| 				if v.vars[name] != value { | ||||
| 					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			if len(vars) != 0 { | ||||
| 				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMethodMatcher(t *testing.T) { | ||||
| 	for _, v := range methodMatcherTests { | ||||
| 		request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil) | ||||
| 		var routeMatch RouteMatch | ||||
| 		result := v.matcher.Match(request, &routeMatch) | ||||
| 		if result != v.result { | ||||
| 			if v.result { | ||||
| 				t.Errorf("%#v: should match %v.", v.matcher, v.method) | ||||
| 			} else { | ||||
| 				t.Errorf("%#v: should not match %v.", v.matcher, v.method) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPathMatcher(t *testing.T) { | ||||
| 	for _, v := range pathMatcherTests { | ||||
| 		request, _ := http.NewRequest("GET", v.url, nil) | ||||
| 		var routeMatch RouteMatch | ||||
| 		result := v.matcher.Match(request, &routeMatch) | ||||
| 		vars := routeMatch.Vars | ||||
| 		if result != v.result { | ||||
| 			if v.result { | ||||
| 				t.Errorf("%#v: should match %v.", v.matcher, v.url) | ||||
| 			} else { | ||||
| 				t.Errorf("%#v: should not match %v.", v.matcher, v.url) | ||||
| 			} | ||||
| 		} | ||||
| 		if result { | ||||
| 			if len(vars) != len(v.vars) { | ||||
| 				t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars)) | ||||
| 			} | ||||
| 			for name, value := range vars { | ||||
| 				if v.vars[name] != value { | ||||
| 					t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			if len(vars) != 0 { | ||||
| 				t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSchemeMatcher(t *testing.T) { | ||||
| 	for _, v := range schemeMatcherTests { | ||||
| 		request, _ := http.NewRequest("GET", v.url, nil) | ||||
| 		var routeMatch RouteMatch | ||||
| 		result := v.matcher.Match(request, &routeMatch) | ||||
| 		if result != v.result { | ||||
| 			if v.result { | ||||
| 				t.Errorf("%#v: should match %v.", v.matcher, v.url) | ||||
| 			} else { | ||||
| 				t.Errorf("%#v: should not match %v.", v.matcher, v.url) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestUrlBuilding(t *testing.T) { | ||||
|  | ||||
| 	for _, v := range urlBuildingTests { | ||||
| 		u, _ := v.route.URL(v.vars...) | ||||
| 		url := u.String() | ||||
| 		if url != v.url { | ||||
| 			t.Errorf("expected %v, got %v", v.url, url) | ||||
| 			/* | ||||
| 				reversePath := "" | ||||
| 				reverseHost := "" | ||||
| 				if v.route.pathTemplate != nil { | ||||
| 						reversePath = v.route.pathTemplate.Reverse | ||||
| 				} | ||||
| 				if v.route.hostTemplate != nil { | ||||
| 						reverseHost = v.route.hostTemplate.Reverse | ||||
| 				} | ||||
|  | ||||
| 				t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost) | ||||
| 			*/ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ArticleHandler := func(w http.ResponseWriter, r *http.Request) { | ||||
| 	} | ||||
|  | ||||
| 	router := NewRouter() | ||||
| 	router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article") | ||||
|  | ||||
| 	url, _ := router.Get("article").URL("category", "technology", "id", "42") | ||||
| 	expected := "/articles/technology/42" | ||||
| 	if url.String() != expected { | ||||
| 		t.Errorf("Expected %v, got %v", expected, url.String()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMatchedRouteName(t *testing.T) { | ||||
| 	routeName := "stock" | ||||
| 	router := NewRouter() | ||||
| 	route := router.NewRoute().Path("/products/").Name(routeName) | ||||
|  | ||||
| 	url := "http://www.domain.com/products/" | ||||
| 	request, _ := http.NewRequest("GET", url, nil) | ||||
| 	var rv RouteMatch | ||||
| 	ok := router.Match(request, &rv) | ||||
|  | ||||
| 	if !ok || rv.Route != route { | ||||
| 		t.Errorf("Expected same route, got %+v.", rv.Route) | ||||
| 	} | ||||
|  | ||||
| 	retName := rv.Route.GetName() | ||||
| 	if retName != routeName { | ||||
| 		t.Errorf("Expected %q, got %q.", routeName, retName) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSubRouting(t *testing.T) { | ||||
| 	// Example from docs. | ||||
| 	router := NewRouter() | ||||
| 	subrouter := router.NewRoute().Host("www.domain.com").Subrouter() | ||||
| 	route := subrouter.NewRoute().Path("/products/").Name("products") | ||||
|  | ||||
| 	url := "http://www.domain.com/products/" | ||||
| 	request, _ := http.NewRequest("GET", url, nil) | ||||
| 	var rv RouteMatch | ||||
| 	ok := router.Match(request, &rv) | ||||
|  | ||||
| 	if !ok || rv.Route != route { | ||||
| 		t.Errorf("Expected same route, got %+v.", rv.Route) | ||||
| 	} | ||||
|  | ||||
| 	u, _ := router.Get("products").URL() | ||||
| 	builtUrl := u.String() | ||||
| 	// Yay, subroute aware of the domain when building! | ||||
| 	if builtUrl != url { | ||||
| 		t.Errorf("Expected %q, got %q.", url, builtUrl) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestVariableNames(t *testing.T) { | ||||
| 	route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}") | ||||
| 	if route.err == nil { | ||||
| 		t.Errorf("Expected error for duplicated variable names") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRedirectSlash(t *testing.T) { | ||||
| 	var route *Route | ||||
| 	var routeMatch RouteMatch | ||||
| 	r := NewRouter() | ||||
|  | ||||
| 	r.StrictSlash(false) | ||||
| 	route = r.NewRoute() | ||||
| 	if route.strictSlash != false { | ||||
| 		t.Errorf("Expected false redirectSlash.") | ||||
| 	} | ||||
|  | ||||
| 	r.StrictSlash(true) | ||||
| 	route = r.NewRoute() | ||||
| 	if route.strictSlash != true { | ||||
| 		t.Errorf("Expected true redirectSlash.") | ||||
| 	} | ||||
|  | ||||
| 	route = new(Route) | ||||
| 	route.strictSlash = true | ||||
| 	route.Path("/{arg1}/{arg2:[0-9]+}/") | ||||
| 	request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil) | ||||
| 	routeMatch = RouteMatch{} | ||||
| 	_ = route.Match(request, &routeMatch) | ||||
| 	vars := routeMatch.Vars | ||||
| 	if vars["arg1"] != "foo" { | ||||
| 		t.Errorf("Expected foo.") | ||||
| 	} | ||||
| 	if vars["arg2"] != "123" { | ||||
| 		t.Errorf("Expected 123.") | ||||
| 	} | ||||
| 	rsp := NewRecorder() | ||||
| 	routeMatch.Handler.ServeHTTP(rsp, request) | ||||
| 	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" { | ||||
| 		t.Errorf("Expected redirect header.") | ||||
| 	} | ||||
|  | ||||
| 	route = new(Route) | ||||
| 	route.strictSlash = true | ||||
| 	route.Path("/{arg1}/{arg2:[0-9]+}") | ||||
| 	request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil) | ||||
| 	routeMatch = RouteMatch{} | ||||
| 	_ = route.Match(request, &routeMatch) | ||||
| 	vars = routeMatch.Vars | ||||
| 	if vars["arg1"] != "foo" { | ||||
| 		t.Errorf("Expected foo.") | ||||
| 	} | ||||
| 	if vars["arg2"] != "123" { | ||||
| 		t.Errorf("Expected 123.") | ||||
| 	} | ||||
| 	rsp = NewRecorder() | ||||
| 	routeMatch.Handler.ServeHTTP(rsp, request) | ||||
| 	if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" { | ||||
| 		t.Errorf("Expected redirect header.") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test for the new regexp library, still not available in stable Go. | ||||
| func TestNewRegexp(t *testing.T) { | ||||
| 	var p *routeRegexp | ||||
| 	var matches []string | ||||
|  | ||||
| 	tests := map[string]map[string][]string{ | ||||
| 		"/{foo:a{2}}": { | ||||
| 			"/a":    nil, | ||||
| 			"/aa":   {"aa"}, | ||||
| 			"/aaa":  nil, | ||||
| 			"/aaaa": nil, | ||||
| 		}, | ||||
| 		"/{foo:a{2,}}": { | ||||
| 			"/a":    nil, | ||||
| 			"/aa":   {"aa"}, | ||||
| 			"/aaa":  {"aaa"}, | ||||
| 			"/aaaa": {"aaaa"}, | ||||
| 		}, | ||||
| 		"/{foo:a{2,3}}": { | ||||
| 			"/a":    nil, | ||||
| 			"/aa":   {"aa"}, | ||||
| 			"/aaa":  {"aaa"}, | ||||
| 			"/aaaa": nil, | ||||
| 		}, | ||||
| 		"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": { | ||||
| 			"/a":       nil, | ||||
| 			"/ab":      nil, | ||||
| 			"/abc":     nil, | ||||
| 			"/abcd":    nil, | ||||
| 			"/abc/ab":  {"abc", "ab"}, | ||||
| 			"/abc/abc": nil, | ||||
| 			"/abcd/ab": nil, | ||||
| 		}, | ||||
| 		`/{foo:\w{3,}}/{bar:\d{2,}}`: { | ||||
| 			"/a":        nil, | ||||
| 			"/ab":       nil, | ||||
| 			"/abc":      nil, | ||||
| 			"/abc/1":    nil, | ||||
| 			"/abc/12":   {"abc", "12"}, | ||||
| 			"/abcd/12":  {"abcd", "12"}, | ||||
| 			"/abcd/123": {"abcd", "123"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for pattern, paths := range tests { | ||||
| 		p, _ = newRouteRegexp(pattern, false, false, false, false) | ||||
| 		for path, result := range paths { | ||||
| 			matches = p.regexp.FindStringSubmatch(path) | ||||
| 			if result == nil { | ||||
| 				if matches != nil { | ||||
| 					t.Errorf("%v should not match %v.", pattern, path) | ||||
| 				} | ||||
| 			} else { | ||||
| 				if len(matches) != len(result)+1 { | ||||
| 					t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches)) | ||||
| 				} else { | ||||
| 					for k, v := range result { | ||||
| 						if matches[k+1] != v { | ||||
| 							t.Errorf("Expected %v, got %v.", v, matches[k+1]) | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										272
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/regexp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/regexp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package mux | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // newRouteRegexp parses a route template and returns a routeRegexp, | ||||
| // used to match a host, a path or a query string. | ||||
| // | ||||
| // It will extract named variables, assemble a regexp to be matched, create | ||||
| // a "reverse" template to build URLs and compile regexps to validate variable | ||||
| // values used in URL building. | ||||
| // | ||||
| // Previously we accepted only Python-like identifiers for variable | ||||
| // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that | ||||
| // name and pattern can't be empty, and names can't contain a colon. | ||||
| func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { | ||||
| 	// Check if it is well-formed. | ||||
| 	idxs, errBraces := braceIndices(tpl) | ||||
| 	if errBraces != nil { | ||||
| 		return nil, errBraces | ||||
| 	} | ||||
| 	// Backup the original. | ||||
| 	template := tpl | ||||
| 	// Now let's parse it. | ||||
| 	defaultPattern := "[^/]+" | ||||
| 	if matchQuery { | ||||
| 		defaultPattern = "[^?&]+" | ||||
| 		matchPrefix = true | ||||
| 	} else if matchHost { | ||||
| 		defaultPattern = "[^.]+" | ||||
| 		matchPrefix = false | ||||
| 	} | ||||
| 	// Only match strict slash if not matching | ||||
| 	if matchPrefix || matchHost || matchQuery { | ||||
| 		strictSlash = false | ||||
| 	} | ||||
| 	// Set a flag for strictSlash. | ||||
| 	endSlash := false | ||||
| 	if strictSlash && strings.HasSuffix(tpl, "/") { | ||||
| 		tpl = tpl[:len(tpl)-1] | ||||
| 		endSlash = true | ||||
| 	} | ||||
| 	varsN := make([]string, len(idxs)/2) | ||||
| 	varsR := make([]*regexp.Regexp, len(idxs)/2) | ||||
| 	pattern := bytes.NewBufferString("") | ||||
| 	if !matchQuery { | ||||
| 		pattern.WriteByte('^') | ||||
| 	} | ||||
| 	reverse := bytes.NewBufferString("") | ||||
| 	var end int | ||||
| 	var err error | ||||
| 	for i := 0; i < len(idxs); i += 2 { | ||||
| 		// Set all values we are interested in. | ||||
| 		raw := tpl[end:idxs[i]] | ||||
| 		end = idxs[i+1] | ||||
| 		parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) | ||||
| 		name := parts[0] | ||||
| 		patt := defaultPattern | ||||
| 		if len(parts) == 2 { | ||||
| 			patt = parts[1] | ||||
| 		} | ||||
| 		// Name or pattern can't be empty. | ||||
| 		if name == "" || patt == "" { | ||||
| 			return nil, fmt.Errorf("mux: missing name or pattern in %q", | ||||
| 				tpl[idxs[i]:end]) | ||||
| 		} | ||||
| 		// Build the regexp pattern. | ||||
| 		fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) | ||||
| 		// Build the reverse template. | ||||
| 		fmt.Fprintf(reverse, "%s%%s", raw) | ||||
| 		// Append variable name and compiled pattern. | ||||
| 		varsN[i/2] = name | ||||
| 		varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	// Add the remaining. | ||||
| 	raw := tpl[end:] | ||||
| 	pattern.WriteString(regexp.QuoteMeta(raw)) | ||||
| 	if strictSlash { | ||||
| 		pattern.WriteString("[/]?") | ||||
| 	} | ||||
| 	if !matchPrefix { | ||||
| 		pattern.WriteByte('$') | ||||
| 	} | ||||
| 	reverse.WriteString(raw) | ||||
| 	if endSlash { | ||||
| 		reverse.WriteByte('/') | ||||
| 	} | ||||
| 	// Compile full regexp. | ||||
| 	reg, errCompile := regexp.Compile(pattern.String()) | ||||
| 	if errCompile != nil { | ||||
| 		return nil, errCompile | ||||
| 	} | ||||
| 	// Done! | ||||
| 	return &routeRegexp{ | ||||
| 		template:    template, | ||||
| 		matchHost:   matchHost, | ||||
| 		matchQuery:  matchQuery, | ||||
| 		strictSlash: strictSlash, | ||||
| 		regexp:      reg, | ||||
| 		reverse:     reverse.String(), | ||||
| 		varsN:       varsN, | ||||
| 		varsR:       varsR, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // routeRegexp stores a regexp to match a host or path and information to | ||||
| // collect and validate route variables. | ||||
| type routeRegexp struct { | ||||
| 	// The unmodified template. | ||||
| 	template string | ||||
| 	// True for host match, false for path or query string match. | ||||
| 	matchHost bool | ||||
| 	// True for query string match, false for path and host match. | ||||
| 	matchQuery bool | ||||
| 	// The strictSlash value defined on the route, but disabled if PathPrefix was used. | ||||
| 	strictSlash bool | ||||
| 	// Expanded regexp. | ||||
| 	regexp *regexp.Regexp | ||||
| 	// Reverse template. | ||||
| 	reverse string | ||||
| 	// Variable names. | ||||
| 	varsN []string | ||||
| 	// Variable regexps (validators). | ||||
| 	varsR []*regexp.Regexp | ||||
| } | ||||
|  | ||||
| // Match matches the regexp against the URL host or path. | ||||
| func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { | ||||
| 	if !r.matchHost { | ||||
| 		if r.matchQuery { | ||||
| 			return r.regexp.MatchString(req.URL.RawQuery) | ||||
| 		} else { | ||||
| 			return r.regexp.MatchString(req.URL.Path) | ||||
| 		} | ||||
| 	} | ||||
| 	return r.regexp.MatchString(getHost(req)) | ||||
| } | ||||
|  | ||||
| // url builds a URL part using the given values. | ||||
| func (r *routeRegexp) url(values map[string]string) (string, error) { | ||||
| 	urlValues := make([]interface{}, len(r.varsN)) | ||||
| 	for k, v := range r.varsN { | ||||
| 		value, ok := values[v] | ||||
| 		if !ok { | ||||
| 			return "", fmt.Errorf("mux: missing route variable %q", v) | ||||
| 		} | ||||
| 		urlValues[k] = value | ||||
| 	} | ||||
| 	rv := fmt.Sprintf(r.reverse, urlValues...) | ||||
| 	if !r.regexp.MatchString(rv) { | ||||
| 		// The URL is checked against the full regexp, instead of checking | ||||
| 		// individual variables. This is faster but to provide a good error | ||||
| 		// message, we check individual regexps if the URL doesn't match. | ||||
| 		for k, v := range r.varsN { | ||||
| 			if !r.varsR[k].MatchString(values[v]) { | ||||
| 				return "", fmt.Errorf( | ||||
| 					"mux: variable %q doesn't match, expected %q", values[v], | ||||
| 					r.varsR[k].String()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return rv, nil | ||||
| } | ||||
|  | ||||
| // braceIndices returns the first level curly brace indices from a string. | ||||
| // It returns an error in case of unbalanced braces. | ||||
| func braceIndices(s string) ([]int, error) { | ||||
| 	var level, idx int | ||||
| 	idxs := make([]int, 0) | ||||
| 	for i := 0; i < len(s); i++ { | ||||
| 		switch s[i] { | ||||
| 		case '{': | ||||
| 			if level++; level == 1 { | ||||
| 				idx = i | ||||
| 			} | ||||
| 		case '}': | ||||
| 			if level--; level == 0 { | ||||
| 				idxs = append(idxs, idx, i+1) | ||||
| 			} else if level < 0 { | ||||
| 				return nil, fmt.Errorf("mux: unbalanced braces in %q", s) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if level != 0 { | ||||
| 		return nil, fmt.Errorf("mux: unbalanced braces in %q", s) | ||||
| 	} | ||||
| 	return idxs, nil | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // routeRegexpGroup | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // routeRegexpGroup groups the route matchers that carry variables. | ||||
| type routeRegexpGroup struct { | ||||
| 	host    *routeRegexp | ||||
| 	path    *routeRegexp | ||||
| 	queries []*routeRegexp | ||||
| } | ||||
|  | ||||
| // setMatch extracts the variables from the URL once a route matches. | ||||
| func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { | ||||
| 	// Store host variables. | ||||
| 	if v.host != nil { | ||||
| 		hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) | ||||
| 		if hostVars != nil { | ||||
| 			for k, v := range v.host.varsN { | ||||
| 				m.Vars[v] = hostVars[k+1] | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// Store path variables. | ||||
| 	if v.path != nil { | ||||
| 		pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) | ||||
| 		if pathVars != nil { | ||||
| 			for k, v := range v.path.varsN { | ||||
| 				m.Vars[v] = pathVars[k+1] | ||||
| 			} | ||||
| 			// Check if we should redirect. | ||||
| 			if v.path.strictSlash { | ||||
| 				p1 := strings.HasSuffix(req.URL.Path, "/") | ||||
| 				p2 := strings.HasSuffix(v.path.template, "/") | ||||
| 				if p1 != p2 { | ||||
| 					u, _ := url.Parse(req.URL.String()) | ||||
| 					if p1 { | ||||
| 						u.Path = u.Path[:len(u.Path)-1] | ||||
| 					} else { | ||||
| 						u.Path += "/" | ||||
| 					} | ||||
| 					m.Handler = http.RedirectHandler(u.String(), 301) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// Store query string variables. | ||||
| 	rawQuery := req.URL.RawQuery | ||||
| 	for _, q := range v.queries { | ||||
| 		queryVars := q.regexp.FindStringSubmatch(rawQuery) | ||||
| 		if queryVars != nil { | ||||
| 			for k, v := range q.varsN { | ||||
| 				m.Vars[v] = queryVars[k+1] | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // getHost tries its best to return the request host. | ||||
| func getHost(r *http.Request) string { | ||||
| 	if r.URL.IsAbs() { | ||||
| 		return r.URL.Host | ||||
| 	} | ||||
| 	host := r.Host | ||||
| 	// Slice off any port information. | ||||
| 	if i := strings.Index(host, ":"); i != -1 { | ||||
| 		host = host[:i] | ||||
| 	} | ||||
| 	return host | ||||
|  | ||||
| } | ||||
							
								
								
									
										571
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/route.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										571
									
								
								Godeps/_workspace/src/github.com/gorilla/mux/route.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,571 @@ | ||||
| // Copyright 2012 The Gorilla Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package mux | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Route stores information to match a request and build URLs. | ||||
| type Route struct { | ||||
| 	// Parent where the route was registered (a Router). | ||||
| 	parent parentRoute | ||||
| 	// Request handler for the route. | ||||
| 	handler http.Handler | ||||
| 	// List of matchers. | ||||
| 	matchers []matcher | ||||
| 	// Manager for the variables from host and path. | ||||
| 	regexp *routeRegexpGroup | ||||
| 	// If true, when the path pattern is "/path/", accessing "/path" will | ||||
| 	// redirect to the former and vice versa. | ||||
| 	strictSlash bool | ||||
| 	// If true, this route never matches: it is only used to build URLs. | ||||
| 	buildOnly bool | ||||
| 	// The name used to build URLs. | ||||
| 	name string | ||||
| 	// Error resulted from building a route. | ||||
| 	err error | ||||
|  | ||||
| 	buildVarsFunc BuildVarsFunc | ||||
| } | ||||
|  | ||||
| // Match matches the route against the request. | ||||
| func (r *Route) Match(req *http.Request, match *RouteMatch) bool { | ||||
| 	if r.buildOnly || r.err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	// Match everything. | ||||
| 	for _, m := range r.matchers { | ||||
| 		if matched := m.Match(req, match); !matched { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	// Yay, we have a match. Let's collect some info about it. | ||||
| 	if match.Route == nil { | ||||
| 		match.Route = r | ||||
| 	} | ||||
| 	if match.Handler == nil { | ||||
| 		match.Handler = r.handler | ||||
| 	} | ||||
| 	if match.Vars == nil { | ||||
| 		match.Vars = make(map[string]string) | ||||
| 	} | ||||
| 	// Set variables. | ||||
| 	if r.regexp != nil { | ||||
| 		r.regexp.setMatch(req, match, r) | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // Route attributes | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // GetError returns an error resulted from building the route, if any. | ||||
| func (r *Route) GetError() error { | ||||
| 	return r.err | ||||
| } | ||||
|  | ||||
| // BuildOnly sets the route to never match: it is only used to build URLs. | ||||
| func (r *Route) BuildOnly() *Route { | ||||
| 	r.buildOnly = true | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Handler -------------------------------------------------------------------- | ||||
|  | ||||
| // Handler sets a handler for the route. | ||||
| func (r *Route) Handler(handler http.Handler) *Route { | ||||
| 	if r.err == nil { | ||||
| 		r.handler = handler | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // HandlerFunc sets a handler function for the route. | ||||
| func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { | ||||
| 	return r.Handler(http.HandlerFunc(f)) | ||||
| } | ||||
|  | ||||
| // GetHandler returns the handler for the route, if any. | ||||
| func (r *Route) GetHandler() http.Handler { | ||||
| 	return r.handler | ||||
| } | ||||
|  | ||||
| // Name ----------------------------------------------------------------------- | ||||
|  | ||||
| // Name sets the name for the route, used to build URLs. | ||||
| // If the name was registered already it will be overwritten. | ||||
| func (r *Route) Name(name string) *Route { | ||||
| 	if r.name != "" { | ||||
| 		r.err = fmt.Errorf("mux: route already has name %q, can't set %q", | ||||
| 			r.name, name) | ||||
| 	} | ||||
| 	if r.err == nil { | ||||
| 		r.name = name | ||||
| 		r.getNamedRoutes()[name] = r | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // GetName returns the name for the route, if any. | ||||
| func (r *Route) GetName() string { | ||||
| 	return r.name | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // Matchers | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // matcher types try to match a request. | ||||
| type matcher interface { | ||||
| 	Match(*http.Request, *RouteMatch) bool | ||||
| } | ||||
|  | ||||
| // addMatcher adds a matcher to the route. | ||||
| func (r *Route) addMatcher(m matcher) *Route { | ||||
| 	if r.err == nil { | ||||
| 		r.matchers = append(r.matchers, m) | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // addRegexpMatcher adds a host or path matcher and builder to a route. | ||||
| func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { | ||||
| 	if r.err != nil { | ||||
| 		return r.err | ||||
| 	} | ||||
| 	r.regexp = r.getRegexpGroup() | ||||
| 	if !matchHost && !matchQuery { | ||||
| 		if len(tpl) == 0 || tpl[0] != '/' { | ||||
| 			return fmt.Errorf("mux: path must start with a slash, got %q", tpl) | ||||
| 		} | ||||
| 		if r.regexp.path != nil { | ||||
| 			tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl | ||||
| 		} | ||||
| 	} | ||||
| 	rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, q := range r.regexp.queries { | ||||
| 		if err = uniqueVars(rr.varsN, q.varsN); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if matchHost { | ||||
| 		if r.regexp.path != nil { | ||||
| 			if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		r.regexp.host = rr | ||||
| 	} else { | ||||
| 		if r.regexp.host != nil { | ||||
| 			if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		if matchQuery { | ||||
| 			r.regexp.queries = append(r.regexp.queries, rr) | ||||
| 		} else { | ||||
| 			r.regexp.path = rr | ||||
| 		} | ||||
| 	} | ||||
| 	r.addMatcher(rr) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Headers -------------------------------------------------------------------- | ||||
|  | ||||
| // headerMatcher matches the request against header values. | ||||
| type headerMatcher map[string]string | ||||
|  | ||||
| func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { | ||||
| 	return matchMap(m, r.Header, true) | ||||
| } | ||||
|  | ||||
| // Headers adds a matcher for request header values. | ||||
| // It accepts a sequence of key/value pairs to be matched. For example: | ||||
| // | ||||
| //     r := mux.NewRouter() | ||||
| //     r.Headers("Content-Type", "application/json", | ||||
| //               "X-Requested-With", "XMLHttpRequest") | ||||
| // | ||||
| // The above route will only match if both request header values match. | ||||
| // | ||||
| // It the value is an empty string, it will match any value if the key is set. | ||||
| func (r *Route) Headers(pairs ...string) *Route { | ||||
| 	if r.err == nil { | ||||
| 		var headers map[string]string | ||||
| 		headers, r.err = mapFromPairs(pairs...) | ||||
| 		return r.addMatcher(headerMatcher(headers)) | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Host ----------------------------------------------------------------------- | ||||
|  | ||||
| // Host adds a matcher for the URL host. | ||||
| // It accepts a template with zero or more URL variables enclosed by {}. | ||||
| // Variables can define an optional regexp pattern to me matched: | ||||
| // | ||||
| // - {name} matches anything until the next dot. | ||||
| // | ||||
| // - {name:pattern} matches the given regexp pattern. | ||||
| // | ||||
| // For example: | ||||
| // | ||||
| //     r := mux.NewRouter() | ||||
| //     r.Host("www.domain.com") | ||||
| //     r.Host("{subdomain}.domain.com") | ||||
| //     r.Host("{subdomain:[a-z]+}.domain.com") | ||||
| // | ||||
| // Variable names must be unique in a given route. They can be retrieved | ||||
| // calling mux.Vars(request). | ||||
| func (r *Route) Host(tpl string) *Route { | ||||
| 	r.err = r.addRegexpMatcher(tpl, true, false, false) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // MatcherFunc ---------------------------------------------------------------- | ||||
|  | ||||
| // MatcherFunc is the function signature used by custom matchers. | ||||
| type MatcherFunc func(*http.Request, *RouteMatch) bool | ||||
|  | ||||
| func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { | ||||
| 	return m(r, match) | ||||
| } | ||||
|  | ||||
| // MatcherFunc adds a custom function to be used as request matcher. | ||||
| func (r *Route) MatcherFunc(f MatcherFunc) *Route { | ||||
| 	return r.addMatcher(f) | ||||
| } | ||||
|  | ||||
| // Methods -------------------------------------------------------------------- | ||||
|  | ||||
| // methodMatcher matches the request against HTTP methods. | ||||
| type methodMatcher []string | ||||
|  | ||||
| func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { | ||||
| 	return matchInArray(m, r.Method) | ||||
| } | ||||
|  | ||||
| // Methods adds a matcher for HTTP methods. | ||||
| // It accepts a sequence of one or more methods to be matched, e.g.: | ||||
| // "GET", "POST", "PUT". | ||||
| func (r *Route) Methods(methods ...string) *Route { | ||||
| 	for k, v := range methods { | ||||
| 		methods[k] = strings.ToUpper(v) | ||||
| 	} | ||||
| 	return r.addMatcher(methodMatcher(methods)) | ||||
| } | ||||
|  | ||||
| // Path ----------------------------------------------------------------------- | ||||
|  | ||||
| // Path adds a matcher for the URL path. | ||||
| // It accepts a template with zero or more URL variables enclosed by {}. The | ||||
| // template must start with a "/". | ||||
| // Variables can define an optional regexp pattern to me matched: | ||||
| // | ||||
| // - {name} matches anything until the next slash. | ||||
| // | ||||
| // - {name:pattern} matches the given regexp pattern. | ||||
| // | ||||
| // For example: | ||||
| // | ||||
| //     r := mux.NewRouter() | ||||
| //     r.Path("/products/").Handler(ProductsHandler) | ||||
| //     r.Path("/products/{key}").Handler(ProductsHandler) | ||||
| //     r.Path("/articles/{category}/{id:[0-9]+}"). | ||||
| //       Handler(ArticleHandler) | ||||
| // | ||||
| // Variable names must be unique in a given route. They can be retrieved | ||||
| // calling mux.Vars(request). | ||||
| func (r *Route) Path(tpl string) *Route { | ||||
| 	r.err = r.addRegexpMatcher(tpl, false, false, false) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // PathPrefix ----------------------------------------------------------------- | ||||
|  | ||||
| // PathPrefix adds a matcher for the URL path prefix. This matches if the given | ||||
| // template is a prefix of the full URL path. See Route.Path() for details on | ||||
| // the tpl argument. | ||||
| // | ||||
| // Note that it does not treat slashes specially ("/foobar/" will be matched by | ||||
| // the prefix "/foo") so you may want to use a trailing slash here. | ||||
| // | ||||
| // Also note that the setting of Router.StrictSlash() has no effect on routes | ||||
| // with a PathPrefix matcher. | ||||
| func (r *Route) PathPrefix(tpl string) *Route { | ||||
| 	r.err = r.addRegexpMatcher(tpl, false, true, false) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Query ---------------------------------------------------------------------- | ||||
|  | ||||
| // Queries adds a matcher for URL query values. | ||||
| // It accepts a sequence of key/value pairs. Values may define variables. | ||||
| // For example: | ||||
| // | ||||
| //     r := mux.NewRouter() | ||||
| //     r.Queries("foo", "bar", "id", "{id:[0-9]+}") | ||||
| // | ||||
| // The above route will only match if the URL contains the defined queries | ||||
| // values, e.g.: ?foo=bar&id=42. | ||||
| // | ||||
| // It the value is an empty string, it will match any value if the key is set. | ||||
| // | ||||
| // Variables can define an optional regexp pattern to me matched: | ||||
| // | ||||
| // - {name} matches anything until the next slash. | ||||
| // | ||||
| // - {name:pattern} matches the given regexp pattern. | ||||
| func (r *Route) Queries(pairs ...string) *Route { | ||||
| 	length := len(pairs) | ||||
| 	if length%2 != 0 { | ||||
| 		r.err = fmt.Errorf( | ||||
| 			"mux: number of parameters must be multiple of 2, got %v", pairs) | ||||
| 		return nil | ||||
| 	} | ||||
| 	for i := 0; i < length; i += 2 { | ||||
| 		if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil { | ||||
| 			return r | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Schemes -------------------------------------------------------------------- | ||||
|  | ||||
| // schemeMatcher matches the request against URL schemes. | ||||
| type schemeMatcher []string | ||||
|  | ||||
| func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { | ||||
| 	return matchInArray(m, r.URL.Scheme) | ||||
| } | ||||
|  | ||||
| // Schemes adds a matcher for URL schemes. | ||||
| // It accepts a sequence of schemes to be matched, e.g.: "http", "https". | ||||
| func (r *Route) Schemes(schemes ...string) *Route { | ||||
| 	for k, v := range schemes { | ||||
| 		schemes[k] = strings.ToLower(v) | ||||
| 	} | ||||
| 	return r.addMatcher(schemeMatcher(schemes)) | ||||
| } | ||||
|  | ||||
| // BuildVarsFunc -------------------------------------------------------------- | ||||
|  | ||||
| // BuildVarsFunc is the function signature used by custom build variable | ||||
| // functions (which can modify route variables before a route's URL is built). | ||||
| type BuildVarsFunc func(map[string]string) map[string]string | ||||
|  | ||||
| // BuildVarsFunc adds a custom function to be used to modify build variables | ||||
| // before a route's URL is built. | ||||
| func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { | ||||
| 	r.buildVarsFunc = f | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Subrouter ------------------------------------------------------------------ | ||||
|  | ||||
| // Subrouter creates a subrouter for the route. | ||||
| // | ||||
| // It will test the inner routes only if the parent route matched. For example: | ||||
| // | ||||
| //     r := mux.NewRouter() | ||||
| //     s := r.Host("www.domain.com").Subrouter() | ||||
| //     s.HandleFunc("/products/", ProductsHandler) | ||||
| //     s.HandleFunc("/products/{key}", ProductHandler) | ||||
| //     s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) | ||||
| // | ||||
| // Here, the routes registered in the subrouter won't be tested if the host | ||||
| // doesn't match. | ||||
| func (r *Route) Subrouter() *Router { | ||||
| 	router := &Router{parent: r, strictSlash: r.strictSlash} | ||||
| 	r.addMatcher(router) | ||||
| 	return router | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // URL building | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // URL builds a URL for the route. | ||||
| // | ||||
| // It accepts a sequence of key/value pairs for the route variables. For | ||||
| // example, given this route: | ||||
| // | ||||
| //     r := mux.NewRouter() | ||||
| //     r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). | ||||
| //       Name("article") | ||||
| // | ||||
| // ...a URL for it can be built using: | ||||
| // | ||||
| //     url, err := r.Get("article").URL("category", "technology", "id", "42") | ||||
| // | ||||
| // ...which will return an url.URL with the following path: | ||||
| // | ||||
| //     "/articles/technology/42" | ||||
| // | ||||
| // This also works for host variables: | ||||
| // | ||||
| //     r := mux.NewRouter() | ||||
| //     r.Host("{subdomain}.domain.com"). | ||||
| //       HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). | ||||
| //       Name("article") | ||||
| // | ||||
| //     // url.String() will be "http://news.domain.com/articles/technology/42" | ||||
| //     url, err := r.Get("article").URL("subdomain", "news", | ||||
| //                                      "category", "technology", | ||||
| //                                      "id", "42") | ||||
| // | ||||
| // All variables defined in the route are required, and their values must | ||||
| // conform to the corresponding patterns. | ||||
| func (r *Route) URL(pairs ...string) (*url.URL, error) { | ||||
| 	if r.err != nil { | ||||
| 		return nil, r.err | ||||
| 	} | ||||
| 	if r.regexp == nil { | ||||
| 		return nil, errors.New("mux: route doesn't have a host or path") | ||||
| 	} | ||||
| 	values, err := r.prepareVars(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var scheme, host, path string | ||||
| 	if r.regexp.host != nil { | ||||
| 		// Set a default scheme. | ||||
| 		scheme = "http" | ||||
| 		if host, err = r.regexp.host.url(values); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	if r.regexp.path != nil { | ||||
| 		if path, err = r.regexp.path.url(values); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	return &url.URL{ | ||||
| 		Scheme: scheme, | ||||
| 		Host:   host, | ||||
| 		Path:   path, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // URLHost builds the host part of the URL for a route. See Route.URL(). | ||||
| // | ||||
| // The route must have a host defined. | ||||
| func (r *Route) URLHost(pairs ...string) (*url.URL, error) { | ||||
| 	if r.err != nil { | ||||
| 		return nil, r.err | ||||
| 	} | ||||
| 	if r.regexp == nil || r.regexp.host == nil { | ||||
| 		return nil, errors.New("mux: route doesn't have a host") | ||||
| 	} | ||||
| 	values, err := r.prepareVars(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	host, err := r.regexp.host.url(values) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &url.URL{ | ||||
| 		Scheme: "http", | ||||
| 		Host:   host, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // URLPath builds the path part of the URL for a route. See Route.URL(). | ||||
| // | ||||
| // The route must have a path defined. | ||||
| func (r *Route) URLPath(pairs ...string) (*url.URL, error) { | ||||
| 	if r.err != nil { | ||||
| 		return nil, r.err | ||||
| 	} | ||||
| 	if r.regexp == nil || r.regexp.path == nil { | ||||
| 		return nil, errors.New("mux: route doesn't have a path") | ||||
| 	} | ||||
| 	values, err := r.prepareVars(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	path, err := r.regexp.path.url(values) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &url.URL{ | ||||
| 		Path: path, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // prepareVars converts the route variable pairs into a map. If the route has a | ||||
| // BuildVarsFunc, it is invoked. | ||||
| func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { | ||||
| 	m, err := mapFromPairs(pairs...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return r.buildVars(m), nil | ||||
| } | ||||
|  | ||||
| func (r *Route) buildVars(m map[string]string) map[string]string { | ||||
| 	if r.parent != nil { | ||||
| 		m = r.parent.buildVars(m) | ||||
| 	} | ||||
| 	if r.buildVarsFunc != nil { | ||||
| 		m = r.buildVarsFunc(m) | ||||
| 	} | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // ---------------------------------------------------------------------------- | ||||
| // parentRoute | ||||
| // ---------------------------------------------------------------------------- | ||||
|  | ||||
| // parentRoute allows routes to know about parent host and path definitions. | ||||
| type parentRoute interface { | ||||
| 	getNamedRoutes() map[string]*Route | ||||
| 	getRegexpGroup() *routeRegexpGroup | ||||
| 	buildVars(map[string]string) map[string]string | ||||
| } | ||||
|  | ||||
| // getNamedRoutes returns the map where named routes are registered. | ||||
| func (r *Route) getNamedRoutes() map[string]*Route { | ||||
| 	if r.parent == nil { | ||||
| 		// During tests router is not always set. | ||||
| 		r.parent = NewRouter() | ||||
| 	} | ||||
| 	return r.parent.getNamedRoutes() | ||||
| } | ||||
|  | ||||
| // getRegexpGroup returns regexp definitions from this route. | ||||
| func (r *Route) getRegexpGroup() *routeRegexpGroup { | ||||
| 	if r.regexp == nil { | ||||
| 		if r.parent == nil { | ||||
| 			// During tests router is not always set. | ||||
| 			r.parent = NewRouter() | ||||
| 		} | ||||
| 		regexp := r.parent.getRegexpGroup() | ||||
| 		if regexp == nil { | ||||
| 			r.regexp = new(routeRegexpGroup) | ||||
| 		} else { | ||||
| 			// Copy. | ||||
| 			r.regexp = &routeRegexpGroup{ | ||||
| 				host:    regexp.host, | ||||
| 				path:    regexp.path, | ||||
| 				queries: regexp.queries, | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return r.regexp | ||||
| } | ||||
							
								
								
									
										7
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| language: go | ||||
|  | ||||
| services: | ||||
|     - redis-server | ||||
|  | ||||
| go: | ||||
|     - 1.4 | ||||
							
								
								
									
										21
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2015 Alexander F Rødseth | ||||
|  | ||||
| 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. | ||||
							
								
								
									
										92
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| Simple Redis | ||||
| ============ | ||||
|  | ||||
| [](https://travis-ci.org/xyproto/simpleredis) | ||||
| [](http://godoc.org/github.com/xyproto/simpleredis) | ||||
|  | ||||
|  | ||||
| Easy way to use Redis from Go. | ||||
|  | ||||
|  | ||||
| Online API Documentation | ||||
| ------------------------ | ||||
|  | ||||
| [godoc.org](http://godoc.org/github.com/xyproto/simpleredis) | ||||
|  | ||||
|  | ||||
| Features and limitations | ||||
| ------------------------ | ||||
|  | ||||
| * Supports simple use of lists, hashmaps, sets and key/values | ||||
| * Deals mainly with strings | ||||
| * Uses the [redigo](https://github.com/garyburd/redigo) package | ||||
|  | ||||
|  | ||||
| Example usage | ||||
| ------------- | ||||
|  | ||||
| ~~~go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/xyproto/simpleredis" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// Check if the redis service is up | ||||
| 	if err := simpleredis.TestConnection(); err != nil { | ||||
| 		log.Fatalln("Could not connect to Redis. Is the service up and running?") | ||||
| 	} | ||||
|  | ||||
| 	// Use instead for testing if a different host/port is up. | ||||
| 	// simpleredis.TestConnectionHost("localhost:6379") | ||||
|  | ||||
| 	// Create a connection pool, connect to the given redis server | ||||
| 	pool := simpleredis.NewConnectionPool() | ||||
|  | ||||
| 	// Use this for connecting to a different redis host/port | ||||
| 	// pool := simpleredis.NewConnectionPoolHost("localhost:6379") | ||||
|  | ||||
| 	// For connecting to a different redis host/port, with a password | ||||
| 	// pool := simpleredis.NewConnectionPoolHost("password@redishost:6379") | ||||
|  | ||||
| 	// Close the connection pool right after this function returns | ||||
| 	defer pool.Close() | ||||
|  | ||||
| 	// Create a list named "greetings" | ||||
| 	list := simpleredis.NewList(pool, "greetings") | ||||
|  | ||||
| 	// Add "hello" to the list, check if there are errors | ||||
| 	if list.Add("hello") != nil { | ||||
| 		log.Fatalln("Could not add an item to list!") | ||||
| 	} | ||||
|  | ||||
| 	// Get the last item of the list | ||||
| 	if item, err := list.GetLast(); err != nil { | ||||
| 		log.Fatalln("Could not fetch the last item from the list!") | ||||
| 	} else { | ||||
| 		log.Println("The value of the stored item is:", item) | ||||
| 	} | ||||
|  | ||||
| 	// Remove the list | ||||
| 	if list.Remove() != nil { | ||||
| 		log.Fatalln("Could not remove the list!") | ||||
| 	} | ||||
| } | ||||
| ~~~ | ||||
|  | ||||
| Testing | ||||
| ------- | ||||
|  | ||||
| Redis must be up and running locally for the `go test` tests to work. | ||||
|  | ||||
|  | ||||
| Version, license and author | ||||
| --------------------------- | ||||
|  | ||||
| * Version: 1.0 | ||||
| * License: MIT | ||||
| * Author: Alexander F Rødseth | ||||
|  | ||||
							
								
								
									
										2
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/TODO.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/TODO.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| * Add all the tests from xyproto/db/db_test.go | ||||
| * Conform to the IHost interface in xyproto/db/interface.go | ||||
							
								
								
									
										49
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/example/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/example/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
|  | ||||
| 	"github.com/xyproto/simpleredis" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// Check if the redis service is up | ||||
| 	if err := simpleredis.TestConnection(); err != nil { | ||||
| 		log.Fatalln("Could not connect to Redis. Is the service up and running?") | ||||
| 	} | ||||
|  | ||||
| 	// Use instead for testing if a different host/port is up. | ||||
| 	// simpleredis.TestConnectionHost("localhost:6379") | ||||
|  | ||||
| 	// Create a connection pool, connect to the given redis server | ||||
| 	pool := simpleredis.NewConnectionPool() | ||||
|  | ||||
| 	// Use this for connecting to a different redis host/port | ||||
| 	// pool := simpleredis.NewConnectionPoolHost("localhost:6379") | ||||
|  | ||||
| 	// For connecting to a different redis host/port, with a password | ||||
| 	// pool := simpleredis.NewConnectionPoolHost("password@redishost:6379") | ||||
|  | ||||
| 	// Close the connection pool right after this function returns | ||||
| 	defer pool.Close() | ||||
|  | ||||
| 	// Create a list named "greetings" | ||||
| 	list := simpleredis.NewList(pool, "greetings") | ||||
|  | ||||
| 	// Add "hello" to the list, check if there are errors | ||||
| 	if list.Add("hello") != nil { | ||||
| 		log.Fatalln("Could not add an item to list!") | ||||
| 	} | ||||
|  | ||||
| 	// Get the last item of the list | ||||
| 	if item, err := list.GetLast(); err != nil { | ||||
| 		log.Fatalln("Could not fetch the last item from the list!") | ||||
| 	} else { | ||||
| 		log.Println("The value of the stored item is:", item) | ||||
| 	} | ||||
|  | ||||
| 	// Remove the list | ||||
| 	if list.Remove() != nil { | ||||
| 		log.Fatalln("Could not remove the list!") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										411
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/simpleredis.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										411
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/simpleredis.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,411 @@ | ||||
| // Easy way to use Redis from Go. | ||||
| package simpleredis | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/garyburd/redigo/redis" | ||||
| ) | ||||
|  | ||||
| // Common for each of the redis datastructures used here | ||||
| type redisDatastructure struct { | ||||
| 	pool    *ConnectionPool | ||||
| 	id      string | ||||
| 	dbindex int | ||||
| } | ||||
|  | ||||
| type ( | ||||
| 	// A pool of readily available Redis connections | ||||
| 	ConnectionPool redis.Pool | ||||
|  | ||||
| 	List     redisDatastructure | ||||
| 	Set      redisDatastructure | ||||
| 	HashMap  redisDatastructure | ||||
| 	KeyValue redisDatastructure | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// Version number. Stable API within major version numbers. | ||||
| 	Version = 1.0 | ||||
| 	// The default [url]:port that Redis is running at | ||||
| 	defaultRedisServer = ":6379" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// How many connections should stay ready for requests, at a maximum? | ||||
| 	// When an idle connection is used, new idle connections are created. | ||||
| 	maxIdleConnections = 3 | ||||
| ) | ||||
|  | ||||
| /* --- Helper functions --- */ | ||||
|  | ||||
| // Connect to the local instance of Redis at port 6379 | ||||
| func newRedisConnection() (redis.Conn, error) { | ||||
| 	return newRedisConnectionTo(defaultRedisServer) | ||||
| } | ||||
|  | ||||
| // Connect to host:port, host may be omitted, so ":6379" is valid. | ||||
| // Will not try to AUTH with any given password (password@host:port). | ||||
| func newRedisConnectionTo(hostColonPort string) (redis.Conn, error) { | ||||
| 	// Discard the password, if provided | ||||
| 	if _, theRest, ok := twoFields(hostColonPort, "@"); ok { | ||||
| 		hostColonPort = theRest | ||||
| 	} | ||||
| 	hostColonPort = strings.TrimSpace(hostColonPort) | ||||
| 	return redis.Dial("tcp", hostColonPort) | ||||
| } | ||||
|  | ||||
| // Get a string from a list of results at a given position | ||||
| func getString(bi []interface{}, i int) string { | ||||
| 	return string(bi[i].([]uint8)) | ||||
| } | ||||
|  | ||||
| // Test if the local Redis server is up and running | ||||
| func TestConnection() (err error) { | ||||
| 	return TestConnectionHost(defaultRedisServer) | ||||
| } | ||||
|  | ||||
| // Test if a given Redis server at host:port is up and running. | ||||
| // Does not try to PING or AUTH. | ||||
| func TestConnectionHost(hostColonPort string) (err error) { | ||||
| 	// Connect to the given host:port | ||||
| 	conn, err := newRedisConnectionTo(hostColonPort) | ||||
| 	if conn != nil { | ||||
| 		conn.Close() | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			err = errors.New("Could not connect to redis server: " + hostColonPort) | ||||
| 		} | ||||
| 	}() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| /* --- ConnectionPool functions --- */ | ||||
|  | ||||
| // Create a new connection pool | ||||
| func NewConnectionPool() *ConnectionPool { | ||||
| 	// The second argument is the maximum number of idle connections | ||||
| 	redisPool := redis.NewPool(newRedisConnection, maxIdleConnections) | ||||
| 	pool := ConnectionPool(*redisPool) | ||||
| 	return &pool | ||||
| } | ||||
|  | ||||
| // Split a string into two parts, given a delimiter. | ||||
| // Returns the two parts and true if it works out. | ||||
| func twoFields(s, delim string) (string, string, bool) { | ||||
| 	if strings.Count(s, delim) != 1 { | ||||
| 		return s, "", false | ||||
| 	} | ||||
| 	fields := strings.Split(s, delim) | ||||
| 	return fields[0], fields[1], true | ||||
| } | ||||
|  | ||||
| // Create a new connection pool given a host:port string. | ||||
| // A password may be supplied as well, on the form "password@host:port". | ||||
| func NewConnectionPoolHost(hostColonPort string) *ConnectionPool { | ||||
| 	// Create a redis Pool | ||||
| 	redisPool := redis.NewPool( | ||||
| 		// Anonymous function for calling new RedisConnectionTo with the host:port | ||||
| 		func() (redis.Conn, error) { | ||||
| 			conn, err := newRedisConnectionTo(hostColonPort) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			// If a password is given, use it to authenticate | ||||
| 			if password, _, ok := twoFields(hostColonPort, "@"); ok { | ||||
| 				if password != "" { | ||||
| 					if _, err := conn.Do("AUTH", password); err != nil { | ||||
| 						conn.Close() | ||||
| 						return nil, err | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			return conn, err | ||||
| 		}, | ||||
| 		// Maximum number of idle connections to the redis database | ||||
| 		maxIdleConnections) | ||||
| 	pool := ConnectionPool(*redisPool) | ||||
| 	return &pool | ||||
| } | ||||
|  | ||||
| // Set the number of maximum *idle* connections standing ready when | ||||
| // creating new connection pools. When an idle connection is used, | ||||
| // a new idle connection is created. The default is 3 and should be fine | ||||
| // for most cases. | ||||
| func SetMaxIdleConnections(maximum int) { | ||||
| 	maxIdleConnections = maximum | ||||
| } | ||||
|  | ||||
| // Get one of the available connections from the connection pool, given a database index | ||||
| func (pool *ConnectionPool) Get(dbindex int) redis.Conn { | ||||
| 	redisPool := redis.Pool(*pool) | ||||
| 	conn := redisPool.Get() | ||||
| 	// The default database index is 0 | ||||
| 	if dbindex != 0 { | ||||
| 		// SELECT is not critical, ignore the return values | ||||
| 		conn.Do("SELECT", strconv.Itoa(dbindex)) | ||||
| 	} | ||||
| 	return conn | ||||
| } | ||||
|  | ||||
| // Ping the server by sending a PING command | ||||
| func (pool *ConnectionPool) Ping() (pong bool) { | ||||
| 	redisPool := redis.Pool(*pool) | ||||
| 	conn := redisPool.Get() | ||||
| 	_, err := conn.Do("PING") | ||||
| 	return err == nil | ||||
| } | ||||
|  | ||||
| // Close down the connection pool | ||||
| func (pool *ConnectionPool) Close() { | ||||
| 	redisPool := redis.Pool(*pool) | ||||
| 	redisPool.Close() | ||||
| } | ||||
|  | ||||
| /* --- List functions --- */ | ||||
|  | ||||
| // Create a new list | ||||
| func NewList(pool *ConnectionPool, id string) *List { | ||||
| 	return &List{pool, id, 0} | ||||
| } | ||||
|  | ||||
| // Select a different database | ||||
| func (rl *List) SelectDatabase(dbindex int) { | ||||
| 	rl.dbindex = dbindex | ||||
| } | ||||
|  | ||||
| // Add an element to the list | ||||
| func (rl *List) Add(value string) error { | ||||
| 	conn := rl.pool.Get(rl.dbindex) | ||||
| 	_, err := conn.Do("RPUSH", rl.id, value) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Get all elements of a list | ||||
| func (rl *List) GetAll() ([]string, error) { | ||||
| 	conn := rl.pool.Get(rl.dbindex) | ||||
| 	result, err := redis.Values(conn.Do("LRANGE", rl.id, "0", "-1")) | ||||
| 	strs := make([]string, len(result)) | ||||
| 	for i := 0; i < len(result); i++ { | ||||
| 		strs[i] = getString(result, i) | ||||
| 	} | ||||
| 	return strs, err | ||||
| } | ||||
|  | ||||
| // Get the last element of a list | ||||
| func (rl *List) GetLast() (string, error) { | ||||
| 	conn := rl.pool.Get(rl.dbindex) | ||||
| 	result, err := redis.Values(conn.Do("LRANGE", rl.id, "-1", "-1")) | ||||
| 	if len(result) == 1 { | ||||
| 		return getString(result, 0), err | ||||
| 	} | ||||
| 	return "", err | ||||
| } | ||||
|  | ||||
| // Get the last N elements of a list | ||||
| func (rl *List) GetLastN(n int) ([]string, error) { | ||||
| 	conn := rl.pool.Get(rl.dbindex) | ||||
| 	result, err := redis.Values(conn.Do("LRANGE", rl.id, "-"+strconv.Itoa(n), "-1")) | ||||
| 	strs := make([]string, len(result)) | ||||
| 	for i := 0; i < len(result); i++ { | ||||
| 		strs[i] = getString(result, i) | ||||
| 	} | ||||
| 	return strs, err | ||||
| } | ||||
|  | ||||
| // Remove this list | ||||
| func (rl *List) Remove() error { | ||||
| 	conn := rl.pool.Get(rl.dbindex) | ||||
| 	_, err := conn.Do("DEL", rl.id) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| /* --- Set functions --- */ | ||||
|  | ||||
| // Create a new set | ||||
| func NewSet(pool *ConnectionPool, id string) *Set { | ||||
| 	return &Set{pool, id, 0} | ||||
| } | ||||
|  | ||||
| // Select a different database | ||||
| func (rs *Set) SelectDatabase(dbindex int) { | ||||
| 	rs.dbindex = dbindex | ||||
| } | ||||
|  | ||||
| // Add an element to the set | ||||
| func (rs *Set) Add(value string) error { | ||||
| 	conn := rs.pool.Get(rs.dbindex) | ||||
| 	_, err := conn.Do("SADD", rs.id, value) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Check if a given value is in the set | ||||
| func (rs *Set) Has(value string) (bool, error) { | ||||
| 	conn := rs.pool.Get(rs.dbindex) | ||||
| 	retval, err := conn.Do("SISMEMBER", rs.id, value) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return redis.Bool(retval, err) | ||||
| } | ||||
|  | ||||
| // Get all elements of the set | ||||
| func (rs *Set) GetAll() ([]string, error) { | ||||
| 	conn := rs.pool.Get(rs.dbindex) | ||||
| 	result, err := redis.Values(conn.Do("SMEMBERS", rs.id)) | ||||
| 	strs := make([]string, len(result)) | ||||
| 	for i := 0; i < len(result); i++ { | ||||
| 		strs[i] = getString(result, i) | ||||
| 	} | ||||
| 	return strs, err | ||||
| } | ||||
|  | ||||
| // Remove an element from the set | ||||
| func (rs *Set) Del(value string) error { | ||||
| 	conn := rs.pool.Get(rs.dbindex) | ||||
| 	_, err := conn.Do("SREM", rs.id, value) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Remove this set | ||||
| func (rs *Set) Remove() error { | ||||
| 	conn := rs.pool.Get(rs.dbindex) | ||||
| 	_, err := conn.Do("DEL", rs.id) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| /* --- HashMap functions --- */ | ||||
|  | ||||
| // Create a new hashmap | ||||
| func NewHashMap(pool *ConnectionPool, id string) *HashMap { | ||||
| 	return &HashMap{pool, id, 0} | ||||
| } | ||||
|  | ||||
| // Select a different database | ||||
| func (rh *HashMap) SelectDatabase(dbindex int) { | ||||
| 	rh.dbindex = dbindex | ||||
| } | ||||
|  | ||||
| // Set a value in a hashmap given the element id (for instance a user id) and the key (for instance "password") | ||||
| func (rh *HashMap) Set(elementid, key, value string) error { | ||||
| 	conn := rh.pool.Get(rh.dbindex) | ||||
| 	_, err := conn.Do("HSET", rh.id+":"+elementid, key, value) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Get a value from a hashmap given the element id (for instance a user id) and the key (for instance "password") | ||||
| func (rh *HashMap) Get(elementid, key string) (string, error) { | ||||
| 	conn := rh.pool.Get(rh.dbindex) | ||||
| 	result, err := redis.String(conn.Do("HGET", rh.id+":"+elementid, key)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // Check if a given elementid + key is in the hash map | ||||
| func (rh *HashMap) Has(elementid, key string) (bool, error) { | ||||
| 	conn := rh.pool.Get(rh.dbindex) | ||||
| 	retval, err := conn.Do("HEXISTS", rh.id+":"+elementid, key) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return redis.Bool(retval, err) | ||||
| } | ||||
|  | ||||
| // Check if a given elementid exists as a hash map at all | ||||
| func (rh *HashMap) Exists(elementid string) (bool, error) { | ||||
| 	// TODO: key is not meant to be a wildcard, check for "*" | ||||
| 	return hasKey(rh.pool, rh.id+":"+elementid, rh.dbindex) | ||||
| } | ||||
|  | ||||
| // Get all elementid's for all hash elements | ||||
| func (rh *HashMap) GetAll() ([]string, error) { | ||||
| 	conn := rh.pool.Get(rh.dbindex) | ||||
| 	result, err := redis.Values(conn.Do("KEYS", rh.id+":*")) | ||||
| 	strs := make([]string, len(result)) | ||||
| 	idlen := len(rh.id) | ||||
| 	for i := 0; i < len(result); i++ { | ||||
| 		strs[i] = getString(result, i)[idlen+1:] | ||||
| 	} | ||||
| 	return strs, err | ||||
| } | ||||
|  | ||||
| // Remove a key for an entry in a hashmap (for instance the email field for a user) | ||||
| func (rh *HashMap) DelKey(elementid, key string) error { | ||||
| 	conn := rh.pool.Get(rh.dbindex) | ||||
| 	_, err := conn.Do("HDEL", rh.id+":"+elementid, key) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Remove an element (for instance a user) | ||||
| func (rh *HashMap) Del(elementid string) error { | ||||
| 	conn := rh.pool.Get(rh.dbindex) | ||||
| 	_, err := conn.Do("DEL", rh.id+":"+elementid) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Remove this hashmap | ||||
| func (rh *HashMap) Remove() error { | ||||
| 	conn := rh.pool.Get(rh.dbindex) | ||||
| 	_, err := conn.Do("DEL", rh.id) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| /* --- KeyValue functions --- */ | ||||
|  | ||||
| // Create a new key/value | ||||
| func NewKeyValue(pool *ConnectionPool, id string) *KeyValue { | ||||
| 	return &KeyValue{pool, id, 0} | ||||
| } | ||||
|  | ||||
| // Select a different database | ||||
| func (rkv *KeyValue) SelectDatabase(dbindex int) { | ||||
| 	rkv.dbindex = dbindex | ||||
| } | ||||
|  | ||||
| // Set a key and value | ||||
| func (rkv *KeyValue) Set(key, value string) error { | ||||
| 	conn := rkv.pool.Get(rkv.dbindex) | ||||
| 	_, err := conn.Do("SET", rkv.id+":"+key, value) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Get a value given a key | ||||
| func (rkv *KeyValue) Get(key string) (string, error) { | ||||
| 	conn := rkv.pool.Get(rkv.dbindex) | ||||
| 	result, err := redis.String(conn.Do("GET", rkv.id+":"+key)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // Remove a key | ||||
| func (rkv *KeyValue) Del(key string) error { | ||||
| 	conn := rkv.pool.Get(rkv.dbindex) | ||||
| 	_, err := conn.Do("DEL", rkv.id+":"+key) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Remove this key/value | ||||
| func (rkv *KeyValue) Remove() error { | ||||
| 	conn := rkv.pool.Get(rkv.dbindex) | ||||
| 	_, err := conn.Do("DEL", rkv.id) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // --- Generic redis functions --- | ||||
|  | ||||
| // Check if a key exists. The key can be a wildcard (ie. "user*"). | ||||
| func hasKey(pool *ConnectionPool, wildcard string, dbindex int) (bool, error) { | ||||
| 	conn := pool.Get(dbindex) | ||||
| 	result, err := redis.Values(conn.Do("KEYS", wildcard)) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	return len(result) > 0, nil | ||||
| } | ||||
							
								
								
									
										66
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/simpleredis_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Godeps/_workspace/src/github.com/xyproto/simpleredis/simpleredis_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| package simpleredis | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| var pool *ConnectionPool | ||||
|  | ||||
| func TestLocalConnection(t *testing.T) { | ||||
| 	if err := TestConnection(); err != nil { | ||||
| 		t.Errorf(err.Error()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRemoteConnection(t *testing.T) { | ||||
| 	if err := TestConnectionHost("foobared@ :6379"); err != nil { | ||||
| 		t.Errorf(err.Error()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestConnectionPool(t *testing.T) { | ||||
| 	pool = NewConnectionPool() | ||||
| } | ||||
|  | ||||
| func TestConnectionPoolHost(t *testing.T) { | ||||
| 	pool = NewConnectionPoolHost("localhost:6379") | ||||
| } | ||||
|  | ||||
| // Tests with password "foobared" if the previous connection test | ||||
| // did not result in a connection that responds to PING. | ||||
| func TestConnectionPoolHostPassword(t *testing.T) { | ||||
| 	if !pool.Ping() { | ||||
| 		// Try connecting with the default password | ||||
| 		pool = NewConnectionPoolHost("foobared@localhost:6379") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestList(t *testing.T) { | ||||
| 	const ( | ||||
| 		listname = "abc123_test_test_test_123abc" | ||||
| 		testdata = "123abc" | ||||
| 	) | ||||
| 	list := NewList(pool, listname) | ||||
| 	list.SelectDatabase(1) | ||||
| 	if err := list.Add(testdata); err != nil { | ||||
| 		t.Errorf("Error, could not add item to list! %s", err.Error()) | ||||
| 	} | ||||
| 	items, err := list.GetAll() | ||||
| 	if len(items) != 1 { | ||||
| 		t.Errorf("Error, wrong list length! %v", len(items)) | ||||
| 	} | ||||
| 	if (len(items) > 0) && (items[0] != testdata) { | ||||
| 		t.Errorf("Error, wrong list contents! %v", items) | ||||
| 	} | ||||
| 	err = list.Remove() | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Error, could not remove list! %s", err.Error()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTwoFields(t *testing.T) { | ||||
| 	test, test23, ok := twoFields("test1@test2@test3", "@") | ||||
| 	if ok && ((test != "test1") || (test23 != "test2@test3")) { | ||||
| 		t.Error("Error in twoFields functions") | ||||
| 	} | ||||
| } | ||||
| @@ -109,6 +109,3 @@ Thus we plan to add another tier of queueing, which empties the REDIS transactio | ||||
| For questions on running this app, you can ask on the google containers group (freenode ~ google-containers@googlegroups.com or #google-containers on IRC) | ||||
|  | ||||
| For questions about bigpetstore, and how the data is generated, ask on the apache bigtop mailing list. | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -16,17 +16,16 @@ See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| //package main | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/codegangsta/negroni" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/xyproto/simpleredis" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/codegangsta/negroni" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/xyproto/simpleredis" | ||||
| ) | ||||
| 
 | ||||
| //return the path to static assets (i.e. index.html) | ||||
 Submodule examples/k8petstore/web-server/src/github.com/codegangsta/negroni deleted from 1dd3ab0ff5
									
								
							 Submodule examples/k8petstore/web-server/src/github.com/garyburd/redigo deleted from 535138d7bc
									
								
							 Submodule examples/k8petstore/web-server/src/github.com/gorilla/context deleted from 215affda49
									
								
							 Submodule examples/k8petstore/web-server/src/github.com/gorilla/mux deleted from 8096f47503
									
								
							 Submodule examples/k8petstore/web-server/src/github.com/xyproto/simpleredis deleted from 5292687f53
									
								
							| @@ -48,6 +48,7 @@ readonly KUBE_TEST_TARGETS=( | ||||
|   cmd/integration | ||||
|   cmd/gendocs | ||||
|   cmd/genman | ||||
|   examples/k8petstore/web-server | ||||
| ) | ||||
| readonly KUBE_TEST_BINARIES=("${KUBE_TEST_TARGETS[@]##*/}") | ||||
| readonly KUBE_TEST_BINARIES_WIN=("${KUBE_TEST_BINARIES[@]/%/.exe}") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user