├── .gitignore ├── CHANGELOG.md ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README.md ├── cmd ├── keygen.go ├── reseed.go ├── utils.go └── verify.go ├── main.go ├── reseed ├── blacklist.go ├── server.go ├── service.go ├── utils.go └── zip.go ├── su3 ├── crypto.go └── su3.go └── vendor ├── github.com ├── codegangsta │ └── cli │ │ ├── .flake8 │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── altsrc │ │ ├── altsrc.go │ │ ├── flag.go │ │ ├── flag_generated.go │ │ ├── flag_test.go │ │ ├── helpers_test.go │ │ ├── input_source_context.go │ │ ├── map_input_source.go │ │ ├── toml_command_test.go │ │ ├── toml_file_loader.go │ │ ├── yaml_command_test.go │ │ └── yaml_file_loader.go │ │ ├── app.go │ │ ├── app_test.go │ │ ├── appveyor.yml │ │ ├── autocomplete │ │ ├── bash_autocomplete │ │ └── zsh_autocomplete │ │ ├── category.go │ │ ├── cli.go │ │ ├── command.go │ │ ├── command_test.go │ │ ├── context.go │ │ ├── context_test.go │ │ ├── errors.go │ │ ├── errors_test.go │ │ ├── flag-types.json │ │ ├── flag.go │ │ ├── flag_generated.go │ │ ├── flag_test.go │ │ ├── funcs.go │ │ ├── generate-flag-types │ │ ├── help.go │ │ ├── help_test.go │ │ ├── helpers_test.go │ │ ├── helpers_unix_test.go │ │ ├── helpers_windows_test.go │ │ └── runtests ├── garyburd │ └── redigo │ │ ├── .github │ │ ├── CONTRIBUTING.md │ │ └── ISSUE_TEMPLATE.md │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.markdown │ │ ├── internal │ │ ├── commandinfo.go │ │ ├── commandinfo_test.go │ │ └── redistest │ │ │ └── testdb.go │ │ ├── redis │ │ ├── conn.go │ │ ├── conn_test.go │ │ ├── doc.go │ │ ├── go17.go │ │ ├── log.go │ │ ├── pool.go │ │ ├── pool_test.go │ │ ├── pre_go17.go │ │ ├── pubsub.go │ │ ├── pubsub_test.go │ │ ├── redis.go │ │ ├── reply.go │ │ ├── reply_test.go │ │ ├── scan.go │ │ ├── scan_test.go │ │ ├── script.go │ │ ├── script_test.go │ │ ├── test_test.go │ │ └── zpop_example_test.go │ │ └── redisx │ │ ├── connmux.go │ │ ├── connmux_test.go │ │ └── doc.go ├── gorilla │ └── handlers │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── canonical.go │ │ ├── canonical_test.go │ │ ├── compress.go │ │ ├── compress_test.go │ │ ├── cors.go │ │ ├── cors_test.go │ │ ├── doc.go │ │ ├── handlers.go │ │ ├── handlers_go18.go │ │ ├── handlers_go18_test.go │ │ ├── handlers_pre18.go │ │ ├── handlers_test.go │ │ ├── proxy_headers.go │ │ ├── proxy_headers_test.go │ │ ├── recovery.go │ │ └── recovery_test.go ├── hashicorp │ └── golang-lru │ │ ├── .gitignore │ │ ├── 2q.go │ │ ├── 2q_test.go │ │ ├── LICENSE │ │ ├── README.md │ │ ├── arc.go │ │ ├── arc_test.go │ │ ├── lru.go │ │ ├── lru_test.go │ │ └── simplelru │ │ ├── lru.go │ │ └── lru_test.go └── justinas │ └── alice │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── chain.go │ └── chain_test.go └── gopkg.in └── throttled └── throttled.v2 ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── deprecated.go ├── deprecated_test.go ├── doc.go ├── example_test.go ├── http.go ├── http_test.go ├── rate.go ├── rate_test.go ├── store.go ├── store ├── deprecated.go ├── memstore │ ├── memstore.go │ └── memstore_test.go ├── redigostore │ ├── redigostore.go │ └── redisstore_test.go └── storetest │ ├── doc.go │ └── storetest.go ├── varyby.go └── varyby_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | /i2p-tools 2 | /cert.pem 3 | /key.pem 4 | /_netdb 5 | i2pseeds.su3 6 | *.pem -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2016-12-21 2 | * deactivating previous random time delta, makes only sense when patching ri too 3 | * app.Version = "0.1.6" 4 | 5 | 2016-10-09 6 | * seed the math random generator with time.Now().UnixNano() 7 | * added 6h+6h random time delta at su3-age to increase anonymity 8 | * app.Version = "0.1.5" 9 | 10 | 11 | 2016-05-15 12 | * README.md updated 13 | * allowed routerInfos age increased from 96 to 192 hours 14 | * app.Version = "0.1.4" 15 | 16 | 2016-03-05 17 | * app.Version = "0.1.3" 18 | * CRL creation added 19 | 20 | 2016-01-31 21 | * allowed TLS ciphers updated (hardened) 22 | * TLS certificate generation: RSA 4096 --> ECDSAWithSHA512 384bit secp384r1 23 | * ECDHE handshake: only CurveP384 + CurveP521, default CurveP256 removed 24 | * TLS certificate valid: 2y --> 5y 25 | * throttled.PerDay(4) --> PerHour(4), to enable limited testing 26 | * su3 RebuildInterval: 24h --> 90h, higher anonymity for the running i2p-router 27 | * numRi per su3 file: 75 --> 77 28 | 29 | 2016-01 30 | * fork from https://github.com/MDrollette/i2p-tools -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/codegangsta/cli" 6 | packages = ["."] 7 | revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" 8 | version = "v1.20.0" 9 | 10 | [[projects]] 11 | name = "github.com/garyburd/redigo" 12 | packages = ["internal","redis"] 13 | revision = "433969511232c397de61b1442f9fd49ec06ae9ba" 14 | version = "v1.1.0" 15 | 16 | [[projects]] 17 | name = "github.com/gorilla/handlers" 18 | packages = ["."] 19 | revision = "a4043c62cc2329bacda331d33fc908ab11ef0ec3" 20 | version = "v1.2.1" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "github.com/hashicorp/golang-lru" 25 | packages = [".","simplelru"] 26 | revision = "0a025b7e63adc15a622f29b0b2c4c3848243bbf6" 27 | 28 | [[projects]] 29 | branch = "master" 30 | name = "github.com/justinas/alice" 31 | packages = ["."] 32 | revision = "1051eaf52fcafdd87ead59d28b065f1fcb8274ec" 33 | 34 | [[projects]] 35 | name = "gopkg.in/throttled/throttled.v2" 36 | packages = [".","store","store/memstore","store/redigostore"] 37 | revision = "b5675e93f9d999b22f92d859a5bf2138d3641af4" 38 | version = "v2.0.3" 39 | 40 | [solve-meta] 41 | analyzer-name = "dep" 42 | analyzer-version = 1 43 | inputs-digest = "577e9731d3208e9a20eb0d81685d237d74c5dd727f214dafdfb4fe50308cbf02" 44 | solver-name = "gps-cdcl" 45 | solver-version = 1 46 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | name = "github.com/codegangsta/cli" 26 | version = "1.20.0" 27 | 28 | [[constraint]] 29 | name = "github.com/gorilla/handlers" 30 | version = "1.2.1" 31 | 32 | [[constraint]] 33 | branch = "master" 34 | name = "github.com/justinas/alice" 35 | 36 | [[constraint]] 37 | name = "gopkg.in/throttled/throttled.v2" 38 | version = "2.0.3" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Matt Drollette 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I2P Reseed Tools 2 | ================== 3 | 4 | This tool provides a secure and efficient reseed server for the I2P network. There are several utility commands to create, sign, and validate SU3 files. 5 | 6 | ## Installation 7 | 8 | If you have go installed you can download, build, and install this tool with `go get` 9 | 10 | ``` 11 | go get github.com/MDrollette/i2p-tools 12 | i2p-tools -h 13 | ``` 14 | 15 | ## Usage 16 | 17 | ### Locally behind a webserver (reverse proxy setup), preferred: 18 | 19 | ``` 20 | i2p-tools reseed --signer=you@mail.i2p --netdb=/home/i2p/.i2p/netDb --port=8443 --ip=127.0.0.1 --trustProxy 21 | ``` 22 | 23 | ### Without a webserver, standalone with TLS support 24 | 25 | ``` 26 | i2p-tools reseed --signer=you@mail.i2p --netdb=/home/i2p/.i2p/netDb --tlsHost=your-domain.tld 27 | ``` 28 | 29 | If this is your first time running a reseed server (ie. you don't have any existing keys), 30 | you can simply run the command and follow the prompts to create the appropriate keys, crl and certificates. 31 | Afterwards an HTTPS reseed server will start on the default port and generate 6 files in your current directory 32 | (a TLS key, certificate and crl, and a su3-file signing key, certificate and crl). 33 | 34 | Get the source code here on github or a pre-build binary anonymously on 35 | 36 | http://reseed.i2p/ 37 | http://j7xszhsjy7orrnbdys7yykrssv5imkn4eid7n5ikcnxuhpaaw6cq.b32.i2p/ 38 | 39 | also a short guide and complete tech info. 40 | -------------------------------------------------------------------------------- /cmd/keygen.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/codegangsta/cli" 7 | ) 8 | 9 | func NewKeygenCommand() cli.Command { 10 | return cli.Command{ 11 | Name: "keygen", 12 | Usage: "Generate keys for reseed su3 signing and TLS serving.", 13 | Action: keygenAction, 14 | Flags: []cli.Flag{ 15 | cli.StringFlag{ 16 | Name: "signer", 17 | Usage: "Generate a private key and certificate for the given su3 signing ID (ex. something@mail.i2p)", 18 | }, 19 | cli.StringFlag{ 20 | Name: "tlsHost", 21 | Usage: "Generate a self-signed TLS certificate and private key for the given host", 22 | }, 23 | }, 24 | } 25 | } 26 | 27 | func keygenAction(c *cli.Context) { 28 | signerID := c.String("signer") 29 | tlsHost := c.String("tlsHost") 30 | 31 | if signerID == "" && tlsHost == "" { 32 | fmt.Println("You must specify either --tlsHost or --signer") 33 | return 34 | } 35 | 36 | if signerID != "" { 37 | if err := createSigningCertificate(signerID); nil != err { 38 | fmt.Println(err) 39 | return 40 | } 41 | } 42 | 43 | if tlsHost != "" { 44 | if err := createTLSCertificate(tlsHost); nil != err { 45 | fmt.Println(err) 46 | return 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/reseed.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "runtime" 8 | "time" 9 | 10 | "github.com/MDrollette/i2p-tools/reseed" 11 | "github.com/codegangsta/cli" 12 | ) 13 | 14 | func NewReseedCommand() cli.Command { 15 | return cli.Command{ 16 | Name: "reseed", 17 | Usage: "Start a reseed server", 18 | Action: reseedAction, 19 | Flags: []cli.Flag{ 20 | cli.StringFlag{ 21 | Name: "signer", 22 | Usage: "Your su3 signing ID (ex. something@mail.i2p)", 23 | }, 24 | cli.StringFlag{ 25 | Name: "tlsHost", 26 | Usage: "The public hostname used on your TLS certificate", 27 | }, 28 | cli.StringFlag{ 29 | Name: "key", 30 | Usage: "Path to your su3 signing private key", 31 | }, 32 | cli.StringFlag{ 33 | Name: "netdb", 34 | Usage: "Path to NetDB directory containing routerInfos", 35 | }, 36 | cli.StringFlag{ 37 | Name: "tlsCert", 38 | Usage: "Path to a TLS certificate", 39 | }, 40 | cli.StringFlag{ 41 | Name: "tlsKey", 42 | Usage: "Path to a TLS private key", 43 | }, 44 | cli.StringFlag{ 45 | Name: "ip", 46 | Value: "0.0.0.0", 47 | Usage: "IP address to listen on", 48 | }, 49 | cli.StringFlag{ 50 | Name: "port", 51 | Value: "8443", 52 | Usage: "Port to listen on", 53 | }, 54 | cli.IntFlag{ 55 | Name: "numRi", 56 | Value: 77, 57 | Usage: "Number of routerInfos to include in each su3 file", 58 | }, 59 | cli.IntFlag{ 60 | Name: "numSu3", 61 | Value: 0, 62 | Usage: "Number of su3 files to build (0 = automatic based on size of netdb)", 63 | }, 64 | cli.StringFlag{ 65 | Name: "interval", 66 | Value: "90h", 67 | Usage: "Duration between SU3 cache rebuilds (ex. 12h, 15m)", 68 | }, 69 | cli.StringFlag{ 70 | Name: "prefix", 71 | Value: "", 72 | Usage: "Prefix path for the HTTP(S) server. (ex. /netdb)", 73 | }, 74 | cli.BoolFlag{ 75 | Name: "trustProxy", 76 | Usage: "If provided, we will trust the 'X-Forwarded-For' header in requests (ex. behind cloudflare)", 77 | }, 78 | cli.StringFlag{ 79 | Name: "blacklist", 80 | Value: "", 81 | Usage: "Path to a txt file containing a list of IPs to deny connections from.", 82 | }, 83 | cli.DurationFlag{ 84 | Name: "stats", 85 | Value: 0, 86 | Usage: "Periodically print memory stats.", 87 | }, 88 | }, 89 | } 90 | } 91 | 92 | func reseedAction(c *cli.Context) { 93 | // validate flags 94 | netdbDir := c.String("netdb") 95 | if netdbDir == "" { 96 | fmt.Println("--netdb is required") 97 | return 98 | } 99 | 100 | signerID := c.String("signer") 101 | if signerID == "" { 102 | fmt.Println("--signer is required") 103 | return 104 | } 105 | 106 | var tlsCert, tlsKey string 107 | tlsHost := c.String("tlsHost") 108 | if tlsHost != "" { 109 | tlsKey = c.String("tlsKey") 110 | // if no key is specified, default to the host.pem in the current dir 111 | if tlsKey == "" { 112 | tlsKey = tlsHost + ".pem" 113 | } 114 | 115 | tlsCert = c.String("tlsCert") 116 | // if no certificate is specified, default to the host.crt in the current dir 117 | if tlsCert == "" { 118 | tlsCert = tlsHost + ".crt" 119 | } 120 | 121 | // prompt to create tls keys if they don't exist? 122 | err := checkOrNewTLSCert(tlsHost, &tlsCert, &tlsKey) 123 | if nil != err { 124 | log.Fatalln(err) 125 | } 126 | } 127 | 128 | reloadIntvl, err := time.ParseDuration(c.String("interval")) 129 | if nil != err { 130 | fmt.Printf("'%s' is not a valid time interval.\n", reloadIntvl) 131 | return 132 | } 133 | 134 | signerKey := c.String("key") 135 | // if no key is specified, default to the signerID.pem in the current dir 136 | if signerKey == "" { 137 | signerKey = signerFile(signerID) + ".pem" 138 | } 139 | 140 | // load our signing privKey 141 | privKey, err := getOrNewSigningCert(&signerKey, signerID) 142 | if nil != err { 143 | log.Fatalln(err) 144 | } 145 | 146 | // create a local file netdb provider 147 | netdb := reseed.NewLocalNetDb(netdbDir) 148 | 149 | // create a reseeder 150 | reseeder := reseed.NewReseeder(netdb) 151 | reseeder.SigningKey = privKey 152 | reseeder.SignerID = []byte(signerID) 153 | reseeder.NumRi = c.Int("numRi") 154 | reseeder.NumSu3 = c.Int("numSu3") 155 | reseeder.RebuildInterval = reloadIntvl 156 | reseeder.Start() 157 | 158 | // create a server 159 | server := reseed.NewServer(c.String("prefix"), c.Bool("trustProxy")) 160 | server.Reseeder = reseeder 161 | server.Addr = net.JoinHostPort(c.String("ip"), c.String("port")) 162 | 163 | // load a blacklist 164 | blacklist := reseed.NewBlacklist() 165 | server.Blacklist = blacklist 166 | blacklistFile := c.String("blacklist") 167 | if "" != blacklistFile { 168 | blacklist.LoadFile(blacklistFile) 169 | } 170 | 171 | // print stats once in a while 172 | if c.Duration("stats") != 0 { 173 | go func() { 174 | var mem runtime.MemStats 175 | for _ = range time.Tick(c.Duration("stats")) { 176 | runtime.ReadMemStats(&mem) 177 | log.Printf("TotalAllocs: %d Kb, Allocs: %d Kb, Mallocs: %d, NumGC: %d", mem.TotalAlloc/1024, mem.Alloc/1024, mem.Mallocs, mem.NumGC) 178 | } 179 | }() 180 | } 181 | 182 | if tlsHost != "" && tlsCert != "" && tlsKey != "" { 183 | log.Printf("HTTPS server started on %s\n", server.Addr) 184 | log.Fatalln(server.ListenAndServeTLS(tlsCert, tlsKey)) 185 | } else { 186 | log.Printf("HTTP server started on %s\n", server.Addr) 187 | log.Fatalln(server.ListenAndServe()) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /cmd/verify.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/MDrollette/i2p-tools/reseed" 8 | "github.com/MDrollette/i2p-tools/su3" 9 | "github.com/codegangsta/cli" 10 | ) 11 | 12 | func NewSu3VerifyCommand() cli.Command { 13 | return cli.Command{ 14 | Name: "verify", 15 | Usage: "Verify a Su3 file", 16 | Description: "Verify a Su3 file", 17 | Action: su3VerifyAction, 18 | Flags: []cli.Flag{ 19 | cli.BoolFlag{ 20 | Name: "extract", 21 | Usage: "Also extract the contents of the su3", 22 | }, 23 | }, 24 | } 25 | } 26 | 27 | func su3VerifyAction(c *cli.Context) { 28 | su3File := su3.New() 29 | 30 | data, err := ioutil.ReadFile(c.Args().Get(0)) 31 | if nil != err { 32 | panic(err) 33 | } 34 | if err := su3File.UnmarshalBinary(data); err != nil { 35 | panic(err) 36 | } 37 | 38 | fmt.Println(su3File.String()) 39 | 40 | // get the reseeder key 41 | ks := reseed.KeyStore{Path: "./certificates"} 42 | cert, err := ks.ReseederCertificate(su3File.SignerID) 43 | if nil != err { 44 | fmt.Println(err) 45 | return 46 | } 47 | 48 | if err := su3File.VerifySignature(cert); nil != err { 49 | panic(err) 50 | } 51 | 52 | fmt.Printf("Signature is valid for signer '%s'\n", su3File.SignerID) 53 | 54 | if c.Bool("extract") { 55 | // @todo: don't assume zip 56 | ioutil.WriteFile("extracted.zip", su3File.BodyBytes(), 0755) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | 7 | "github.com/MDrollette/i2p-tools/cmd" 8 | "github.com/codegangsta/cli" 9 | ) 10 | 11 | func main() { 12 | // use at most half the cpu cores 13 | runtime.GOMAXPROCS(runtime.NumCPU() / 2) 14 | 15 | app := cli.NewApp() 16 | app.Name = "i2p-tools" 17 | app.Version = "0.1.7" 18 | app.Usage = "I2P tools and reseed server" 19 | app.Author = "MDrollette" 20 | app.Email = "matt@rows.io" 21 | app.Flags = []cli.Flag{} 22 | app.Commands = []cli.Command{ 23 | cmd.NewReseedCommand(), 24 | cmd.NewSu3VerifyCommand(), 25 | cmd.NewKeygenCommand(), 26 | // cmd.NewSu3VerifyPublicCommand(), 27 | } 28 | 29 | if err := app.Run(os.Args); err != nil { 30 | os.Exit(1) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /reseed/blacklist.go: -------------------------------------------------------------------------------- 1 | package reseed 2 | 3 | import ( 4 | "io/ioutil" 5 | "net" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | type Blacklist struct { 11 | blacklist map[string]bool 12 | m sync.RWMutex 13 | } 14 | 15 | func NewBlacklist() *Blacklist { 16 | return &Blacklist{blacklist: make(map[string]bool), m: sync.RWMutex{}} 17 | } 18 | 19 | func (s *Blacklist) LoadFile(file string) error { 20 | if file != "" { 21 | if content, err := ioutil.ReadFile(file); err == nil { 22 | for _, ip := range strings.Split(string(content), "\n") { 23 | s.BlockIP(ip) 24 | } 25 | } else { 26 | return err 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func (s *Blacklist) BlockIP(ip string) { 34 | s.m.Lock() 35 | defer s.m.Unlock() 36 | 37 | s.blacklist[ip] = true 38 | } 39 | 40 | func (s *Blacklist) isBlocked(ip string) bool { 41 | s.m.RLock() 42 | defer s.m.RUnlock() 43 | 44 | blocked, found := s.blacklist[ip] 45 | 46 | return found && blocked 47 | } 48 | 49 | type blacklistListener struct { 50 | *net.TCPListener 51 | blacklist *Blacklist 52 | } 53 | 54 | func (ln blacklistListener) Accept() (net.Conn, error) { 55 | tc, err := ln.AcceptTCP() 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | ip, _, err := net.SplitHostPort(tc.RemoteAddr().String()) 61 | if err != nil { 62 | tc.Close() 63 | return tc, err 64 | } 65 | 66 | if ln.blacklist.isBlocked(ip) { 67 | tc.Close() 68 | return tc, nil 69 | } 70 | 71 | return tc, err 72 | } 73 | 74 | func newBlacklistListener(ln net.Listener, bl *Blacklist) blacklistListener { 75 | return blacklistListener{ln.(*net.TCPListener), bl} 76 | } 77 | -------------------------------------------------------------------------------- /reseed/server.go: -------------------------------------------------------------------------------- 1 | package reseed 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | 13 | "github.com/gorilla/handlers" 14 | "github.com/justinas/alice" 15 | "gopkg.in/throttled/throttled.v2" 16 | "gopkg.in/throttled/throttled.v2/store" 17 | ) 18 | 19 | const ( 20 | i2pUserAgent = "Wget/1.11.4" 21 | ) 22 | 23 | type Server struct { 24 | *http.Server 25 | Reseeder Reseeder 26 | Blacklist *Blacklist 27 | } 28 | 29 | func NewServer(prefix string, trustProxy bool) *Server { 30 | config := &tls.Config{ 31 | MinVersion: tls.VersionTLS10, 32 | PreferServerCipherSuites: true, 33 | CipherSuites: []uint16{ 34 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 35 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 36 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 37 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 38 | tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 39 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 40 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 41 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 42 | }, 43 | CurvePreferences: []tls.CurveID{tls.CurveP384, tls.CurveP521}, // default CurveP256 removed 44 | } 45 | h := &http.Server{TLSConfig: config} 46 | server := Server{Server: h, Reseeder: nil} 47 | 48 | th := throttled.RateLimit(throttled.PerHour(4), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(200000)) 49 | 50 | middlewareChain := alice.New() 51 | if trustProxy { 52 | middlewareChain = middlewareChain.Append(proxiedMiddleware) 53 | } 54 | 55 | errorHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 | w.WriteHeader(http.StatusNotFound) 57 | if _, err := w.Write(nil); nil != err { 58 | log.Println(err) 59 | } 60 | }) 61 | 62 | mux := http.NewServeMux() 63 | mux.Handle("/", middlewareChain.Append(disableKeepAliveMiddleware, loggingMiddleware).Then(errorHandler)) 64 | mux.Handle(prefix+"/i2pseeds.su3", middlewareChain.Append(disableKeepAliveMiddleware, loggingMiddleware, verifyMiddleware, th.Throttle).Then(http.HandlerFunc(server.reseedHandler))) 65 | server.Handler = mux 66 | 67 | return &server 68 | } 69 | 70 | func (srv *Server) ListenAndServe() error { 71 | addr := srv.Addr 72 | if addr == "" { 73 | addr = ":http" 74 | } 75 | ln, err := net.Listen("tcp", addr) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return srv.Serve(newBlacklistListener(ln, srv.Blacklist)) 81 | } 82 | 83 | func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { 84 | addr := srv.Addr 85 | if addr == "" { 86 | addr = ":https" 87 | } 88 | 89 | if srv.TLSConfig == nil { 90 | srv.TLSConfig = &tls.Config{} 91 | } 92 | 93 | if srv.TLSConfig.NextProtos == nil { 94 | srv.TLSConfig.NextProtos = []string{"http/1.1"} 95 | } 96 | 97 | var err error 98 | srv.TLSConfig.Certificates = make([]tls.Certificate, 1) 99 | srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | ln, err := net.Listen("tcp", addr) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | tlsListener := tls.NewListener(newBlacklistListener(ln, srv.Blacklist), srv.TLSConfig) 110 | return srv.Serve(tlsListener) 111 | } 112 | 113 | func (srv *Server) reseedHandler(w http.ResponseWriter, r *http.Request) { 114 | var peer Peer 115 | if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { 116 | peer = Peer(ip) 117 | } else { 118 | peer = Peer(r.RemoteAddr) 119 | } 120 | 121 | su3Bytes, err := srv.Reseeder.PeerSu3Bytes(peer) 122 | if nil != err { 123 | http.Error(w, "500 Unable to serve su3", http.StatusInternalServerError) 124 | return 125 | } 126 | 127 | w.Header().Set("Content-Disposition", "attachment; filename=i2pseeds.su3") 128 | w.Header().Set("Content-Type", "application/octet-stream") 129 | w.Header().Set("Content-Length", strconv.FormatInt(int64(len(su3Bytes)), 10)) 130 | 131 | io.Copy(w, bytes.NewReader(su3Bytes)) 132 | } 133 | 134 | func disableKeepAliveMiddleware(next http.Handler) http.Handler { 135 | fn := func(w http.ResponseWriter, r *http.Request) { 136 | w.Header().Set("Connection", "close") 137 | next.ServeHTTP(w, r) 138 | } 139 | return http.HandlerFunc(fn) 140 | } 141 | 142 | func loggingMiddleware(next http.Handler) http.Handler { 143 | return handlers.CombinedLoggingHandler(os.Stdout, next) 144 | } 145 | 146 | func verifyMiddleware(next http.Handler) http.Handler { 147 | fn := func(w http.ResponseWriter, r *http.Request) { 148 | if i2pUserAgent != r.UserAgent() { 149 | http.Error(w, "403 Forbidden", http.StatusForbidden) 150 | return 151 | } 152 | 153 | next.ServeHTTP(w, r) 154 | } 155 | return http.HandlerFunc(fn) 156 | } 157 | 158 | func proxiedMiddleware(next http.Handler) http.Handler { 159 | fn := func(w http.ResponseWriter, r *http.Request) { 160 | if prior, ok := r.Header["X-Forwarded-For"]; ok { 161 | r.RemoteAddr = prior[0] 162 | } 163 | 164 | next.ServeHTTP(w, r) 165 | } 166 | return http.HandlerFunc(fn) 167 | } 168 | -------------------------------------------------------------------------------- /reseed/utils.go: -------------------------------------------------------------------------------- 1 | package reseed 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/rand" 6 | "crypto/x509" 7 | "crypto/x509/pkix" 8 | "encoding/pem" 9 | "io/ioutil" 10 | "math/big" 11 | "net" 12 | "path/filepath" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | type KeyStore struct { 18 | Path string 19 | } 20 | 21 | func (ks *KeyStore) ReseederCertificate(signer []byte) (*x509.Certificate, error) { 22 | certFile := filepath.Base(SignerFilename(string(signer))) 23 | certString, err := ioutil.ReadFile(filepath.Join(ks.Path, "reseed", certFile)) 24 | if nil != err { 25 | return nil, err 26 | } 27 | 28 | certPem, _ := pem.Decode(certString) 29 | return x509.ParseCertificate(certPem.Bytes) 30 | } 31 | 32 | func SignerFilename(signer string) string { 33 | return strings.Replace(signer, "@", "_at_", 1) + ".crt" 34 | } 35 | 36 | func NewTLSCertificate(host string, priv *ecdsa.PrivateKey) ([]byte, error) { 37 | notBefore := time.Now() 38 | notAfter := notBefore.Add(5 * 365 * 24 * time.Hour) 39 | 40 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 41 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | template := x509.Certificate{ 47 | SerialNumber: serialNumber, 48 | Subject: pkix.Name{ 49 | Organization: []string{"I2P Anonymous Network"}, 50 | OrganizationalUnit: []string{"I2P"}, 51 | Locality: []string{"XX"}, 52 | StreetAddress: []string{"XX"}, 53 | Country: []string{"XX"}, 54 | CommonName: host, 55 | }, 56 | NotBefore: notBefore, 57 | NotAfter: notAfter, 58 | SignatureAlgorithm: x509.ECDSAWithSHA512, 59 | 60 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 61 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 62 | BasicConstraintsValid: true, 63 | IsCA: true, 64 | } 65 | 66 | hosts := strings.Split(host, ",") 67 | for _, h := range hosts { 68 | if ip := net.ParseIP(h); ip != nil { 69 | template.IPAddresses = append(template.IPAddresses, ip) 70 | } else { 71 | template.DNSNames = append(template.DNSNames, h) 72 | } 73 | } 74 | 75 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return derBytes, nil 81 | } 82 | -------------------------------------------------------------------------------- /reseed/zip.go: -------------------------------------------------------------------------------- 1 | package reseed 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "io/ioutil" 7 | ) 8 | 9 | func zipSeeds(seeds []routerInfo) ([]byte, error) { 10 | // Create a buffer to write our archive to. 11 | buf := new(bytes.Buffer) 12 | 13 | // Create a new zip archive. 14 | zipWriter := zip.NewWriter(buf) 15 | 16 | // Add some files to the archive. 17 | for _, file := range seeds { 18 | fileHeader := &zip.FileHeader{Name: file.Name, Method: zip.Deflate} 19 | fileHeader.SetModTime(file.ModTime) 20 | zipFile, err := zipWriter.CreateHeader(fileHeader) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | _, err = zipFile.Write(file.Data) 26 | if err != nil { 27 | return nil, err 28 | } 29 | } 30 | 31 | if err := zipWriter.Close(); err != nil { 32 | return nil, err 33 | } 34 | 35 | return buf.Bytes(), nil 36 | } 37 | 38 | func uzipSeeds(c []byte) ([]routerInfo, error) { 39 | input := bytes.NewReader(c) 40 | zipReader, err := zip.NewReader(input, int64(len(c))) 41 | if nil != err { 42 | return nil, err 43 | } 44 | 45 | var seeds []routerInfo 46 | for _, f := range zipReader.File { 47 | rc, err := f.Open() 48 | if err != nil { 49 | return nil, err 50 | } 51 | data, err := ioutil.ReadAll(rc) 52 | rc.Close() 53 | if nil != err { 54 | return nil, err 55 | } 56 | 57 | seeds = append(seeds, routerInfo{Name: f.Name, Data: data}) 58 | } 59 | 60 | return seeds, nil 61 | } 62 | -------------------------------------------------------------------------------- /su3/crypto.go: -------------------------------------------------------------------------------- 1 | package su3 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "crypto/x509/pkix" 11 | "encoding/asn1" 12 | "errors" 13 | "math/big" 14 | "time" 15 | ) 16 | 17 | type dsaSignature struct { 18 | R, S *big.Int 19 | } 20 | 21 | type ecdsaSignature dsaSignature 22 | 23 | func checkSignature(c *x509.Certificate, algo x509.SignatureAlgorithm, signed, signature []byte) (err error) { 24 | var hashType crypto.Hash 25 | 26 | switch algo { 27 | case x509.SHA1WithRSA, x509.DSAWithSHA1, x509.ECDSAWithSHA1: 28 | hashType = crypto.SHA1 29 | case x509.SHA256WithRSA, x509.DSAWithSHA256, x509.ECDSAWithSHA256: 30 | hashType = crypto.SHA256 31 | case x509.SHA384WithRSA, x509.ECDSAWithSHA384: 32 | hashType = crypto.SHA384 33 | case x509.SHA512WithRSA, x509.ECDSAWithSHA512: 34 | hashType = crypto.SHA512 35 | default: 36 | return x509.ErrUnsupportedAlgorithm 37 | } 38 | 39 | if !hashType.Available() { 40 | return x509.ErrUnsupportedAlgorithm 41 | } 42 | h := hashType.New() 43 | 44 | h.Write(signed) 45 | digest := h.Sum(nil) 46 | 47 | switch pub := c.PublicKey.(type) { 48 | case *rsa.PublicKey: 49 | // the digest is already hashed, so we force a 0 here 50 | return rsa.VerifyPKCS1v15(pub, 0, digest, signature) 51 | case *dsa.PublicKey: 52 | dsaSig := new(dsaSignature) 53 | if _, err := asn1.Unmarshal(signature, dsaSig); err != nil { 54 | return err 55 | } 56 | if dsaSig.R.Sign() <= 0 || dsaSig.S.Sign() <= 0 { 57 | return errors.New("x509: DSA signature contained zero or negative values") 58 | } 59 | if !dsa.Verify(pub, digest, dsaSig.R, dsaSig.S) { 60 | return errors.New("x509: DSA verification failure") 61 | } 62 | return 63 | case *ecdsa.PublicKey: 64 | ecdsaSig := new(ecdsaSignature) 65 | if _, err := asn1.Unmarshal(signature, ecdsaSig); err != nil { 66 | return err 67 | } 68 | if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 { 69 | return errors.New("x509: ECDSA signature contained zero or negative values") 70 | } 71 | if !ecdsa.Verify(pub, digest, ecdsaSig.R, ecdsaSig.S) { 72 | return errors.New("x509: ECDSA verification failure") 73 | } 74 | return 75 | } 76 | return x509.ErrUnsupportedAlgorithm 77 | } 78 | 79 | func NewSigningCertificate(signerID string, privateKey *rsa.PrivateKey) ([]byte, error) { 80 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 81 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | template := &x509.Certificate{ 87 | BasicConstraintsValid: true, 88 | IsCA: true, 89 | SubjectKeyId: []byte(signerID), 90 | SerialNumber: serialNumber, 91 | Subject: pkix.Name{ 92 | Organization: []string{"I2P Anonymous Network"}, 93 | OrganizationalUnit: []string{"I2P"}, 94 | Locality: []string{"XX"}, 95 | StreetAddress: []string{"XX"}, 96 | Country: []string{"XX"}, 97 | CommonName: signerID, 98 | }, 99 | NotBefore: time.Now(), 100 | NotAfter: time.Now().AddDate(10, 0, 0), 101 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 102 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 103 | } 104 | 105 | publicKey := &privateKey.PublicKey 106 | 107 | // create a self-signed certificate. template = parent 108 | var parent = template 109 | cert, err := x509.CreateCertificate(rand.Reader, template, parent, publicKey, privateKey) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | return cert, nil 115 | } 116 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/.gitignore: -------------------------------------------------------------------------------- 1 | *.coverprofile 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | dist: trusty 4 | osx_image: xcode8.3 5 | go: 1.8.x 6 | 7 | os: 8 | - linux 9 | - osx 10 | 11 | cache: 12 | directories: 13 | - node_modules 14 | 15 | before_script: 16 | - go get github.com/urfave/gfmrun/... || true 17 | - go get golang.org/x/tools/cmd/goimports 18 | - if [ ! -f node_modules/.bin/markdown-toc ] ; then 19 | npm install markdown-toc ; 20 | fi 21 | 22 | script: 23 | - ./runtests gen 24 | - ./runtests vet 25 | - ./runtests test 26 | - ./runtests gfmrun 27 | - ./runtests toc 28 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jeremy Saenz & Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/altsrc/altsrc.go: -------------------------------------------------------------------------------- 1 | package altsrc 2 | 3 | //go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go 4 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/altsrc/helpers_test.go: -------------------------------------------------------------------------------- 1 | package altsrc 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func expect(t *testing.T, a interface{}, b interface{}) { 9 | if !reflect.DeepEqual(b, a) { 10 | t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 11 | } 12 | } 13 | 14 | func refute(t *testing.T, a interface{}, b interface{}) { 15 | if a == b { 16 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/altsrc/input_source_context.go: -------------------------------------------------------------------------------- 1 | package altsrc 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/urfave/cli.v1" 7 | ) 8 | 9 | // InputSourceContext is an interface used to allow 10 | // other input sources to be implemented as needed. 11 | type InputSourceContext interface { 12 | Int(name string) (int, error) 13 | Duration(name string) (time.Duration, error) 14 | Float64(name string) (float64, error) 15 | String(name string) (string, error) 16 | StringSlice(name string) ([]string, error) 17 | IntSlice(name string) ([]int, error) 18 | Generic(name string) (cli.Generic, error) 19 | Bool(name string) (bool, error) 20 | BoolT(name string) (bool, error) 21 | } 22 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/altsrc/toml_file_loader.go: -------------------------------------------------------------------------------- 1 | // Disabling building of toml support in cases where golang is 1.0 or 1.1 2 | // as the encoding library is not implemented or supported. 3 | 4 | // +build go1.2 5 | 6 | package altsrc 7 | 8 | import ( 9 | "fmt" 10 | "reflect" 11 | 12 | "github.com/BurntSushi/toml" 13 | "gopkg.in/urfave/cli.v1" 14 | ) 15 | 16 | type tomlMap struct { 17 | Map map[interface{}]interface{} 18 | } 19 | 20 | func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { 21 | ret = make(map[interface{}]interface{}) 22 | m := i.(map[string]interface{}) 23 | for key, val := range m { 24 | v := reflect.ValueOf(val) 25 | switch v.Kind() { 26 | case reflect.Bool: 27 | ret[key] = val.(bool) 28 | case reflect.String: 29 | ret[key] = val.(string) 30 | case reflect.Int: 31 | ret[key] = int(val.(int)) 32 | case reflect.Int8: 33 | ret[key] = int(val.(int8)) 34 | case reflect.Int16: 35 | ret[key] = int(val.(int16)) 36 | case reflect.Int32: 37 | ret[key] = int(val.(int32)) 38 | case reflect.Int64: 39 | ret[key] = int(val.(int64)) 40 | case reflect.Uint: 41 | ret[key] = int(val.(uint)) 42 | case reflect.Uint8: 43 | ret[key] = int(val.(uint8)) 44 | case reflect.Uint16: 45 | ret[key] = int(val.(uint16)) 46 | case reflect.Uint32: 47 | ret[key] = int(val.(uint32)) 48 | case reflect.Uint64: 49 | ret[key] = int(val.(uint64)) 50 | case reflect.Float32: 51 | ret[key] = float64(val.(float32)) 52 | case reflect.Float64: 53 | ret[key] = float64(val.(float64)) 54 | case reflect.Map: 55 | if tmp, err := unmarshalMap(val); err == nil { 56 | ret[key] = tmp 57 | } else { 58 | return nil, err 59 | } 60 | case reflect.Array, reflect.Slice: 61 | ret[key] = val.([]interface{}) 62 | default: 63 | return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind()) 64 | } 65 | } 66 | return ret, nil 67 | } 68 | 69 | func (self *tomlMap) UnmarshalTOML(i interface{}) error { 70 | if tmp, err := unmarshalMap(i); err == nil { 71 | self.Map = tmp 72 | } else { 73 | return err 74 | } 75 | return nil 76 | } 77 | 78 | type tomlSourceContext struct { 79 | FilePath string 80 | } 81 | 82 | // NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath. 83 | func NewTomlSourceFromFile(file string) (InputSourceContext, error) { 84 | tsc := &tomlSourceContext{FilePath: file} 85 | var results tomlMap = tomlMap{} 86 | if err := readCommandToml(tsc.FilePath, &results); err != nil { 87 | return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error()) 88 | } 89 | return &MapInputSource{valueMap: results.Map}, nil 90 | } 91 | 92 | // NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context. 93 | func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { 94 | return func(context *cli.Context) (InputSourceContext, error) { 95 | filePath := context.String(flagFileName) 96 | return NewTomlSourceFromFile(filePath) 97 | } 98 | } 99 | 100 | func readCommandToml(filePath string, container interface{}) (err error) { 101 | b, err := loadDataFrom(filePath) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | err = toml.Unmarshal(b, container) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | err = nil 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/altsrc/yaml_file_loader.go: -------------------------------------------------------------------------------- 1 | // Disabling building of yaml support in cases where golang is 1.0 or 1.1 2 | // as the encoding library is not implemented or supported. 3 | 4 | // +build go1.2 5 | 6 | package altsrc 7 | 8 | import ( 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "runtime" 15 | "strings" 16 | 17 | "gopkg.in/urfave/cli.v1" 18 | 19 | "gopkg.in/yaml.v2" 20 | ) 21 | 22 | type yamlSourceContext struct { 23 | FilePath string 24 | } 25 | 26 | // NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. 27 | func NewYamlSourceFromFile(file string) (InputSourceContext, error) { 28 | ysc := &yamlSourceContext{FilePath: file} 29 | var results map[interface{}]interface{} 30 | err := readCommandYaml(ysc.FilePath, &results) 31 | if err != nil { 32 | return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error()) 33 | } 34 | 35 | return &MapInputSource{valueMap: results}, nil 36 | } 37 | 38 | // NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. 39 | func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { 40 | return func(context *cli.Context) (InputSourceContext, error) { 41 | filePath := context.String(flagFileName) 42 | return NewYamlSourceFromFile(filePath) 43 | } 44 | } 45 | 46 | func readCommandYaml(filePath string, container interface{}) (err error) { 47 | b, err := loadDataFrom(filePath) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | err = yaml.Unmarshal(b, container) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | err = nil 58 | return 59 | } 60 | 61 | func loadDataFrom(filePath string) ([]byte, error) { 62 | u, err := url.Parse(filePath) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | if u.Host != "" { // i have a host, now do i support the scheme? 68 | switch u.Scheme { 69 | case "http", "https": 70 | res, err := http.Get(filePath) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return ioutil.ReadAll(res.Body) 75 | default: 76 | return nil, fmt.Errorf("scheme of %s is unsupported", filePath) 77 | } 78 | } else if u.Path != "" { // i dont have a host, but I have a path. I am a local file. 79 | if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { 80 | return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) 81 | } 82 | return ioutil.ReadFile(filePath) 83 | } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { 84 | // on Windows systems u.Path is always empty, so we need to check the string directly. 85 | if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { 86 | return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) 87 | } 88 | return ioutil.ReadFile(filePath) 89 | } else { 90 | return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | os: Windows Server 2016 4 | 5 | image: Visual Studio 2017 6 | 7 | clone_folder: c:\gopath\src\github.com\urfave\cli 8 | 9 | environment: 10 | GOPATH: C:\gopath 11 | GOVERSION: 1.8.x 12 | PYTHON: C:\Python36-x64 13 | PYTHON_VERSION: 3.6.x 14 | PYTHON_ARCH: 64 15 | 16 | install: 17 | - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% 18 | - go version 19 | - go env 20 | - go get github.com/urfave/gfmrun/... 21 | - go get -v -t ./... 22 | 23 | build_script: 24 | - python runtests vet 25 | - python runtests test 26 | - python runtests gfmrun 27 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/autocomplete/bash_autocomplete: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | : ${PROG:=$(basename ${BASH_SOURCE})} 4 | 5 | _cli_bash_autocomplete() { 6 | local cur opts base 7 | COMPREPLY=() 8 | cur="${COMP_WORDS[COMP_CWORD]}" 9 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) 10 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 11 | return 0 12 | } 13 | 14 | complete -F _cli_bash_autocomplete $PROG 15 | 16 | unset PROG 17 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/autocomplete/zsh_autocomplete: -------------------------------------------------------------------------------- 1 | autoload -U compinit && compinit 2 | autoload -U bashcompinit && bashcompinit 3 | 4 | script_dir=$(dirname $0) 5 | source ${script_dir}/bash_autocomplete 6 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/category.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | // CommandCategories is a slice of *CommandCategory. 4 | type CommandCategories []*CommandCategory 5 | 6 | // CommandCategory is a category containing commands. 7 | type CommandCategory struct { 8 | Name string 9 | Commands Commands 10 | } 11 | 12 | func (c CommandCategories) Less(i, j int) bool { 13 | return c[i].Name < c[j].Name 14 | } 15 | 16 | func (c CommandCategories) Len() int { 17 | return len(c) 18 | } 19 | 20 | func (c CommandCategories) Swap(i, j int) { 21 | c[i], c[j] = c[j], c[i] 22 | } 23 | 24 | // AddCommand adds a command to a category. 25 | func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { 26 | for _, commandCategory := range c { 27 | if commandCategory.Name == category { 28 | commandCategory.Commands = append(commandCategory.Commands, command) 29 | return c 30 | } 31 | } 32 | return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) 33 | } 34 | 35 | // VisibleCommands returns a slice of the Commands with Hidden=false 36 | func (c *CommandCategory) VisibleCommands() []Command { 37 | ret := []Command{} 38 | for _, command := range c.Commands { 39 | if !command.Hidden { 40 | ret = append(ret, command) 41 | } 42 | } 43 | return ret 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Package cli provides a minimal framework for creating and organizing command line 2 | // Go applications. cli is designed to be easy to understand and write, the most simple 3 | // cli application can be written as follows: 4 | // func main() { 5 | // cli.NewApp().Run(os.Args) 6 | // } 7 | // 8 | // Of course this application does not do much, so let's make this an actual application: 9 | // func main() { 10 | // app := cli.NewApp() 11 | // app.Name = "greet" 12 | // app.Usage = "say a greeting" 13 | // app.Action = func(c *cli.Context) error { 14 | // println("Greetings") 15 | // return nil 16 | // } 17 | // 18 | // app.Run(os.Args) 19 | // } 20 | package cli 21 | 22 | //go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go 23 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/errors.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // OsExiter is the function used when the app exits. If not set defaults to os.Exit. 11 | var OsExiter = os.Exit 12 | 13 | // ErrWriter is used to write errors to the user. This can be anything 14 | // implementing the io.Writer interface and defaults to os.Stderr. 15 | var ErrWriter io.Writer = os.Stderr 16 | 17 | // MultiError is an error that wraps multiple errors. 18 | type MultiError struct { 19 | Errors []error 20 | } 21 | 22 | // NewMultiError creates a new MultiError. Pass in one or more errors. 23 | func NewMultiError(err ...error) MultiError { 24 | return MultiError{Errors: err} 25 | } 26 | 27 | // Error implements the error interface. 28 | func (m MultiError) Error() string { 29 | errs := make([]string, len(m.Errors)) 30 | for i, err := range m.Errors { 31 | errs[i] = err.Error() 32 | } 33 | 34 | return strings.Join(errs, "\n") 35 | } 36 | 37 | type ErrorFormatter interface { 38 | Format(s fmt.State, verb rune) 39 | } 40 | 41 | // ExitCoder is the interface checked by `App` and `Command` for a custom exit 42 | // code 43 | type ExitCoder interface { 44 | error 45 | ExitCode() int 46 | } 47 | 48 | // ExitError fulfills both the builtin `error` interface and `ExitCoder` 49 | type ExitError struct { 50 | exitCode int 51 | message interface{} 52 | } 53 | 54 | // NewExitError makes a new *ExitError 55 | func NewExitError(message interface{}, exitCode int) *ExitError { 56 | return &ExitError{ 57 | exitCode: exitCode, 58 | message: message, 59 | } 60 | } 61 | 62 | // Error returns the string message, fulfilling the interface required by 63 | // `error` 64 | func (ee *ExitError) Error() string { 65 | return fmt.Sprintf("%v", ee.message) 66 | } 67 | 68 | // ExitCode returns the exit code, fulfilling the interface required by 69 | // `ExitCoder` 70 | func (ee *ExitError) ExitCode() int { 71 | return ee.exitCode 72 | } 73 | 74 | // HandleExitCoder checks if the error fulfills the ExitCoder interface, and if 75 | // so prints the error to stderr (if it is non-empty) and calls OsExiter with the 76 | // given exit code. If the given error is a MultiError, then this func is 77 | // called on all members of the Errors slice and calls OsExiter with the last exit code. 78 | func HandleExitCoder(err error) { 79 | if err == nil { 80 | return 81 | } 82 | 83 | if exitErr, ok := err.(ExitCoder); ok { 84 | if err.Error() != "" { 85 | if _, ok := exitErr.(ErrorFormatter); ok { 86 | fmt.Fprintf(ErrWriter, "%+v\n", err) 87 | } else { 88 | fmt.Fprintln(ErrWriter, err) 89 | } 90 | } 91 | OsExiter(exitErr.ExitCode()) 92 | return 93 | } 94 | 95 | if multiErr, ok := err.(MultiError); ok { 96 | code := handleMultiError(multiErr) 97 | OsExiter(code) 98 | return 99 | } 100 | } 101 | 102 | func handleMultiError(multiErr MultiError) int { 103 | code := 1 104 | for _, merr := range multiErr.Errors { 105 | if multiErr2, ok := merr.(MultiError); ok { 106 | code = handleMultiError(multiErr2) 107 | } else { 108 | fmt.Fprintln(ErrWriter, merr) 109 | if exitErr, ok := merr.(ExitCoder); ok { 110 | code = exitErr.ExitCode() 111 | } 112 | } 113 | } 114 | return code 115 | } 116 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/errors_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | func TestHandleExitCoder_nil(t *testing.T) { 11 | exitCode := 0 12 | called := false 13 | 14 | OsExiter = func(rc int) { 15 | if !called { 16 | exitCode = rc 17 | called = true 18 | } 19 | } 20 | 21 | defer func() { OsExiter = fakeOsExiter }() 22 | 23 | HandleExitCoder(nil) 24 | 25 | expect(t, exitCode, 0) 26 | expect(t, called, false) 27 | } 28 | 29 | func TestHandleExitCoder_ExitCoder(t *testing.T) { 30 | exitCode := 0 31 | called := false 32 | 33 | OsExiter = func(rc int) { 34 | if !called { 35 | exitCode = rc 36 | called = true 37 | } 38 | } 39 | 40 | defer func() { OsExiter = fakeOsExiter }() 41 | 42 | HandleExitCoder(NewExitError("galactic perimeter breach", 9)) 43 | 44 | expect(t, exitCode, 9) 45 | expect(t, called, true) 46 | } 47 | 48 | func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { 49 | exitCode := 0 50 | called := false 51 | 52 | OsExiter = func(rc int) { 53 | if !called { 54 | exitCode = rc 55 | called = true 56 | } 57 | } 58 | 59 | defer func() { OsExiter = fakeOsExiter }() 60 | 61 | exitErr := NewExitError("galactic perimeter breach", 9) 62 | exitErr2 := NewExitError("last ExitCoder", 11) 63 | err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2) 64 | HandleExitCoder(err) 65 | 66 | expect(t, exitCode, 11) 67 | expect(t, called, true) 68 | } 69 | 70 | // make a stub to not import pkg/errors 71 | type ErrorWithFormat struct { 72 | error 73 | } 74 | 75 | func NewErrorWithFormat(m string) *ErrorWithFormat { 76 | return &ErrorWithFormat{error: errors.New(m)} 77 | } 78 | 79 | func (f *ErrorWithFormat) Format(s fmt.State, verb rune) { 80 | fmt.Fprintf(s, "This the format: %v", f.error) 81 | } 82 | 83 | func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { 84 | called := false 85 | 86 | OsExiter = func(rc int) { 87 | if !called { 88 | called = true 89 | } 90 | } 91 | ErrWriter = &bytes.Buffer{} 92 | 93 | defer func() { 94 | OsExiter = fakeOsExiter 95 | ErrWriter = fakeErrWriter 96 | }() 97 | 98 | err := NewExitError(NewErrorWithFormat("I am formatted"), 1) 99 | HandleExitCoder(err) 100 | 101 | expect(t, called, true) 102 | expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: I am formatted\n") 103 | } 104 | 105 | func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { 106 | called := false 107 | 108 | OsExiter = func(rc int) { 109 | if !called { 110 | called = true 111 | } 112 | } 113 | ErrWriter = &bytes.Buffer{} 114 | 115 | defer func() { OsExiter = fakeOsExiter }() 116 | 117 | err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) 118 | HandleExitCoder(err) 119 | 120 | expect(t, called, true) 121 | expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n") 122 | } 123 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/flag-types.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Bool", 4 | "type": "bool", 5 | "value": false, 6 | "context_default": "false", 7 | "parser": "strconv.ParseBool(f.Value.String())" 8 | }, 9 | { 10 | "name": "BoolT", 11 | "type": "bool", 12 | "value": false, 13 | "doctail": " that is true by default", 14 | "context_default": "false", 15 | "parser": "strconv.ParseBool(f.Value.String())" 16 | }, 17 | { 18 | "name": "Duration", 19 | "type": "time.Duration", 20 | "doctail": " (see https://golang.org/pkg/time/#ParseDuration)", 21 | "context_default": "0", 22 | "parser": "time.ParseDuration(f.Value.String())" 23 | }, 24 | { 25 | "name": "Float64", 26 | "type": "float64", 27 | "context_default": "0", 28 | "parser": "strconv.ParseFloat(f.Value.String(), 64)" 29 | }, 30 | { 31 | "name": "Generic", 32 | "type": "Generic", 33 | "dest": false, 34 | "context_default": "nil", 35 | "context_type": "interface{}" 36 | }, 37 | { 38 | "name": "Int64", 39 | "type": "int64", 40 | "context_default": "0", 41 | "parser": "strconv.ParseInt(f.Value.String(), 0, 64)" 42 | }, 43 | { 44 | "name": "Int", 45 | "type": "int", 46 | "context_default": "0", 47 | "parser": "strconv.ParseInt(f.Value.String(), 0, 64)", 48 | "parser_cast": "int(parsed)" 49 | }, 50 | { 51 | "name": "IntSlice", 52 | "type": "*IntSlice", 53 | "dest": false, 54 | "context_default": "nil", 55 | "context_type": "[]int", 56 | "parser": "(f.Value.(*IntSlice)).Value(), error(nil)" 57 | }, 58 | { 59 | "name": "Int64Slice", 60 | "type": "*Int64Slice", 61 | "dest": false, 62 | "context_default": "nil", 63 | "context_type": "[]int64", 64 | "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)" 65 | }, 66 | { 67 | "name": "String", 68 | "type": "string", 69 | "context_default": "\"\"", 70 | "parser": "f.Value.String(), error(nil)" 71 | }, 72 | { 73 | "name": "StringSlice", 74 | "type": "*StringSlice", 75 | "dest": false, 76 | "context_default": "nil", 77 | "context_type": "[]string", 78 | "parser": "(f.Value.(*StringSlice)).Value(), error(nil)" 79 | }, 80 | { 81 | "name": "Uint64", 82 | "type": "uint64", 83 | "context_default": "0", 84 | "parser": "strconv.ParseUint(f.Value.String(), 0, 64)" 85 | }, 86 | { 87 | "name": "Uint", 88 | "type": "uint", 89 | "context_default": "0", 90 | "parser": "strconv.ParseUint(f.Value.String(), 0, 64)", 91 | "parser_cast": "uint(parsed)" 92 | } 93 | ] 94 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/funcs.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | // BashCompleteFunc is an action to execute when the bash-completion flag is set 4 | type BashCompleteFunc func(*Context) 5 | 6 | // BeforeFunc is an action to execute before any subcommands are run, but after 7 | // the context is ready if a non-nil error is returned, no subcommands are run 8 | type BeforeFunc func(*Context) error 9 | 10 | // AfterFunc is an action to execute after any subcommands are run, but after the 11 | // subcommand has finished it is run even if Action() panics 12 | type AfterFunc func(*Context) error 13 | 14 | // ActionFunc is the action to execute when no subcommands are specified 15 | type ActionFunc func(*Context) error 16 | 17 | // CommandNotFoundFunc is executed if the proper command cannot be found 18 | type CommandNotFoundFunc func(*Context, string) 19 | 20 | // OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying 21 | // customized usage error messages. This function is able to replace the 22 | // original error messages. If this function is not set, the "Incorrect usage" 23 | // is displayed and the execution is interrupted. 24 | type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error 25 | 26 | // FlagStringFunc is used by the help generation to display a flag, which is 27 | // expected to be a single line. 28 | type FlagStringFunc func(Flag) string 29 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/helpers_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "runtime" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | wd, _ = os.Getwd() 13 | ) 14 | 15 | func expect(t *testing.T, a interface{}, b interface{}) { 16 | _, fn, line, _ := runtime.Caller(1) 17 | fn = strings.Replace(fn, wd+"/", "", -1) 18 | 19 | if !reflect.DeepEqual(a, b) { 20 | t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 21 | } 22 | } 23 | 24 | func refute(t *testing.T, a interface{}, b interface{}) { 25 | if reflect.DeepEqual(a, b) { 26 | t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/helpers_unix_test.go: -------------------------------------------------------------------------------- 1 | // +build darwin dragonfly freebsd linux netbsd openbsd solaris 2 | 3 | package cli 4 | 5 | import "os" 6 | 7 | func clearenv() { 8 | os.Clearenv() 9 | } 10 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/helpers_windows_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | ) 7 | 8 | // os.Clearenv() doesn't actually unset variables on Windows 9 | // See: https://github.com/golang/go/issues/17902 10 | func clearenv() { 11 | for _, s := range os.Environ() { 12 | for j := 1; j < len(s); j++ { 13 | if s[j] == '=' { 14 | keyp, _ := syscall.UTF16PtrFromString(s[0:j]) 15 | syscall.SetEnvironmentVariable(keyp, nil) 16 | break 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vendor/github.com/codegangsta/cli/runtests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | 4 | import argparse 5 | import os 6 | import sys 7 | import tempfile 8 | 9 | from subprocess import check_call, check_output 10 | 11 | 12 | PACKAGE_NAME = os.environ.get( 13 | 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' 14 | ) 15 | 16 | 17 | def main(sysargs=sys.argv[:]): 18 | targets = { 19 | 'vet': _vet, 20 | 'test': _test, 21 | 'gfmrun': _gfmrun, 22 | 'toc': _toc, 23 | 'gen': _gen, 24 | } 25 | 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument( 28 | 'target', nargs='?', choices=tuple(targets.keys()), default='test' 29 | ) 30 | args = parser.parse_args(sysargs[1:]) 31 | 32 | targets[args.target]() 33 | return 0 34 | 35 | 36 | def _test(): 37 | if check_output('go version'.split()).split()[2] < 'go1.2': 38 | _run('go test -v .') 39 | return 40 | 41 | coverprofiles = [] 42 | for subpackage in ['', 'altsrc']: 43 | coverprofile = 'cli.coverprofile' 44 | if subpackage != '': 45 | coverprofile = '{}.coverprofile'.format(subpackage) 46 | 47 | coverprofiles.append(coverprofile) 48 | 49 | _run('go test -v'.split() + [ 50 | '-coverprofile={}'.format(coverprofile), 51 | ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') 52 | ]) 53 | 54 | combined_name = _combine_coverprofiles(coverprofiles) 55 | _run('go tool cover -func={}'.format(combined_name)) 56 | os.remove(combined_name) 57 | 58 | 59 | def _gfmrun(): 60 | go_version = check_output('go version'.split()).split()[2] 61 | if go_version < 'go1.3': 62 | print('runtests: skip on {}'.format(go_version), file=sys.stderr) 63 | return 64 | _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md']) 65 | 66 | 67 | def _vet(): 68 | _run('go vet ./...') 69 | 70 | 71 | def _toc(): 72 | _run('node_modules/.bin/markdown-toc -i README.md') 73 | _run('git diff --exit-code') 74 | 75 | 76 | def _gen(): 77 | go_version = check_output('go version'.split()).split()[2] 78 | if go_version < 'go1.5': 79 | print('runtests: skip on {}'.format(go_version), file=sys.stderr) 80 | return 81 | 82 | _run('go generate ./...') 83 | _run('git diff --exit-code') 84 | 85 | 86 | def _run(command): 87 | if hasattr(command, 'split'): 88 | command = command.split() 89 | print('runtests: {}'.format(' '.join(command)), file=sys.stderr) 90 | check_call(command) 91 | 92 | 93 | def _gfmrun_count(): 94 | with open('README.md') as infile: 95 | lines = infile.read().splitlines() 96 | return len(filter(_is_go_runnable, lines)) 97 | 98 | 99 | def _is_go_runnable(line): 100 | return line.startswith('package main') 101 | 102 | 103 | def _combine_coverprofiles(coverprofiles): 104 | combined = tempfile.NamedTemporaryFile( 105 | suffix='.coverprofile', delete=False 106 | ) 107 | combined.write('mode: set\n') 108 | 109 | for coverprofile in coverprofiles: 110 | with open(coverprofile, 'r') as infile: 111 | for line in infile.readlines(): 112 | if not line.startswith('mode: '): 113 | combined.write(line) 114 | 115 | combined.flush() 116 | name = combined.name 117 | combined.close() 118 | return name 119 | 120 | 121 | if __name__ == '__main__': 122 | sys.exit(main()) 123 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Ask questions at 2 | [StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis). 3 | 4 | [Open an issue](https://github.com/garyburd/redigo/issues/new) to discuss your 5 | plans before doing any work on Redigo. 6 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis 2 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | services: 4 | - redis-server 5 | 6 | go: 7 | - 1.4 8 | - 1.5 9 | - 1.6 10 | - 1.7 11 | - 1.8 12 | - tip 13 | 14 | script: 15 | - go get -t -v ./... 16 | - diff -u <(echo -n) <(gofmt -d .) 17 | - go vet $(go list ./... | grep -v /vendor/) 18 | - go test -v -race ./... 19 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/README.markdown: -------------------------------------------------------------------------------- 1 | Redigo 2 | ====== 3 | 4 | [![Build Status](https://travis-ci.org/garyburd/redigo.svg?branch=master)](https://travis-ci.org/garyburd/redigo) 5 | [![GoDoc](https://godoc.org/github.com/garyburd/redigo/redis?status.svg)](https://godoc.org/github.com/garyburd/redigo/redis) 6 | 7 | Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database. 8 | 9 | Features 10 | ------- 11 | 12 | * A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands. 13 | * [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions. 14 | * [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe). 15 | * [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool). 16 | * [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA. 17 | * [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies. 18 | 19 | Documentation 20 | ------------- 21 | 22 | - [API Reference](http://godoc.org/github.com/garyburd/redigo/redis) 23 | - [FAQ](https://github.com/garyburd/redigo/wiki/FAQ) 24 | 25 | Installation 26 | ------------ 27 | 28 | Install Redigo using the "go get" command: 29 | 30 | go get github.com/garyburd/redigo/redis 31 | 32 | The Go distribution is Redigo's only dependency. 33 | 34 | Related Projects 35 | ---------------- 36 | 37 | - [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo. 38 | - [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation. 39 | - [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo 40 | - [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo 41 | 42 | Contributing 43 | ------------ 44 | 45 | See [CONTRIBUTING.md](https://github.com/garyburd/redigo/blob/master/.github/CONTRIBUTING.md). 46 | 47 | License 48 | ------- 49 | 50 | Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). 51 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/internal/commandinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package internal // import "github.com/garyburd/redigo/internal" 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | const ( 22 | WatchState = 1 << iota 23 | MultiState 24 | SubscribeState 25 | MonitorState 26 | ) 27 | 28 | type CommandInfo struct { 29 | Set, Clear int 30 | } 31 | 32 | var commandInfos = map[string]CommandInfo{ 33 | "WATCH": {Set: WatchState}, 34 | "UNWATCH": {Clear: WatchState}, 35 | "MULTI": {Set: MultiState}, 36 | "EXEC": {Clear: WatchState | MultiState}, 37 | "DISCARD": {Clear: WatchState | MultiState}, 38 | "PSUBSCRIBE": {Set: SubscribeState}, 39 | "SUBSCRIBE": {Set: SubscribeState}, 40 | "MONITOR": {Set: MonitorState}, 41 | } 42 | 43 | func init() { 44 | for n, ci := range commandInfos { 45 | commandInfos[strings.ToLower(n)] = ci 46 | } 47 | } 48 | 49 | func LookupCommandInfo(commandName string) CommandInfo { 50 | if ci, ok := commandInfos[commandName]; ok { 51 | return ci 52 | } 53 | return commandInfos[strings.ToUpper(commandName)] 54 | } 55 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/internal/commandinfo_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "testing" 4 | 5 | func TestLookupCommandInfo(t *testing.T) { 6 | for _, n := range []string{"watch", "WATCH", "wAtch"} { 7 | if LookupCommandInfo(n) == (CommandInfo{}) { 8 | t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n) 9 | } 10 | } 11 | } 12 | 13 | func benchmarkLookupCommandInfo(b *testing.B, names ...string) { 14 | for i := 0; i < b.N; i++ { 15 | for _, c := range names { 16 | LookupCommandInfo(c) 17 | } 18 | } 19 | } 20 | 21 | func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) { 22 | benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR") 23 | } 24 | 25 | func BenchmarkLookupCommandInfoMixedCase(b *testing.B) { 26 | benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR") 27 | } 28 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/internal/redistest/testdb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redistest contains utilities for writing Redigo tests. 16 | package redistest 17 | 18 | import ( 19 | "errors" 20 | "time" 21 | 22 | "github.com/garyburd/redigo/redis" 23 | ) 24 | 25 | type testConn struct { 26 | redis.Conn 27 | } 28 | 29 | func (t testConn) Close() error { 30 | _, err := t.Conn.Do("SELECT", "9") 31 | if err != nil { 32 | return nil 33 | } 34 | _, err = t.Conn.Do("FLUSHDB") 35 | if err != nil { 36 | return err 37 | } 38 | return t.Conn.Close() 39 | } 40 | 41 | // Dial dials the local Redis server and selects database 9. To prevent 42 | // stomping on real data, DialTestDB fails if database 9 contains data. The 43 | // returned connection flushes database 9 on close. 44 | func Dial() (redis.Conn, error) { 45 | c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | _, err = c.Do("SELECT", "9") 51 | if err != nil { 52 | c.Close() 53 | return nil, err 54 | } 55 | 56 | n, err := redis.Int(c.Do("DBSIZE")) 57 | if err != nil { 58 | c.Close() 59 | return nil, err 60 | } 61 | 62 | if n != 0 { 63 | c.Close() 64 | return nil, errors.New("database #9 is not empty, test can not continue") 65 | } 66 | 67 | return testConn{c}, nil 68 | } 69 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/go17.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | // similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case 8 | func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { 9 | if cfg == nil { 10 | return &tls.Config{InsecureSkipVerify: skipVerify} 11 | } 12 | return &tls.Config{ 13 | Rand: cfg.Rand, 14 | Time: cfg.Time, 15 | Certificates: cfg.Certificates, 16 | NameToCertificate: cfg.NameToCertificate, 17 | GetCertificate: cfg.GetCertificate, 18 | RootCAs: cfg.RootCAs, 19 | NextProtos: cfg.NextProtos, 20 | ServerName: cfg.ServerName, 21 | ClientAuth: cfg.ClientAuth, 22 | ClientCAs: cfg.ClientCAs, 23 | InsecureSkipVerify: cfg.InsecureSkipVerify, 24 | CipherSuites: cfg.CipherSuites, 25 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 26 | ClientSessionCache: cfg.ClientSessionCache, 27 | MinVersion: cfg.MinVersion, 28 | MaxVersion: cfg.MaxVersion, 29 | CurvePreferences: cfg.CurvePreferences, 30 | DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, 31 | Renegotiation: cfg.Renegotiation, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "log" 21 | ) 22 | 23 | // NewLoggingConn returns a logging wrapper around a connection. 24 | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { 25 | if prefix != "" { 26 | prefix = prefix + "." 27 | } 28 | return &loggingConn{conn, logger, prefix} 29 | } 30 | 31 | type loggingConn struct { 32 | Conn 33 | logger *log.Logger 34 | prefix string 35 | } 36 | 37 | func (c *loggingConn) Close() error { 38 | err := c.Conn.Close() 39 | var buf bytes.Buffer 40 | fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) 41 | c.logger.Output(2, buf.String()) 42 | return err 43 | } 44 | 45 | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { 46 | const chop = 32 47 | switch v := v.(type) { 48 | case []byte: 49 | if len(v) > chop { 50 | fmt.Fprintf(buf, "%q...", v[:chop]) 51 | } else { 52 | fmt.Fprintf(buf, "%q", v) 53 | } 54 | case string: 55 | if len(v) > chop { 56 | fmt.Fprintf(buf, "%q...", v[:chop]) 57 | } else { 58 | fmt.Fprintf(buf, "%q", v) 59 | } 60 | case []interface{}: 61 | if len(v) == 0 { 62 | buf.WriteString("[]") 63 | } else { 64 | sep := "[" 65 | fin := "]" 66 | if len(v) > chop { 67 | v = v[:chop] 68 | fin = "...]" 69 | } 70 | for _, vv := range v { 71 | buf.WriteString(sep) 72 | c.printValue(buf, vv) 73 | sep = ", " 74 | } 75 | buf.WriteString(fin) 76 | } 77 | default: 78 | fmt.Fprint(buf, v) 79 | } 80 | } 81 | 82 | func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { 83 | var buf bytes.Buffer 84 | fmt.Fprintf(&buf, "%s%s(", c.prefix, method) 85 | if method != "Receive" { 86 | buf.WriteString(commandName) 87 | for _, arg := range args { 88 | buf.WriteString(", ") 89 | c.printValue(&buf, arg) 90 | } 91 | } 92 | buf.WriteString(") -> (") 93 | if method != "Send" { 94 | c.printValue(&buf, reply) 95 | buf.WriteString(", ") 96 | } 97 | fmt.Fprintf(&buf, "%v)", err) 98 | c.logger.Output(3, buf.String()) 99 | } 100 | 101 | func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { 102 | reply, err := c.Conn.Do(commandName, args...) 103 | c.print("Do", commandName, args, reply, err) 104 | return reply, err 105 | } 106 | 107 | func (c *loggingConn) Send(commandName string, args ...interface{}) error { 108 | err := c.Conn.Send(commandName, args...) 109 | c.print("Send", commandName, args, nil, err) 110 | return err 111 | } 112 | 113 | func (c *loggingConn) Receive() (interface{}, error) { 114 | reply, err := c.Conn.Receive() 115 | c.print("Receive", "", nil, reply, err) 116 | return reply, err 117 | } 118 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pre_go17.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | // similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case 8 | func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { 9 | if cfg == nil { 10 | return &tls.Config{InsecureSkipVerify: skipVerify} 11 | } 12 | return &tls.Config{ 13 | Rand: cfg.Rand, 14 | Time: cfg.Time, 15 | Certificates: cfg.Certificates, 16 | NameToCertificate: cfg.NameToCertificate, 17 | GetCertificate: cfg.GetCertificate, 18 | RootCAs: cfg.RootCAs, 19 | NextProtos: cfg.NextProtos, 20 | ServerName: cfg.ServerName, 21 | ClientAuth: cfg.ClientAuth, 22 | ClientCAs: cfg.ClientCAs, 23 | InsecureSkipVerify: cfg.InsecureSkipVerify, 24 | CipherSuites: cfg.CipherSuites, 25 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 26 | ClientSessionCache: cfg.ClientSessionCache, 27 | MinVersion: cfg.MinVersion, 28 | MaxVersion: cfg.MaxVersion, 29 | CurvePreferences: cfg.CurvePreferences, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import "errors" 18 | 19 | // Subscription represents a subscribe or unsubscribe notification. 20 | type Subscription struct { 21 | 22 | // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" 23 | Kind string 24 | 25 | // The channel that was changed. 26 | Channel string 27 | 28 | // The current number of subscriptions for connection. 29 | Count int 30 | } 31 | 32 | // Message represents a message notification. 33 | type Message struct { 34 | 35 | // The originating channel. 36 | Channel string 37 | 38 | // The message data. 39 | Data []byte 40 | } 41 | 42 | // PMessage represents a pmessage notification. 43 | type PMessage struct { 44 | 45 | // The matched pattern. 46 | Pattern string 47 | 48 | // The originating channel. 49 | Channel string 50 | 51 | // The message data. 52 | Data []byte 53 | } 54 | 55 | // Pong represents a pubsub pong notification. 56 | type Pong struct { 57 | Data string 58 | } 59 | 60 | // PubSubConn wraps a Conn with convenience methods for subscribers. 61 | type PubSubConn struct { 62 | Conn Conn 63 | } 64 | 65 | // Close closes the connection. 66 | func (c PubSubConn) Close() error { 67 | return c.Conn.Close() 68 | } 69 | 70 | // Subscribe subscribes the connection to the specified channels. 71 | func (c PubSubConn) Subscribe(channel ...interface{}) error { 72 | c.Conn.Send("SUBSCRIBE", channel...) 73 | return c.Conn.Flush() 74 | } 75 | 76 | // PSubscribe subscribes the connection to the given patterns. 77 | func (c PubSubConn) PSubscribe(channel ...interface{}) error { 78 | c.Conn.Send("PSUBSCRIBE", channel...) 79 | return c.Conn.Flush() 80 | } 81 | 82 | // Unsubscribe unsubscribes the connection from the given channels, or from all 83 | // of them if none is given. 84 | func (c PubSubConn) Unsubscribe(channel ...interface{}) error { 85 | c.Conn.Send("UNSUBSCRIBE", channel...) 86 | return c.Conn.Flush() 87 | } 88 | 89 | // PUnsubscribe unsubscribes the connection from the given patterns, or from all 90 | // of them if none is given. 91 | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { 92 | c.Conn.Send("PUNSUBSCRIBE", channel...) 93 | return c.Conn.Flush() 94 | } 95 | 96 | // Ping sends a PING to the server with the specified data. 97 | func (c PubSubConn) Ping(data string) error { 98 | c.Conn.Send("PING", data) 99 | return c.Conn.Flush() 100 | } 101 | 102 | // Receive returns a pushed message as a Subscription, Message, PMessage, Pong 103 | // or error. The return value is intended to be used directly in a type switch 104 | // as illustrated in the PubSubConn example. 105 | func (c PubSubConn) Receive() interface{} { 106 | reply, err := Values(c.Conn.Receive()) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | var kind string 112 | reply, err = Scan(reply, &kind) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | switch kind { 118 | case "message": 119 | var m Message 120 | if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { 121 | return err 122 | } 123 | return m 124 | case "pmessage": 125 | var pm PMessage 126 | if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { 127 | return err 128 | } 129 | return pm 130 | case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": 131 | s := Subscription{Kind: kind} 132 | if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { 133 | return err 134 | } 135 | return s 136 | case "pong": 137 | var p Pong 138 | if _, err := Scan(reply, &p.Data); err != nil { 139 | return err 140 | } 141 | return p 142 | } 143 | return errors.New("redigo: unknown pubsub notification") 144 | } 145 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pubsub_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "sync" 21 | "testing" 22 | 23 | "github.com/garyburd/redigo/redis" 24 | ) 25 | 26 | func publish(channel, value interface{}) { 27 | c, err := dial() 28 | if err != nil { 29 | fmt.Println(err) 30 | return 31 | } 32 | defer c.Close() 33 | c.Do("PUBLISH", channel, value) 34 | } 35 | 36 | // Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine. 37 | func ExamplePubSubConn() { 38 | c, err := dial() 39 | if err != nil { 40 | fmt.Println(err) 41 | return 42 | } 43 | defer c.Close() 44 | var wg sync.WaitGroup 45 | wg.Add(2) 46 | 47 | psc := redis.PubSubConn{Conn: c} 48 | 49 | // This goroutine receives and prints pushed notifications from the server. 50 | // The goroutine exits when the connection is unsubscribed from all 51 | // channels or there is an error. 52 | go func() { 53 | defer wg.Done() 54 | for { 55 | switch n := psc.Receive().(type) { 56 | case redis.Message: 57 | fmt.Printf("Message: %s %s\n", n.Channel, n.Data) 58 | case redis.PMessage: 59 | fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data) 60 | case redis.Subscription: 61 | fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count) 62 | if n.Count == 0 { 63 | return 64 | } 65 | case error: 66 | fmt.Printf("error: %v\n", n) 67 | return 68 | } 69 | } 70 | }() 71 | 72 | // This goroutine manages subscriptions for the connection. 73 | go func() { 74 | defer wg.Done() 75 | 76 | psc.Subscribe("example") 77 | psc.PSubscribe("p*") 78 | 79 | // The following function calls publish a message using another 80 | // connection to the Redis server. 81 | publish("example", "hello") 82 | publish("example", "world") 83 | publish("pexample", "foo") 84 | publish("pexample", "bar") 85 | 86 | // Unsubscribe from all connections. This will cause the receiving 87 | // goroutine to exit. 88 | psc.Unsubscribe() 89 | psc.PUnsubscribe() 90 | }() 91 | 92 | wg.Wait() 93 | 94 | // Output: 95 | // Subscription: subscribe example 1 96 | // Subscription: psubscribe p* 2 97 | // Message: example hello 98 | // Message: example world 99 | // PMessage: p* pexample foo 100 | // PMessage: p* pexample bar 101 | // Subscription: unsubscribe example 1 102 | // Subscription: punsubscribe p* 0 103 | } 104 | 105 | func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) { 106 | actual := c.Receive() 107 | if !reflect.DeepEqual(actual, expected) { 108 | t.Errorf("%s = %v, want %v", message, actual, expected) 109 | } 110 | } 111 | 112 | func TestPushed(t *testing.T) { 113 | pc, err := redis.DialDefaultServer() 114 | if err != nil { 115 | t.Fatalf("error connection to database, %v", err) 116 | } 117 | defer pc.Close() 118 | 119 | sc, err := redis.DialDefaultServer() 120 | if err != nil { 121 | t.Fatalf("error connection to database, %v", err) 122 | } 123 | defer sc.Close() 124 | 125 | c := redis.PubSubConn{Conn: sc} 126 | 127 | c.Subscribe("c1") 128 | expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}) 129 | c.Subscribe("c2") 130 | expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2}) 131 | c.PSubscribe("p1") 132 | expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3}) 133 | c.PSubscribe("p2") 134 | expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4}) 135 | c.PUnsubscribe() 136 | expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3}) 137 | expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2}) 138 | 139 | pc.Do("PUBLISH", "c1", "hello") 140 | expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")}) 141 | 142 | c.Ping("hello") 143 | expectPushed(t, c, `Ping("hello")`, redis.Pong{Data: "hello"}) 144 | 145 | c.Conn.Send("PING") 146 | c.Conn.Flush() 147 | expectPushed(t, c, `Send("PING")`, redis.Pong{}) 148 | } 149 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | // Error represents an error returned in a command reply. 18 | type Error string 19 | 20 | func (err Error) Error() string { return string(err) } 21 | 22 | // Conn represents a connection to a Redis server. 23 | type Conn interface { 24 | // Close closes the connection. 25 | Close() error 26 | 27 | // Err returns a non-nil value when the connection is not usable. 28 | Err() error 29 | 30 | // Do sends a command to the server and returns the received reply. 31 | Do(commandName string, args ...interface{}) (reply interface{}, err error) 32 | 33 | // Send writes the command to the client's output buffer. 34 | Send(commandName string, args ...interface{}) error 35 | 36 | // Flush flushes the output buffer to the Redis server. 37 | Flush() error 38 | 39 | // Receive receives a single reply from the Redis server 40 | Receive() (reply interface{}, err error) 41 | } 42 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/reply_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/garyburd/redigo/redis" 23 | ) 24 | 25 | type valueError struct { 26 | v interface{} 27 | err error 28 | } 29 | 30 | func ve(v interface{}, err error) valueError { 31 | return valueError{v, err} 32 | } 33 | 34 | var replyTests = []struct { 35 | name interface{} 36 | actual valueError 37 | expected valueError 38 | }{ 39 | { 40 | "ints([v1, v2])", 41 | ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)), 42 | ve([]int{4, 5}, nil), 43 | }, 44 | { 45 | "ints(nil)", 46 | ve(redis.Ints(nil, nil)), 47 | ve([]int(nil), redis.ErrNil), 48 | }, 49 | { 50 | "strings([v1, v2])", 51 | ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)), 52 | ve([]string{"v1", "v2"}, nil), 53 | }, 54 | { 55 | "strings(nil)", 56 | ve(redis.Strings(nil, nil)), 57 | ve([]string(nil), redis.ErrNil), 58 | }, 59 | { 60 | "byteslices([v1, v2])", 61 | ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)), 62 | ve([][]byte{[]byte("v1"), []byte("v2")}, nil), 63 | }, 64 | { 65 | "byteslices(nil)", 66 | ve(redis.ByteSlices(nil, nil)), 67 | ve([][]byte(nil), redis.ErrNil), 68 | }, 69 | { 70 | "values([v1, v2])", 71 | ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)), 72 | ve([]interface{}{[]byte("v1"), []byte("v2")}, nil), 73 | }, 74 | { 75 | "values(nil)", 76 | ve(redis.Values(nil, nil)), 77 | ve([]interface{}(nil), redis.ErrNil), 78 | }, 79 | { 80 | "float64(1.0)", 81 | ve(redis.Float64([]byte("1.0"), nil)), 82 | ve(float64(1.0), nil), 83 | }, 84 | { 85 | "float64(nil)", 86 | ve(redis.Float64(nil, nil)), 87 | ve(float64(0.0), redis.ErrNil), 88 | }, 89 | { 90 | "uint64(1)", 91 | ve(redis.Uint64(int64(1), nil)), 92 | ve(uint64(1), nil), 93 | }, 94 | { 95 | "uint64(-1)", 96 | ve(redis.Uint64(int64(-1), nil)), 97 | ve(uint64(0), redis.ErrNegativeInt), 98 | }, 99 | { 100 | "positions([[1, 2], nil, [3, 4]])", 101 | ve(redis.Positions([]interface{}{[]interface{}{[]byte("1"), []byte("2")}, nil, []interface{}{[]byte("3"), []byte("4")}}, nil)), 102 | ve([]*[2]float64{{1.0, 2.0}, nil, {3.0, 4.0}}, nil), 103 | }, 104 | } 105 | 106 | func TestReply(t *testing.T) { 107 | for _, rt := range replyTests { 108 | if rt.actual.err != rt.expected.err { 109 | t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err) 110 | continue 111 | } 112 | if !reflect.DeepEqual(rt.actual.v, rt.expected.v) { 113 | t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v) 114 | } 115 | } 116 | } 117 | 118 | // dial wraps DialDefaultServer() with a more suitable function name for examples. 119 | func dial() (redis.Conn, error) { 120 | return redis.DialDefaultServer() 121 | } 122 | 123 | func ExampleBool() { 124 | c, err := dial() 125 | if err != nil { 126 | fmt.Println(err) 127 | return 128 | } 129 | defer c.Close() 130 | 131 | c.Do("SET", "foo", 1) 132 | exists, _ := redis.Bool(c.Do("EXISTS", "foo")) 133 | fmt.Printf("%#v\n", exists) 134 | // Output: 135 | // true 136 | } 137 | 138 | func ExampleInt() { 139 | c, err := dial() 140 | if err != nil { 141 | fmt.Println(err) 142 | return 143 | } 144 | defer c.Close() 145 | 146 | c.Do("SET", "k1", 1) 147 | n, _ := redis.Int(c.Do("GET", "k1")) 148 | fmt.Printf("%#v\n", n) 149 | n, _ = redis.Int(c.Do("INCR", "k1")) 150 | fmt.Printf("%#v\n", n) 151 | // Output: 152 | // 1 153 | // 2 154 | } 155 | 156 | func ExampleInts() { 157 | c, err := dial() 158 | if err != nil { 159 | fmt.Println(err) 160 | return 161 | } 162 | defer c.Close() 163 | 164 | c.Do("SADD", "set_with_integers", 4, 5, 6) 165 | ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers")) 166 | fmt.Printf("%#v\n", ints) 167 | // Output: 168 | // []int{4, 5, 6} 169 | } 170 | 171 | func ExampleString() { 172 | c, err := dial() 173 | if err != nil { 174 | fmt.Println(err) 175 | return 176 | } 177 | defer c.Close() 178 | 179 | c.Do("SET", "hello", "world") 180 | s, err := redis.String(c.Do("GET", "hello")) 181 | fmt.Printf("%#v\n", s) 182 | // Output: 183 | // "world" 184 | } 185 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Script encapsulates the source, hash and key count for a Lua script. See 25 | // http://redis.io/commands/eval for information on scripts in Redis. 26 | type Script struct { 27 | keyCount int 28 | src string 29 | hash string 30 | } 31 | 32 | // NewScript returns a new script object. If keyCount is greater than or equal 33 | // to zero, then the count is automatically inserted in the EVAL command 34 | // argument list. If keyCount is less than zero, then the application supplies 35 | // the count as the first value in the keysAndArgs argument to the Do, Send and 36 | // SendHash methods. 37 | func NewScript(keyCount int, src string) *Script { 38 | h := sha1.New() 39 | io.WriteString(h, src) 40 | return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} 41 | } 42 | 43 | func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { 44 | var args []interface{} 45 | if s.keyCount < 0 { 46 | args = make([]interface{}, 1+len(keysAndArgs)) 47 | args[0] = spec 48 | copy(args[1:], keysAndArgs) 49 | } else { 50 | args = make([]interface{}, 2+len(keysAndArgs)) 51 | args[0] = spec 52 | args[1] = s.keyCount 53 | copy(args[2:], keysAndArgs) 54 | } 55 | return args 56 | } 57 | 58 | // Do evaluates the script. Under the covers, Do optimistically evaluates the 59 | // script using the EVALSHA command. If the command fails because the script is 60 | // not loaded, then Do evaluates the script using the EVAL command (thus 61 | // causing the script to load). 62 | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { 63 | v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) 64 | if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { 65 | v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) 66 | } 67 | return v, err 68 | } 69 | 70 | // SendHash evaluates the script without waiting for the reply. The script is 71 | // evaluated with the EVALSHA command. The application must ensure that the 72 | // script is loaded by a previous call to Send, Do or Load methods. 73 | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { 74 | return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) 75 | } 76 | 77 | // Send evaluates the script without waiting for the reply. 78 | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { 79 | return c.Send("EVAL", s.args(s.src, keysAndArgs)...) 80 | } 81 | 82 | // Load loads the script without evaluating it. 83 | func (s *Script) Load(c Conn) error { 84 | _, err := c.Do("SCRIPT", "LOAD", s.src) 85 | return err 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/script_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "testing" 21 | "time" 22 | 23 | "github.com/garyburd/redigo/redis" 24 | ) 25 | 26 | var ( 27 | // These variables are declared at package level to remove distracting 28 | // details from the examples. 29 | c redis.Conn 30 | reply interface{} 31 | err error 32 | ) 33 | 34 | func ExampleScript() { 35 | // Initialize a package-level variable with a script. 36 | var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`) 37 | 38 | // In a function, use the script Do method to evaluate the script. The Do 39 | // method optimistically uses the EVALSHA command. If the script is not 40 | // loaded, then the Do method falls back to the EVAL command. 41 | reply, err = getScript.Do(c, "foo") 42 | } 43 | 44 | func TestScript(t *testing.T) { 45 | c, err := redis.DialDefaultServer() 46 | if err != nil { 47 | t.Fatalf("error connection to database, %v", err) 48 | } 49 | defer c.Close() 50 | 51 | // To test fall back in Do, we make script unique by adding comment with current time. 52 | script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano()) 53 | s := redis.NewScript(2, script) 54 | reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")} 55 | 56 | v, err := s.Do(c, "key1", "key2", "arg1", "arg2") 57 | if err != nil { 58 | t.Errorf("s.Do(c, ...) returned %v", err) 59 | } 60 | 61 | if !reflect.DeepEqual(v, reply) { 62 | t.Errorf("s.Do(c, ..); = %v, want %v", v, reply) 63 | } 64 | 65 | err = s.Load(c) 66 | if err != nil { 67 | t.Errorf("s.Load(c) returned %v", err) 68 | } 69 | 70 | err = s.SendHash(c, "key1", "key2", "arg1", "arg2") 71 | if err != nil { 72 | t.Errorf("s.SendHash(c, ...) returned %v", err) 73 | } 74 | 75 | err = c.Flush() 76 | if err != nil { 77 | t.Errorf("c.Flush() returned %v", err) 78 | } 79 | 80 | v, err = c.Receive() 81 | if !reflect.DeepEqual(v, reply) { 82 | t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply) 83 | } 84 | 85 | err = s.Send(c, "key1", "key2", "arg1", "arg2") 86 | if err != nil { 87 | t.Errorf("s.Send(c, ...) returned %v", err) 88 | } 89 | 90 | err = c.Flush() 91 | if err != nil { 92 | t.Errorf("c.Flush() returned %v", err) 93 | } 94 | 95 | v, err = c.Receive() 96 | if !reflect.DeepEqual(v, reply) { 97 | t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply) 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/test_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bufio" 19 | "errors" 20 | "flag" 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "os" 25 | "os/exec" 26 | "strconv" 27 | "strings" 28 | "sync" 29 | "testing" 30 | "time" 31 | ) 32 | 33 | func SetNowFunc(f func() time.Time) { 34 | nowFunc = f 35 | } 36 | 37 | var ( 38 | ErrNegativeInt = errNegativeInt 39 | 40 | serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary") 41 | serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers") 42 | serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`") 43 | serverLog = ioutil.Discard 44 | 45 | defaultServerMu sync.Mutex 46 | defaultServer *Server 47 | defaultServerErr error 48 | ) 49 | 50 | type Server struct { 51 | name string 52 | cmd *exec.Cmd 53 | done chan struct{} 54 | } 55 | 56 | func NewServer(name string, args ...string) (*Server, error) { 57 | s := &Server{ 58 | name: name, 59 | cmd: exec.Command(*serverPath, args...), 60 | done: make(chan struct{}), 61 | } 62 | 63 | r, err := s.cmd.StdoutPipe() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | err = s.cmd.Start() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | ready := make(chan error, 1) 74 | go s.watch(r, ready) 75 | 76 | select { 77 | case err = <-ready: 78 | case <-time.After(time.Second * 10): 79 | err = errors.New("timeout waiting for server to start") 80 | } 81 | 82 | if err != nil { 83 | s.Stop() 84 | return nil, err 85 | } 86 | 87 | return s, nil 88 | } 89 | 90 | func (s *Server) watch(r io.Reader, ready chan error) { 91 | fmt.Fprintf(serverLog, "%d START %s \n", s.cmd.Process.Pid, s.name) 92 | var listening bool 93 | var text string 94 | scn := bufio.NewScanner(r) 95 | for scn.Scan() { 96 | text = scn.Text() 97 | fmt.Fprintf(serverLog, "%s\n", text) 98 | if !listening { 99 | if strings.Contains(text, "The server is now ready to accept connections on port") { 100 | listening = true 101 | ready <- nil 102 | } 103 | } 104 | } 105 | if !listening { 106 | ready <- fmt.Errorf("server exited: %s", text) 107 | } 108 | s.cmd.Wait() 109 | fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name) 110 | close(s.done) 111 | } 112 | 113 | func (s *Server) Stop() { 114 | s.cmd.Process.Signal(os.Interrupt) 115 | <-s.done 116 | } 117 | 118 | // stopDefaultServer stops the server created by DialDefaultServer. 119 | func stopDefaultServer() { 120 | defaultServerMu.Lock() 121 | defer defaultServerMu.Unlock() 122 | if defaultServer != nil { 123 | defaultServer.Stop() 124 | defaultServer = nil 125 | } 126 | } 127 | 128 | // startDefaultServer starts the default server if not already running. 129 | func startDefaultServer() error { 130 | defaultServerMu.Lock() 131 | defer defaultServerMu.Unlock() 132 | if defaultServer != nil || defaultServerErr != nil { 133 | return defaultServerErr 134 | } 135 | defaultServer, defaultServerErr = NewServer( 136 | "default", 137 | "--port", strconv.Itoa(*serverBasePort), 138 | "--save", "", 139 | "--appendonly", "no") 140 | return defaultServerErr 141 | } 142 | 143 | // DialDefaultServer starts the test server if not already started and dials a 144 | // connection to the server. 145 | func DialDefaultServer() (Conn, error) { 146 | if err := startDefaultServer(); err != nil { 147 | return nil, err 148 | } 149 | c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second)) 150 | if err != nil { 151 | return nil, err 152 | } 153 | c.Do("FLUSHDB") 154 | return c, nil 155 | } 156 | 157 | func TestMain(m *testing.M) { 158 | os.Exit(func() int { 159 | flag.Parse() 160 | 161 | var f *os.File 162 | if *serverLogName != "" { 163 | var err error 164 | f, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) 165 | if err != nil { 166 | fmt.Fprintf(os.Stderr, "Error opening redis-log: %v\n", err) 167 | return 1 168 | } 169 | defer f.Close() 170 | serverLog = f 171 | } 172 | 173 | defer stopDefaultServer() 174 | 175 | return m.Run() 176 | }()) 177 | } 178 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/zpop_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis_test 16 | 17 | import ( 18 | "fmt" 19 | "github.com/garyburd/redigo/redis" 20 | ) 21 | 22 | // zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. 23 | func zpop(c redis.Conn, key string) (result string, err error) { 24 | 25 | defer func() { 26 | // Return connection to normal state on error. 27 | if err != nil { 28 | c.Do("DISCARD") 29 | } 30 | }() 31 | 32 | // Loop until transaction is successful. 33 | for { 34 | if _, err := c.Do("WATCH", key); err != nil { 35 | return "", err 36 | } 37 | 38 | members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0)) 39 | if err != nil { 40 | return "", err 41 | } 42 | if len(members) != 1 { 43 | return "", redis.ErrNil 44 | } 45 | 46 | c.Send("MULTI") 47 | c.Send("ZREM", key, members[0]) 48 | queued, err := c.Do("EXEC") 49 | if err != nil { 50 | return "", err 51 | } 52 | 53 | if queued != nil { 54 | result = members[0] 55 | break 56 | } 57 | } 58 | 59 | return result, nil 60 | } 61 | 62 | // zpopScript pops a value from a ZSET. 63 | var zpopScript = redis.NewScript(1, ` 64 | local r = redis.call('ZRANGE', KEYS[1], 0, 0) 65 | if r ~= nil then 66 | r = r[1] 67 | redis.call('ZREM', KEYS[1], r) 68 | end 69 | return r 70 | `) 71 | 72 | // This example implements ZPOP as described at 73 | // http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting. 74 | func Example_zpop() { 75 | c, err := dial() 76 | if err != nil { 77 | fmt.Println(err) 78 | return 79 | } 80 | defer c.Close() 81 | 82 | // Add test data using a pipeline. 83 | 84 | for i, member := range []string{"red", "blue", "green"} { 85 | c.Send("ZADD", "zset", i, member) 86 | } 87 | if _, err := c.Do(""); err != nil { 88 | fmt.Println(err) 89 | return 90 | } 91 | 92 | // Pop using WATCH/MULTI/EXEC 93 | 94 | v, err := zpop(c, "zset") 95 | if err != nil { 96 | fmt.Println(err) 97 | return 98 | } 99 | fmt.Println(v) 100 | 101 | // Pop using a script. 102 | 103 | v, err = redis.String(zpopScript.Do(c, "zset")) 104 | if err != nil { 105 | fmt.Println(err) 106 | return 107 | } 108 | fmt.Println(v) 109 | 110 | // Output: 111 | // red 112 | // blue 113 | } 114 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redisx/connmux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redisx 16 | 17 | import ( 18 | "errors" 19 | "sync" 20 | 21 | "github.com/garyburd/redigo/internal" 22 | "github.com/garyburd/redigo/redis" 23 | ) 24 | 25 | // ConnMux multiplexes one or more connections to a single underlying 26 | // connection. The ConnMux connections do not support concurrency, commands 27 | // that associate server side state with the connection or commands that put 28 | // the connection in a special mode. 29 | type ConnMux struct { 30 | c redis.Conn 31 | 32 | sendMu sync.Mutex 33 | sendID uint 34 | 35 | recvMu sync.Mutex 36 | recvID uint 37 | recvWait map[uint]chan struct{} 38 | } 39 | 40 | func NewConnMux(c redis.Conn) *ConnMux { 41 | return &ConnMux{c: c, recvWait: make(map[uint]chan struct{})} 42 | } 43 | 44 | // Get gets a connection. The application must close the returned connection. 45 | func (p *ConnMux) Get() redis.Conn { 46 | c := &muxConn{p: p} 47 | c.ids = c.buf[:0] 48 | return c 49 | } 50 | 51 | // Close closes the underlying connection. 52 | func (p *ConnMux) Close() error { 53 | return p.c.Close() 54 | } 55 | 56 | type muxConn struct { 57 | p *ConnMux 58 | ids []uint 59 | buf [8]uint 60 | } 61 | 62 | func (c *muxConn) send(flush bool, cmd string, args ...interface{}) error { 63 | if internal.LookupCommandInfo(cmd).Set != 0 { 64 | return errors.New("command not supported by mux pool") 65 | } 66 | p := c.p 67 | p.sendMu.Lock() 68 | id := p.sendID 69 | c.ids = append(c.ids, id) 70 | p.sendID++ 71 | err := p.c.Send(cmd, args...) 72 | if flush { 73 | err = p.c.Flush() 74 | } 75 | p.sendMu.Unlock() 76 | return err 77 | } 78 | 79 | func (c *muxConn) Send(cmd string, args ...interface{}) error { 80 | return c.send(false, cmd, args...) 81 | } 82 | 83 | func (c *muxConn) Flush() error { 84 | p := c.p 85 | p.sendMu.Lock() 86 | err := p.c.Flush() 87 | p.sendMu.Unlock() 88 | return err 89 | } 90 | 91 | func (c *muxConn) Receive() (interface{}, error) { 92 | if len(c.ids) == 0 { 93 | return nil, errors.New("mux pool underflow") 94 | } 95 | 96 | id := c.ids[0] 97 | c.ids = c.ids[1:] 98 | if len(c.ids) == 0 { 99 | c.ids = c.buf[:0] 100 | } 101 | 102 | p := c.p 103 | p.recvMu.Lock() 104 | if p.recvID != id { 105 | ch := make(chan struct{}) 106 | p.recvWait[id] = ch 107 | p.recvMu.Unlock() 108 | <-ch 109 | p.recvMu.Lock() 110 | if p.recvID != id { 111 | panic("out of sync") 112 | } 113 | } 114 | 115 | v, err := p.c.Receive() 116 | 117 | id++ 118 | p.recvID = id 119 | ch, ok := p.recvWait[id] 120 | if ok { 121 | delete(p.recvWait, id) 122 | } 123 | p.recvMu.Unlock() 124 | if ok { 125 | ch <- struct{}{} 126 | } 127 | 128 | return v, err 129 | } 130 | 131 | func (c *muxConn) Close() error { 132 | var err error 133 | if len(c.ids) == 0 { 134 | return nil 135 | } 136 | c.Flush() 137 | for _ = range c.ids { 138 | _, err = c.Receive() 139 | } 140 | return err 141 | } 142 | 143 | func (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, error) { 144 | if err := c.send(true, cmd, args...); err != nil { 145 | return nil, err 146 | } 147 | return c.Receive() 148 | } 149 | 150 | func (c *muxConn) Err() error { 151 | return c.p.c.Err() 152 | } 153 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redisx/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redisx contains experimental features for Redigo. Features in this 16 | // package may be modified or deleted at any time. 17 | package redisx // import "github.com/garyburd/redigo/redisx" 18 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | - go: 1.4 7 | - go: 1.5 8 | - go: 1.6 9 | - go: 1.7 10 | - go: tip 11 | allow_failures: 12 | - go: tip 13 | 14 | script: 15 | - go get -t -v ./... 16 | - diff -u <(echo -n) <(gofmt -d .) 17 | - go vet $(go list ./... | grep -v /vendor/) 18 | - go test -v -race ./... 19 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Gorilla Handlers Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/README.md: -------------------------------------------------------------------------------- 1 | gorilla/handlers 2 | ================ 3 | [![GoDoc](https://godoc.org/github.com/gorilla/handlers?status.svg)](https://godoc.org/github.com/gorilla/handlers) [![Build Status](https://travis-ci.org/gorilla/handlers.svg?branch=master)](https://travis-ci.org/gorilla/handlers) 4 | [![Sourcegraph](https://sourcegraph.com/github.com/gorilla/handlers/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/handlers?badge) 5 | 6 | 7 | Package handlers is a collection of handlers (aka "HTTP middleware") for use 8 | with Go's `net/http` package (or any framework supporting `http.Handler`), including: 9 | 10 | * [**LoggingHandler**](https://godoc.org/github.com/gorilla/handlers#LoggingHandler) for logging HTTP requests in the Apache [Common Log 11 | Format](http://httpd.apache.org/docs/2.2/logs.html#common). 12 | * [**CombinedLoggingHandler**](https://godoc.org/github.com/gorilla/handlers#CombinedLoggingHandler) for logging HTTP requests in the Apache [Combined Log 13 | Format](http://httpd.apache.org/docs/2.2/logs.html#combined) commonly used by 14 | both Apache and nginx. 15 | * [**CompressHandler**](https://godoc.org/github.com/gorilla/handlers#CompressHandler) for gzipping responses. 16 | * [**ContentTypeHandler**](https://godoc.org/github.com/gorilla/handlers#ContentTypeHandler) for validating requests against a list of accepted 17 | content types. 18 | * [**MethodHandler**](https://godoc.org/github.com/gorilla/handlers#MethodHandler) for matching HTTP methods against handlers in a 19 | `map[string]http.Handler` 20 | * [**ProxyHeaders**](https://godoc.org/github.com/gorilla/handlers#ProxyHeaders) for populating `r.RemoteAddr` and `r.URL.Scheme` based on the 21 | `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and RFC7239 `Forwarded` 22 | headers when running a Go server behind a HTTP reverse proxy. 23 | * [**CanonicalHost**](https://godoc.org/github.com/gorilla/handlers#CanonicalHost) for re-directing to the preferred host when handling multiple 24 | domains (i.e. multiple CNAME aliases). 25 | * [**RecoveryHandler**](https://godoc.org/github.com/gorilla/handlers#RecoveryHandler) for recovering from unexpected panics. 26 | 27 | Other handlers are documented [on the Gorilla 28 | website](http://www.gorillatoolkit.org/pkg/handlers). 29 | 30 | ## Example 31 | 32 | A simple example using `handlers.LoggingHandler` and `handlers.CompressHandler`: 33 | 34 | ```go 35 | import ( 36 | "net/http" 37 | "github.com/gorilla/handlers" 38 | ) 39 | 40 | func main() { 41 | r := http.NewServeMux() 42 | 43 | // Only log requests to our admin dashboard to stdout 44 | r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard))) 45 | r.HandleFunc("/", ShowIndex) 46 | 47 | // Wrap our server with our gzip handler to gzip compress all responses. 48 | http.ListenAndServe(":8000", handlers.CompressHandler(r)) 49 | } 50 | ``` 51 | 52 | ## License 53 | 54 | BSD licensed. See the included LICENSE file for details. 55 | 56 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/canonical.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "strings" 7 | ) 8 | 9 | type canonical struct { 10 | h http.Handler 11 | domain string 12 | code int 13 | } 14 | 15 | // CanonicalHost is HTTP middleware that re-directs requests to the canonical 16 | // domain. It accepts a domain and a status code (e.g. 301 or 302) and 17 | // re-directs clients to this domain. The existing request path is maintained. 18 | // 19 | // Note: If the provided domain is considered invalid by url.Parse or otherwise 20 | // returns an empty scheme or host, clients are not re-directed. 21 | // 22 | // Example: 23 | // 24 | // r := mux.NewRouter() 25 | // canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302) 26 | // r.HandleFunc("/route", YourHandler) 27 | // 28 | // log.Fatal(http.ListenAndServe(":7000", canonical(r))) 29 | // 30 | func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler { 31 | fn := func(h http.Handler) http.Handler { 32 | return canonical{h, domain, code} 33 | } 34 | 35 | return fn 36 | } 37 | 38 | func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) { 39 | dest, err := url.Parse(c.domain) 40 | if err != nil { 41 | // Call the next handler if the provided domain fails to parse. 42 | c.h.ServeHTTP(w, r) 43 | return 44 | } 45 | 46 | if dest.Scheme == "" || dest.Host == "" { 47 | // Call the next handler if the scheme or host are empty. 48 | // Note that url.Parse won't fail on in this case. 49 | c.h.ServeHTTP(w, r) 50 | return 51 | } 52 | 53 | if !strings.EqualFold(cleanHost(r.Host), dest.Host) { 54 | // Re-build the destination URL 55 | dest := dest.Scheme + "://" + dest.Host + r.URL.Path 56 | if r.URL.RawQuery != "" { 57 | dest += "?" + r.URL.RawQuery 58 | } 59 | http.Redirect(w, r, dest, c.code) 60 | return 61 | } 62 | 63 | c.h.ServeHTTP(w, r) 64 | } 65 | 66 | // cleanHost cleans invalid Host headers by stripping anything after '/' or ' '. 67 | // This is backported from Go 1.5 (in response to issue #11206) and attempts to 68 | // mitigate malformed Host headers that do not match the format in RFC7230. 69 | func cleanHost(in string) string { 70 | if i := strings.IndexAny(in, " /"); i != -1 { 71 | return in[:i] 72 | } 73 | return in 74 | } 75 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/canonical_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "log" 7 | "net/http" 8 | "net/http/httptest" 9 | "net/url" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func TestCleanHost(t *testing.T) { 15 | tests := []struct { 16 | in, want string 17 | }{ 18 | {"www.google.com", "www.google.com"}, 19 | {"www.google.com foo", "www.google.com"}, 20 | {"www.google.com/foo", "www.google.com"}, 21 | {" first character is a space", ""}, 22 | } 23 | for _, tt := range tests { 24 | got := cleanHost(tt.in) 25 | if tt.want != got { 26 | t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) 27 | } 28 | } 29 | } 30 | 31 | func TestCanonicalHost(t *testing.T) { 32 | gorilla := "http://www.gorillatoolkit.org" 33 | 34 | rr := httptest.NewRecorder() 35 | r := newRequest("GET", "http://www.example.com/") 36 | 37 | testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 38 | 39 | // Test a re-direct: should return a 302 Found. 40 | CanonicalHost(gorilla, http.StatusFound)(testHandler).ServeHTTP(rr, r) 41 | 42 | if rr.Code != http.StatusFound { 43 | t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusFound) 44 | } 45 | 46 | if rr.Header().Get("Location") != gorilla+r.URL.Path { 47 | t.Fatalf("bad re-direct: got %q want %q", rr.Header().Get("Location"), gorilla+r.URL.Path) 48 | } 49 | 50 | } 51 | 52 | func TestKeepsQueryString(t *testing.T) { 53 | google := "https://www.google.com" 54 | 55 | rr := httptest.NewRecorder() 56 | querystring := url.Values{"q": {"golang"}, "format": {"json"}}.Encode() 57 | r := newRequest("GET", "http://www.example.com/search?"+querystring) 58 | 59 | testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 60 | CanonicalHost(google, http.StatusFound)(testHandler).ServeHTTP(rr, r) 61 | 62 | want := google + r.URL.Path + "?" + querystring 63 | if rr.Header().Get("Location") != want { 64 | t.Fatalf("bad re-direct: got %q want %q", rr.Header().Get("Location"), want) 65 | } 66 | } 67 | 68 | func TestBadDomain(t *testing.T) { 69 | rr := httptest.NewRecorder() 70 | r := newRequest("GET", "http://www.example.com/") 71 | 72 | testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 73 | 74 | // Test a bad domain - should return 200 OK. 75 | CanonicalHost("%", http.StatusFound)(testHandler).ServeHTTP(rr, r) 76 | 77 | if rr.Code != http.StatusOK { 78 | t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK) 79 | } 80 | } 81 | 82 | func TestEmptyHost(t *testing.T) { 83 | rr := httptest.NewRecorder() 84 | r := newRequest("GET", "http://www.example.com/") 85 | 86 | testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 87 | 88 | // Test a domain that returns an empty url.Host from url.Parse. 89 | CanonicalHost("hello.com", http.StatusFound)(testHandler).ServeHTTP(rr, r) 90 | 91 | if rr.Code != http.StatusOK { 92 | t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK) 93 | } 94 | } 95 | 96 | func TestHeaderWrites(t *testing.T) { 97 | gorilla := "http://www.gorillatoolkit.org" 98 | 99 | testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 100 | w.WriteHeader(200) 101 | }) 102 | 103 | // Catch the log output to ensure we don't write multiple headers. 104 | var b bytes.Buffer 105 | buf := bufio.NewWriter(&b) 106 | tl := log.New(buf, "test: ", log.Lshortfile) 107 | 108 | srv := httptest.NewServer( 109 | CanonicalHost(gorilla, http.StatusFound)(testHandler)) 110 | defer srv.Close() 111 | srv.Config.ErrorLog = tl 112 | 113 | _, err := http.Get(srv.URL) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | err = buf.Flush() 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | 123 | // We rely on the error not changing: net/http does not export it. 124 | if strings.Contains(b.String(), "multiple response.WriteHeader calls") { 125 | t.Fatalf("re-direct did not return early: multiple header writes") 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/compress.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package handlers 6 | 7 | import ( 8 | "compress/flate" 9 | "compress/gzip" 10 | "io" 11 | "net/http" 12 | "strings" 13 | ) 14 | 15 | type compressResponseWriter struct { 16 | io.Writer 17 | http.ResponseWriter 18 | http.Hijacker 19 | http.Flusher 20 | http.CloseNotifier 21 | } 22 | 23 | func (w *compressResponseWriter) WriteHeader(c int) { 24 | w.ResponseWriter.Header().Del("Content-Length") 25 | w.ResponseWriter.WriteHeader(c) 26 | } 27 | 28 | func (w *compressResponseWriter) Header() http.Header { 29 | return w.ResponseWriter.Header() 30 | } 31 | 32 | func (w *compressResponseWriter) Write(b []byte) (int, error) { 33 | h := w.ResponseWriter.Header() 34 | if h.Get("Content-Type") == "" { 35 | h.Set("Content-Type", http.DetectContentType(b)) 36 | } 37 | h.Del("Content-Length") 38 | 39 | return w.Writer.Write(b) 40 | } 41 | 42 | type flusher interface { 43 | Flush() error 44 | } 45 | 46 | func (w *compressResponseWriter) Flush() { 47 | // Flush compressed data if compressor supports it. 48 | if f, ok := w.Writer.(flusher); ok { 49 | f.Flush() 50 | } 51 | // Flush HTTP response. 52 | if w.Flusher != nil { 53 | w.Flusher.Flush() 54 | } 55 | } 56 | 57 | // CompressHandler gzip compresses HTTP responses for clients that support it 58 | // via the 'Accept-Encoding' header. 59 | // 60 | // Compressing TLS traffic may leak the page contents to an attacker if the 61 | // page contains user input: http://security.stackexchange.com/a/102015/12208 62 | func CompressHandler(h http.Handler) http.Handler { 63 | return CompressHandlerLevel(h, gzip.DefaultCompression) 64 | } 65 | 66 | // CompressHandlerLevel gzip compresses HTTP responses with specified compression level 67 | // for clients that support it via the 'Accept-Encoding' header. 68 | // 69 | // The compression level should be gzip.DefaultCompression, gzip.NoCompression, 70 | // or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive. 71 | // gzip.DefaultCompression is used in case of invalid compression level. 72 | func CompressHandlerLevel(h http.Handler, level int) http.Handler { 73 | if level < gzip.DefaultCompression || level > gzip.BestCompression { 74 | level = gzip.DefaultCompression 75 | } 76 | 77 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 78 | L: 79 | for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { 80 | switch strings.TrimSpace(enc) { 81 | case "gzip": 82 | w.Header().Set("Content-Encoding", "gzip") 83 | w.Header().Add("Vary", "Accept-Encoding") 84 | 85 | gw, _ := gzip.NewWriterLevel(w, level) 86 | defer gw.Close() 87 | 88 | h, hok := w.(http.Hijacker) 89 | if !hok { /* w is not Hijacker... oh well... */ 90 | h = nil 91 | } 92 | 93 | f, fok := w.(http.Flusher) 94 | if !fok { 95 | f = nil 96 | } 97 | 98 | cn, cnok := w.(http.CloseNotifier) 99 | if !cnok { 100 | cn = nil 101 | } 102 | 103 | w = &compressResponseWriter{ 104 | Writer: gw, 105 | ResponseWriter: w, 106 | Hijacker: h, 107 | Flusher: f, 108 | CloseNotifier: cn, 109 | } 110 | 111 | break L 112 | case "deflate": 113 | w.Header().Set("Content-Encoding", "deflate") 114 | w.Header().Add("Vary", "Accept-Encoding") 115 | 116 | fw, _ := flate.NewWriter(w, level) 117 | defer fw.Close() 118 | 119 | h, hok := w.(http.Hijacker) 120 | if !hok { /* w is not Hijacker... oh well... */ 121 | h = nil 122 | } 123 | 124 | f, fok := w.(http.Flusher) 125 | if !fok { 126 | f = nil 127 | } 128 | 129 | cn, cnok := w.(http.CloseNotifier) 130 | if !cnok { 131 | cn = nil 132 | } 133 | 134 | w = &compressResponseWriter{ 135 | Writer: fw, 136 | ResponseWriter: w, 137 | Hijacker: h, 138 | Flusher: f, 139 | CloseNotifier: cn, 140 | } 141 | 142 | break L 143 | } 144 | } 145 | 146 | h.ServeHTTP(w, r) 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/compress_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package handlers 6 | 7 | import ( 8 | "bufio" 9 | "io" 10 | "net" 11 | "net/http" 12 | "net/http/httptest" 13 | "strconv" 14 | "testing" 15 | ) 16 | 17 | var contentType = "text/plain; charset=utf-8" 18 | 19 | func compressedRequest(w *httptest.ResponseRecorder, compression string) { 20 | CompressHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 | w.Header().Set("Content-Length", strconv.Itoa(9*1024)) 22 | w.Header().Set("Content-Type", contentType) 23 | for i := 0; i < 1024; i++ { 24 | io.WriteString(w, "Gorilla!\n") 25 | } 26 | })).ServeHTTP(w, &http.Request{ 27 | Method: "GET", 28 | Header: http.Header{ 29 | "Accept-Encoding": []string{compression}, 30 | }, 31 | }) 32 | 33 | } 34 | 35 | func TestCompressHandlerNoCompression(t *testing.T) { 36 | w := httptest.NewRecorder() 37 | compressedRequest(w, "") 38 | if enc := w.HeaderMap.Get("Content-Encoding"); enc != "" { 39 | t.Errorf("wrong content encoding, got %q want %q", enc, "") 40 | } 41 | if ct := w.HeaderMap.Get("Content-Type"); ct != contentType { 42 | t.Errorf("wrong content type, got %q want %q", ct, contentType) 43 | } 44 | if w.Body.Len() != 1024*9 { 45 | t.Errorf("wrong len, got %d want %d", w.Body.Len(), 1024*9) 46 | } 47 | if l := w.HeaderMap.Get("Content-Length"); l != "9216" { 48 | t.Errorf("wrong content-length. got %q expected %d", l, 1024*9) 49 | } 50 | } 51 | 52 | func TestCompressHandlerGzip(t *testing.T) { 53 | w := httptest.NewRecorder() 54 | compressedRequest(w, "gzip") 55 | if w.HeaderMap.Get("Content-Encoding") != "gzip" { 56 | t.Errorf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip") 57 | } 58 | if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { 59 | t.Errorf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") 60 | } 61 | if w.Body.Len() != 72 { 62 | t.Errorf("wrong len, got %d want %d", w.Body.Len(), 72) 63 | } 64 | if l := w.HeaderMap.Get("Content-Length"); l != "" { 65 | t.Errorf("wrong content-length. got %q expected %q", l, "") 66 | } 67 | } 68 | 69 | func TestCompressHandlerDeflate(t *testing.T) { 70 | w := httptest.NewRecorder() 71 | compressedRequest(w, "deflate") 72 | if w.HeaderMap.Get("Content-Encoding") != "deflate" { 73 | t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "deflate") 74 | } 75 | if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { 76 | t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") 77 | } 78 | if w.Body.Len() != 54 { 79 | t.Fatalf("wrong len, got %d want %d", w.Body.Len(), 54) 80 | } 81 | } 82 | 83 | func TestCompressHandlerGzipDeflate(t *testing.T) { 84 | w := httptest.NewRecorder() 85 | compressedRequest(w, "gzip, deflate ") 86 | if w.HeaderMap.Get("Content-Encoding") != "gzip" { 87 | t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip") 88 | } 89 | if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" { 90 | t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") 91 | } 92 | } 93 | 94 | type fullyFeaturedResponseWriter struct{} 95 | 96 | // Header/Write/WriteHeader implement the http.ResponseWriter interface. 97 | func (fullyFeaturedResponseWriter) Header() http.Header { 98 | return http.Header{} 99 | } 100 | func (fullyFeaturedResponseWriter) Write([]byte) (int, error) { 101 | return 0, nil 102 | } 103 | func (fullyFeaturedResponseWriter) WriteHeader(int) {} 104 | 105 | // Flush implements the http.Flusher interface. 106 | func (fullyFeaturedResponseWriter) Flush() {} 107 | 108 | // Hijack implements the http.Hijacker interface. 109 | func (fullyFeaturedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 110 | return nil, nil, nil 111 | } 112 | 113 | // CloseNotify implements the http.CloseNotifier interface. 114 | func (fullyFeaturedResponseWriter) CloseNotify() <-chan bool { 115 | return nil 116 | } 117 | 118 | func TestCompressHandlerPreserveInterfaces(t *testing.T) { 119 | // Compile time validation fullyFeaturedResponseWriter implements all the 120 | // interfaces we're asserting in the test case below. 121 | var ( 122 | _ http.Flusher = fullyFeaturedResponseWriter{} 123 | _ http.CloseNotifier = fullyFeaturedResponseWriter{} 124 | _ http.Hijacker = fullyFeaturedResponseWriter{} 125 | ) 126 | var h http.Handler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 127 | comp := r.Header.Get("Accept-Encoding") 128 | if _, ok := rw.(*compressResponseWriter); !ok { 129 | t.Fatalf("ResponseWriter wasn't wrapped by compressResponseWriter, got %T type", rw) 130 | } 131 | if _, ok := rw.(http.Flusher); !ok { 132 | t.Errorf("ResponseWriter lost http.Flusher interface for %q", comp) 133 | } 134 | if _, ok := rw.(http.CloseNotifier); !ok { 135 | t.Errorf("ResponseWriter lost http.CloseNotifier interface for %q", comp) 136 | } 137 | if _, ok := rw.(http.Hijacker); !ok { 138 | t.Errorf("ResponseWriter lost http.Hijacker interface for %q", comp) 139 | } 140 | }) 141 | h = CompressHandler(h) 142 | var ( 143 | rw fullyFeaturedResponseWriter 144 | ) 145 | r, err := http.NewRequest("GET", "/", nil) 146 | if err != nil { 147 | t.Fatalf("Failed to create test request: %v", err) 148 | } 149 | r.Header.Set("Accept-Encoding", "gzip") 150 | h.ServeHTTP(rw, r) 151 | 152 | r.Header.Set("Accept-Encoding", "deflate") 153 | h.ServeHTTP(rw, r) 154 | } 155 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package handlers is a collection of handlers (aka "HTTP middleware") for use 3 | with Go's net/http package (or any framework supporting http.Handler). 4 | 5 | The package includes handlers for logging in standardised formats, compressing 6 | HTTP responses, validating content types and other useful tools for manipulating 7 | requests and responses. 8 | */ 9 | package handlers 10 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/handlers_go18.go: -------------------------------------------------------------------------------- 1 | // +build go1.8 2 | 3 | package handlers 4 | 5 | import ( 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | type loggingResponseWriter interface { 11 | commonLoggingResponseWriter 12 | http.Pusher 13 | } 14 | 15 | func (l *responseLogger) Push(target string, opts *http.PushOptions) error { 16 | p, ok := l.w.(http.Pusher) 17 | if !ok { 18 | return fmt.Errorf("responseLogger does not implement http.Pusher") 19 | } 20 | return p.Push(target, opts) 21 | } 22 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/handlers_go18_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.8 2 | 3 | package handlers 4 | 5 | import ( 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | func TestLoggingHandlerWithPush(t *testing.T) { 13 | handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 14 | if _, ok := w.(http.Pusher); !ok { 15 | t.Fatalf("%T from LoggingHandler does not satisfy http.Pusher interface when built with Go >=1.8", w) 16 | } 17 | w.WriteHeader(200) 18 | }) 19 | 20 | logger := LoggingHandler(ioutil.Discard, handler) 21 | logger.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/")) 22 | } 23 | 24 | func TestCombinedLoggingHandlerWithPush(t *testing.T) { 25 | handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 26 | if _, ok := w.(http.Pusher); !ok { 27 | t.Fatalf("%T from CombinedLoggingHandler does not satisfy http.Pusher interface when built with Go >=1.8", w) 28 | } 29 | w.WriteHeader(200) 30 | }) 31 | 32 | logger := CombinedLoggingHandler(ioutil.Discard, handler) 33 | logger.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/")) 34 | } 35 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/handlers_pre18.go: -------------------------------------------------------------------------------- 1 | // +build !go1.8 2 | 3 | package handlers 4 | 5 | type loggingResponseWriter interface { 6 | commonLoggingResponseWriter 7 | } 8 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/proxy_headers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | // De-facto standard header keys. 11 | xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") 12 | xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host") 13 | xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto") 14 | xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme") 15 | xRealIP = http.CanonicalHeaderKey("X-Real-IP") 16 | ) 17 | 18 | var ( 19 | // RFC7239 defines a new "Forwarded: " header designed to replace the 20 | // existing use of X-Forwarded-* headers. 21 | // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43 22 | forwarded = http.CanonicalHeaderKey("Forwarded") 23 | // Allows for a sub-match of the first value after 'for=' to the next 24 | // comma, semi-colon or space. The match is case-insensitive. 25 | forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`) 26 | // Allows for a sub-match for the first instance of scheme (http|https) 27 | // prefixed by 'proto='. The match is case-insensitive. 28 | protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`) 29 | ) 30 | 31 | // ProxyHeaders inspects common reverse proxy headers and sets the corresponding 32 | // fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP 33 | // for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme 34 | // for the scheme (http|https) and the RFC7239 Forwarded header, which may 35 | // include both client IPs and schemes. 36 | // 37 | // NOTE: This middleware should only be used when behind a reverse 38 | // proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are 39 | // configured not to) strip these headers from client requests, or where these 40 | // headers are accepted "as is" from a remote client (e.g. when Go is not behind 41 | // a proxy), can manifest as a vulnerability if your application uses these 42 | // headers for validating the 'trustworthiness' of a request. 43 | func ProxyHeaders(h http.Handler) http.Handler { 44 | fn := func(w http.ResponseWriter, r *http.Request) { 45 | // Set the remote IP with the value passed from the proxy. 46 | if fwd := getIP(r); fwd != "" { 47 | r.RemoteAddr = fwd 48 | } 49 | 50 | // Set the scheme (proto) with the value passed from the proxy. 51 | if scheme := getScheme(r); scheme != "" { 52 | r.URL.Scheme = scheme 53 | } 54 | // Set the host with the value passed by the proxy 55 | if r.Header.Get(xForwardedHost) != "" { 56 | r.Host = r.Header.Get(xForwardedHost) 57 | } 58 | // Call the next handler in the chain. 59 | h.ServeHTTP(w, r) 60 | } 61 | 62 | return http.HandlerFunc(fn) 63 | } 64 | 65 | // getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239 66 | // Forwarded headers (in that order). 67 | func getIP(r *http.Request) string { 68 | var addr string 69 | 70 | if fwd := r.Header.Get(xForwardedFor); fwd != "" { 71 | // Only grab the first (client) address. Note that '192.168.0.1, 72 | // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after 73 | // the first may represent forwarding proxies earlier in the chain. 74 | s := strings.Index(fwd, ", ") 75 | if s == -1 { 76 | s = len(fwd) 77 | } 78 | addr = fwd[:s] 79 | } else if fwd := r.Header.Get(xRealIP); fwd != "" { 80 | // X-Real-IP should only contain one IP address (the client making the 81 | // request). 82 | addr = fwd 83 | } else if fwd := r.Header.Get(forwarded); fwd != "" { 84 | // match should contain at least two elements if the protocol was 85 | // specified in the Forwarded header. The first element will always be 86 | // the 'for=' capture, which we ignore. In the case of multiple IP 87 | // addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only 88 | // extract the first, which should be the client IP. 89 | if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 { 90 | // IPv6 addresses in Forwarded headers are quoted-strings. We strip 91 | // these quotes. 92 | addr = strings.Trim(match[1], `"`) 93 | } 94 | } 95 | 96 | return addr 97 | } 98 | 99 | // getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239 100 | // Forwarded headers (in that order). 101 | func getScheme(r *http.Request) string { 102 | var scheme string 103 | 104 | // Retrieve the scheme from X-Forwarded-Proto. 105 | if proto := r.Header.Get(xForwardedProto); proto != "" { 106 | scheme = strings.ToLower(proto) 107 | } else if proto = r.Header.Get(xForwardedScheme); proto != "" { 108 | scheme = strings.ToLower(proto) 109 | } else if proto = r.Header.Get(forwarded); proto != "" { 110 | // match should contain at least two elements if the protocol was 111 | // specified in the Forwarded header. The first element will always be 112 | // the 'proto=' capture, which we ignore. In the case of multiple proto 113 | // parameters (invalid) we only extract the first. 114 | if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 { 115 | scheme = strings.ToLower(match[1]) 116 | } 117 | } 118 | 119 | return scheme 120 | } 121 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/proxy_headers_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | type headerTable struct { 10 | key string // header key 11 | val string // header val 12 | expected string // expected result 13 | } 14 | 15 | func TestGetIP(t *testing.T) { 16 | headers := []headerTable{ 17 | {xForwardedFor, "8.8.8.8", "8.8.8.8"}, // Single address 18 | {xForwardedFor, "8.8.8.8, 8.8.4.4", "8.8.8.8"}, // Multiple 19 | {xForwardedFor, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address 20 | {xForwardedFor, "", ""}, // None 21 | {xRealIP, "8.8.8.8", "8.8.8.8"}, // Single address 22 | {xRealIP, "8.8.8.8, 8.8.4.4", "8.8.8.8, 8.8.4.4"}, // Multiple 23 | {xRealIP, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address 24 | {xRealIP, "", ""}, // None 25 | {forwarded, `for="_gazonk"`, "_gazonk"}, // Hostname 26 | {forwarded, `For="[2001:db8:cafe::17]:4711`, `[2001:db8:cafe::17]:4711`}, // IPv6 address 27 | {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, `192.0.2.60`}, // Multiple params 28 | {forwarded, `for=192.0.2.43, for=198.51.100.17`, "192.0.2.43"}, // Multiple params 29 | {forwarded, `for="workstation.local",for=198.51.100.17`, "workstation.local"}, // Hostname 30 | } 31 | 32 | for _, v := range headers { 33 | req := &http.Request{ 34 | Header: http.Header{ 35 | v.key: []string{v.val}, 36 | }} 37 | res := getIP(req) 38 | if res != v.expected { 39 | t.Fatalf("wrong header for %s: got %s want %s", v.key, res, 40 | v.expected) 41 | } 42 | } 43 | } 44 | 45 | func TestGetScheme(t *testing.T) { 46 | headers := []headerTable{ 47 | {xForwardedProto, "https", "https"}, 48 | {xForwardedProto, "http", "http"}, 49 | {xForwardedProto, "HTTP", "http"}, 50 | {xForwardedScheme, "https", "https"}, 51 | {xForwardedScheme, "http", "http"}, 52 | {xForwardedScheme, "HTTP", "http"}, 53 | {forwarded, `For="[2001:db8:cafe::17]:4711`, ""}, // No proto 54 | {forwarded, `for=192.0.2.43, for=198.51.100.17;proto=https`, "https"}, // Multiple params before proto 55 | {forwarded, `for=172.32.10.15; proto=https;by=127.0.0.1`, "https"}, // Space before proto 56 | {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, "http"}, // Multiple params 57 | } 58 | 59 | for _, v := range headers { 60 | req := &http.Request{ 61 | Header: http.Header{ 62 | v.key: []string{v.val}, 63 | }, 64 | } 65 | res := getScheme(req) 66 | if res != v.expected { 67 | t.Fatalf("wrong header for %s: got %s want %s", v.key, res, 68 | v.expected) 69 | } 70 | } 71 | } 72 | 73 | // Test the middleware end-to-end 74 | func TestProxyHeaders(t *testing.T) { 75 | rr := httptest.NewRecorder() 76 | r := newRequest("GET", "/") 77 | 78 | r.Header.Set(xForwardedFor, "8.8.8.8") 79 | r.Header.Set(xForwardedProto, "https") 80 | r.Header.Set(xForwardedHost, "google.com") 81 | var ( 82 | addr string 83 | proto string 84 | host string 85 | ) 86 | ProxyHeaders(http.HandlerFunc( 87 | func(w http.ResponseWriter, r *http.Request) { 88 | addr = r.RemoteAddr 89 | proto = r.URL.Scheme 90 | host = r.Host 91 | })).ServeHTTP(rr, r) 92 | 93 | if rr.Code != http.StatusOK { 94 | t.Fatalf("bad status: got %d want %d", rr.Code, http.StatusOK) 95 | } 96 | 97 | if addr != r.Header.Get(xForwardedFor) { 98 | t.Fatalf("wrong address: got %s want %s", addr, 99 | r.Header.Get(xForwardedFor)) 100 | } 101 | 102 | if proto != r.Header.Get(xForwardedProto) { 103 | t.Fatalf("wrong address: got %s want %s", proto, 104 | r.Header.Get(xForwardedProto)) 105 | } 106 | if host != r.Header.Get(xForwardedHost) { 107 | t.Fatalf("wrong address: got %s want %s", host, 108 | r.Header.Get(xForwardedHost)) 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/recovery.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "runtime/debug" 7 | ) 8 | 9 | // RecoveryHandlerLogger is an interface used by the recovering handler to print logs. 10 | type RecoveryHandlerLogger interface { 11 | Println(...interface{}) 12 | } 13 | 14 | type recoveryHandler struct { 15 | handler http.Handler 16 | logger RecoveryHandlerLogger 17 | printStack bool 18 | } 19 | 20 | // RecoveryOption provides a functional approach to define 21 | // configuration for a handler; such as setting the logging 22 | // whether or not to print strack traces on panic. 23 | type RecoveryOption func(http.Handler) 24 | 25 | func parseRecoveryOptions(h http.Handler, opts ...RecoveryOption) http.Handler { 26 | for _, option := range opts { 27 | option(h) 28 | } 29 | 30 | return h 31 | } 32 | 33 | // RecoveryHandler is HTTP middleware that recovers from a panic, 34 | // logs the panic, writes http.StatusInternalServerError, and 35 | // continues to the next handler. 36 | // 37 | // Example: 38 | // 39 | // r := mux.NewRouter() 40 | // r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 41 | // panic("Unexpected error!") 42 | // }) 43 | // 44 | // http.ListenAndServe(":1123", handlers.RecoveryHandler()(r)) 45 | func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler { 46 | return func(h http.Handler) http.Handler { 47 | r := &recoveryHandler{handler: h} 48 | return parseRecoveryOptions(r, opts...) 49 | } 50 | } 51 | 52 | // RecoveryLogger is a functional option to override 53 | // the default logger 54 | func RecoveryLogger(logger RecoveryHandlerLogger) RecoveryOption { 55 | return func(h http.Handler) { 56 | r := h.(*recoveryHandler) 57 | r.logger = logger 58 | } 59 | } 60 | 61 | // PrintRecoveryStack is a functional option to enable 62 | // or disable printing stack traces on panic. 63 | func PrintRecoveryStack(print bool) RecoveryOption { 64 | return func(h http.Handler) { 65 | r := h.(*recoveryHandler) 66 | r.printStack = print 67 | } 68 | } 69 | 70 | func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 71 | defer func() { 72 | if err := recover(); err != nil { 73 | w.WriteHeader(http.StatusInternalServerError) 74 | h.log(err) 75 | } 76 | }() 77 | 78 | h.handler.ServeHTTP(w, req) 79 | } 80 | 81 | func (h recoveryHandler) log(v ...interface{}) { 82 | if h.logger != nil { 83 | h.logger.Println(v...) 84 | } else { 85 | log.Println(v...) 86 | } 87 | 88 | if h.printStack { 89 | debug.PrintStack() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/handlers/recovery_test.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "net/http" 7 | "net/http/httptest" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | func TestRecoveryLoggerWithDefaultOptions(t *testing.T) { 13 | var buf bytes.Buffer 14 | log.SetOutput(&buf) 15 | 16 | handler := RecoveryHandler() 17 | handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 18 | panic("Unexpected error!") 19 | }) 20 | 21 | recovery := handler(handlerFunc) 22 | recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf")) 23 | 24 | if !strings.Contains(buf.String(), "Unexpected error!") { 25 | t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!") 26 | } 27 | } 28 | 29 | func TestRecoveryLoggerWithCustomLogger(t *testing.T) { 30 | var buf bytes.Buffer 31 | var logger = log.New(&buf, "", log.LstdFlags) 32 | 33 | handler := RecoveryHandler(RecoveryLogger(logger), PrintRecoveryStack(false)) 34 | handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 35 | panic("Unexpected error!") 36 | }) 37 | 38 | recovery := handler(handlerFunc) 39 | recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf")) 40 | 41 | if !strings.Contains(buf.String(), "Unexpected error!") { 42 | t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/hashicorp/golang-lru/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /vendor/github.com/hashicorp/golang-lru/README.md: -------------------------------------------------------------------------------- 1 | golang-lru 2 | ========== 3 | 4 | This provides the `lru` package which implements a fixed-size 5 | thread safe LRU cache. It is based on the cache in Groupcache. 6 | 7 | Documentation 8 | ============= 9 | 10 | Full docs are available on [Godoc](http://godoc.org/github.com/hashicorp/golang-lru) 11 | 12 | Example 13 | ======= 14 | 15 | Using the LRU is very simple: 16 | 17 | ```go 18 | l, _ := New(128) 19 | for i := 0; i < 256; i++ { 20 | l.Add(i, nil) 21 | } 22 | if l.Len() != 128 { 23 | panic(fmt.Sprintf("bad len: %v", l.Len())) 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /vendor/github.com/hashicorp/golang-lru/lru.go: -------------------------------------------------------------------------------- 1 | // This package provides a simple LRU cache. It is based on the 2 | // LRU implementation in groupcache: 3 | // https://github.com/golang/groupcache/tree/master/lru 4 | package lru 5 | 6 | import ( 7 | "sync" 8 | 9 | "github.com/hashicorp/golang-lru/simplelru" 10 | ) 11 | 12 | // Cache is a thread-safe fixed size LRU cache. 13 | type Cache struct { 14 | lru *simplelru.LRU 15 | lock sync.RWMutex 16 | } 17 | 18 | // New creates an LRU of the given size 19 | func New(size int) (*Cache, error) { 20 | return NewWithEvict(size, nil) 21 | } 22 | 23 | // NewWithEvict constructs a fixed size cache with the given eviction 24 | // callback. 25 | func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) { 26 | lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted)) 27 | if err != nil { 28 | return nil, err 29 | } 30 | c := &Cache{ 31 | lru: lru, 32 | } 33 | return c, nil 34 | } 35 | 36 | // Purge is used to completely clear the cache 37 | func (c *Cache) Purge() { 38 | c.lock.Lock() 39 | c.lru.Purge() 40 | c.lock.Unlock() 41 | } 42 | 43 | // Add adds a value to the cache. Returns true if an eviction occurred. 44 | func (c *Cache) Add(key, value interface{}) bool { 45 | c.lock.Lock() 46 | defer c.lock.Unlock() 47 | return c.lru.Add(key, value) 48 | } 49 | 50 | // Get looks up a key's value from the cache. 51 | func (c *Cache) Get(key interface{}) (interface{}, bool) { 52 | c.lock.Lock() 53 | defer c.lock.Unlock() 54 | return c.lru.Get(key) 55 | } 56 | 57 | // Check if a key is in the cache, without updating the recent-ness 58 | // or deleting it for being stale. 59 | func (c *Cache) Contains(key interface{}) bool { 60 | c.lock.RLock() 61 | defer c.lock.RUnlock() 62 | return c.lru.Contains(key) 63 | } 64 | 65 | // Returns the key value (or undefined if not found) without updating 66 | // the "recently used"-ness of the key. 67 | func (c *Cache) Peek(key interface{}) (interface{}, bool) { 68 | c.lock.RLock() 69 | defer c.lock.RUnlock() 70 | return c.lru.Peek(key) 71 | } 72 | 73 | // ContainsOrAdd checks if a key is in the cache without updating the 74 | // recent-ness or deleting it for being stale, and if not, adds the value. 75 | // Returns whether found and whether an eviction occurred. 76 | func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) { 77 | c.lock.Lock() 78 | defer c.lock.Unlock() 79 | 80 | if c.lru.Contains(key) { 81 | return true, false 82 | } else { 83 | evict := c.lru.Add(key, value) 84 | return false, evict 85 | } 86 | } 87 | 88 | // Remove removes the provided key from the cache. 89 | func (c *Cache) Remove(key interface{}) { 90 | c.lock.Lock() 91 | c.lru.Remove(key) 92 | c.lock.Unlock() 93 | } 94 | 95 | // RemoveOldest removes the oldest item from the cache. 96 | func (c *Cache) RemoveOldest() { 97 | c.lock.Lock() 98 | c.lru.RemoveOldest() 99 | c.lock.Unlock() 100 | } 101 | 102 | // Keys returns a slice of the keys in the cache, from oldest to newest. 103 | func (c *Cache) Keys() []interface{} { 104 | c.lock.RLock() 105 | defer c.lock.RUnlock() 106 | return c.lru.Keys() 107 | } 108 | 109 | // Len returns the number of items in the cache. 110 | func (c *Cache) Len() int { 111 | c.lock.RLock() 112 | defer c.lock.RUnlock() 113 | return c.lru.Len() 114 | } 115 | -------------------------------------------------------------------------------- /vendor/github.com/hashicorp/golang-lru/lru_test.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkLRU_Rand(b *testing.B) { 9 | l, err := New(8192) 10 | if err != nil { 11 | b.Fatalf("err: %v", err) 12 | } 13 | 14 | trace := make([]int64, b.N*2) 15 | for i := 0; i < b.N*2; i++ { 16 | trace[i] = rand.Int63() % 32768 17 | } 18 | 19 | b.ResetTimer() 20 | 21 | var hit, miss int 22 | for i := 0; i < 2*b.N; i++ { 23 | if i%2 == 0 { 24 | l.Add(trace[i], trace[i]) 25 | } else { 26 | _, ok := l.Get(trace[i]) 27 | if ok { 28 | hit++ 29 | } else { 30 | miss++ 31 | } 32 | } 33 | } 34 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) 35 | } 36 | 37 | func BenchmarkLRU_Freq(b *testing.B) { 38 | l, err := New(8192) 39 | if err != nil { 40 | b.Fatalf("err: %v", err) 41 | } 42 | 43 | trace := make([]int64, b.N*2) 44 | for i := 0; i < b.N*2; i++ { 45 | if i%2 == 0 { 46 | trace[i] = rand.Int63() % 16384 47 | } else { 48 | trace[i] = rand.Int63() % 32768 49 | } 50 | } 51 | 52 | b.ResetTimer() 53 | 54 | for i := 0; i < b.N; i++ { 55 | l.Add(trace[i], trace[i]) 56 | } 57 | var hit, miss int 58 | for i := 0; i < b.N; i++ { 59 | _, ok := l.Get(trace[i]) 60 | if ok { 61 | hit++ 62 | } else { 63 | miss++ 64 | } 65 | } 66 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) 67 | } 68 | 69 | func TestLRU(t *testing.T) { 70 | evictCounter := 0 71 | onEvicted := func(k interface{}, v interface{}) { 72 | if k != v { 73 | t.Fatalf("Evict values not equal (%v!=%v)", k, v) 74 | } 75 | evictCounter += 1 76 | } 77 | l, err := NewWithEvict(128, onEvicted) 78 | if err != nil { 79 | t.Fatalf("err: %v", err) 80 | } 81 | 82 | for i := 0; i < 256; i++ { 83 | l.Add(i, i) 84 | } 85 | if l.Len() != 128 { 86 | t.Fatalf("bad len: %v", l.Len()) 87 | } 88 | 89 | if evictCounter != 128 { 90 | t.Fatalf("bad evict count: %v", evictCounter) 91 | } 92 | 93 | for i, k := range l.Keys() { 94 | if v, ok := l.Get(k); !ok || v != k || v != i+128 { 95 | t.Fatalf("bad key: %v", k) 96 | } 97 | } 98 | for i := 0; i < 128; i++ { 99 | _, ok := l.Get(i) 100 | if ok { 101 | t.Fatalf("should be evicted") 102 | } 103 | } 104 | for i := 128; i < 256; i++ { 105 | _, ok := l.Get(i) 106 | if !ok { 107 | t.Fatalf("should not be evicted") 108 | } 109 | } 110 | for i := 128; i < 192; i++ { 111 | l.Remove(i) 112 | _, ok := l.Get(i) 113 | if ok { 114 | t.Fatalf("should be deleted") 115 | } 116 | } 117 | 118 | l.Get(192) // expect 192 to be last key in l.Keys() 119 | 120 | for i, k := range l.Keys() { 121 | if (i < 63 && k != i+193) || (i == 63 && k != 192) { 122 | t.Fatalf("out of order key: %v", k) 123 | } 124 | } 125 | 126 | l.Purge() 127 | if l.Len() != 0 { 128 | t.Fatalf("bad len: %v", l.Len()) 129 | } 130 | if _, ok := l.Get(200); ok { 131 | t.Fatalf("should contain nothing") 132 | } 133 | } 134 | 135 | // test that Add returns true/false if an eviction occurred 136 | func TestLRUAdd(t *testing.T) { 137 | evictCounter := 0 138 | onEvicted := func(k interface{}, v interface{}) { 139 | evictCounter += 1 140 | } 141 | 142 | l, err := NewWithEvict(1, onEvicted) 143 | if err != nil { 144 | t.Fatalf("err: %v", err) 145 | } 146 | 147 | if l.Add(1, 1) == true || evictCounter != 0 { 148 | t.Errorf("should not have an eviction") 149 | } 150 | if l.Add(2, 2) == false || evictCounter != 1 { 151 | t.Errorf("should have an eviction") 152 | } 153 | } 154 | 155 | // test that Contains doesn't update recent-ness 156 | func TestLRUContains(t *testing.T) { 157 | l, err := New(2) 158 | if err != nil { 159 | t.Fatalf("err: %v", err) 160 | } 161 | 162 | l.Add(1, 1) 163 | l.Add(2, 2) 164 | if !l.Contains(1) { 165 | t.Errorf("1 should be contained") 166 | } 167 | 168 | l.Add(3, 3) 169 | if l.Contains(1) { 170 | t.Errorf("Contains should not have updated recent-ness of 1") 171 | } 172 | } 173 | 174 | // test that Contains doesn't update recent-ness 175 | func TestLRUContainsOrAdd(t *testing.T) { 176 | l, err := New(2) 177 | if err != nil { 178 | t.Fatalf("err: %v", err) 179 | } 180 | 181 | l.Add(1, 1) 182 | l.Add(2, 2) 183 | contains, evict := l.ContainsOrAdd(1, 1) 184 | if !contains { 185 | t.Errorf("1 should be contained") 186 | } 187 | if evict { 188 | t.Errorf("nothing should be evicted here") 189 | } 190 | 191 | l.Add(3, 3) 192 | contains, evict = l.ContainsOrAdd(1, 1) 193 | if contains { 194 | t.Errorf("1 should not have been contained") 195 | } 196 | if !evict { 197 | t.Errorf("an eviction should have occurred") 198 | } 199 | if !l.Contains(1) { 200 | t.Errorf("now 1 should be contained") 201 | } 202 | } 203 | 204 | // test that Peek doesn't update recent-ness 205 | func TestLRUPeek(t *testing.T) { 206 | l, err := New(2) 207 | if err != nil { 208 | t.Fatalf("err: %v", err) 209 | } 210 | 211 | l.Add(1, 1) 212 | l.Add(2, 2) 213 | if v, ok := l.Peek(1); !ok || v != 1 { 214 | t.Errorf("1 should be set to 1: %v, %v", v, ok) 215 | } 216 | 217 | l.Add(3, 3) 218 | if l.Contains(1) { 219 | t.Errorf("should not have updated recent-ness of 1") 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /vendor/github.com/hashicorp/golang-lru/simplelru/lru.go: -------------------------------------------------------------------------------- 1 | package simplelru 2 | 3 | import ( 4 | "container/list" 5 | "errors" 6 | ) 7 | 8 | // EvictCallback is used to get a callback when a cache entry is evicted 9 | type EvictCallback func(key interface{}, value interface{}) 10 | 11 | // LRU implements a non-thread safe fixed size LRU cache 12 | type LRU struct { 13 | size int 14 | evictList *list.List 15 | items map[interface{}]*list.Element 16 | onEvict EvictCallback 17 | } 18 | 19 | // entry is used to hold a value in the evictList 20 | type entry struct { 21 | key interface{} 22 | value interface{} 23 | } 24 | 25 | // NewLRU constructs an LRU of the given size 26 | func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { 27 | if size <= 0 { 28 | return nil, errors.New("Must provide a positive size") 29 | } 30 | c := &LRU{ 31 | size: size, 32 | evictList: list.New(), 33 | items: make(map[interface{}]*list.Element), 34 | onEvict: onEvict, 35 | } 36 | return c, nil 37 | } 38 | 39 | // Purge is used to completely clear the cache 40 | func (c *LRU) Purge() { 41 | for k, v := range c.items { 42 | if c.onEvict != nil { 43 | c.onEvict(k, v.Value.(*entry).value) 44 | } 45 | delete(c.items, k) 46 | } 47 | c.evictList.Init() 48 | } 49 | 50 | // Add adds a value to the cache. Returns true if an eviction occurred. 51 | func (c *LRU) Add(key, value interface{}) bool { 52 | // Check for existing item 53 | if ent, ok := c.items[key]; ok { 54 | c.evictList.MoveToFront(ent) 55 | ent.Value.(*entry).value = value 56 | return false 57 | } 58 | 59 | // Add new item 60 | ent := &entry{key, value} 61 | entry := c.evictList.PushFront(ent) 62 | c.items[key] = entry 63 | 64 | evict := c.evictList.Len() > c.size 65 | // Verify size not exceeded 66 | if evict { 67 | c.removeOldest() 68 | } 69 | return evict 70 | } 71 | 72 | // Get looks up a key's value from the cache. 73 | func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { 74 | if ent, ok := c.items[key]; ok { 75 | c.evictList.MoveToFront(ent) 76 | return ent.Value.(*entry).value, true 77 | } 78 | return 79 | } 80 | 81 | // Check if a key is in the cache, without updating the recent-ness 82 | // or deleting it for being stale. 83 | func (c *LRU) Contains(key interface{}) (ok bool) { 84 | _, ok = c.items[key] 85 | return ok 86 | } 87 | 88 | // Returns the key value (or undefined if not found) without updating 89 | // the "recently used"-ness of the key. 90 | func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { 91 | if ent, ok := c.items[key]; ok { 92 | return ent.Value.(*entry).value, true 93 | } 94 | return nil, ok 95 | } 96 | 97 | // Remove removes the provided key from the cache, returning if the 98 | // key was contained. 99 | func (c *LRU) Remove(key interface{}) bool { 100 | if ent, ok := c.items[key]; ok { 101 | c.removeElement(ent) 102 | return true 103 | } 104 | return false 105 | } 106 | 107 | // RemoveOldest removes the oldest item from the cache. 108 | func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { 109 | ent := c.evictList.Back() 110 | if ent != nil { 111 | c.removeElement(ent) 112 | kv := ent.Value.(*entry) 113 | return kv.key, kv.value, true 114 | } 115 | return nil, nil, false 116 | } 117 | 118 | // GetOldest returns the oldest entry 119 | func (c *LRU) GetOldest() (interface{}, interface{}, bool) { 120 | ent := c.evictList.Back() 121 | if ent != nil { 122 | kv := ent.Value.(*entry) 123 | return kv.key, kv.value, true 124 | } 125 | return nil, nil, false 126 | } 127 | 128 | // Keys returns a slice of the keys in the cache, from oldest to newest. 129 | func (c *LRU) Keys() []interface{} { 130 | keys := make([]interface{}, len(c.items)) 131 | i := 0 132 | for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { 133 | keys[i] = ent.Value.(*entry).key 134 | i++ 135 | } 136 | return keys 137 | } 138 | 139 | // Len returns the number of items in the cache. 140 | func (c *LRU) Len() int { 141 | return c.evictList.Len() 142 | } 143 | 144 | // removeOldest removes the oldest item from the cache. 145 | func (c *LRU) removeOldest() { 146 | ent := c.evictList.Back() 147 | if ent != nil { 148 | c.removeElement(ent) 149 | } 150 | } 151 | 152 | // removeElement is used to remove a given list element from the cache 153 | func (c *LRU) removeElement(e *list.Element) { 154 | c.evictList.Remove(e) 155 | kv := e.Value.(*entry) 156 | delete(c.items, kv.key) 157 | if c.onEvict != nil { 158 | c.onEvict(kv.key, kv.value) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go: -------------------------------------------------------------------------------- 1 | package simplelru 2 | 3 | import "testing" 4 | 5 | func TestLRU(t *testing.T) { 6 | evictCounter := 0 7 | onEvicted := func(k interface{}, v interface{}) { 8 | if k != v { 9 | t.Fatalf("Evict values not equal (%v!=%v)", k, v) 10 | } 11 | evictCounter += 1 12 | } 13 | l, err := NewLRU(128, onEvicted) 14 | if err != nil { 15 | t.Fatalf("err: %v", err) 16 | } 17 | 18 | for i := 0; i < 256; i++ { 19 | l.Add(i, i) 20 | } 21 | if l.Len() != 128 { 22 | t.Fatalf("bad len: %v", l.Len()) 23 | } 24 | 25 | if evictCounter != 128 { 26 | t.Fatalf("bad evict count: %v", evictCounter) 27 | } 28 | 29 | for i, k := range l.Keys() { 30 | if v, ok := l.Get(k); !ok || v != k || v != i+128 { 31 | t.Fatalf("bad key: %v", k) 32 | } 33 | } 34 | for i := 0; i < 128; i++ { 35 | _, ok := l.Get(i) 36 | if ok { 37 | t.Fatalf("should be evicted") 38 | } 39 | } 40 | for i := 128; i < 256; i++ { 41 | _, ok := l.Get(i) 42 | if !ok { 43 | t.Fatalf("should not be evicted") 44 | } 45 | } 46 | for i := 128; i < 192; i++ { 47 | ok := l.Remove(i) 48 | if !ok { 49 | t.Fatalf("should be contained") 50 | } 51 | ok = l.Remove(i) 52 | if ok { 53 | t.Fatalf("should not be contained") 54 | } 55 | _, ok = l.Get(i) 56 | if ok { 57 | t.Fatalf("should be deleted") 58 | } 59 | } 60 | 61 | l.Get(192) // expect 192 to be last key in l.Keys() 62 | 63 | for i, k := range l.Keys() { 64 | if (i < 63 && k != i+193) || (i == 63 && k != 192) { 65 | t.Fatalf("out of order key: %v", k) 66 | } 67 | } 68 | 69 | l.Purge() 70 | if l.Len() != 0 { 71 | t.Fatalf("bad len: %v", l.Len()) 72 | } 73 | if _, ok := l.Get(200); ok { 74 | t.Fatalf("should contain nothing") 75 | } 76 | } 77 | 78 | func TestLRU_GetOldest_RemoveOldest(t *testing.T) { 79 | l, err := NewLRU(128, nil) 80 | if err != nil { 81 | t.Fatalf("err: %v", err) 82 | } 83 | for i := 0; i < 256; i++ { 84 | l.Add(i, i) 85 | } 86 | k, _, ok := l.GetOldest() 87 | if !ok { 88 | t.Fatalf("missing") 89 | } 90 | if k.(int) != 128 { 91 | t.Fatalf("bad: %v", k) 92 | } 93 | 94 | k, _, ok = l.RemoveOldest() 95 | if !ok { 96 | t.Fatalf("missing") 97 | } 98 | if k.(int) != 128 { 99 | t.Fatalf("bad: %v", k) 100 | } 101 | 102 | k, _, ok = l.RemoveOldest() 103 | if !ok { 104 | t.Fatalf("missing") 105 | } 106 | if k.(int) != 129 { 107 | t.Fatalf("bad: %v", k) 108 | } 109 | } 110 | 111 | // Test that Add returns true/false if an eviction occurred 112 | func TestLRU_Add(t *testing.T) { 113 | evictCounter := 0 114 | onEvicted := func(k interface{}, v interface{}) { 115 | evictCounter += 1 116 | } 117 | 118 | l, err := NewLRU(1, onEvicted) 119 | if err != nil { 120 | t.Fatalf("err: %v", err) 121 | } 122 | 123 | if l.Add(1, 1) == true || evictCounter != 0 { 124 | t.Errorf("should not have an eviction") 125 | } 126 | if l.Add(2, 2) == false || evictCounter != 1 { 127 | t.Errorf("should have an eviction") 128 | } 129 | } 130 | 131 | // Test that Contains doesn't update recent-ness 132 | func TestLRU_Contains(t *testing.T) { 133 | l, err := NewLRU(2, nil) 134 | if err != nil { 135 | t.Fatalf("err: %v", err) 136 | } 137 | 138 | l.Add(1, 1) 139 | l.Add(2, 2) 140 | if !l.Contains(1) { 141 | t.Errorf("1 should be contained") 142 | } 143 | 144 | l.Add(3, 3) 145 | if l.Contains(1) { 146 | t.Errorf("Contains should not have updated recent-ness of 1") 147 | } 148 | } 149 | 150 | // Test that Peek doesn't update recent-ness 151 | func TestLRU_Peek(t *testing.T) { 152 | l, err := NewLRU(2, nil) 153 | if err != nil { 154 | t.Fatalf("err: %v", err) 155 | } 156 | 157 | l.Add(1, 1) 158 | l.Add(2, 2) 159 | if v, ok := l.Peek(1); !ok || v != 1 { 160 | t.Errorf("1 should be set to 1: %v, %v", v, ok) 161 | } 162 | 163 | l.Add(3, 3) 164 | if l.Contains(1) { 165 | t.Errorf("should not have updated recent-ness of 1") 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /vendor/github.com/justinas/alice/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | matrix: 4 | include: 5 | - go: 1.0 6 | - go: 1.1 7 | - go: 1.2 8 | - go: 1.3 9 | - go: 1.4 10 | - go: 1.5 11 | - go: 1.6 12 | - go: 1.7 13 | - go: tip 14 | allow_failures: 15 | - go: tip 16 | -------------------------------------------------------------------------------- /vendor/github.com/justinas/alice/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Justinas Stankevicius 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/github.com/justinas/alice/README.md: -------------------------------------------------------------------------------- 1 | # Alice 2 | 3 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](http://godoc.org/github.com/justinas/alice) 4 | [![Build Status](https://travis-ci.org/justinas/alice.svg?branch=master)](https://travis-ci.org/justinas/alice) 5 | [![Coverage](http://gocover.io/_badge/github.com/justinas/alice)](http://gocover.io/github.com/justinas/alice) 6 | 7 | Alice provides a convenient way to chain 8 | your HTTP middleware functions and the app handler. 9 | 10 | In short, it transforms 11 | 12 | ```go 13 | Middleware1(Middleware2(Middleware3(App))) 14 | ``` 15 | 16 | to 17 | 18 | ```go 19 | alice.New(Middleware1, Middleware2, Middleware3).Then(App) 20 | ``` 21 | 22 | ### Why? 23 | 24 | None of the other middleware chaining solutions 25 | behaves exactly like Alice. 26 | Alice is as minimal as it gets: 27 | in essence, it's just a for loop that does the wrapping for you. 28 | 29 | Check out [this blog post](http://justinas.org/alice-painless-middleware-chaining-for-go/) 30 | for explanation how Alice is different from other chaining solutions. 31 | 32 | ### Usage 33 | 34 | Your middleware constructors should have the form of 35 | 36 | ```go 37 | func (http.Handler) http.Handler 38 | ``` 39 | 40 | Some middleware provide this out of the box. 41 | For ones that don't, it's trivial to write one yourself. 42 | 43 | ```go 44 | func myStripPrefix(h http.Handler) http.Handler { 45 | return http.StripPrefix("/old", h) 46 | } 47 | ``` 48 | 49 | This complete example shows the full power of Alice. 50 | 51 | ```go 52 | package main 53 | 54 | import ( 55 | "net/http" 56 | "time" 57 | 58 | "github.com/throttled/throttled" 59 | "github.com/justinas/alice" 60 | "github.com/justinas/nosurf" 61 | ) 62 | 63 | func timeoutHandler(h http.Handler) http.Handler { 64 | return http.TimeoutHandler(h, 1*time.Second, "timed out") 65 | } 66 | 67 | func myApp(w http.ResponseWriter, r *http.Request) { 68 | w.Write([]byte("Hello world!")) 69 | } 70 | 71 | func main() { 72 | th := throttled.Interval(throttled.PerSec(10), 1, &throttled.VaryBy{Path: true}, 50) 73 | myHandler := http.HandlerFunc(myApp) 74 | 75 | chain := alice.New(th.Throttle, timeoutHandler, nosurf.NewPure).Then(myHandler) 76 | http.ListenAndServe(":8000", chain) 77 | } 78 | ``` 79 | 80 | Here, the request will pass [throttled](https://github.com/PuerkitoBio/throttled) first, 81 | then an http.TimeoutHandler we've set up, 82 | then [nosurf](https://github.com/justinas/nosurf) 83 | and will finally reach our handler. 84 | 85 | Note that Alice makes **no guarantees** for 86 | how one or another piece of middleware will behave. 87 | Once it passes the execution to the outer layer of middleware, 88 | it has no saying in whether middleware will execute the inner handlers. 89 | This is intentional behavior. 90 | 91 | Alice works with Go 1.0 and higher. 92 | 93 | ### Contributing 94 | 95 | 0. Find an issue that bugs you / open a new one. 96 | 1. Discuss. 97 | 2. Branch off, commit, test. 98 | 3. Make a pull request / attach the commits to the issue. 99 | -------------------------------------------------------------------------------- /vendor/github.com/justinas/alice/chain.go: -------------------------------------------------------------------------------- 1 | // Package alice provides a convenient way to chain http handlers. 2 | package alice 3 | 4 | import "net/http" 5 | 6 | // A constructor for a piece of middleware. 7 | // Some middleware use this constructor out of the box, 8 | // so in most cases you can just pass somepackage.New 9 | type Constructor func(http.Handler) http.Handler 10 | 11 | // Chain acts as a list of http.Handler constructors. 12 | // Chain is effectively immutable: 13 | // once created, it will always hold 14 | // the same set of constructors in the same order. 15 | type Chain struct { 16 | constructors []Constructor 17 | } 18 | 19 | // New creates a new chain, 20 | // memorizing the given list of middleware constructors. 21 | // New serves no other function, 22 | // constructors are only called upon a call to Then(). 23 | func New(constructors ...Constructor) Chain { 24 | return Chain{append(([]Constructor)(nil), constructors...)} 25 | } 26 | 27 | // Then chains the middleware and returns the final http.Handler. 28 | // New(m1, m2, m3).Then(h) 29 | // is equivalent to: 30 | // m1(m2(m3(h))) 31 | // When the request comes in, it will be passed to m1, then m2, then m3 32 | // and finally, the given handler 33 | // (assuming every middleware calls the following one). 34 | // 35 | // A chain can be safely reused by calling Then() several times. 36 | // stdStack := alice.New(ratelimitHandler, csrfHandler) 37 | // indexPipe = stdStack.Then(indexHandler) 38 | // authPipe = stdStack.Then(authHandler) 39 | // Note that constructors are called on every call to Then() 40 | // and thus several instances of the same middleware will be created 41 | // when a chain is reused in this way. 42 | // For proper middleware, this should cause no problems. 43 | // 44 | // Then() treats nil as http.DefaultServeMux. 45 | func (c Chain) Then(h http.Handler) http.Handler { 46 | if h == nil { 47 | h = http.DefaultServeMux 48 | } 49 | 50 | for i := range c.constructors { 51 | h = c.constructors[len(c.constructors)-1-i](h) 52 | } 53 | 54 | return h 55 | } 56 | 57 | // ThenFunc works identically to Then, but takes 58 | // a HandlerFunc instead of a Handler. 59 | // 60 | // The following two statements are equivalent: 61 | // c.Then(http.HandlerFunc(fn)) 62 | // c.ThenFunc(fn) 63 | // 64 | // ThenFunc provides all the guarantees of Then. 65 | func (c Chain) ThenFunc(fn http.HandlerFunc) http.Handler { 66 | if fn == nil { 67 | return c.Then(nil) 68 | } 69 | return c.Then(fn) 70 | } 71 | 72 | // Append extends a chain, adding the specified constructors 73 | // as the last ones in the request flow. 74 | // 75 | // Append returns a new chain, leaving the original one untouched. 76 | // 77 | // stdChain := alice.New(m1, m2) 78 | // extChain := stdChain.Append(m3, m4) 79 | // // requests in stdChain go m1 -> m2 80 | // // requests in extChain go m1 -> m2 -> m3 -> m4 81 | func (c Chain) Append(constructors ...Constructor) Chain { 82 | newCons := make([]Constructor, 0, len(c.constructors)+len(constructors)) 83 | newCons = append(newCons, c.constructors...) 84 | newCons = append(newCons, constructors...) 85 | 86 | return Chain{newCons} 87 | } 88 | 89 | // Extend extends a chain by adding the specified chain 90 | // as the last one in the request flow. 91 | // 92 | // Extend returns a new chain, leaving the original one untouched. 93 | // 94 | // stdChain := alice.New(m1, m2) 95 | // ext1Chain := alice.New(m3, m4) 96 | // ext2Chain := stdChain.Extend(ext1Chain) 97 | // // requests in stdChain go m1 -> m2 98 | // // requests in ext1Chain go m3 -> m4 99 | // // requests in ext2Chain go m1 -> m2 -> m3 -> m4 100 | // 101 | // Another example: 102 | // aHtmlAfterNosurf := alice.New(m2) 103 | // aHtml := alice.New(m1, func(h http.Handler) http.Handler { 104 | // csrf := nosurf.New(h) 105 | // csrf.SetFailureHandler(aHtmlAfterNosurf.ThenFunc(csrfFail)) 106 | // return csrf 107 | // }).Extend(aHtmlAfterNosurf) 108 | // // requests to aHtml hitting nosurfs success handler go m1 -> nosurf -> m2 -> target-handler 109 | // // requests to aHtml hitting nosurfs failure handler go m1 -> nosurf -> m2 -> csrfFail 110 | func (c Chain) Extend(chain Chain) Chain { 111 | return c.Append(chain.constructors...) 112 | } 113 | -------------------------------------------------------------------------------- /vendor/github.com/justinas/alice/chain_test.go: -------------------------------------------------------------------------------- 1 | // Package alice implements a middleware chaining solution. 2 | package alice 3 | 4 | import ( 5 | "net/http" 6 | "net/http/httptest" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | // A constructor for middleware 12 | // that writes its own "tag" into the RW and does nothing else. 13 | // Useful in checking if a chain is behaving in the right order. 14 | func tagMiddleware(tag string) Constructor { 15 | return func(h http.Handler) http.Handler { 16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | w.Write([]byte(tag)) 18 | h.ServeHTTP(w, r) 19 | }) 20 | } 21 | } 22 | 23 | // Not recommended (https://golang.org/pkg/reflect/#Value.Pointer), 24 | // but the best we can do. 25 | func funcsEqual(f1, f2 interface{}) bool { 26 | val1 := reflect.ValueOf(f1) 27 | val2 := reflect.ValueOf(f2) 28 | return val1.Pointer() == val2.Pointer() 29 | } 30 | 31 | var testApp = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 32 | w.Write([]byte("app\n")) 33 | }) 34 | 35 | func TestNew(t *testing.T) { 36 | c1 := func(h http.Handler) http.Handler { 37 | return nil 38 | } 39 | 40 | c2 := func(h http.Handler) http.Handler { 41 | return http.StripPrefix("potato", nil) 42 | } 43 | 44 | slice := []Constructor{c1, c2} 45 | 46 | chain := New(slice...) 47 | for k := range slice { 48 | if !funcsEqual(chain.constructors[k], slice[k]) { 49 | t.Error("New does not add constructors correctly") 50 | } 51 | } 52 | } 53 | 54 | func TestThenWorksWithNoMiddleware(t *testing.T) { 55 | if !funcsEqual(New().Then(testApp), testApp) { 56 | t.Error("Then does not work with no middleware") 57 | } 58 | } 59 | 60 | func TestThenTreatsNilAsDefaultServeMux(t *testing.T) { 61 | if New().Then(nil) != http.DefaultServeMux { 62 | t.Error("Then does not treat nil as DefaultServeMux") 63 | } 64 | } 65 | 66 | func TestThenFuncTreatsNilAsDefaultServeMux(t *testing.T) { 67 | if New().ThenFunc(nil) != http.DefaultServeMux { 68 | t.Error("ThenFunc does not treat nil as DefaultServeMux") 69 | } 70 | } 71 | 72 | func TestThenFuncConstructsHandlerFunc(t *testing.T) { 73 | fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 74 | w.WriteHeader(200) 75 | }) 76 | chained := New().ThenFunc(fn) 77 | rec := httptest.NewRecorder() 78 | 79 | chained.ServeHTTP(rec, (*http.Request)(nil)) 80 | 81 | if reflect.TypeOf(chained) != reflect.TypeOf((http.HandlerFunc)(nil)) { 82 | t.Error("ThenFunc does not construct HandlerFunc") 83 | } 84 | } 85 | 86 | func TestThenOrdersHandlersCorrectly(t *testing.T) { 87 | t1 := tagMiddleware("t1\n") 88 | t2 := tagMiddleware("t2\n") 89 | t3 := tagMiddleware("t3\n") 90 | 91 | chained := New(t1, t2, t3).Then(testApp) 92 | 93 | w := httptest.NewRecorder() 94 | r, err := http.NewRequest("GET", "/", nil) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | chained.ServeHTTP(w, r) 100 | 101 | if w.Body.String() != "t1\nt2\nt3\napp\n" { 102 | t.Error("Then does not order handlers correctly") 103 | } 104 | } 105 | 106 | func TestAppendAddsHandlersCorrectly(t *testing.T) { 107 | chain := New(tagMiddleware("t1\n"), tagMiddleware("t2\n")) 108 | newChain := chain.Append(tagMiddleware("t3\n"), tagMiddleware("t4\n")) 109 | 110 | if len(chain.constructors) != 2 { 111 | t.Error("chain should have 2 constructors") 112 | } 113 | if len(newChain.constructors) != 4 { 114 | t.Error("newChain should have 4 constructors") 115 | } 116 | 117 | chained := newChain.Then(testApp) 118 | 119 | w := httptest.NewRecorder() 120 | r, err := http.NewRequest("GET", "/", nil) 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | 125 | chained.ServeHTTP(w, r) 126 | 127 | if w.Body.String() != "t1\nt2\nt3\nt4\napp\n" { 128 | t.Error("Append does not add handlers correctly") 129 | } 130 | } 131 | 132 | func TestAppendRespectsImmutability(t *testing.T) { 133 | chain := New(tagMiddleware("")) 134 | newChain := chain.Append(tagMiddleware("")) 135 | 136 | if &chain.constructors[0] == &newChain.constructors[0] { 137 | t.Error("Apppend does not respect immutability") 138 | } 139 | } 140 | 141 | func TestExtendAddsHandlersCorrectly(t *testing.T) { 142 | chain1 := New(tagMiddleware("t1\n"), tagMiddleware("t2\n")) 143 | chain2 := New(tagMiddleware("t3\n"), tagMiddleware("t4\n")) 144 | newChain := chain1.Extend(chain2) 145 | 146 | if len(chain1.constructors) != 2 { 147 | t.Error("chain1 should contain 2 constructors") 148 | } 149 | if len(chain2.constructors) != 2 { 150 | t.Error("chain2 should contain 2 constructors") 151 | } 152 | if len(newChain.constructors) != 4 { 153 | t.Error("newChain should contain 4 constructors") 154 | } 155 | 156 | chained := newChain.Then(testApp) 157 | 158 | w := httptest.NewRecorder() 159 | r, err := http.NewRequest("GET", "/", nil) 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | 164 | chained.ServeHTTP(w, r) 165 | 166 | if w.Body.String() != "t1\nt2\nt3\nt4\napp\n" { 167 | t.Error("Extend does not add handlers in correctly") 168 | } 169 | } 170 | 171 | func TestExtendRespectsImmutability(t *testing.T) { 172 | chain := New(tagMiddleware("")) 173 | newChain := chain.Extend(New(tagMiddleware(""))) 174 | 175 | if &chain.constructors[0] == &newChain.constructors[0] { 176 | t.Error("Extend does not respect immutability") 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *.swo 4 | *.test 5 | *.out 6 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: go 4 | 5 | go: 6 | - 1.3 7 | - tip 8 | 9 | notifications: 10 | email: false 11 | 12 | services: 13 | - redis-server 14 | 15 | install: 16 | - make get-deps 17 | # Move to the gopkg location that rather than the default clone location 18 | # otherwise Go 1.4+ complains about incorrect import paths. 19 | - export GOPKG_DIR=$GOPATH/src/gopkg.in/throttled 20 | - mkdir -p $GOPKG_DIR 21 | - mv $TRAVIS_BUILD_DIR $GOPKG_DIR/throttled.v2 22 | - cd $GOPKG_DIR/throttled.v2 23 | 24 | script: make 25 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Martin Angers and Contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * 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. 9 | 10 | * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | 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 HOLDER 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. 13 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | .PHONY: test test-cover bench lint get-deps .go-test .go-test-cover 4 | 5 | test: .go-test bench lint 6 | 7 | test-cover: .go-test-cover bench lint 8 | 9 | bench: 10 | go test -race -bench=. -cpu=1,2,4 11 | 12 | lint: 13 | gofmt -l . 14 | ifneq ($(TRAVIS_GO_VERSION),1.3) # go vet doesn't play nicely on 1.3 15 | go vet ./... 16 | endif 17 | which golint # Fail if golint doesn't exist 18 | -golint . # Don't fail on golint warnings themselves 19 | -golint store # Don't fail on golint warnings themselves 20 | 21 | get-deps: 22 | go get github.com/garyburd/redigo/redis 23 | go get github.com/hashicorp/golang-lru 24 | go get github.com/golang/lint/golint 25 | 26 | .go-test: 27 | go test ./... 28 | 29 | .go-test-cover: 30 | go test -coverprofile=throttled.coverage.out . 31 | go test -coverprofile=store.coverage.out ./store 32 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/README.md: -------------------------------------------------------------------------------- 1 | # Throttled [![build status](https://secure.travis-ci.org/throttled/throttled.png)](https://travis-ci.org/throttled/throttled) [![GoDoc](https://godoc.org/gopkg.in/throttled/throttled.v2?status.png)](https://godoc.org/gopkg.in/throttled/throttled.v2) 2 | 3 | Package throttled implements rate limiting access to resources such as 4 | HTTP endpoints. 5 | 6 | The 2.0.0 release made some major changes to the throttled API. If 7 | this change broke your code in problematic ways or you wish a feature 8 | of the old API had been retained, please open an issue. We don't 9 | guarantee any particular changes but would like to hear more about 10 | what our users need. Thanks! 11 | 12 | ## Installation 13 | 14 | throttled uses gopkg.in for semantic versioning: 15 | `go get gopkg.in/throttled/throttled.v2` 16 | 17 | As of July 27, 2015, the package is located under its own Github 18 | organization. Please adjust your imports to 19 | `gopkg.in/throttled/throttled.v2`. 20 | 21 | The 1.x release series is compatible with the original, unversioned 22 | library written by [Martin Angers][puerkitobio]. There is a 23 | [blog post explaining that version's usage on 0value.com][blog]. 24 | 25 | ## Documentation 26 | 27 | API documentation is available on [godoc.org][doc]. The following 28 | example demonstrates the usage of HTTPLimiter for rate-limiting access 29 | to an http.Handler to 20 requests per path per minute with bursts of 30 | up to 5 additional requests: 31 | 32 | store, err := memstore.New(65536) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | quota := throttled.RateQuota{throttled.PerMin(20), 5} 38 | rateLimiter, err := throttled.NewGCRARateLimiter(store, quota) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | httpRateLimiter := throttled.HTTPRateLimiter{ 44 | RateLimiter: rateLimiter, 45 | VaryBy: &throttled.VaryBy{Path: true}, 46 | } 47 | 48 | http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler)) 49 | 50 | ## Contributing 51 | 52 | Since throttled uses gopkg.in for versioning, running `go get` against 53 | a fork or cloning from Github to the default path will break 54 | imports. Instead, use the following process for setting up your 55 | environment and contributing: 56 | 57 | ```sh 58 | # Retrieve the source and dependencies. 59 | go get gopkg.in/throttled/throttled.v2/... 60 | 61 | # Fork the project on Github. For all following directions replace 62 | # with your Github username. Add your fork as a remote. 63 | cd $GOPATH/src/gopkg.in/throttled/throttled.v2 64 | git remote add fork git@github.com:/throttled.git 65 | 66 | # Create a branch, make your changes, test them and commit. 67 | git checkout -b my-new-feature 68 | # 69 | make test 70 | git commit -a 71 | git push -u fork my-new-feature 72 | ``` 73 | 74 | When your changes are ready, [open a pull request][pr] using "compare 75 | across forks". 76 | 77 | ## License 78 | 79 | The [BSD 3-clause license][bsd]. Copyright (c) 2014 Martin Angers and Contributors. 80 | 81 | [blog]: http://0value.com/throttled--guardian-of-the-web-server 82 | [bsd]: https://opensource.org/licenses/BSD-3-Clause 83 | [doc]: https://godoc.org/gopkg.in/throttled/throttled.v2 84 | [puerkitobio]: https://github.com/puerkitobio/ 85 | [pr]: https://github.com/throttled/throttled/compare 86 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/deprecated.go: -------------------------------------------------------------------------------- 1 | package throttled 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | // DEPRECATED. Quota returns the number of requests allowed and the custom time window. 9 | func (q Rate) Quota() (int, time.Duration) { 10 | return q.count, q.period * time.Duration(q.count) 11 | } 12 | 13 | // DEPRECATED. Q represents a custom quota. 14 | type Q struct { 15 | Requests int 16 | Window time.Duration 17 | } 18 | 19 | // DEPRECATED. Quota returns the number of requests allowed and the custom time window. 20 | func (q Q) Quota() (int, time.Duration) { 21 | return q.Requests, q.Window 22 | } 23 | 24 | // DEPRECATED. The Quota interface defines the method to implement to describe 25 | // a time-window quota, as required by the RateLimit throttler. 26 | type Quota interface { 27 | // Quota returns a number of requests allowed, and a duration. 28 | Quota() (int, time.Duration) 29 | } 30 | 31 | // DEPRECATED. Throttler is a backwards-compatible alias for HTTPLimiter. 32 | type Throttler struct { 33 | HTTPRateLimiter 34 | } 35 | 36 | // DEPRECATED. Throttle is an alias for HTTPLimiter#Limit 37 | func (t *Throttler) Throttle(h http.Handler) http.Handler { 38 | return t.RateLimit(h) 39 | } 40 | 41 | // DEPRECATED. RateLimit creates a Throttler that conforms to the given 42 | // rate limits 43 | func RateLimit(q Quota, vary *VaryBy, store GCRAStore) *Throttler { 44 | count, period := q.Quota() 45 | 46 | if count < 1 { 47 | count = 1 48 | } 49 | if period <= 0 { 50 | period = time.Second 51 | } 52 | 53 | rate := Rate{period: period / time.Duration(count)} 54 | limiter, err := NewGCRARateLimiter(store, RateQuota{rate, count - 1}) 55 | 56 | // This panic in unavoidable because the original interface does 57 | // not support returning an error. 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | return &Throttler{ 63 | HTTPRateLimiter{ 64 | RateLimiter: limiter, 65 | VaryBy: vary, 66 | }, 67 | } 68 | } 69 | 70 | // DEPRECATED. Store is an alias for GCRAStore 71 | type Store interface { 72 | GCRAStore 73 | } 74 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go: -------------------------------------------------------------------------------- 1 | package throttled_test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "gopkg.in/throttled/throttled.v2" 9 | "gopkg.in/throttled/throttled.v2/store" 10 | ) 11 | 12 | // Ensure that the current implementation remains compatible with the 13 | // supported but deprecated usage until the next major version. 14 | func TestDeprecatedUsage(t *testing.T) { 15 | // Declare interfaces to statically check that names haven't changed 16 | var st throttled.Store 17 | var thr *throttled.Throttler 18 | var q throttled.Quota 19 | 20 | st = store.NewMemStore(100) 21 | vary := &throttled.VaryBy{Path: true} 22 | q = throttled.PerMin(2) 23 | thr = throttled.RateLimit(q, vary, st) 24 | handler := thr.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 | w.WriteHeader(200) 26 | })) 27 | 28 | cases := []struct { 29 | path string 30 | code int 31 | headers map[string]string 32 | }{ 33 | {"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}}, 34 | {"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60"}}, 35 | {"/foo", 429, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60", "Retry-After": "30"}}, 36 | {"/bar", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}}, 37 | } 38 | 39 | for i, c := range cases { 40 | req, err := http.NewRequest("GET", c.path, nil) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | rr := httptest.NewRecorder() 46 | handler.ServeHTTP(rr, req) 47 | if have, want := rr.Code, c.code; have != want { 48 | t.Errorf("Expected request %d at %s to return %d but got %d", 49 | i, c.path, want, have) 50 | } 51 | 52 | for name, want := range c.headers { 53 | if have := rr.HeaderMap.Get(name); have != want { 54 | t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'", 55 | i, c.path, name, want, have) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/doc.go: -------------------------------------------------------------------------------- 1 | // Package throttled implements rate limiting access to resources such 2 | // as HTTP endpoints. 3 | package throttled // import "gopkg.in/throttled/throttled.v2" 4 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/example_test.go: -------------------------------------------------------------------------------- 1 | package throttled_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "gopkg.in/throttled/throttled.v2" 9 | "gopkg.in/throttled/throttled.v2/store/memstore" 10 | ) 11 | 12 | var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | w.Write([]byte("hi there!")) 14 | }) 15 | 16 | // ExampleHTTPRateLimiter demonstrates the usage of HTTPRateLimiter 17 | // for rate-limiting access to an http.Handler to 20 requests per path 18 | // per minute with a maximum burst of 5 requests. 19 | func ExampleHTTPRateLimiter() { 20 | store, err := memstore.New(65536) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | // Maximum burst of 5 which refills at 20 tokens per minute. 26 | quota := throttled.RateQuota{throttled.PerMin(20), 5} 27 | 28 | rateLimiter, err := throttled.NewGCRARateLimiter(store, quota) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | httpRateLimiter := throttled.HTTPRateLimiter{ 34 | RateLimiter: rateLimiter, 35 | VaryBy: &throttled.VaryBy{Path: true}, 36 | } 37 | 38 | http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler)) 39 | } 40 | 41 | // Demonstrates direct use of GCRARateLimiter's RateLimit function (and the 42 | // more general RateLimiter interface). This should be used anywhere where 43 | // granular control over rate limiting is required. 44 | func ExampleGCRARateLimiter() { 45 | store, err := memstore.New(65536) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | 50 | // Maximum burst of 5 which refills at 1 token per hour. 51 | quota := throttled.RateQuota{throttled.PerHour(1), 5} 52 | 53 | rateLimiter, err := throttled.NewGCRARateLimiter(store, quota) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | // Bucket according to the number i / 10 (so 1 falls into the bucket 0 59 | // while 11 falls into the bucket 1). This has the effect of allowing a 60 | // burst of 5 plus 1 (a single emission interval) on every ten iterations 61 | // of the loop. See the output for better clarity here. 62 | // 63 | // We also refill the bucket at 1 token per hour, but that has no effect 64 | // for the purposes of this example. 65 | for i := 0; i < 20; i++ { 66 | bucket := fmt.Sprintf("by-order:%v", i/10) 67 | 68 | limited, result, err := rateLimiter.RateLimit(bucket, 1) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | if limited { 74 | fmt.Printf("Iteration %2v; bucket %v: FAILED. Rate limit exceeded.\n", 75 | i, bucket) 76 | } else { 77 | fmt.Printf("Iteration %2v; bucket %v: Operation successful (remaining=%v).\n", 78 | i, bucket, result.Remaining) 79 | } 80 | } 81 | 82 | // Output: 83 | // Iteration 0; bucket by-order:0: Operation successful (remaining=5). 84 | // Iteration 1; bucket by-order:0: Operation successful (remaining=4). 85 | // Iteration 2; bucket by-order:0: Operation successful (remaining=3). 86 | // Iteration 3; bucket by-order:0: Operation successful (remaining=2). 87 | // Iteration 4; bucket by-order:0: Operation successful (remaining=1). 88 | // Iteration 5; bucket by-order:0: Operation successful (remaining=0). 89 | // Iteration 6; bucket by-order:0: FAILED. Rate limit exceeded. 90 | // Iteration 7; bucket by-order:0: FAILED. Rate limit exceeded. 91 | // Iteration 8; bucket by-order:0: FAILED. Rate limit exceeded. 92 | // Iteration 9; bucket by-order:0: FAILED. Rate limit exceeded. 93 | // Iteration 10; bucket by-order:1: Operation successful (remaining=5). 94 | // Iteration 11; bucket by-order:1: Operation successful (remaining=4). 95 | // Iteration 12; bucket by-order:1: Operation successful (remaining=3). 96 | // Iteration 13; bucket by-order:1: Operation successful (remaining=2). 97 | // Iteration 14; bucket by-order:1: Operation successful (remaining=1). 98 | // Iteration 15; bucket by-order:1: Operation successful (remaining=0). 99 | // Iteration 16; bucket by-order:1: FAILED. Rate limit exceeded. 100 | // Iteration 17; bucket by-order:1: FAILED. Rate limit exceeded. 101 | // Iteration 18; bucket by-order:1: FAILED. Rate limit exceeded. 102 | // Iteration 19; bucket by-order:1: FAILED. Rate limit exceeded. 103 | } 104 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/http.go: -------------------------------------------------------------------------------- 1 | package throttled 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | var ( 11 | // DefaultDeniedHandler is the default DeniedHandler for an 12 | // HTTPRateLimiter. It returns a 429 status code with a generic 13 | // message. 14 | DefaultDeniedHandler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | http.Error(w, "limit exceeded", 429) 16 | })) 17 | 18 | // DefaultError is the default Error function for an HTTPRateLimiter. 19 | // It returns a 500 status code with a generic message. 20 | DefaultError = func(w http.ResponseWriter, r *http.Request, err error) { 21 | http.Error(w, "internal error", http.StatusInternalServerError) 22 | } 23 | ) 24 | 25 | // HTTPRateLimiter faciliates using a Limiter to limit HTTP requests. 26 | type HTTPRateLimiter struct { 27 | // DeniedHandler is called if the request is disallowed. If it is 28 | // nil, the DefaultDeniedHandler variable is used. 29 | DeniedHandler http.Handler 30 | 31 | // Error is called if the RateLimiter returns an error. If it is 32 | // nil, the DefaultErrorFunc is used. 33 | Error func(w http.ResponseWriter, r *http.Request, err error) 34 | 35 | // Limiter is call for each request to determine whether the 36 | // request is permitted and update internal state. It must be set. 37 | RateLimiter RateLimiter 38 | 39 | // VaryBy is called for each request to generate a key for the 40 | // limiter. If it is nil, all requests use an empty string key. 41 | VaryBy interface { 42 | Key(*http.Request) string 43 | } 44 | } 45 | 46 | // RateLimit wraps an http.Handler to limit incoming requests. 47 | // Requests that are not limited will be passed to the handler 48 | // unchanged. Limited requests will be passed to the DeniedHandler. 49 | // X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and 50 | // Retry-After headers will be written to the response based on the 51 | // values in the RateLimitResult. 52 | func (t *HTTPRateLimiter) RateLimit(h http.Handler) http.Handler { 53 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 54 | if t.RateLimiter == nil { 55 | t.error(w, r, errors.New("You must set a RateLimiter on HTTPRateLimiter")) 56 | } 57 | 58 | var k string 59 | if t.VaryBy != nil { 60 | k = t.VaryBy.Key(r) 61 | } 62 | 63 | limited, context, err := t.RateLimiter.RateLimit(k, 1) 64 | 65 | if err != nil { 66 | t.error(w, r, err) 67 | return 68 | } 69 | 70 | setRateLimitHeaders(w, context) 71 | 72 | if !limited { 73 | h.ServeHTTP(w, r) 74 | } else { 75 | dh := t.DeniedHandler 76 | if dh == nil { 77 | dh = DefaultDeniedHandler 78 | } 79 | dh.ServeHTTP(w, r) 80 | } 81 | }) 82 | } 83 | 84 | func (t *HTTPRateLimiter) error(w http.ResponseWriter, r *http.Request, err error) { 85 | e := t.Error 86 | if e == nil { 87 | e = DefaultError 88 | } 89 | e(w, r, err) 90 | } 91 | 92 | func setRateLimitHeaders(w http.ResponseWriter, context RateLimitResult) { 93 | if v := context.Limit; v >= 0 { 94 | w.Header().Add("X-RateLimit-Limit", strconv.Itoa(v)) 95 | } 96 | 97 | if v := context.Remaining; v >= 0 { 98 | w.Header().Add("X-RateLimit-Remaining", strconv.Itoa(v)) 99 | } 100 | 101 | if v := context.ResetAfter; v >= 0 { 102 | vi := int(math.Ceil(v.Seconds())) 103 | w.Header().Add("X-RateLimit-Reset", strconv.Itoa(vi)) 104 | } 105 | 106 | if v := context.RetryAfter; v >= 0 { 107 | vi := int(math.Ceil(v.Seconds())) 108 | w.Header().Add("Retry-After", strconv.Itoa(vi)) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/http_test.go: -------------------------------------------------------------------------------- 1 | package throttled_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | "time" 9 | 10 | "gopkg.in/throttled/throttled.v2" 11 | ) 12 | 13 | type stubLimiter struct { 14 | } 15 | 16 | func (sl *stubLimiter) RateLimit(key string, quantity int) (bool, throttled.RateLimitResult, error) { 17 | switch key { 18 | case "limit": 19 | return true, throttled.RateLimitResult{-1, -1, -1, time.Minute}, nil 20 | case "error": 21 | return false, throttled.RateLimitResult{}, errors.New("stubLimiter error") 22 | default: 23 | return false, throttled.RateLimitResult{1, 2, time.Minute, -1}, nil 24 | } 25 | } 26 | 27 | type pathGetter struct{} 28 | 29 | func (*pathGetter) Key(r *http.Request) string { 30 | return r.URL.Path 31 | } 32 | 33 | type httpTestCase struct { 34 | path string 35 | code int 36 | headers map[string]string 37 | } 38 | 39 | func TestHTTPRateLimiter(t *testing.T) { 40 | limiter := throttled.HTTPRateLimiter{ 41 | RateLimiter: &stubLimiter{}, 42 | VaryBy: &pathGetter{}, 43 | } 44 | 45 | handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 46 | w.WriteHeader(200) 47 | })) 48 | 49 | runHTTPTestCases(t, handler, []httpTestCase{ 50 | {"ok", 200, map[string]string{"X-Ratelimit-Limit": "1", "X-Ratelimit-Remaining": "2", "X-Ratelimit-Reset": "60"}}, 51 | {"error", 500, map[string]string{}}, 52 | {"limit", 429, map[string]string{"Retry-After": "60"}}, 53 | }) 54 | } 55 | 56 | func TestCustomHTTPRateLimiterHandlers(t *testing.T) { 57 | limiter := throttled.HTTPRateLimiter{ 58 | RateLimiter: &stubLimiter{}, 59 | VaryBy: &pathGetter{}, 60 | DeniedHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 61 | http.Error(w, "custom limit exceeded", 400) 62 | }), 63 | Error: func(w http.ResponseWriter, r *http.Request, err error) { 64 | http.Error(w, "custom internal error", 501) 65 | }, 66 | } 67 | 68 | handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 69 | w.WriteHeader(200) 70 | })) 71 | 72 | runHTTPTestCases(t, handler, []httpTestCase{ 73 | {"limit", 400, map[string]string{}}, 74 | {"error", 501, map[string]string{}}, 75 | }) 76 | } 77 | 78 | func runHTTPTestCases(t *testing.T, h http.Handler, cs []httpTestCase) { 79 | for i, c := range cs { 80 | req, err := http.NewRequest("GET", c.path, nil) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | 85 | rr := httptest.NewRecorder() 86 | h.ServeHTTP(rr, req) 87 | if have, want := rr.Code, c.code; have != want { 88 | t.Errorf("Expected request %d at %s to return %d but got %d", 89 | i, c.path, want, have) 90 | } 91 | 92 | for name, want := range c.headers { 93 | if have := rr.HeaderMap.Get(name); have != want { 94 | t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'", 95 | i, c.path, name, want, have) 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/rate_test.go: -------------------------------------------------------------------------------- 1 | package throttled_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "gopkg.in/throttled/throttled.v2" 8 | "gopkg.in/throttled/throttled.v2/store/memstore" 9 | ) 10 | 11 | const deniedStatus = 429 12 | 13 | type testStore struct { 14 | store throttled.GCRAStore 15 | 16 | clock time.Time 17 | failUpdates bool 18 | } 19 | 20 | func (ts *testStore) GetWithTime(key string) (int64, time.Time, error) { 21 | v, _, e := ts.store.GetWithTime(key) 22 | return v, ts.clock, e 23 | } 24 | 25 | func (ts *testStore) SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) { 26 | if ts.failUpdates { 27 | return false, nil 28 | } 29 | return ts.store.SetIfNotExistsWithTTL(key, value, ttl) 30 | } 31 | 32 | func (ts *testStore) CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) { 33 | if ts.failUpdates { 34 | return false, nil 35 | } 36 | return ts.store.CompareAndSwapWithTTL(key, old, new, ttl) 37 | } 38 | 39 | func TestRateLimit(t *testing.T) { 40 | limit := 5 41 | rq := throttled.RateQuota{throttled.PerSec(1), limit - 1} 42 | start := time.Unix(0, 0) 43 | cases := []struct { 44 | now time.Time 45 | volume, remaining int 46 | reset, retry time.Duration 47 | limited bool 48 | }{ 49 | // You can never make a request larger than the maximum 50 | 0: {start, 6, 5, 0, -1, true}, 51 | // Rate limit normal requests appropriately 52 | 1: {start, 1, 4, time.Second, -1, false}, 53 | 2: {start, 1, 3, 2 * time.Second, -1, false}, 54 | 3: {start, 1, 2, 3 * time.Second, -1, false}, 55 | 4: {start, 1, 1, 4 * time.Second, -1, false}, 56 | 5: {start, 1, 0, 5 * time.Second, -1, false}, 57 | 6: {start, 1, 0, 5 * time.Second, time.Second, true}, 58 | 7: {start.Add(3000 * time.Millisecond), 1, 2, 3000 * time.Millisecond, -1, false}, 59 | 8: {start.Add(3100 * time.Millisecond), 1, 1, 3900 * time.Millisecond, -1, false}, 60 | 9: {start.Add(4000 * time.Millisecond), 1, 1, 4000 * time.Millisecond, -1, false}, 61 | 10: {start.Add(8000 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false}, 62 | 11: {start.Add(9500 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false}, 63 | // Zero-volume request just peeks at the state 64 | 12: {start.Add(9500 * time.Millisecond), 0, 4, time.Second, -1, false}, 65 | // High-volume request uses up more of the limit 66 | 13: {start.Add(9500 * time.Millisecond), 2, 2, 3 * time.Second, -1, false}, 67 | // Large requests cannot exceed limits 68 | 14: {start.Add(9500 * time.Millisecond), 5, 2, 3 * time.Second, 3 * time.Second, true}, 69 | } 70 | 71 | mst, err := memstore.New(0) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | st := testStore{store: mst} 76 | 77 | rl, err := throttled.NewGCRARateLimiter(&st, rq) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | 82 | // Start the server 83 | for i, c := range cases { 84 | st.clock = c.now 85 | 86 | limited, context, err := rl.RateLimit("foo", c.volume) 87 | if err != nil { 88 | t.Fatalf("%d: %#v", i, err) 89 | } 90 | 91 | if limited != c.limited { 92 | t.Errorf("%d: expected Limited to be %t but got %t", i, c.limited, limited) 93 | } 94 | 95 | if have, want := context.Limit, limit; have != want { 96 | t.Errorf("%d: expected Limit to be %d but got %d", i, want, have) 97 | } 98 | 99 | if have, want := context.Remaining, c.remaining; have != want { 100 | t.Errorf("%d: expected Remaining to be %d but got %d", i, want, have) 101 | } 102 | 103 | if have, want := context.ResetAfter, c.reset; have != want { 104 | t.Errorf("%d: expected ResetAfter to be %s but got %s", i, want, have) 105 | } 106 | 107 | if have, want := context.RetryAfter, c.retry; have != want { 108 | t.Errorf("%d: expected RetryAfter to be %d but got %d", i, want, have) 109 | } 110 | } 111 | } 112 | 113 | func TestRateLimitUpdateFailures(t *testing.T) { 114 | rq := throttled.RateQuota{throttled.PerSec(1), 1} 115 | mst, err := memstore.New(0) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | st := testStore{store: mst, failUpdates: true} 120 | rl, err := throttled.NewGCRARateLimiter(&st, rq) 121 | if err != nil { 122 | t.Fatal(err) 123 | } 124 | 125 | if _, _, err := rl.RateLimit("foo", 1); err == nil { 126 | t.Error("Expected limiting to fail when store updates fail") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/store.go: -------------------------------------------------------------------------------- 1 | package throttled 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // GCRAStore is the interface to implement to store state for a GCRA 8 | // rate limiter 9 | type GCRAStore interface { 10 | // GetWithTime returns the value of the key if it is in the store 11 | // or -1 if it does not exist. It also returns the current time at 12 | // the Store. The time must be representable as a positive int64 13 | // of nanoseconds since the epoch. 14 | // 15 | // GCRA assumes that all instances sharing the same Store also 16 | // share the same clock. Using separate clocks will work if the 17 | // skew is small but not recommended in practice unless you're 18 | // lucky enough to be hooked up to GPS or atomic clocks. 19 | GetWithTime(key string) (int64, time.Time, error) 20 | 21 | // SetIfNotExistsWithTTL sets the value of key only if it is not 22 | // already set in the store it returns whether a new value was 23 | // set. If the store supports expiring keys and a new value was 24 | // set, the key will expire after the provided ttl. 25 | SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) 26 | 27 | // CompareAndSwapWithTTL atomically compares the value at key to 28 | // the old value. If it matches, it sets it to the new value and 29 | // returns true. Otherwise, it returns false. If the key does not 30 | // exist in the store, it returns false with no error. If the 31 | // store supports expiring keys and the swap succeeded, the key 32 | // will expire after the provided ttl. 33 | CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) 34 | } 35 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go: -------------------------------------------------------------------------------- 1 | // Package store contains deprecated aliases for subpackages 2 | package store // import "gopkg.in/throttled/throttled.v2/store" 3 | 4 | import ( 5 | "github.com/garyburd/redigo/redis" 6 | 7 | "gopkg.in/throttled/throttled.v2/store/memstore" 8 | "gopkg.in/throttled/throttled.v2/store/redigostore" 9 | ) 10 | 11 | // DEPRECATED. NewMemStore is a compatible alias for mem.New 12 | func NewMemStore(maxKeys int) *memstore.MemStore { 13 | st, err := memstore.New(maxKeys) 14 | if err != nil { 15 | // As of this writing, `lru.New` can only return an error if you pass 16 | // maxKeys <= 0 so this should never occur. 17 | panic(err) 18 | } 19 | return st 20 | } 21 | 22 | // DEPRECATED. NewMemStore is a compatible alias for redis.New 23 | func NewRedisStore(pool *redis.Pool, keyPrefix string, db int) *redigostore.RedigoStore { 24 | st, err := redigostore.New(pool, keyPrefix, db) 25 | if err != nil { 26 | // As of this writing, creating a Redis store never returns an error 27 | // so this should be safe while providing some ability to return errors 28 | // in the future. 29 | panic(err) 30 | } 31 | return st 32 | } 33 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go: -------------------------------------------------------------------------------- 1 | // Package memstore offers an in-memory store implementation for throttled. 2 | package memstore // import "gopkg.in/throttled/throttled.v2/store/memstore" 3 | 4 | import ( 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/hashicorp/golang-lru" 10 | ) 11 | 12 | // MemStore is an in-memory store implementation for throttled. It 13 | // supports evicting the least recently used keys to control memory 14 | // usage. It is stored in memory in the current process and thus 15 | // doesn't share state with other rate limiters. 16 | type MemStore struct { 17 | sync.RWMutex 18 | keys *lru.Cache 19 | m map[string]*int64 20 | } 21 | 22 | // New initializes a Store. If maxKeys > 0, the number of different 23 | // keys is restricted to the specified amount. In this case, it uses 24 | // an LRU algorithm to evict older keys to make room for newer 25 | // ones. If maxKeys <= 0, there is no limit on the number of keys, 26 | // which may use an unbounded amount of memory. 27 | func New(maxKeys int) (*MemStore, error) { 28 | var m *MemStore 29 | 30 | if maxKeys > 0 { 31 | keys, err := lru.New(maxKeys) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | m = &MemStore{ 37 | keys: keys, 38 | } 39 | } else { 40 | m = &MemStore{ 41 | m: make(map[string]*int64), 42 | } 43 | } 44 | return m, nil 45 | } 46 | 47 | // GetWithTime returns the value of the key if it is in the store or 48 | // -1 if it does not exist. It also returns the current local time on 49 | // the machine. 50 | func (ms *MemStore) GetWithTime(key string) (int64, time.Time, error) { 51 | now := time.Now() 52 | valP, ok := ms.get(key, false) 53 | 54 | if !ok { 55 | return -1, now, nil 56 | } 57 | 58 | return atomic.LoadInt64(valP), now, nil 59 | } 60 | 61 | // SetIfNotExistsWithTTL sets the value of key only if it is not 62 | // already set in the store it returns whether a new value was set. It 63 | // ignores the ttl. 64 | func (ms *MemStore) SetIfNotExistsWithTTL(key string, value int64, _ time.Duration) (bool, error) { 65 | _, ok := ms.get(key, false) 66 | 67 | if ok { 68 | return false, nil 69 | } 70 | 71 | ms.Lock() 72 | defer ms.Unlock() 73 | 74 | _, ok = ms.get(key, true) 75 | 76 | if ok { 77 | return false, nil 78 | } 79 | 80 | // Store a pointer to a new instance so that the caller 81 | // can't mutate the value after setting 82 | v := value 83 | 84 | if ms.keys != nil { 85 | ms.keys.Add(key, &v) 86 | } else { 87 | ms.m[key] = &v 88 | } 89 | 90 | return true, nil 91 | } 92 | 93 | // CompareAndSwapWithTTL atomically compares the value at key to the 94 | // old value. If it matches, it sets it to the new value and returns 95 | // true. Otherwise, it returns false. If the key does not exist in the 96 | // store, it returns false with no error. It ignores the ttl. 97 | func (ms *MemStore) CompareAndSwapWithTTL(key string, old, new int64, _ time.Duration) (bool, error) { 98 | valP, ok := ms.get(key, false) 99 | 100 | if !ok { 101 | return false, nil 102 | } 103 | 104 | return atomic.CompareAndSwapInt64(valP, old, new), nil 105 | } 106 | 107 | func (ms *MemStore) get(key string, locked bool) (*int64, bool) { 108 | var valP *int64 109 | var ok bool 110 | 111 | if ms.keys != nil { 112 | var valI interface{} 113 | 114 | valI, ok = ms.keys.Get(key) 115 | if ok { 116 | valP = valI.(*int64) 117 | } 118 | } else { 119 | if !locked { 120 | ms.RLock() 121 | defer ms.RUnlock() 122 | } 123 | valP, ok = ms.m[key] 124 | } 125 | 126 | return valP, ok 127 | } 128 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go: -------------------------------------------------------------------------------- 1 | package memstore_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "gopkg.in/throttled/throttled.v2/store/memstore" 7 | "gopkg.in/throttled/throttled.v2/store/storetest" 8 | ) 9 | 10 | func TestMemStoreLRU(t *testing.T) { 11 | st, err := memstore.New(10) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | storetest.TestGCRAStore(t, st) 16 | } 17 | 18 | func TestMemStoreUnlimited(t *testing.T) { 19 | st, err := memstore.New(10) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | storetest.TestGCRAStore(t, st) 24 | } 25 | 26 | func BenchmarkMemStoreLRU(b *testing.B) { 27 | st, err := memstore.New(10) 28 | if err != nil { 29 | b.Fatal(err) 30 | } 31 | storetest.BenchmarkGCRAStore(b, st) 32 | } 33 | 34 | func BenchmarkMemStoreUnlimited(b *testing.B) { 35 | st, err := memstore.New(0) 36 | if err != nil { 37 | b.Fatal(err) 38 | } 39 | storetest.BenchmarkGCRAStore(b, st) 40 | } 41 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go: -------------------------------------------------------------------------------- 1 | // Package redigostore offers Redis-based store implementation for throttled using redigo. 2 | package redigostore // import "gopkg.in/throttled/throttled.v2/store/redigostore" 3 | 4 | import ( 5 | "strings" 6 | "time" 7 | 8 | "github.com/garyburd/redigo/redis" 9 | ) 10 | 11 | const ( 12 | redisCASMissingKey = "key does not exist" 13 | redisCASScript = ` 14 | local v = redis.call('get', KEYS[1]) 15 | if v == false then 16 | return redis.error_reply("key does not exist") 17 | end 18 | if v ~= ARGV[1] then 19 | return 0 20 | end 21 | if ARGV[3] ~= "0" then 22 | redis.call('setex', KEYS[1], ARGV[3], ARGV[2]) 23 | else 24 | redis.call('set', KEYS[1], ARGV[2]) 25 | end 26 | return 1 27 | ` 28 | ) 29 | 30 | // RedigoStore implements a Redis-based store using redigo. 31 | type RedigoStore struct { 32 | pool *redis.Pool 33 | prefix string 34 | db int 35 | } 36 | 37 | // New creates a new Redis-based store, using the provided pool to get 38 | // its connections. The keys will have the specified keyPrefix, which 39 | // may be an empty string, and the database index specified by db will 40 | // be selected to store the keys. Any updating operations will reset 41 | // the key TTL to the provided value rounded down to the nearest 42 | // second. Depends on Redis 2.6+ for EVAL support. 43 | func New(pool *redis.Pool, keyPrefix string, db int) (*RedigoStore, error) { 44 | return &RedigoStore{ 45 | pool: pool, 46 | prefix: keyPrefix, 47 | db: db, 48 | }, nil 49 | } 50 | 51 | // GetWithTime returns the value of the key if it is in the store 52 | // or -1 if it does not exist. It also returns the current time at 53 | // the redis server to microsecond precision. 54 | func (r *RedigoStore) GetWithTime(key string) (int64, time.Time, error) { 55 | var now time.Time 56 | 57 | key = r.prefix + key 58 | 59 | conn, err := r.getConn() 60 | if err != nil { 61 | return 0, now, err 62 | } 63 | defer conn.Close() 64 | 65 | conn.Send("TIME") 66 | conn.Send("GET", key) 67 | conn.Flush() 68 | timeReply, err := redis.Values(conn.Receive()) 69 | if err != nil { 70 | return 0, now, err 71 | } 72 | 73 | var s, us int64 74 | if _, err := redis.Scan(timeReply, &s, &us); err != nil { 75 | return 0, now, err 76 | } 77 | now = time.Unix(s, us*int64(time.Microsecond)) 78 | 79 | v, err := redis.Int64(conn.Receive()) 80 | if err == redis.ErrNil { 81 | return -1, now, nil 82 | } else if err != nil { 83 | return 0, now, err 84 | } 85 | 86 | return v, now, nil 87 | } 88 | 89 | // SetIfNotExistsWithTTL sets the value of key only if it is not 90 | // already set in the store it returns whether a new value was set. 91 | // If a new value was set, the ttl in the key is also set, though this 92 | // operation is not performed atomically. 93 | func (r *RedigoStore) SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) { 94 | key = r.prefix + key 95 | 96 | conn, err := r.getConn() 97 | if err != nil { 98 | return false, err 99 | } 100 | defer conn.Close() 101 | 102 | v, err := redis.Int64(conn.Do("SETNX", key, value)) 103 | if err != nil { 104 | return false, err 105 | } 106 | 107 | updated := v == 1 108 | 109 | if ttl >= time.Second { 110 | if _, err := conn.Do("EXPIRE", key, int(ttl.Seconds())); err != nil { 111 | return updated, err 112 | } 113 | } 114 | 115 | return updated, nil 116 | } 117 | 118 | // CompareAndSwapWithTTL atomically compares the value at key to the 119 | // old value. If it matches, it sets it to the new value and returns 120 | // true. Otherwise, it returns false. If the key does not exist in the 121 | // store, it returns false with no error. If the swap succeeds, the 122 | // ttl for the key is updated atomically. 123 | func (r *RedigoStore) CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) { 124 | key = r.prefix + key 125 | conn, err := r.getConn() 126 | if err != nil { 127 | return false, err 128 | } 129 | defer conn.Close() 130 | 131 | swapped, err := redis.Bool(conn.Do("EVAL", redisCASScript, 1, key, old, new, int(ttl.Seconds()))) 132 | if err != nil { 133 | if strings.Contains(err.Error(), redisCASMissingKey) { 134 | return false, nil 135 | } 136 | 137 | return false, err 138 | } 139 | 140 | return swapped, nil 141 | } 142 | 143 | // Select the specified database index. 144 | func (r *RedigoStore) getConn() (redis.Conn, error) { 145 | conn := r.pool.Get() 146 | 147 | // Select the specified database 148 | if r.db > 0 { 149 | if _, err := redis.String(conn.Do("SELECT", r.db)); err != nil { 150 | conn.Close() 151 | return nil, err 152 | } 153 | } 154 | 155 | return conn, nil 156 | } 157 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go: -------------------------------------------------------------------------------- 1 | package redigostore_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/garyburd/redigo/redis" 8 | 9 | "gopkg.in/throttled/throttled.v2/store/redigostore" 10 | "gopkg.in/throttled/throttled.v2/store/storetest" 11 | ) 12 | 13 | const ( 14 | redisTestDB = 1 15 | redisTestPrefix = "throttled:" 16 | ) 17 | 18 | func getPool() *redis.Pool { 19 | pool := &redis.Pool{ 20 | MaxIdle: 3, 21 | IdleTimeout: 30 * time.Second, 22 | Dial: func() (redis.Conn, error) { 23 | return redis.Dial("tcp", ":6379") 24 | }, 25 | TestOnBorrow: func(c redis.Conn, t time.Time) error { 26 | _, err := c.Do("PING") 27 | return err 28 | }, 29 | } 30 | return pool 31 | } 32 | 33 | func TestRedisStore(t *testing.T) { 34 | c, st := setupRedis(t, 0) 35 | defer c.Close() 36 | defer clearRedis(c) 37 | 38 | clearRedis(c) 39 | storetest.TestGCRAStore(t, st) 40 | storetest.TestGCRAStoreTTL(t, st) 41 | } 42 | 43 | func BenchmarkRedisStore(b *testing.B) { 44 | c, st := setupRedis(b, 0) 45 | defer c.Close() 46 | defer clearRedis(c) 47 | 48 | storetest.BenchmarkGCRAStore(b, st) 49 | } 50 | 51 | func clearRedis(c redis.Conn) error { 52 | keys, err := redis.Values(c.Do("KEYS", redisTestPrefix+"*")) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if _, err := redis.Int(c.Do("DEL", keys...)); err != nil { 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | 64 | func setupRedis(tb testing.TB, ttl time.Duration) (redis.Conn, *redigostore.RedigoStore) { 65 | pool := getPool() 66 | c := pool.Get() 67 | 68 | if _, err := redis.String(c.Do("PING")); err != nil { 69 | c.Close() 70 | tb.Skip("redis server not available on localhost port 6379") 71 | } 72 | 73 | if _, err := redis.String(c.Do("SELECT", redisTestDB)); err != nil { 74 | c.Close() 75 | tb.Fatal(err) 76 | } 77 | 78 | st, err := redigostore.New(pool, redisTestPrefix, redisTestDB) 79 | if err != nil { 80 | c.Close() 81 | tb.Fatal(err) 82 | } 83 | 84 | return c, st 85 | } 86 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go: -------------------------------------------------------------------------------- 1 | // Package storetest provides a helper for testing throttled stores. 2 | package storetest // import "gopkg.in/throttled/throttled.v2/store/storetest" 3 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/varyby.go: -------------------------------------------------------------------------------- 1 | package throttled 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | // VaryBy defines the criteria to use to group requests. 10 | type VaryBy struct { 11 | // Vary by the RemoteAddr as specified by the net/http.Request field. 12 | RemoteAddr bool 13 | 14 | // Vary by the HTTP Method as specified by the net/http.Request field. 15 | Method bool 16 | 17 | // Vary by the URL's Path as specified by the Path field of the net/http.Request 18 | // URL field. 19 | Path bool 20 | 21 | // Vary by this list of header names, read from the net/http.Request Header field. 22 | Headers []string 23 | 24 | // Vary by this list of parameters, read from the net/http.Request FormValue method. 25 | Params []string 26 | 27 | // Vary by this list of cookie names, read from the net/http.Request Cookie method. 28 | Cookies []string 29 | 30 | // Use this separator string to concatenate the various criteria of the VaryBy struct. 31 | // Defaults to a newline character if empty (\n). 32 | Separator string 33 | 34 | // DEPRECATED. Custom specifies the custom-generated key to use for this request. 35 | // If not nil, the value returned by this function is used instead of any 36 | // VaryBy criteria. 37 | Custom func(r *http.Request) string 38 | } 39 | 40 | // Key returns the key for this request based on the criteria defined by the VaryBy struct. 41 | func (vb *VaryBy) Key(r *http.Request) string { 42 | var buf bytes.Buffer 43 | 44 | if vb == nil { 45 | return "" // Special case for no vary-by option 46 | } 47 | if vb.Custom != nil { 48 | // A custom key generator is specified 49 | return vb.Custom(r) 50 | } 51 | sep := vb.Separator 52 | if sep == "" { 53 | sep = "\n" // Separator defaults to newline 54 | } 55 | if vb.RemoteAddr { 56 | buf.WriteString(strings.ToLower(r.RemoteAddr) + sep) 57 | } 58 | if vb.Method { 59 | buf.WriteString(strings.ToLower(r.Method) + sep) 60 | } 61 | for _, h := range vb.Headers { 62 | buf.WriteString(strings.ToLower(r.Header.Get(h)) + sep) 63 | } 64 | if vb.Path { 65 | buf.WriteString(r.URL.Path + sep) 66 | } 67 | for _, p := range vb.Params { 68 | buf.WriteString(r.FormValue(p) + sep) 69 | } 70 | for _, c := range vb.Cookies { 71 | ck, err := r.Cookie(c) 72 | if err == nil { 73 | buf.WriteString(ck.Value) 74 | } 75 | buf.WriteString(sep) // Write the separator anyway, whether or not the cookie exists 76 | } 77 | return buf.String() 78 | } 79 | -------------------------------------------------------------------------------- /vendor/gopkg.in/throttled/throttled.v2/varyby_test.go: -------------------------------------------------------------------------------- 1 | package throttled_test 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "testing" 7 | 8 | "gopkg.in/throttled/throttled.v2" 9 | ) 10 | 11 | func TestVaryBy(t *testing.T) { 12 | u, err := url.Parse("http://localhost/test/path?q=s") 13 | if err != nil { 14 | panic(err) 15 | } 16 | ck := &http.Cookie{Name: "ssn", Value: "test"} 17 | cases := []struct { 18 | vb *throttled.VaryBy 19 | r *http.Request 20 | k string 21 | }{ 22 | 0: {nil, &http.Request{}, ""}, 23 | 1: {&throttled.VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"}, 24 | 2: { 25 | &throttled.VaryBy{Method: true, Path: true}, 26 | &http.Request{Method: "POST", URL: u}, 27 | "post\n/test/path\n", 28 | }, 29 | 3: { 30 | &throttled.VaryBy{Headers: []string{"Content-length"}}, 31 | &http.Request{Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}}, 32 | "123\n", 33 | }, 34 | 4: { 35 | &throttled.VaryBy{Separator: ",", Method: true, Headers: []string{"Content-length"}, Params: []string{"q", "user"}}, 36 | &http.Request{Method: "GET", Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}, Form: url.Values{"q": []string{"s"}, "pwd": []string{"secret"}, "user": []string{"test"}}}, 37 | "get,123,s,test,", 38 | }, 39 | 5: { 40 | &throttled.VaryBy{Cookies: []string{"ssn"}}, 41 | &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}}, 42 | "test\n", 43 | }, 44 | 6: { 45 | &throttled.VaryBy{Cookies: []string{"ssn"}, RemoteAddr: true, Custom: func(r *http.Request) string { 46 | return "blah" 47 | }}, 48 | &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}}, 49 | "blah", 50 | }, 51 | } 52 | for i, c := range cases { 53 | got := c.vb.Key(c.r) 54 | if got != c.k { 55 | t.Errorf("%d: expected '%s' (%d), got '%s' (%d)", i, c.k, len(c.k), got, len(got)) 56 | } 57 | } 58 | } 59 | --------------------------------------------------------------------------------