Fixes mm-1355 adds rate limiting apis
Этот коммит содержится в:
58
Godeps/Godeps.json
сгенерированный
58
Godeps/Godeps.json
сгенерированный
@@ -21,43 +21,19 @@
|
||||
"ImportPath": "github.com/anachronistic/apns",
|
||||
"Rev": "f90152b7e0ae845938ce1258d90072fd116131ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/aws",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/internal/endpoints",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/internal/protocol/query",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/internal/protocol/rest",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/internal/protocol/restxml",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/internal/signer/v4",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/awslabs/aws-sdk-go/service/route53",
|
||||
"Rev": "44bfbd5e852715e1acfcd49da3f5688e4b5377ac"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/braintree/manners",
|
||||
"Comment": "0.3.1-2-g5280e25",
|
||||
"Rev": "5280e250f2795914acbeb2bf3b55dd5a2d1fba52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/garyburd/redigo/internal",
|
||||
"Rev": "a47585eaae68b1d14b02940d2af1b9194f3caa9c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/garyburd/redigo/redis",
|
||||
"Rev": "a47585eaae68b1d14b02940d2af1b9194f3caa9c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-gorp/gorp",
|
||||
"Comment": "v1.7-65-g3c15f67",
|
||||
@@ -76,6 +52,10 @@
|
||||
"ImportPath": "github.com/goamz/goamz/s3",
|
||||
"Rev": "ad637a587dd8314770a1084481dd7b5d4fa1232f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/golang/groupcache/lru",
|
||||
"Rev": "604ed5785183e59ae2789449d89e73f3a2a77987"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/context",
|
||||
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
|
||||
@@ -114,6 +94,10 @@
|
||||
"ImportPath": "github.com/stretchr/testify/mock",
|
||||
"Rev": "dab07ac62d4905d3e48d17dc549c684ac3b7c15a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/throttled/throttled",
|
||||
"Rev": "fe4c91d601f05354c8a7feba917ab9d3b634f68d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vaughan0/go-ini",
|
||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
@@ -126,20 +110,10 @@
|
||||
"ImportPath": "golang.org/x/image/bmp",
|
||||
"Rev": "eb11b45157c1b71f30b3cec66306f1cd779a689e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/bufio.v1",
|
||||
"Comment": "v1",
|
||||
"Rev": "567b2bfa514e796916c4747494d6ff5132a1dfce"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/fsnotify.v1",
|
||||
"Comment": "v1.2.0",
|
||||
"Rev": "96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/redis.v2",
|
||||
"Comment": "v2.3.2",
|
||||
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
142
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/awsutil/path_value.go
сгенерированный
поставляемый
142
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/awsutil/path_value.go
сгенерированный
поставляемый
@@ -1,142 +0,0 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`)
|
||||
|
||||
func rValuesAtPath(v interface{}, path string, create bool) []reflect.Value {
|
||||
pathparts := strings.Split(path, "||")
|
||||
if len(pathparts) > 1 {
|
||||
for _, pathpart := range pathparts {
|
||||
vals := rValuesAtPath(v, pathpart, create)
|
||||
if vals != nil && len(vals) > 0 {
|
||||
return vals
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
values := []reflect.Value{reflect.Indirect(reflect.ValueOf(v))}
|
||||
components := strings.Split(path, ".")
|
||||
for len(values) > 0 && len(components) > 0 {
|
||||
var index *int64
|
||||
var indexStar bool
|
||||
c := strings.TrimSpace(components[0])
|
||||
if c == "" { // no actual component, illegal syntax
|
||||
return nil
|
||||
} else if c != "*" && strings.ToLower(c[0:1]) == c[0:1] {
|
||||
// TODO normalize case for user
|
||||
return nil // don't support unexported fields
|
||||
}
|
||||
|
||||
// parse this component
|
||||
if m := indexRe.FindStringSubmatch(c); m != nil {
|
||||
c = m[1]
|
||||
if m[2] == "" {
|
||||
index = nil
|
||||
indexStar = true
|
||||
} else {
|
||||
i, _ := strconv.ParseInt(m[2], 10, 32)
|
||||
index = &i
|
||||
indexStar = false
|
||||
}
|
||||
}
|
||||
|
||||
nextvals := []reflect.Value{}
|
||||
for _, value := range values {
|
||||
// pull component name out of struct member
|
||||
if value.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
|
||||
if c == "*" { // pull all members
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
if f := reflect.Indirect(value.Field(i)); f.IsValid() {
|
||||
nextvals = append(nextvals, f)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
value = value.FieldByName(c)
|
||||
if create && value.Kind() == reflect.Ptr && value.IsNil() {
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
value = value.Elem()
|
||||
} else {
|
||||
value = reflect.Indirect(value)
|
||||
}
|
||||
|
||||
if value.IsValid() {
|
||||
nextvals = append(nextvals, value)
|
||||
}
|
||||
}
|
||||
values = nextvals
|
||||
|
||||
if indexStar || index != nil {
|
||||
nextvals = []reflect.Value{}
|
||||
for _, value := range values {
|
||||
value := reflect.Indirect(value)
|
||||
if value.Kind() != reflect.Slice {
|
||||
continue
|
||||
}
|
||||
|
||||
if indexStar { // grab all indices
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
idx := reflect.Indirect(value.Index(i))
|
||||
if idx.IsValid() {
|
||||
nextvals = append(nextvals, idx)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// pull out index
|
||||
i := int(*index)
|
||||
if i >= value.Len() { // check out of bounds
|
||||
if create {
|
||||
// TODO resize slice
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if i < 0 { // support negative indexing
|
||||
i = value.Len() + i
|
||||
}
|
||||
value = reflect.Indirect(value.Index(i))
|
||||
|
||||
if value.IsValid() {
|
||||
nextvals = append(nextvals, value)
|
||||
}
|
||||
}
|
||||
values = nextvals
|
||||
}
|
||||
|
||||
components = components[1:]
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// ValuesAtPath returns a list of objects at the lexical path inside of a structure
|
||||
func ValuesAtPath(i interface{}, path string) []interface{} {
|
||||
if rvals := rValuesAtPath(i, path, false); rvals != nil {
|
||||
vals := make([]interface{}, len(rvals))
|
||||
for i, rval := range rvals {
|
||||
vals[i] = rval.Interface()
|
||||
}
|
||||
return vals
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetValueAtPath sets an object at the lexical path inside of a structure
|
||||
func SetValueAtPath(i interface{}, path string, v interface{}) {
|
||||
if rvals := rValuesAtPath(i, path, true); rvals != nil {
|
||||
for _, rval := range rvals {
|
||||
rval.Set(reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/awsutil/path_value_test.go
сгенерированный
поставляемый
60
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/awsutil/path_value_test.go
сгенерированный
поставляемый
@@ -1,60 +0,0 @@
|
||||
package awsutil_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws/awsutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Struct struct {
|
||||
A []Struct
|
||||
a []Struct
|
||||
B *Struct
|
||||
D *Struct
|
||||
C string
|
||||
}
|
||||
|
||||
var data = Struct{
|
||||
A: []Struct{Struct{C: "value1"}, Struct{C: "value2"}, Struct{C: "value3"}},
|
||||
a: []Struct{Struct{C: "value1"}, Struct{C: "value2"}, Struct{C: "value3"}},
|
||||
B: &Struct{B: &Struct{C: "terminal"}, D: &Struct{C: "terminal2"}},
|
||||
C: "initial",
|
||||
}
|
||||
|
||||
func TestValueAtPathSuccess(t *testing.T) {
|
||||
assert.Equal(t, []interface{}{"initial"}, awsutil.ValuesAtPath(data, "C"))
|
||||
assert.Equal(t, []interface{}{"value1"}, awsutil.ValuesAtPath(data, "A[0].C"))
|
||||
assert.Equal(t, []interface{}{"value2"}, awsutil.ValuesAtPath(data, "A[1].C"))
|
||||
assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtPath(data, "A[2].C"))
|
||||
assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtPath(data, "A[-1].C"))
|
||||
assert.Equal(t, []interface{}{"value1", "value2", "value3"}, awsutil.ValuesAtPath(data, "A[].C"))
|
||||
assert.Equal(t, []interface{}{"terminal"}, awsutil.ValuesAtPath(data, "B . B . C"))
|
||||
assert.Equal(t, []interface{}{"terminal", "terminal2"}, awsutil.ValuesAtPath(data, "B.*.C"))
|
||||
assert.Equal(t, []interface{}{"initial"}, awsutil.ValuesAtPath(data, "A.D.X || C"))
|
||||
}
|
||||
|
||||
func TestValueAtPathFailure(t *testing.T) {
|
||||
assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, "C.x"))
|
||||
assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, ".x"))
|
||||
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "X.Y.Z"))
|
||||
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "A[100].C"))
|
||||
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "A[3].C"))
|
||||
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "B.B.C.Z"))
|
||||
assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, "a[-1].C"))
|
||||
assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(nil, "A.B.C"))
|
||||
}
|
||||
|
||||
func TestSetValueAtPathSuccess(t *testing.T) {
|
||||
var s Struct
|
||||
awsutil.SetValueAtPath(&s, "C", "test1")
|
||||
awsutil.SetValueAtPath(&s, "B.B.C", "test2")
|
||||
awsutil.SetValueAtPath(&s, "B.D.C", "test3")
|
||||
assert.Equal(t, "test1", s.C)
|
||||
assert.Equal(t, "test2", s.B.B.C)
|
||||
assert.Equal(t, "test3", s.B.D.C)
|
||||
|
||||
awsutil.SetValueAtPath(&s, "B.*.C", "test0")
|
||||
assert.Equal(t, "test0", s.B.B.C)
|
||||
assert.Equal(t, "test0", s.B.D.C)
|
||||
}
|
||||
88
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/awsutil/string_value.go
сгенерированный
поставляемый
88
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/awsutil/string_value.go
сгенерированный
поставляемый
@@ -1,88 +0,0 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func StringValue(i interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
stringValue(reflect.ValueOf(i), 0, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
names := []string{}
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
name := v.Type().Field(i).Name
|
||||
f := v.Field(i)
|
||||
if name[0:1] == strings.ToLower(name[0:1]) {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice) && f.IsNil() {
|
||||
continue // ignore unset fields
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
for i, n := range names {
|
||||
val := v.FieldByName(n)
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(n + ": ")
|
||||
stringValue(val, indent+2, buf)
|
||||
|
||||
if i < len(names)-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
case reflect.Slice:
|
||||
nl, id, id2 := "", "", ""
|
||||
if v.Len() > 3 {
|
||||
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
|
||||
}
|
||||
buf.WriteString("[" + nl)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
buf.WriteString(id2)
|
||||
stringValue(v.Index(i), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString("," + nl)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(nl + id + "]")
|
||||
case reflect.Map:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
for i, k := range v.MapKeys() {
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(k.String() + ": ")
|
||||
stringValue(v.MapIndex(k), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
default:
|
||||
format := "%v"
|
||||
switch v.Interface().(type) {
|
||||
case string:
|
||||
format = "%q"
|
||||
}
|
||||
fmt.Fprintf(buf, format, v.Interface())
|
||||
}
|
||||
}
|
||||
101
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/config.go
сгенерированный
поставляемый
101
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/config.go
сгенерированный
поставляемый
@@ -1,101 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
const DEFAULT_RETRIES = -1
|
||||
|
||||
var DefaultConfig = &Config{
|
||||
Credentials: DefaultCreds(),
|
||||
Endpoint: "",
|
||||
Region: os.Getenv("AWS_REGION"),
|
||||
DisableSSL: false,
|
||||
ManualSend: false,
|
||||
HTTPClient: http.DefaultClient,
|
||||
LogLevel: 0,
|
||||
Logger: os.Stdout,
|
||||
MaxRetries: DEFAULT_RETRIES,
|
||||
DisableParamValidation: false,
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Credentials CredentialsProvider
|
||||
Endpoint string
|
||||
Region string
|
||||
DisableSSL bool
|
||||
ManualSend bool
|
||||
HTTPClient *http.Client
|
||||
LogLevel uint
|
||||
Logger io.Writer
|
||||
MaxRetries int
|
||||
DisableParamValidation bool
|
||||
}
|
||||
|
||||
func (c Config) Merge(newcfg *Config) *Config {
|
||||
cfg := Config{}
|
||||
|
||||
if newcfg != nil && newcfg.Credentials != nil {
|
||||
cfg.Credentials = newcfg.Credentials
|
||||
} else {
|
||||
cfg.Credentials = c.Credentials
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.Endpoint != "" {
|
||||
cfg.Endpoint = newcfg.Endpoint
|
||||
} else {
|
||||
cfg.Endpoint = c.Endpoint
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.Region != "" {
|
||||
cfg.Region = newcfg.Region
|
||||
} else {
|
||||
cfg.Region = c.Region
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.DisableSSL {
|
||||
cfg.DisableSSL = newcfg.DisableSSL
|
||||
} else {
|
||||
cfg.DisableSSL = c.DisableSSL
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.ManualSend {
|
||||
cfg.ManualSend = newcfg.ManualSend
|
||||
} else {
|
||||
cfg.ManualSend = c.ManualSend
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.HTTPClient != nil {
|
||||
cfg.HTTPClient = newcfg.HTTPClient
|
||||
} else {
|
||||
cfg.HTTPClient = c.HTTPClient
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.LogLevel != 0 {
|
||||
cfg.LogLevel = newcfg.LogLevel
|
||||
} else {
|
||||
cfg.LogLevel = c.LogLevel
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.Logger != nil {
|
||||
cfg.Logger = newcfg.Logger
|
||||
} else {
|
||||
cfg.Logger = c.Logger
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.MaxRetries != DEFAULT_RETRIES {
|
||||
cfg.MaxRetries = newcfg.MaxRetries
|
||||
} else {
|
||||
cfg.MaxRetries = c.MaxRetries
|
||||
}
|
||||
|
||||
if newcfg != nil && newcfg.DisableParamValidation {
|
||||
cfg.DisableParamValidation = newcfg.DisableParamValidation
|
||||
} else {
|
||||
cfg.DisableParamValidation = c.DisableParamValidation
|
||||
}
|
||||
|
||||
return &cfg
|
||||
}
|
||||
288
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/credentials.go
сгенерированный
поставляемый
288
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/credentials.go
сгенерированный
поставляемый
@@ -1,288 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var currentTime = time.Now
|
||||
|
||||
// Credentials are used to authenticate and authorize calls that you make to
|
||||
// AWS.
|
||||
type Credentials struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
// A CredentialsProvider is a provider of credentials.
|
||||
type CredentialsProvider interface {
|
||||
// Credentials returns a set of credentials (or an error if no credentials
|
||||
// could be provided).
|
||||
Credentials() (*Credentials, error)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be
|
||||
// found in the process's environment.
|
||||
ErrAccessKeyIDNotFound = fmt.Errorf("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
|
||||
// ErrSecretAccessKeyNotFound is returned when the AWS Secret Access Key
|
||||
// can't be found in the process's environment.
|
||||
ErrSecretAccessKeyNotFound = fmt.Errorf("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
|
||||
)
|
||||
|
||||
type DefaultCredentialsProvider struct {
|
||||
}
|
||||
|
||||
func (p *DefaultCredentialsProvider) Credentials() (*Credentials, error) {
|
||||
env, err := EnvCreds()
|
||||
if err == nil {
|
||||
return env.Credentials()
|
||||
}
|
||||
|
||||
profile, err := ProfileCreds("", "", 10*time.Minute)
|
||||
if err == nil {
|
||||
profileCreds, err := profile.Credentials()
|
||||
if err == nil {
|
||||
return profileCreds, nil
|
||||
}
|
||||
}
|
||||
|
||||
return IAMCreds().Credentials()
|
||||
}
|
||||
|
||||
func DefaultCreds() CredentialsProvider {
|
||||
return &DefaultCredentialsProvider{}
|
||||
}
|
||||
|
||||
// DetectCreds returns a CredentialsProvider based on the available information.
|
||||
//
|
||||
// If the access key ID and secret access key are provided, it returns a basic
|
||||
// provider.
|
||||
//
|
||||
// If credentials are available via environment variables, it returns an
|
||||
// environment provider.
|
||||
//
|
||||
// If a profile configuration file is available in the default location and has
|
||||
// a default profile configured, it returns a profile provider.
|
||||
//
|
||||
// Otherwise, it returns an IAM instance provider.
|
||||
func DetectCreds(accessKeyID, secretAccessKey, sessionToken string) CredentialsProvider {
|
||||
if accessKeyID != "" && secretAccessKey != "" {
|
||||
return Creds(accessKeyID, secretAccessKey, sessionToken)
|
||||
}
|
||||
|
||||
env, err := EnvCreds()
|
||||
if err == nil {
|
||||
return env
|
||||
}
|
||||
|
||||
profile, err := ProfileCreds("", "", 10*time.Minute)
|
||||
if err != nil {
|
||||
return IAMCreds()
|
||||
}
|
||||
|
||||
_, err = profile.Credentials()
|
||||
if err != nil {
|
||||
return IAMCreds()
|
||||
}
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
// EnvCreds returns a static provider of AWS credentials from the process's
|
||||
// environment, or an error if none are found.
|
||||
func EnvCreds() (CredentialsProvider, error) {
|
||||
id := os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
if id == "" {
|
||||
id = os.Getenv("AWS_ACCESS_KEY")
|
||||
}
|
||||
|
||||
secret := os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||
if secret == "" {
|
||||
secret = os.Getenv("AWS_SECRET_KEY")
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
return nil, ErrAccessKeyIDNotFound
|
||||
}
|
||||
|
||||
if secret == "" {
|
||||
return nil, ErrSecretAccessKeyNotFound
|
||||
}
|
||||
|
||||
return Creds(id, secret, os.Getenv("AWS_SESSION_TOKEN")), nil
|
||||
}
|
||||
|
||||
// Creds returns a static provider of credentials.
|
||||
func Creds(accessKeyID, secretAccessKey, sessionToken string) CredentialsProvider {
|
||||
return staticCredentialsProvider{
|
||||
creds: Credentials{
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
SessionToken: sessionToken,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IAMCreds returns a provider which pulls credentials from the local EC2
|
||||
// instance's IAM roles.
|
||||
func IAMCreds() CredentialsProvider {
|
||||
return &iamProvider{}
|
||||
}
|
||||
|
||||
// ProfileCreds returns a provider which pulls credentials from the profile
|
||||
// configuration file.
|
||||
func ProfileCreds(filename, profile string, expiry time.Duration) (CredentialsProvider, error) {
|
||||
if filename == "" {
|
||||
homeDir := os.Getenv("HOME") // *nix
|
||||
if homeDir == "" { // Windows
|
||||
homeDir = os.Getenv("USERPROFILE")
|
||||
}
|
||||
if homeDir == "" {
|
||||
return nil, errors.New("User home directory not found.")
|
||||
}
|
||||
|
||||
filename = filepath.Join(homeDir, ".aws", "credentials")
|
||||
}
|
||||
|
||||
if profile == "" {
|
||||
profile = "default"
|
||||
}
|
||||
|
||||
return &profileProvider{
|
||||
filename: filename,
|
||||
profile: profile,
|
||||
expiry: expiry,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type profileProvider struct {
|
||||
filename string
|
||||
profile string
|
||||
expiry time.Duration
|
||||
|
||||
creds Credentials
|
||||
m sync.Mutex
|
||||
expiration time.Time
|
||||
}
|
||||
|
||||
func (p *profileProvider) Credentials() (*Credentials, error) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.expiration.After(currentTime()) {
|
||||
return &p.creds, nil
|
||||
}
|
||||
|
||||
config, err := ini.LoadFile(p.filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profile := config.Section(p.profile)
|
||||
|
||||
accessKeyID, ok := profile["aws_access_key_id"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("profile %s in %s did not contain aws_access_key_id", p.profile, p.filename)
|
||||
}
|
||||
|
||||
secretAccessKey, ok := profile["aws_secret_access_key"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("profile %s in %s did not contain aws_secret_access_key", p.profile, p.filename)
|
||||
}
|
||||
|
||||
sessionToken := profile["aws_session_token"]
|
||||
|
||||
p.creds = Credentials{
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
SessionToken: sessionToken,
|
||||
}
|
||||
p.expiration = currentTime().Add(p.expiry)
|
||||
|
||||
return &p.creds, nil
|
||||
}
|
||||
|
||||
type iamProvider struct {
|
||||
creds Credentials
|
||||
m sync.Mutex
|
||||
expiration time.Time
|
||||
}
|
||||
|
||||
var metadataCredentialsEndpoint = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||
|
||||
// IAMClient is the HTTP client used to query the metadata endpoint for IAM
|
||||
// credentials.
|
||||
var IAMClient = http.Client{
|
||||
Timeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
func (p *iamProvider) Credentials() (*Credentials, error) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
if p.expiration.After(currentTime()) {
|
||||
return &p.creds, nil
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Expiration time.Time
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
Token string
|
||||
}
|
||||
|
||||
resp, err := IAMClient.Get(metadataCredentialsEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing IAM credentials")
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
// Take the first line of the body of the metadata endpoint
|
||||
s := bufio.NewScanner(resp.Body)
|
||||
if !s.Scan() {
|
||||
return nil, fmt.Errorf("unable to find default IAM credentials")
|
||||
} else if s.Err() != nil {
|
||||
return nil, fmt.Errorf("%s listing IAM credentials", s.Err())
|
||||
}
|
||||
|
||||
resp, err = IAMClient.Get(metadataCredentialsEndpoint + s.Text())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting %s IAM credentials", s.Text())
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||||
return nil, fmt.Errorf("decoding %s IAM credentials", s.Text())
|
||||
}
|
||||
|
||||
p.creds = Credentials{
|
||||
AccessKeyID: body.AccessKeyID,
|
||||
SecretAccessKey: body.SecretAccessKey,
|
||||
SessionToken: body.Token,
|
||||
}
|
||||
p.expiration = body.Expiration
|
||||
|
||||
return &p.creds, nil
|
||||
}
|
||||
|
||||
type staticCredentialsProvider struct {
|
||||
creds Credentials
|
||||
}
|
||||
|
||||
func (p staticCredentialsProvider) Credentials() (*Credentials, error) {
|
||||
return &p.creds, nil
|
||||
}
|
||||
236
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/credentials_test.go
сгенерированный
поставляемый
236
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/credentials_test.go
сгенерированный
поставляемый
@@ -1,236 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEnvCreds(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||
os.Setenv("AWS_SESSION_TOKEN", "token")
|
||||
|
||||
prov, err := EnvCreds()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
creds, err := prov.Credentials()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v, want := creds.AccessKeyID, "access"; v != want {
|
||||
t.Errorf("Access key ID was %v, expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SecretAccessKey, "secret"; v != want {
|
||||
t.Errorf("Secret access key was %v, expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SessionToken, "token"; v != want {
|
||||
t.Errorf("Security token was %v, expected %v", v, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvCredsNoAccessKeyID(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
|
||||
|
||||
prov, err := EnvCreds()
|
||||
if err != ErrAccessKeyIDNotFound {
|
||||
t.Fatalf("ErrAccessKeyIDNotFound expected, but was %#v/%#v", prov, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvCredsNoSecretAccessKey(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_ACCESS_KEY_ID", "access")
|
||||
|
||||
prov, err := EnvCreds()
|
||||
if err != ErrSecretAccessKeyNotFound {
|
||||
t.Fatalf("ErrSecretAccessKeyNotFound expected, but was %#v/%#v", prov, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvCredsAlternateNames(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("AWS_ACCESS_KEY", "access")
|
||||
os.Setenv("AWS_SECRET_KEY", "secret")
|
||||
|
||||
prov, err := EnvCreds()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
creds, err := prov.Credentials()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v, want := creds.AccessKeyID, "access"; v != want {
|
||||
t.Errorf("Access key ID was %v, expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SecretAccessKey, "secret"; v != want {
|
||||
t.Errorf("Secret access key was %v, expected %v", v, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIAMCreds(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI == "/" {
|
||||
fmt.Fprintln(w, "/creds")
|
||||
} else {
|
||||
fmt.Fprintln(w, `{
|
||||
"AccessKeyId" : "accessKey",
|
||||
"SecretAccessKey" : "secret",
|
||||
"Token" : "token",
|
||||
"Expiration" : "2014-12-16T01:51:37Z"
|
||||
}`)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
defer func(s string) {
|
||||
metadataCredentialsEndpoint = s
|
||||
}(metadataCredentialsEndpoint)
|
||||
metadataCredentialsEndpoint = server.URL
|
||||
|
||||
defer func() {
|
||||
currentTime = time.Now
|
||||
}()
|
||||
currentTime = func() time.Time {
|
||||
return time.Date(2014, 12, 15, 21, 26, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
prov := IAMCreds()
|
||||
creds, err := prov.Credentials()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v, want := creds.AccessKeyID, "accessKey"; v != want {
|
||||
t.Errorf("AcccessKeyID was %v, but expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SecretAccessKey, "secret"; v != want {
|
||||
t.Errorf("SecretAccessKey was %v, but expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SessionToken, "token"; v != want {
|
||||
t.Errorf("SessionToken was %v, but expected %v", v, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfileCreds(t *testing.T) {
|
||||
prov, err := ProfileCreds("example.ini", "", 10*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
creds, err := prov.Credentials()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v, want := creds.AccessKeyID, "accessKey"; v != want {
|
||||
t.Errorf("AcccessKeyID was %v, but expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SecretAccessKey, "secret"; v != want {
|
||||
t.Errorf("SecretAccessKey was %v, but expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SessionToken, "token"; v != want {
|
||||
t.Errorf("SessionToken was %v, but expected %v", v, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfileCredsWithoutToken(t *testing.T) {
|
||||
prov, err := ProfileCreds("example.ini", "no_token", 10*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
creds, err := prov.Credentials()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if v, want := creds.AccessKeyID, "accessKey"; v != want {
|
||||
t.Errorf("AcccessKeyID was %v, but expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SecretAccessKey, "secret"; v != want {
|
||||
t.Errorf("SecretAccessKey was %v, but expected %v", v, want)
|
||||
}
|
||||
|
||||
if v, want := creds.SessionToken, ""; v != want {
|
||||
t.Errorf("SessionToken was %v, but expected %v", v, want)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkProfileCreds(b *testing.B) {
|
||||
prov, err := ProfileCreds("example.ini", "", 10*time.Minute)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := prov.Credentials()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIAMCreds(b *testing.B) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI == "/" {
|
||||
fmt.Fprintln(w, "/creds")
|
||||
} else {
|
||||
fmt.Fprintln(w, `{
|
||||
"AccessKeyId" : "accessKey",
|
||||
"SecretAccessKey" : "secret",
|
||||
"Token" : "token",
|
||||
"Expiration" : "2014-12-16T01:51:37Z"
|
||||
}`)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
defer func(s string) {
|
||||
metadataCredentialsEndpoint = s
|
||||
}(metadataCredentialsEndpoint)
|
||||
metadataCredentialsEndpoint = server.URL
|
||||
|
||||
defer func() {
|
||||
currentTime = time.Now
|
||||
}()
|
||||
currentTime = func() time.Time {
|
||||
return time.Date(2014, 12, 15, 21, 26, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
prov := IAMCreds()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := prov.Credentials()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
26
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/error.go
сгенерированный
поставляемый
26
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/error.go
сгенерированный
поставляемый
@@ -1,26 +0,0 @@
|
||||
package aws
|
||||
|
||||
import "time"
|
||||
|
||||
// An APIError is an error returned by an AWS API.
|
||||
type APIError struct {
|
||||
StatusCode int // HTTP status code e.g. 200
|
||||
Code string
|
||||
Message string
|
||||
RequestID string
|
||||
Retryable bool
|
||||
RetryDelay time.Duration
|
||||
RetryCount uint
|
||||
}
|
||||
|
||||
func (e APIError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func Error(e error) *APIError {
|
||||
if err, ok := e.(APIError); ok {
|
||||
return &err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
8
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/example.ini
сгенерированный
поставляемый
8
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/example.ini
сгенерированный
поставляемый
@@ -1,8 +0,0 @@
|
||||
[default]
|
||||
aws_access_key_id = accessKey
|
||||
aws_secret_access_key = secret
|
||||
aws_session_token = token
|
||||
|
||||
[no_token]
|
||||
aws_access_key_id = accessKey
|
||||
aws_secret_access_key = secret
|
||||
78
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/handler_functions.go
сгенерированный
поставляемый
78
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/handler_functions.go
сгенерированный
поставляемый
@@ -1,78 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sleepDelay = func(delay time.Duration) {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
type lener interface {
|
||||
Len() int
|
||||
}
|
||||
|
||||
func BuildContentLength(r *Request) {
|
||||
if r.HTTPRequest.Header.Get("Content-Length") != "" {
|
||||
return
|
||||
}
|
||||
|
||||
var length int64
|
||||
switch body := r.Body.(type) {
|
||||
case nil:
|
||||
length = 0
|
||||
case lener:
|
||||
length = int64(body.Len())
|
||||
case io.Seeker:
|
||||
cur, _ := body.Seek(0, 1)
|
||||
end, _ := body.Seek(0, 2)
|
||||
body.Seek(cur, 0) // make sure to seek back to original location
|
||||
length = end - cur
|
||||
default:
|
||||
panic("Cannot get length of body, must provide `ContentLength`")
|
||||
}
|
||||
|
||||
r.HTTPRequest.ContentLength = length
|
||||
r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length))
|
||||
}
|
||||
|
||||
func UserAgentHandler(r *Request) {
|
||||
r.HTTPRequest.Header.Set("User-Agent", SDKName+"/"+SDKVersion)
|
||||
}
|
||||
|
||||
func SendHandler(r *Request) {
|
||||
r.HTTPResponse, r.Error = r.Service.Config.HTTPClient.Do(r.HTTPRequest)
|
||||
}
|
||||
|
||||
func ValidateResponseHandler(r *Request) {
|
||||
if r.HTTPResponse.StatusCode == 0 || r.HTTPResponse.StatusCode >= 400 {
|
||||
err := APIError{
|
||||
StatusCode: r.HTTPResponse.StatusCode,
|
||||
RetryCount: r.RetryCount,
|
||||
}
|
||||
r.Error = err
|
||||
err.Retryable = r.Service.ShouldRetry(r)
|
||||
err.RetryDelay = r.Service.RetryRules(r)
|
||||
r.Error = err
|
||||
}
|
||||
}
|
||||
|
||||
func AfterRetryHandler(r *Request) {
|
||||
delay := 0 * time.Second
|
||||
willRetry := false
|
||||
|
||||
if err := Error(r.Error); err != nil {
|
||||
delay = err.RetryDelay
|
||||
if err.Retryable && r.RetryCount < r.Service.MaxRetries() {
|
||||
r.RetryCount++
|
||||
willRetry = true
|
||||
}
|
||||
}
|
||||
|
||||
if willRetry {
|
||||
r.Error = nil
|
||||
sleepDelay(delay)
|
||||
}
|
||||
}
|
||||
65
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/handlers.go
сгенерированный
поставляемый
65
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/handlers.go
сгенерированный
поставляемый
@@ -1,65 +0,0 @@
|
||||
package aws
|
||||
|
||||
import "container/list"
|
||||
|
||||
type Handlers struct {
|
||||
Validate HandlerList
|
||||
Build HandlerList
|
||||
Sign HandlerList
|
||||
Send HandlerList
|
||||
ValidateResponse HandlerList
|
||||
Unmarshal HandlerList
|
||||
UnmarshalMeta HandlerList
|
||||
UnmarshalError HandlerList
|
||||
Retry HandlerList
|
||||
AfterRetry HandlerList
|
||||
}
|
||||
|
||||
func (h *Handlers) copy() Handlers {
|
||||
return Handlers{
|
||||
Validate: h.Validate.copy(),
|
||||
Build: h.Build.copy(),
|
||||
Sign: h.Sign.copy(),
|
||||
Send: h.Send.copy(),
|
||||
ValidateResponse: h.ValidateResponse.copy(),
|
||||
Unmarshal: h.Unmarshal.copy(),
|
||||
UnmarshalError: h.UnmarshalError.copy(),
|
||||
UnmarshalMeta: h.UnmarshalMeta.copy(),
|
||||
Retry: h.Retry.copy(),
|
||||
AfterRetry: h.AfterRetry.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes callback functions for all handlers
|
||||
func (h *Handlers) Clear() {
|
||||
h.Validate.Init()
|
||||
h.Build.Init()
|
||||
h.Send.Init()
|
||||
h.Sign.Init()
|
||||
h.Unmarshal.Init()
|
||||
h.UnmarshalMeta.Init()
|
||||
h.UnmarshalError.Init()
|
||||
h.ValidateResponse.Init()
|
||||
h.Retry.Init()
|
||||
h.AfterRetry.Init()
|
||||
}
|
||||
|
||||
type HandlerList struct {
|
||||
list.List
|
||||
}
|
||||
|
||||
func (l HandlerList) copy() HandlerList {
|
||||
var n HandlerList
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
h := e.Value.(func(*Request))
|
||||
n.PushBack(h)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (l *HandlerList) Run(r *Request) {
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
h := e.Value.(func(*Request))
|
||||
h(r)
|
||||
}
|
||||
}
|
||||
24
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/handlers_test.go
сгенерированный
поставляемый
24
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/handlers_test.go
сгенерированный
поставляемый
@@ -1,24 +0,0 @@
|
||||
package aws
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHandlerList(t *testing.T) {
|
||||
r := &Request{}
|
||||
l := HandlerList{}
|
||||
l.PushBack(func(r *Request) { r.Data = Boolean(true) })
|
||||
l.Run(r)
|
||||
if r.Data == nil {
|
||||
t.Error("Expected handler to execute")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleHandlers(t *testing.T) {
|
||||
r := &Request{}
|
||||
l := HandlerList{}
|
||||
l.PushBack(func(r *Request) { r.Data = Boolean(true) })
|
||||
l.PushBack(func(r *Request) { r.Data = nil })
|
||||
l.Run(r)
|
||||
if r.Data != nil {
|
||||
t.Error("Expected handler to execute")
|
||||
}
|
||||
}
|
||||
80
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/param_validator.go
сгенерированный
поставляемый
80
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/param_validator.go
сгенерированный
поставляемый
@@ -1,80 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ValidateParameters(r *Request) {
|
||||
if r.ParamsFilled() {
|
||||
v := validator{errors: []string{}}
|
||||
v.validateAny(reflect.ValueOf(r.Params), "")
|
||||
|
||||
if count := len(v.errors); count > 0 {
|
||||
format := "%d validation errors:\n- %s"
|
||||
msg := fmt.Sprintf(format, count, strings.Join(v.errors, "\n- "))
|
||||
r.Error = APIError{Code: "InvalidParameter", Message: msg}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type validator struct {
|
||||
errors []string
|
||||
}
|
||||
|
||||
func (v *validator) validateAny(value reflect.Value, path string) {
|
||||
value = reflect.Indirect(value)
|
||||
if !value.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Struct:
|
||||
v.validateStruct(value, path)
|
||||
case reflect.Slice:
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
v.validateAny(value.Index(i), path+fmt.Sprintf("[%d]", i))
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, n := range value.MapKeys() {
|
||||
v.validateAny(value.MapIndex(n), path+fmt.Sprintf("[%q]", n.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *validator) validateStruct(value reflect.Value, path string) {
|
||||
prefix := "."
|
||||
if path == "" {
|
||||
prefix = ""
|
||||
}
|
||||
|
||||
for i := 0; i < value.Type().NumField(); i++ {
|
||||
f := value.Type().Field(i)
|
||||
if strings.ToLower(f.Name[0:1]) == f.Name[0:1] {
|
||||
continue
|
||||
}
|
||||
fvalue := value.FieldByName(f.Name)
|
||||
|
||||
notset := false
|
||||
if f.Tag.Get("required") != "" {
|
||||
switch fvalue.Kind() {
|
||||
case reflect.Ptr, reflect.Slice:
|
||||
if fvalue.IsNil() {
|
||||
notset = true
|
||||
}
|
||||
default:
|
||||
if !fvalue.IsValid() {
|
||||
notset = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if notset {
|
||||
msg := "missing required parameter: " + path + prefix + f.Name
|
||||
v.errors = append(v.errors, msg)
|
||||
} else {
|
||||
v.validateAny(fvalue, path+prefix+f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
85
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/param_validator_test.go
сгенерированный
поставляемый
85
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/param_validator_test.go
сгенерированный
поставляемый
@@ -1,85 +0,0 @@
|
||||
package aws_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var service = func() *aws.Service {
|
||||
s := &aws.Service{
|
||||
Config: &aws.Config{},
|
||||
ServiceName: "mock-service",
|
||||
APIVersion: "2015-01-01",
|
||||
}
|
||||
return s
|
||||
}()
|
||||
|
||||
type StructShape struct {
|
||||
RequiredList []*ConditionalStructShape `required:"true"`
|
||||
RequiredMap *map[string]*ConditionalStructShape `required:"true"`
|
||||
RequiredBool *bool `required:"true"`
|
||||
OptionalStruct *ConditionalStructShape
|
||||
|
||||
hiddenParameter *string
|
||||
|
||||
metadataStructureShape
|
||||
}
|
||||
|
||||
type metadataStructureShape struct {
|
||||
SDKShapeTraits bool
|
||||
}
|
||||
|
||||
type ConditionalStructShape struct {
|
||||
Name *string `required:"true"`
|
||||
SDKShapeTraits bool
|
||||
}
|
||||
|
||||
func TestNoErrors(t *testing.T) {
|
||||
input := &StructShape{
|
||||
RequiredList: []*ConditionalStructShape{},
|
||||
RequiredMap: &map[string]*ConditionalStructShape{
|
||||
"key1": &ConditionalStructShape{Name: aws.String("Name")},
|
||||
"key2": &ConditionalStructShape{Name: aws.String("Name")},
|
||||
},
|
||||
RequiredBool: aws.Boolean(true),
|
||||
OptionalStruct: &ConditionalStructShape{Name: aws.String("Name")},
|
||||
}
|
||||
|
||||
req := aws.NewRequest(service, &aws.Operation{}, input, nil)
|
||||
aws.ValidateParameters(req)
|
||||
assert.NoError(t, req.Error)
|
||||
}
|
||||
|
||||
func TestMissingRequiredParameters(t *testing.T) {
|
||||
input := &StructShape{}
|
||||
req := aws.NewRequest(service, &aws.Operation{}, input, nil)
|
||||
aws.ValidateParameters(req)
|
||||
err := aws.Error(req.Error)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "InvalidParameter", err.Code)
|
||||
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList\n- missing required parameter: RequiredMap\n- missing required parameter: RequiredBool", err.Message)
|
||||
}
|
||||
|
||||
func TestNestedMissingRequiredParameters(t *testing.T) {
|
||||
input := &StructShape{
|
||||
RequiredList: []*ConditionalStructShape{&ConditionalStructShape{}},
|
||||
RequiredMap: &map[string]*ConditionalStructShape{
|
||||
"key1": &ConditionalStructShape{Name: aws.String("Name")},
|
||||
"key2": &ConditionalStructShape{},
|
||||
},
|
||||
RequiredBool: aws.Boolean(true),
|
||||
OptionalStruct: &ConditionalStructShape{},
|
||||
}
|
||||
|
||||
req := aws.NewRequest(service, &aws.Operation{}, input, nil)
|
||||
aws.ValidateParameters(req)
|
||||
err := aws.Error(req.Error)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "InvalidParameter", err.Code)
|
||||
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList[0].Name\n- missing required parameter: RequiredMap[\"key2\"].Name\n- missing required parameter: OptionalStruct.Name", err.Message)
|
||||
|
||||
}
|
||||
149
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/request.go
сгенерированный
поставляемый
149
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/request.go
сгенерированный
поставляемый
@@ -1,149 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
*Service
|
||||
Handlers Handlers
|
||||
Time time.Time
|
||||
ExpireTime time.Duration
|
||||
Operation *Operation
|
||||
HTTPRequest *http.Request
|
||||
HTTPResponse *http.Response
|
||||
Body io.ReadSeeker
|
||||
Params interface{}
|
||||
Error error
|
||||
Data interface{}
|
||||
RequestID string
|
||||
RetryCount uint
|
||||
|
||||
built bool
|
||||
}
|
||||
|
||||
type Operation struct {
|
||||
Name string
|
||||
HTTPMethod string
|
||||
HTTPPath string
|
||||
}
|
||||
|
||||
func NewRequest(service *Service, operation *Operation, params interface{}, data interface{}) *Request {
|
||||
method := operation.HTTPMethod
|
||||
if method == "" {
|
||||
method = "POST"
|
||||
}
|
||||
p := operation.HTTPPath
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
|
||||
httpReq, _ := http.NewRequest(method, "", nil)
|
||||
httpReq.URL, _ = url.Parse(service.Endpoint + p)
|
||||
|
||||
r := &Request{
|
||||
Service: service,
|
||||
Handlers: service.Handlers.copy(),
|
||||
Time: time.Now(),
|
||||
ExpireTime: 0,
|
||||
Operation: operation,
|
||||
HTTPRequest: httpReq,
|
||||
Body: nil,
|
||||
Params: params,
|
||||
Error: nil,
|
||||
Data: data,
|
||||
}
|
||||
r.SetBufferBody([]byte{})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) ParamsFilled() bool {
|
||||
return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
|
||||
}
|
||||
|
||||
func (r *Request) DataFilled() bool {
|
||||
return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
|
||||
}
|
||||
|
||||
func (r *Request) SetBufferBody(buf []byte) {
|
||||
r.SetReaderBody(bytes.NewReader(buf))
|
||||
}
|
||||
|
||||
func (r *Request) SetReaderBody(reader io.ReadSeeker) {
|
||||
r.HTTPRequest.Body = ioutil.NopCloser(reader)
|
||||
r.Body = reader
|
||||
}
|
||||
|
||||
func (r *Request) Presign(expireTime time.Duration) (string, error) {
|
||||
r.ExpireTime = expireTime
|
||||
r.Sign()
|
||||
if r.Error != nil {
|
||||
return "", r.Error
|
||||
} else {
|
||||
return r.HTTPRequest.URL.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) Build() error {
|
||||
if !r.built {
|
||||
r.Error = nil
|
||||
r.Handlers.Validate.Run(r)
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
r.Handlers.Build.Run(r)
|
||||
r.built = true
|
||||
}
|
||||
|
||||
return r.Error
|
||||
}
|
||||
|
||||
func (r *Request) Sign() error {
|
||||
r.Build()
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
r.Handlers.Sign.Run(r)
|
||||
return r.Error
|
||||
}
|
||||
|
||||
func (r *Request) Send() error {
|
||||
r.Sign()
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
for {
|
||||
r.Handlers.Send.Run(r)
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
r.Handlers.UnmarshalMeta.Run(r)
|
||||
r.Handlers.ValidateResponse.Run(r)
|
||||
if r.Error != nil {
|
||||
r.Handlers.Retry.Run(r)
|
||||
r.Handlers.AfterRetry.Run(r)
|
||||
if r.Error != nil {
|
||||
r.Handlers.UnmarshalError.Run(r)
|
||||
return r.Error
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
r.Handlers.Unmarshal.Run(r)
|
||||
if r.Error != nil {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
118
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/request_test.go
сгенерированный
поставляемый
118
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/request_test.go
сгенерированный
поставляемый
@@ -1,118 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testData struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
func body(str string) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(str)))
|
||||
}
|
||||
|
||||
func unmarshal(req *Request) {
|
||||
defer req.HTTPResponse.Body.Close()
|
||||
if req.Data != nil {
|
||||
json.NewDecoder(req.HTTPResponse.Body).Decode(req.Data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func unmarshalError(req *Request) {
|
||||
bodyBytes, err := ioutil.ReadAll(req.HTTPResponse.Body)
|
||||
if err != nil {
|
||||
req.Error = err
|
||||
return
|
||||
}
|
||||
if len(bodyBytes) == 0 {
|
||||
req.Error = APIError{
|
||||
StatusCode: req.HTTPResponse.StatusCode,
|
||||
Message: req.HTTPResponse.Status,
|
||||
}
|
||||
return
|
||||
}
|
||||
var jsonErr jsonErrorResponse
|
||||
if err := json.Unmarshal(bodyBytes, &jsonErr); err != nil {
|
||||
req.Error = err
|
||||
return
|
||||
}
|
||||
req.Error = APIError{
|
||||
StatusCode: req.HTTPResponse.StatusCode,
|
||||
Code: jsonErr.Code,
|
||||
Message: jsonErr.Message,
|
||||
}
|
||||
}
|
||||
|
||||
type jsonErrorResponse struct {
|
||||
Code string `json:"__type"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func TestRequestRecoverRetry(t *testing.T) {
|
||||
reqNum := 0
|
||||
reqs := []http.Response{
|
||||
http.Response{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||
http.Response{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||
http.Response{StatusCode: 200, Body: body(`{"data":"valid"}`)},
|
||||
}
|
||||
|
||||
s := NewService(&Config{MaxRetries: -1})
|
||||
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||
s.Handlers.Send.Init() // mock sending
|
||||
s.Handlers.Send.PushBack(func(r *Request) {
|
||||
r.HTTPResponse = &reqs[reqNum]
|
||||
reqNum++
|
||||
})
|
||||
out := &testData{}
|
||||
r := NewRequest(s, &Operation{Name: "Operation"}, nil, out)
|
||||
err := r.Send()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, int(r.RetryCount))
|
||||
assert.Equal(t, "valid", out.Data)
|
||||
}
|
||||
|
||||
func TestRequestExhaustRetries(t *testing.T) {
|
||||
delays := []time.Duration{}
|
||||
sleepDelay = func(delay time.Duration) {
|
||||
delays = append(delays, delay)
|
||||
}
|
||||
|
||||
reqNum := 0
|
||||
reqs := []http.Response{
|
||||
http.Response{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||
http.Response{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||
http.Response{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||
http.Response{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
|
||||
}
|
||||
|
||||
s := NewService(&Config{MaxRetries: -1})
|
||||
s.Handlers.Unmarshal.PushBack(unmarshal)
|
||||
s.Handlers.UnmarshalError.PushBack(unmarshalError)
|
||||
s.Handlers.Send.Init() // mock sending
|
||||
s.Handlers.Send.PushBack(func(r *Request) {
|
||||
r.HTTPResponse = &reqs[reqNum]
|
||||
reqNum++
|
||||
})
|
||||
r := NewRequest(s, &Operation{Name: "Operation"}, nil, nil)
|
||||
err := r.Send()
|
||||
apiErr := Error(err)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotNil(t, apiErr)
|
||||
assert.Equal(t, 500, apiErr.StatusCode)
|
||||
assert.Equal(t, "UnknownError", apiErr.Code)
|
||||
assert.Equal(t, "An error occurred.", apiErr.Message)
|
||||
assert.Equal(t, 3, int(r.RetryCount))
|
||||
assert.True(t, reflect.DeepEqual([]time.Duration{30 * time.Millisecond, 60 * time.Millisecond, 120 * time.Millisecond}, delays))
|
||||
}
|
||||
142
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/service.go
сгенерированный
поставляемый
142
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/service.go
сгенерированный
поставляемый
@@ -1,142 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/internal/endpoints"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Config *Config
|
||||
Handlers Handlers
|
||||
ManualSend bool
|
||||
ServiceName string
|
||||
APIVersion string
|
||||
Endpoint string
|
||||
JSONVersion string
|
||||
TargetPrefix string
|
||||
RetryRules func(*Request) time.Duration
|
||||
ShouldRetry func(*Request) bool
|
||||
DefaultMaxRetries uint
|
||||
}
|
||||
|
||||
var schemeRE = regexp.MustCompile("^([^:]+)://")
|
||||
|
||||
func NewService(config *Config) *Service {
|
||||
svc := &Service{Config: config}
|
||||
svc.Initialize()
|
||||
return svc
|
||||
}
|
||||
|
||||
func (s *Service) Initialize() {
|
||||
if s.Config == nil {
|
||||
s.Config = &Config{}
|
||||
}
|
||||
if s.Config.HTTPClient == nil {
|
||||
s.Config.HTTPClient = http.DefaultClient
|
||||
}
|
||||
|
||||
if s.RetryRules == nil {
|
||||
s.RetryRules = retryRules
|
||||
}
|
||||
|
||||
if s.ShouldRetry == nil {
|
||||
s.ShouldRetry = shouldRetry
|
||||
}
|
||||
|
||||
s.DefaultMaxRetries = 3
|
||||
s.Handlers.Build.PushBack(UserAgentHandler)
|
||||
s.Handlers.Sign.PushBack(BuildContentLength)
|
||||
s.Handlers.Send.PushBack(SendHandler)
|
||||
s.Handlers.AfterRetry.PushBack(AfterRetryHandler)
|
||||
s.Handlers.ValidateResponse.PushBack(ValidateResponseHandler)
|
||||
s.AddDebugHandlers()
|
||||
s.buildEndpoint()
|
||||
|
||||
if !s.Config.DisableParamValidation {
|
||||
s.Handlers.Validate.PushBack(ValidateParameters)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) buildEndpoint() {
|
||||
if s.Config.Endpoint != "" {
|
||||
s.Endpoint = s.Config.Endpoint
|
||||
} else {
|
||||
s.Endpoint = endpoints.EndpointForRegion(s.ServiceName, s.Config.Region)
|
||||
}
|
||||
|
||||
if !schemeRE.MatchString(s.Endpoint) {
|
||||
scheme := "https"
|
||||
if s.Config.DisableSSL {
|
||||
scheme = "http"
|
||||
}
|
||||
s.Endpoint = scheme + "://" + s.Endpoint
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) AddDebugHandlers() {
|
||||
out := s.Config.Logger
|
||||
if s.Config.LogLevel == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s.Handlers.Sign.PushBack(func(r *Request) {
|
||||
dumpedBody, _ := httputil.DumpRequest(r.HTTPRequest, true)
|
||||
|
||||
fmt.Fprintf(out, "=> [%s] %s.%s(%+v)\n", r.Time,
|
||||
r.Service.ServiceName, r.Operation.Name, r.Params)
|
||||
fmt.Fprintf(out, "---[ REQUEST PRE-SIGN ]------------------------------\n")
|
||||
fmt.Fprintf(out, "%s\n", string(dumpedBody))
|
||||
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||
})
|
||||
s.Handlers.Send.PushFront(func(r *Request) {
|
||||
dumpedBody, _ := httputil.DumpRequest(r.HTTPRequest, true)
|
||||
|
||||
fmt.Fprintf(out, "---[ REQUEST POST-SIGN ]-----------------------------\n")
|
||||
fmt.Fprintf(out, "%s\n", string(dumpedBody))
|
||||
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||
})
|
||||
s.Handlers.Send.PushBack(func(r *Request) {
|
||||
fmt.Fprintf(out, "---[ RESPONSE ]--------------------------------------\n")
|
||||
if r.HTTPResponse != nil {
|
||||
dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, true)
|
||||
fmt.Fprintf(out, "%s\n", string(dumpedBody))
|
||||
} else if r.Error != nil {
|
||||
fmt.Fprintf(out, "%s\n", r.Error)
|
||||
}
|
||||
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) MaxRetries() uint {
|
||||
if s.Config.MaxRetries < 0 {
|
||||
return s.DefaultMaxRetries
|
||||
} else {
|
||||
return uint(s.Config.MaxRetries)
|
||||
}
|
||||
}
|
||||
|
||||
func retryRules(r *Request) time.Duration {
|
||||
delay := time.Duration(math.Pow(2, float64(r.RetryCount))) * 30
|
||||
return delay * time.Millisecond
|
||||
}
|
||||
|
||||
func shouldRetry(r *Request) bool {
|
||||
if err := Error(r.Error); err != nil {
|
||||
if err.StatusCode >= 500 {
|
||||
return true
|
||||
}
|
||||
|
||||
switch err.Code {
|
||||
case "ExpiredTokenException":
|
||||
case "ProvisionedThroughputExceededException", "Throttling":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
63
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/types.go
сгенерированный
поставляемый
63
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/types.go
сгенерированный
поставляемый
@@ -1,63 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// String converts a Go string into a string pointer.
|
||||
func String(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Boolean converts a Go bool into a boolean pointer.
|
||||
func Boolean(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Long converts a Go int64 into a long pointer.
|
||||
func Long(v int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Double converts a Go float64 into a double pointer.
|
||||
func Double(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Time converts a Go Time into a Time pointer
|
||||
func Time(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
func ReadSeekCloser(r io.Reader) ReaderSeekerCloser {
|
||||
return ReaderSeekerCloser{r}
|
||||
}
|
||||
|
||||
type ReaderSeekerCloser struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r ReaderSeekerCloser) Read(p []byte) (int, error) {
|
||||
switch t := r.r.(type) {
|
||||
case io.Reader:
|
||||
return t.Read(p)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (r ReaderSeekerCloser) Seek(offset int64, whence int) (int64, error) {
|
||||
switch t := r.r.(type) {
|
||||
case io.Seeker:
|
||||
return t.Seek(offset, whence)
|
||||
}
|
||||
return int64(0), nil
|
||||
}
|
||||
|
||||
func (r ReaderSeekerCloser) Close() error {
|
||||
switch t := r.r.(type) {
|
||||
case io.Closer:
|
||||
return t.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
5
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/version.go
сгенерированный
поставляемый
5
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/aws/version.go
сгенерированный
поставляемый
@@ -1,5 +0,0 @@
|
||||
// Package aws provides core functionality for making requests to AWS services.
|
||||
package aws
|
||||
|
||||
const SDKName = "aws-sdk-go"
|
||||
const SDKVersion = "0.5.0"
|
||||
24
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints.go
сгенерированный
поставляемый
24
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints.go
сгенерированный
поставляемый
@@ -1,24 +0,0 @@
|
||||
package endpoints
|
||||
|
||||
//go:generate go run ../model/cli/gen-endpoints/main.go endpoints.json endpoints_map.go
|
||||
|
||||
import "strings"
|
||||
|
||||
func EndpointForRegion(svcName, region string) string {
|
||||
derivedKeys := []string{
|
||||
region + "/" + svcName,
|
||||
region + "/*",
|
||||
"*/" + svcName,
|
||||
"*/*",
|
||||
}
|
||||
|
||||
for _, key := range derivedKeys {
|
||||
if val, ok := endpointsMap.Endpoints[key]; ok {
|
||||
ep := val.Endpoint
|
||||
ep = strings.Replace(ep, "{region}", region, -1)
|
||||
ep = strings.Replace(ep, "{service}", svcName, -1)
|
||||
return ep
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
67
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints.json
сгенерированный
поставляемый
67
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints.json
сгенерированный
поставляемый
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"endpoints": {
|
||||
"*/*": {
|
||||
"endpoint": "{service}.{region}.amazonaws.com"
|
||||
},
|
||||
"cn-north-1/*": {
|
||||
"endpoint": "{service}.{region}.amazonaws.com.cn",
|
||||
"signatureVersion": "v4"
|
||||
},
|
||||
"us-gov-west-1/iam": {
|
||||
"endpoint": "iam.us-gov.amazonaws.com"
|
||||
},
|
||||
"us-gov-west-1/sts": {
|
||||
"endpoint": "sts.us-gov-west-1.amazonaws.com"
|
||||
},
|
||||
"us-gov-west-1/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"*/cloudfront": {
|
||||
"endpoint": "cloudfront.amazonaws.com"
|
||||
},
|
||||
"*/iam": {
|
||||
"endpoint": "iam.amazonaws.com"
|
||||
},
|
||||
"*/importexport": {
|
||||
"endpoint": "importexport.amazonaws.com"
|
||||
},
|
||||
"*/route53": {
|
||||
"endpoint": "route53.amazonaws.com"
|
||||
},
|
||||
"*/sts": {
|
||||
"endpoint": "sts.amazonaws.com"
|
||||
},
|
||||
"us-east-1/sdb": {
|
||||
"endpoint": "sdb.amazonaws.com"
|
||||
},
|
||||
"us-east-1/s3": {
|
||||
"endpoint": "s3.amazonaws.com"
|
||||
},
|
||||
"us-west-1/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"us-west-2/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"eu-west-1/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"ap-southeast-1/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"ap-southeast-2/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"ap-northeast-1/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"sa-east-1/s3": {
|
||||
"endpoint": "s3-{region}.amazonaws.com"
|
||||
},
|
||||
"eu-central-1/s3": {
|
||||
"endpoint": "{service}.{region}.amazonaws.com",
|
||||
"signatureVersion": "v4"
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints_map.go
сгенерированный
поставляемый
78
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints_map.go
сгенерированный
поставляемый
@@ -1,78 +0,0 @@
|
||||
package endpoints
|
||||
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
|
||||
type endpointStruct struct {
|
||||
Version int
|
||||
Endpoints map[string]endpointEntry
|
||||
}
|
||||
|
||||
type endpointEntry struct {
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
var endpointsMap = endpointStruct{
|
||||
Version: 2,
|
||||
Endpoints: map[string]endpointEntry{
|
||||
"*/*": endpointEntry{
|
||||
Endpoint: "{service}.{region}.amazonaws.com",
|
||||
},
|
||||
"*/cloudfront": endpointEntry{
|
||||
Endpoint: "cloudfront.amazonaws.com",
|
||||
},
|
||||
"*/iam": endpointEntry{
|
||||
Endpoint: "iam.amazonaws.com",
|
||||
},
|
||||
"*/importexport": endpointEntry{
|
||||
Endpoint: "importexport.amazonaws.com",
|
||||
},
|
||||
"*/route53": endpointEntry{
|
||||
Endpoint: "route53.amazonaws.com",
|
||||
},
|
||||
"*/sts": endpointEntry{
|
||||
Endpoint: "sts.amazonaws.com",
|
||||
},
|
||||
"ap-northeast-1/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
"ap-southeast-1/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
"ap-southeast-2/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
"cn-north-1/*": endpointEntry{
|
||||
Endpoint: "{service}.{region}.amazonaws.com.cn",
|
||||
},
|
||||
"eu-central-1/s3": endpointEntry{
|
||||
Endpoint: "{service}.{region}.amazonaws.com",
|
||||
},
|
||||
"eu-west-1/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
"sa-east-1/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
"us-east-1/s3": endpointEntry{
|
||||
Endpoint: "s3.amazonaws.com",
|
||||
},
|
||||
"us-east-1/sdb": endpointEntry{
|
||||
Endpoint: "sdb.amazonaws.com",
|
||||
},
|
||||
"us-gov-west-1/iam": endpointEntry{
|
||||
Endpoint: "iam.us-gov.amazonaws.com",
|
||||
},
|
||||
"us-gov-west-1/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
"us-gov-west-1/sts": endpointEntry{
|
||||
Endpoint: "sts.us-gov-west-1.amazonaws.com",
|
||||
},
|
||||
"us-west-1/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
"us-west-2/s3": endpointEntry{
|
||||
Endpoint: "s3-{region}.amazonaws.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
25
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints_test.go
сгенерированный
поставляемый
25
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/endpoints/endpoints_test.go
сгенерированный
поставляемый
@@ -1,25 +0,0 @@
|
||||
package endpoints
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGlobalEndpoints(t *testing.T) {
|
||||
region := "mock-region-1"
|
||||
svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts"}
|
||||
|
||||
for _, name := range svcs {
|
||||
if EndpointForRegion(name, region) != name+".amazonaws.com" {
|
||||
t.Errorf("expected endpoint for %s to equal %s.amazonaws.com", name, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicesInCN(t *testing.T) {
|
||||
region := "cn-north-1"
|
||||
svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts", "s3"}
|
||||
|
||||
for _, name := range svcs {
|
||||
if EndpointForRegion(name, region) != name+"."+region+".amazonaws.com.cn" {
|
||||
t.Errorf("expected endpoint for %s to equal %s.%s.amazonaws.com.cn", name, name, region)
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/build.go
сгенерированный
поставляемый
30
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/build.go
сгенерированный
поставляемый
@@ -1,30 +0,0 @@
|
||||
package query
|
||||
|
||||
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/query.json build_test.go
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/internal/protocol/query/queryutil"
|
||||
)
|
||||
|
||||
func Build(r *aws.Request) {
|
||||
body := url.Values{
|
||||
"Action": {r.Operation.Name},
|
||||
"Version": {r.Service.APIVersion},
|
||||
}
|
||||
if err := queryutil.Parse(body, r.Params, false); err != nil {
|
||||
r.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
if r.ExpireTime == 0 {
|
||||
r.HTTPRequest.Method = "POST"
|
||||
r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
r.SetBufferBody([]byte(body.Encode()))
|
||||
} else { // This is a pre-signed request
|
||||
r.HTTPRequest.Method = "GET"
|
||||
r.HTTPRequest.URL.RawQuery = body.Encode()
|
||||
}
|
||||
}
|
||||
1167
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/build_test.go
сгенерированный
поставляемый
1167
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/build_test.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
198
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/queryutil/queryutil.go
сгенерированный
поставляемый
198
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/queryutil/queryutil.go
сгенерированный
поставляемый
@@ -1,198 +0,0 @@
|
||||
package queryutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Parse(body url.Values, i interface{}, isEC2 bool) error {
|
||||
q := queryParser{isEC2: isEC2}
|
||||
return q.parseValue(body, reflect.ValueOf(i), "", "")
|
||||
}
|
||||
|
||||
func elemOf(value reflect.Value) reflect.Value {
|
||||
for value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
type queryParser struct {
|
||||
isEC2 bool
|
||||
}
|
||||
|
||||
func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
|
||||
value = elemOf(value)
|
||||
|
||||
// no need to handle zero values
|
||||
if !value.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := tag.Get("type")
|
||||
if t == "" {
|
||||
switch value.Kind() {
|
||||
case reflect.Struct:
|
||||
t = "structure"
|
||||
case reflect.Slice:
|
||||
t = "list"
|
||||
case reflect.Map:
|
||||
t = "map"
|
||||
}
|
||||
}
|
||||
|
||||
switch t {
|
||||
case "structure":
|
||||
return q.parseStruct(v, value, prefix)
|
||||
case "list":
|
||||
return q.parseList(v, value, prefix, tag)
|
||||
case "map":
|
||||
return q.parseMap(v, value, prefix, tag)
|
||||
default:
|
||||
return q.parseScalar(v, value, prefix, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error {
|
||||
if !value.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := value.Type()
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
|
||||
value := elemOf(value.Field(i))
|
||||
field := t.Field(i)
|
||||
var name string
|
||||
|
||||
if q.isEC2 {
|
||||
name = field.Tag.Get("queryName")
|
||||
}
|
||||
if name == "" {
|
||||
if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
|
||||
name = field.Tag.Get("locationNameList")
|
||||
} else if locName := field.Tag.Get("locationName"); locName != "" {
|
||||
name = locName
|
||||
}
|
||||
if name != "" && q.isEC2 {
|
||||
name = strings.ToUpper(name[0:1]) + name[1:]
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = field.Name
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
name = prefix + "." + name
|
||||
}
|
||||
|
||||
if err := q.parseValue(v, value, name, field.Tag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
|
||||
// check for unflattened list member
|
||||
if !q.isEC2 && tag.Get("flattened") == "" {
|
||||
prefix += ".member"
|
||||
}
|
||||
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
slicePrefix := prefix
|
||||
if slicePrefix == "" {
|
||||
slicePrefix = strconv.Itoa(i + 1)
|
||||
} else {
|
||||
slicePrefix = slicePrefix + "." + strconv.Itoa(i+1)
|
||||
}
|
||||
if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
|
||||
// check for unflattened list member
|
||||
if !q.isEC2 && tag.Get("flattened") == "" {
|
||||
prefix += ".entry"
|
||||
}
|
||||
|
||||
// sort keys for improved serialization consistency.
|
||||
// this is not strictly necessary for protocol support.
|
||||
mapKeyValues := value.MapKeys()
|
||||
mapKeys := map[string]reflect.Value{}
|
||||
mapKeyNames := make([]string, len(mapKeyValues))
|
||||
for i, mapKey := range mapKeyValues {
|
||||
name := mapKey.String()
|
||||
mapKeys[name] = mapKey
|
||||
mapKeyNames[i] = name
|
||||
}
|
||||
sort.Strings(mapKeyNames)
|
||||
|
||||
for i, mapKeyName := range mapKeyNames {
|
||||
mapKey := mapKeys[mapKeyName]
|
||||
mapValue := value.MapIndex(mapKey)
|
||||
|
||||
// serialize key
|
||||
var keyName string
|
||||
if prefix == "" {
|
||||
keyName = strconv.Itoa(i+1) + ".key"
|
||||
} else {
|
||||
keyName = prefix + "." + strconv.Itoa(i+1) + ".key"
|
||||
}
|
||||
|
||||
if err := q.parseValue(v, mapKey, keyName, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize value
|
||||
var valueName string
|
||||
if prefix == "" {
|
||||
valueName = strconv.Itoa(i+1) + ".value"
|
||||
} else {
|
||||
valueName = prefix + "." + strconv.Itoa(i+1) + ".value"
|
||||
}
|
||||
|
||||
if err := q.parseValue(v, mapValue, valueName, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error {
|
||||
switch value := r.Interface().(type) {
|
||||
case string:
|
||||
v.Set(name, value)
|
||||
case []byte:
|
||||
v.Set(name, base64.StdEncoding.EncodeToString(value))
|
||||
case bool:
|
||||
v.Set(name, strconv.FormatBool(value))
|
||||
case int64:
|
||||
v.Set(name, strconv.FormatInt(value, 10))
|
||||
case int:
|
||||
v.Set(name, strconv.Itoa(value))
|
||||
case float64:
|
||||
v.Set(name, strconv.FormatFloat(value, 'f', -1, 64))
|
||||
case float32:
|
||||
v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32))
|
||||
case time.Time:
|
||||
const ISO8601UTC = "2006-01-02T15:04:05Z"
|
||||
v.Set(name, value.UTC().Format(ISO8601UTC))
|
||||
default:
|
||||
return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
26
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/unmarshal.go
сгенерированный
поставляемый
26
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/unmarshal.go
сгенерированный
поставляемый
@@ -1,26 +0,0 @@
|
||||
package query
|
||||
|
||||
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/output/query.json unmarshal_test.go
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil"
|
||||
)
|
||||
|
||||
func Unmarshal(r *aws.Request) {
|
||||
defer r.HTTPResponse.Body.Close()
|
||||
if r.DataFilled() {
|
||||
decoder := xml.NewDecoder(r.HTTPResponse.Body)
|
||||
err := xmlutil.UnmarshalXML(r.Data, decoder, r.Operation.Name+"Result")
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalMeta(r *aws.Request) {
|
||||
// TODO implement unmarshaling of request IDs
|
||||
}
|
||||
31
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/unmarshal_error.go
сгенерированный
поставляемый
31
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/unmarshal_error.go
сгенерированный
поставляемый
@@ -1,31 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
)
|
||||
|
||||
type xmlErrorResponse struct {
|
||||
XMLName xml.Name `xml:"ErrorResponse"`
|
||||
Code string `xml:"Error>Code"`
|
||||
Message string `xml:"Error>Message"`
|
||||
RequestID string `xml:"RequestId"`
|
||||
}
|
||||
|
||||
func UnmarshalError(r *aws.Request) {
|
||||
defer r.HTTPResponse.Body.Close()
|
||||
|
||||
resp := &xmlErrorResponse{}
|
||||
err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp)
|
||||
if err != nil && err != io.EOF {
|
||||
r.Error = err
|
||||
} else {
|
||||
r.Error = aws.APIError{
|
||||
StatusCode: r.HTTPResponse.StatusCode,
|
||||
Code: resp.Code,
|
||||
Message: resp.Message,
|
||||
}
|
||||
}
|
||||
}
|
||||
1361
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/unmarshal_test.go
сгенерированный
поставляемый
1361
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/query/unmarshal_test.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
215
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/rest/build.go
сгенерированный
поставляемый
215
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/rest/build.go
сгенерированный
поставляемый
@@ -1,215 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
)
|
||||
|
||||
// RFC822 returns an RFC822 formatted timestamp for AWS protocols
|
||||
const RFC822 = "Mon, 2 Jan 2006 15:04:05 GMT"
|
||||
|
||||
func Build(r *aws.Request) {
|
||||
if r.ParamsFilled() {
|
||||
v := reflect.ValueOf(r.Params).Elem()
|
||||
buildLocationElements(r, v)
|
||||
buildBody(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
func buildLocationElements(r *aws.Request, v reflect.Value) {
|
||||
query := r.HTTPRequest.URL.Query()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
m := v.Field(i)
|
||||
if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if m.IsValid() {
|
||||
field := v.Type().Field(i)
|
||||
name := field.Tag.Get("locationName")
|
||||
if name == "" {
|
||||
name = field.Name
|
||||
}
|
||||
if m.Kind() == reflect.Ptr {
|
||||
m = m.Elem()
|
||||
}
|
||||
if !m.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
switch field.Tag.Get("location") {
|
||||
case "headers": // header maps
|
||||
buildHeaderMap(r, m, field.Tag.Get("locationName"))
|
||||
case "header":
|
||||
buildHeader(r, m, name)
|
||||
case "uri":
|
||||
buildURI(r, m, name)
|
||||
case "querystring":
|
||||
buildQueryString(r, m, name, query)
|
||||
}
|
||||
}
|
||||
if r.Error != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.HTTPRequest.URL.RawQuery = query.Encode()
|
||||
updatePath(r.HTTPRequest.URL, r.HTTPRequest.URL.Path)
|
||||
}
|
||||
|
||||
func buildBody(r *aws.Request, v reflect.Value) {
|
||||
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||
pfield, _ := v.Type().FieldByName(payloadName)
|
||||
if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
|
||||
payload := reflect.Indirect(v.FieldByName(payloadName))
|
||||
if payload.IsValid() && payload.Interface() != nil {
|
||||
switch reader := payload.Interface().(type) {
|
||||
case io.ReadSeeker:
|
||||
r.SetReaderBody(reader)
|
||||
case []byte:
|
||||
r.SetBufferBody(reader)
|
||||
case string:
|
||||
r.SetBufferBody([]byte(reader))
|
||||
default:
|
||||
r.Error = fmt.Errorf("unknown payload type %s", payload.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildHeader(r *aws.Request, v reflect.Value, name string) {
|
||||
str, err := convertType(v)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
} else if str != nil {
|
||||
r.HTTPRequest.Header.Add(name, *str)
|
||||
}
|
||||
}
|
||||
|
||||
func buildHeaderMap(r *aws.Request, v reflect.Value, prefix string) {
|
||||
for _, key := range v.MapKeys() {
|
||||
str, err := convertType(v.MapIndex(key))
|
||||
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
} else if str != nil {
|
||||
r.HTTPRequest.Header.Add(prefix+key.String(), *str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildURI(r *aws.Request, v reflect.Value, name string) {
|
||||
value, err := convertType(v)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
} else if value != nil {
|
||||
uri := r.HTTPRequest.URL.Path
|
||||
uri = strings.Replace(uri, "{"+name+"}", escapePath(*value, true), -1)
|
||||
uri = strings.Replace(uri, "{"+name+"+}", escapePath(*value, false), -1)
|
||||
r.HTTPRequest.URL.Path = uri
|
||||
}
|
||||
}
|
||||
|
||||
func buildQueryString(r *aws.Request, v reflect.Value, name string, query url.Values) {
|
||||
str, err := convertType(v)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
} else if str != nil {
|
||||
query.Set(name, *str)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePath(url *url.URL, urlPath string) {
|
||||
scheme, query := url.Scheme, url.RawQuery
|
||||
|
||||
// clean up path
|
||||
urlPath = path.Clean(urlPath)
|
||||
|
||||
// get formatted URL minus scheme so we can build this into Opaque
|
||||
url.Scheme, url.Path, url.RawQuery = "", "", ""
|
||||
s := url.String()
|
||||
url.Scheme = scheme
|
||||
url.RawQuery = query
|
||||
|
||||
// build opaque URI
|
||||
url.Opaque = s + urlPath
|
||||
}
|
||||
|
||||
// Whether the byte value can be sent without escaping in AWS URLs
|
||||
var noEscape [256]bool
|
||||
var noEscapeInitialized = false
|
||||
|
||||
// initialise noEscape
|
||||
func initNoEscape() {
|
||||
for i := range noEscape {
|
||||
// Amazon expects every character except these escaped
|
||||
noEscape[i] = (i >= 'A' && i <= 'Z') ||
|
||||
(i >= 'a' && i <= 'z') ||
|
||||
(i >= '0' && i <= '9') ||
|
||||
i == '-' ||
|
||||
i == '.' ||
|
||||
i == '_' ||
|
||||
i == '~'
|
||||
}
|
||||
}
|
||||
|
||||
// escapePath escapes part of a URL path in Amazon style
|
||||
func escapePath(path string, encodeSep bool) string {
|
||||
if !noEscapeInitialized {
|
||||
initNoEscape()
|
||||
noEscapeInitialized = true
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(path); i++ {
|
||||
c := path[i]
|
||||
if noEscape[c] || (c == '/' && !encodeSep) {
|
||||
buf.WriteByte(c)
|
||||
} else {
|
||||
buf.WriteByte('%')
|
||||
buf.WriteString(strings.ToUpper(strconv.FormatUint(uint64(c), 16)))
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func convertType(v reflect.Value) (*string, error) {
|
||||
v = reflect.Indirect(v)
|
||||
if !v.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var str string
|
||||
switch value := v.Interface().(type) {
|
||||
case string:
|
||||
str = value
|
||||
case []byte:
|
||||
str = base64.StdEncoding.EncodeToString(value)
|
||||
case bool:
|
||||
str = strconv.FormatBool(value)
|
||||
case int64:
|
||||
str = strconv.FormatInt(value, 10)
|
||||
case float64:
|
||||
str = strconv.FormatFloat(value, 'f', -1, 64)
|
||||
case time.Time:
|
||||
str = value.UTC().Format(RFC822)
|
||||
default:
|
||||
err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type())
|
||||
return nil, err
|
||||
}
|
||||
return &str, nil
|
||||
}
|
||||
43
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/rest/payload.go
сгенерированный
поставляемый
43
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/rest/payload.go
сгенерированный
поставляемый
@@ -1,43 +0,0 @@
|
||||
package rest
|
||||
|
||||
import "reflect"
|
||||
|
||||
func PayloadMember(i interface{}) interface{} {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(i).Elem()
|
||||
if !v.IsValid() {
|
||||
return nil
|
||||
}
|
||||
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||
field, _ := v.Type().FieldByName(payloadName)
|
||||
if field.Tag.Get("type") != "structure" {
|
||||
return nil
|
||||
}
|
||||
|
||||
payload := v.FieldByName(payloadName)
|
||||
if payload.IsValid() || (payload.Kind() == reflect.Ptr && !payload.IsNil()) {
|
||||
return payload.Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PayloadType(i interface{}) string {
|
||||
v := reflect.Indirect(reflect.ValueOf(i))
|
||||
if !v.IsValid() {
|
||||
return ""
|
||||
}
|
||||
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||
if member, ok := v.Type().FieldByName(payloadName); ok {
|
||||
return member.Tag.Get("type")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
174
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/rest/unmarshal.go
сгенерированный
поставляемый
174
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/rest/unmarshal.go
сгенерированный
поставляемый
@@ -1,174 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
)
|
||||
|
||||
func Unmarshal(r *aws.Request) {
|
||||
if r.DataFilled() {
|
||||
v := reflect.Indirect(reflect.ValueOf(r.Data))
|
||||
unmarshalBody(r, v)
|
||||
unmarshalLocationElements(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalBody(r *aws.Request, v reflect.Value) {
|
||||
if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok {
|
||||
if payloadName := field.Tag.Get("payload"); payloadName != "" {
|
||||
pfield, _ := v.Type().FieldByName(payloadName)
|
||||
if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" {
|
||||
payload := reflect.Indirect(v.FieldByName(payloadName))
|
||||
if payload.IsValid() {
|
||||
switch payload.Interface().(type) {
|
||||
case []byte:
|
||||
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
} else {
|
||||
payload.Set(reflect.ValueOf(b))
|
||||
}
|
||||
case string:
|
||||
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
} else {
|
||||
payload.Set(reflect.ValueOf(string(b)))
|
||||
}
|
||||
default:
|
||||
switch payload.Type().String() {
|
||||
case "io.ReadSeeker":
|
||||
payload.Set(reflect.ValueOf(aws.ReadSeekCloser(r.HTTPResponse.Body)))
|
||||
case "aws.ReadSeekCloser", "io.ReadCloser":
|
||||
payload.Set(reflect.ValueOf(r.HTTPResponse.Body))
|
||||
default:
|
||||
r.Error = fmt.Errorf("unknown payload type %s", payload.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalLocationElements(r *aws.Request, v reflect.Value) {
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
m, field := v.Field(i), v.Type().Field(i)
|
||||
if n := field.Name; n[0:1] == strings.ToLower(n[0:1]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if m.IsValid() {
|
||||
name := field.Tag.Get("locationName")
|
||||
if name == "" {
|
||||
name = field.Name
|
||||
}
|
||||
|
||||
switch field.Tag.Get("location") {
|
||||
case "statusCode":
|
||||
unmarshalStatusCode(m, r.HTTPResponse.StatusCode)
|
||||
case "header":
|
||||
err := unmarshalHeader(m, r.HTTPResponse.Header.Get(name))
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
break
|
||||
}
|
||||
case "headers":
|
||||
prefix := field.Tag.Get("locationName")
|
||||
err := unmarshalHeaderMap(m, r.HTTPResponse.Header, prefix)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.Error != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalStatusCode(v reflect.Value, statusCode int) {
|
||||
if !v.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
switch v.Interface().(type) {
|
||||
case *int64:
|
||||
s := int64(statusCode)
|
||||
v.Set(reflect.ValueOf(&s))
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalHeaderMap(r reflect.Value, headers http.Header, prefix string) error {
|
||||
switch r.Interface().(type) {
|
||||
case *map[string]*string: // we only support string map value types
|
||||
out := map[string]*string{}
|
||||
for k, v := range headers {
|
||||
k = http.CanonicalHeaderKey(k)
|
||||
if strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) {
|
||||
out[k[len(prefix):]] = &v[0]
|
||||
}
|
||||
}
|
||||
r.Set(reflect.ValueOf(&out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalHeader(v reflect.Value, header string) error {
|
||||
if !v.IsValid() || (header == "" && v.Elem().Kind() != reflect.String) {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v.Interface().(type) {
|
||||
case *string:
|
||||
v.Set(reflect.ValueOf(&header))
|
||||
case []byte:
|
||||
b, err := base64.StdEncoding.DecodeString(header)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
v.Set(reflect.ValueOf(&b))
|
||||
}
|
||||
case *bool:
|
||||
b, err := strconv.ParseBool(header)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
v.Set(reflect.ValueOf(&b))
|
||||
}
|
||||
case *int64:
|
||||
i, err := strconv.ParseInt(header, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
v.Set(reflect.ValueOf(&i))
|
||||
}
|
||||
case *float64:
|
||||
f, err := strconv.ParseFloat(header, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
v.Set(reflect.ValueOf(&f))
|
||||
}
|
||||
case *time.Time:
|
||||
t, err := time.Parse(RFC822, header)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
v.Set(reflect.ValueOf(&t))
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
2571
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/restxml/build_test.go
сгенерированный
поставляемый
2571
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/restxml/build_test.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
48
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/restxml/restxml.go
сгенерированный
поставляемый
48
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/restxml/restxml.go
сгенерированный
поставляемый
@@ -1,48 +0,0 @@
|
||||
package restxml
|
||||
|
||||
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/rest-xml.json build_test.go
|
||||
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/output/rest-xml.json unmarshal_test.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/internal/protocol/query"
|
||||
"github.com/awslabs/aws-sdk-go/internal/protocol/rest"
|
||||
"github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil"
|
||||
)
|
||||
|
||||
func Build(r *aws.Request) {
|
||||
rest.Build(r)
|
||||
|
||||
if t := rest.PayloadType(r.Params); t == "structure" || t == "" {
|
||||
var buf bytes.Buffer
|
||||
err := xmlutil.BuildXML(r.Params, xml.NewEncoder(&buf))
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return
|
||||
}
|
||||
r.SetBufferBody(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func Unmarshal(r *aws.Request) {
|
||||
if t := rest.PayloadType(r.Data); t == "structure" || t == "" {
|
||||
defer r.HTTPResponse.Body.Close()
|
||||
decoder := xml.NewDecoder(r.HTTPResponse.Body)
|
||||
err := xmlutil.UnmarshalXML(r.Data, decoder, "")
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalMeta(r *aws.Request) {
|
||||
rest.Unmarshal(r)
|
||||
}
|
||||
|
||||
func UnmarshalError(r *aws.Request) {
|
||||
query.UnmarshalError(r)
|
||||
}
|
||||
1171
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/restxml/unmarshal_test.go
сгенерированный
поставляемый
1171
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/restxml/unmarshal_test.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
262
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil/build.go
сгенерированный
поставляемый
262
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil/build.go
сгенерированный
поставляемый
@@ -1,262 +0,0 @@
|
||||
package xmlutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BuildXML(params interface{}, e *xml.Encoder) error {
|
||||
b := xmlBuilder{encoder: e, namespaces: map[string]string{}}
|
||||
root := NewXMLElement(xml.Name{})
|
||||
if err := b.buildValue(reflect.ValueOf(params), root, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range root.Children {
|
||||
for _, v := range c {
|
||||
return StructToXML(e, v, false)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func elemOf(value reflect.Value) reflect.Value {
|
||||
for value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
type xmlBuilder struct {
|
||||
encoder *xml.Encoder
|
||||
namespaces map[string]string
|
||||
}
|
||||
|
||||
func (b *xmlBuilder) buildValue(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||
value = elemOf(value)
|
||||
if !value.IsValid() { // no need to handle zero values
|
||||
return nil
|
||||
} else if tag.Get("location") != "" { // don't handle non-body location values
|
||||
return nil
|
||||
}
|
||||
|
||||
t := tag.Get("type")
|
||||
if t == "" {
|
||||
switch value.Kind() {
|
||||
case reflect.Struct:
|
||||
t = "structure"
|
||||
case reflect.Slice:
|
||||
t = "list"
|
||||
case reflect.Map:
|
||||
t = "map"
|
||||
}
|
||||
}
|
||||
|
||||
switch t {
|
||||
case "structure":
|
||||
if field, ok := value.Type().FieldByName("SDKShapeTraits"); ok {
|
||||
tag = tag + reflect.StructTag(" ") + field.Tag
|
||||
}
|
||||
return b.buildStruct(value, current, tag)
|
||||
case "list":
|
||||
return b.buildList(value, current, tag)
|
||||
case "map":
|
||||
return b.buildMap(value, current, tag)
|
||||
default:
|
||||
return b.buildScalar(value, current, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *xmlBuilder) buildStruct(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||
if !value.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
fieldAdded := false
|
||||
|
||||
// unwrap payloads
|
||||
if payload := tag.Get("payload"); payload != "" {
|
||||
field, _ := value.Type().FieldByName(payload)
|
||||
tag = field.Tag
|
||||
value = elemOf(value.FieldByName(payload))
|
||||
|
||||
if !value.IsValid() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
child := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
|
||||
|
||||
// there is an xmlNamespace associated with this struct
|
||||
if prefix, uri := tag.Get("xmlPrefix"), tag.Get("xmlURI"); uri != "" {
|
||||
ns := xml.Attr{
|
||||
Name: xml.Name{Local: "xmlns"},
|
||||
Value: uri,
|
||||
}
|
||||
if prefix != "" {
|
||||
b.namespaces[prefix] = uri // register the namespace
|
||||
ns.Name.Local = "xmlns:" + prefix
|
||||
}
|
||||
|
||||
child.Attr = append(child.Attr, ns)
|
||||
}
|
||||
|
||||
t := value.Type()
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
|
||||
member := elemOf(value.Field(i))
|
||||
field := t.Field(i)
|
||||
mTag := field.Tag
|
||||
|
||||
if mTag.Get("location") != "" { // skip non-body members
|
||||
continue
|
||||
}
|
||||
|
||||
memberName := mTag.Get("locationName")
|
||||
if memberName == "" {
|
||||
memberName = field.Name
|
||||
mTag = reflect.StructTag(string(mTag) + ` locationName:"` + memberName + `"`)
|
||||
}
|
||||
if err := b.buildValue(member, child, mTag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldAdded = true
|
||||
}
|
||||
|
||||
if fieldAdded { // only append this child if we have one ore more valid members
|
||||
current.AddChild(child)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *xmlBuilder) buildList(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||
if value.IsNil() { // don't build omitted lists
|
||||
return nil
|
||||
}
|
||||
|
||||
// check for unflattened list member
|
||||
flattened := tag.Get("flattened") != ""
|
||||
|
||||
xname := xml.Name{Local: tag.Get("locationName")}
|
||||
if flattened {
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
child := NewXMLElement(xname)
|
||||
current.AddChild(child)
|
||||
if err := b.buildValue(value.Index(i), child, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list := NewXMLElement(xname)
|
||||
current.AddChild(list)
|
||||
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
iname := tag.Get("locationNameList")
|
||||
if iname == "" {
|
||||
iname = "member"
|
||||
}
|
||||
|
||||
child := NewXMLElement(xml.Name{Local: iname})
|
||||
list.AddChild(child)
|
||||
if err := b.buildValue(value.Index(i), child, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *xmlBuilder) buildMap(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||
if value.IsNil() { // don't build omitted maps
|
||||
return nil
|
||||
}
|
||||
|
||||
maproot := NewXMLElement(xml.Name{Local: tag.Get("locationName")})
|
||||
current.AddChild(maproot)
|
||||
current = maproot
|
||||
|
||||
kname, vname := "key", "value"
|
||||
if n := tag.Get("locationNameKey"); n != "" {
|
||||
kname = n
|
||||
}
|
||||
if n := tag.Get("locationNameValue"); n != "" {
|
||||
vname = n
|
||||
}
|
||||
|
||||
// sorting is not required for compliance, but it makes testing easier
|
||||
keys := make([]string, value.Len())
|
||||
for i, k := range value.MapKeys() {
|
||||
keys[i] = k.String()
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
v := value.MapIndex(reflect.ValueOf(k))
|
||||
fmt.Println(k, v.Interface())
|
||||
|
||||
mapcur := current
|
||||
if tag.Get("flattened") == "" { // add "entry" tag to non-flat maps
|
||||
child := NewXMLElement(xml.Name{Local: "entry"})
|
||||
mapcur.AddChild(child)
|
||||
mapcur = child
|
||||
}
|
||||
|
||||
kchild := NewXMLElement(xml.Name{Local: kname})
|
||||
kchild.Text = k
|
||||
vchild := NewXMLElement(xml.Name{Local: vname})
|
||||
mapcur.AddChild(kchild)
|
||||
mapcur.AddChild(vchild)
|
||||
|
||||
if err := b.buildValue(v, vchild, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *xmlBuilder) buildScalar(value reflect.Value, current *XMLNode, tag reflect.StructTag) error {
|
||||
var str string
|
||||
switch converted := value.Interface().(type) {
|
||||
case string:
|
||||
str = converted
|
||||
case []byte:
|
||||
str = base64.StdEncoding.EncodeToString(converted)
|
||||
case bool:
|
||||
str = strconv.FormatBool(converted)
|
||||
case int64:
|
||||
str = strconv.FormatInt(converted, 10)
|
||||
case int:
|
||||
str = strconv.Itoa(converted)
|
||||
case float64:
|
||||
str = strconv.FormatFloat(converted, 'f', -1, 64)
|
||||
case float32:
|
||||
str = strconv.FormatFloat(float64(converted), 'f', -1, 32)
|
||||
case time.Time:
|
||||
const ISO8601UTC = "2006-01-02T15:04:05Z"
|
||||
str = converted.UTC().Format(ISO8601UTC)
|
||||
default:
|
||||
return fmt.Errorf("unsupported value for param %s: %v (%s)",
|
||||
tag.Get("locationName"), value.Interface(), value.Type().Name())
|
||||
}
|
||||
|
||||
xname := xml.Name{Local: tag.Get("locationName")}
|
||||
if tag.Get("xmlAttribute") != "" { // put into current node's attribute list
|
||||
attr := xml.Attr{Name: xname, Value: str}
|
||||
current.Attr = append(current.Attr, attr)
|
||||
} else { // regular text node
|
||||
current.AddChild(&XMLNode{Name: xname, Text: str})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
251
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil/unmarshal.go
сгенерированный
поставляемый
251
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil/unmarshal.go
сгенерированный
поставляемый
@@ -1,251 +0,0 @@
|
||||
package xmlutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func UnmarshalXML(v interface{}, d *xml.Decoder, wrapper string) error {
|
||||
n, _ := XMLToStruct(d, nil)
|
||||
if n.Children != nil {
|
||||
for _, root := range n.Children {
|
||||
for _, c := range root {
|
||||
if wrappedChild, ok := c.Children[wrapper]; ok {
|
||||
c = wrappedChild[0] // pull out wrapped element
|
||||
}
|
||||
|
||||
err := parse(reflect.ValueOf(v), c, "")
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||
rtype := r.Type()
|
||||
if rtype.Kind() == reflect.Ptr {
|
||||
rtype = rtype.Elem() // check kind of actual element type
|
||||
}
|
||||
|
||||
t := tag.Get("type")
|
||||
if t == "" {
|
||||
switch rtype.Kind() {
|
||||
case reflect.Struct:
|
||||
t = "structure"
|
||||
case reflect.Slice:
|
||||
t = "list"
|
||||
case reflect.Map:
|
||||
t = "map"
|
||||
}
|
||||
}
|
||||
|
||||
switch t {
|
||||
case "structure":
|
||||
if field, ok := rtype.FieldByName("SDKShapeTraits"); ok {
|
||||
tag = field.Tag
|
||||
}
|
||||
return parseStruct(r, node, tag)
|
||||
case "list":
|
||||
return parseList(r, node, tag)
|
||||
case "map":
|
||||
return parseMap(r, node, tag)
|
||||
default:
|
||||
return parseScalar(r, node, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func parseStruct(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||
t := r.Type()
|
||||
if r.Kind() == reflect.Ptr {
|
||||
if r.IsNil() { // create the structure if it's nil
|
||||
s := reflect.New(r.Type().Elem())
|
||||
r.Set(s)
|
||||
r = s
|
||||
}
|
||||
|
||||
r = r.Elem()
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
// unwrap any payloads
|
||||
if payload := tag.Get("payload"); payload != "" {
|
||||
field, _ := t.FieldByName(payload)
|
||||
return parseStruct(r.FieldByName(payload), node, field.Tag)
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if c := field.Name[0:1]; strings.ToLower(c) == c {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
|
||||
// figure out what this field is called
|
||||
name := field.Name
|
||||
if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
|
||||
name = field.Tag.Get("locationNameList")
|
||||
} else if locName := field.Tag.Get("locationName"); locName != "" {
|
||||
name = locName
|
||||
}
|
||||
|
||||
// try to find the field by name in elements
|
||||
elems := node.Children[name]
|
||||
|
||||
if elems == nil { // try to find the field in attributes
|
||||
for _, a := range node.Attr {
|
||||
if name == a.Name.Local {
|
||||
// turn this into a text node for de-serializing
|
||||
elems = []*XMLNode{&XMLNode{Text: a.Value}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
member := r.FieldByName(field.Name)
|
||||
for _, elem := range elems {
|
||||
err := parse(member, elem, field.Tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseList(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||
t := r.Type()
|
||||
|
||||
if tag.Get("flattened") == "" { // look at all item entries
|
||||
mname := "member"
|
||||
if name := tag.Get("locationNameList"); name != "" {
|
||||
mname = name
|
||||
}
|
||||
|
||||
if Children, ok := node.Children[mname]; ok {
|
||||
if r.IsNil() {
|
||||
r.Set(reflect.MakeSlice(t, len(Children), len(Children)))
|
||||
}
|
||||
|
||||
for i, c := range Children {
|
||||
err := parse(r.Index(i), c, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // flattened list means this is a single element
|
||||
if r.IsNil() {
|
||||
r.Set(reflect.MakeSlice(t, 0, 0))
|
||||
}
|
||||
|
||||
childR := reflect.Zero(t.Elem())
|
||||
r.Set(reflect.Append(r, childR))
|
||||
err := parse(r.Index(r.Len()-1), node, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseMap(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||
t := r.Type()
|
||||
if r.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
if r.IsNil() {
|
||||
r.Set(reflect.New(t))
|
||||
r.Elem().Set(reflect.MakeMap(t))
|
||||
}
|
||||
|
||||
r = r.Elem()
|
||||
}
|
||||
|
||||
if tag.Get("flattened") == "" { // look at all child entries
|
||||
for _, entry := range node.Children["entry"] {
|
||||
parseMapEntry(r, entry, tag)
|
||||
}
|
||||
} else { // this element is itself an entry
|
||||
parseMapEntry(r, node, tag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseMapEntry(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||
kname, vname := "key", "value"
|
||||
if n := tag.Get("locationNameKey"); n != "" {
|
||||
kname = n
|
||||
}
|
||||
if n := tag.Get("locationNameValue"); n != "" {
|
||||
vname = n
|
||||
}
|
||||
|
||||
keys, ok := node.Children[kname]
|
||||
values := node.Children[vname]
|
||||
if ok {
|
||||
for i, key := range keys {
|
||||
keyR := reflect.ValueOf(key.Text)
|
||||
value := values[i]
|
||||
valueR := reflect.New(r.Type().Elem()).Elem()
|
||||
|
||||
parse(valueR, value, "")
|
||||
r.SetMapIndex(keyR, valueR)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseScalar(r reflect.Value, node *XMLNode, tag reflect.StructTag) error {
|
||||
switch r.Interface().(type) {
|
||||
case *string:
|
||||
r.Set(reflect.ValueOf(&node.Text))
|
||||
return nil
|
||||
case []byte:
|
||||
b, err := base64.StdEncoding.DecodeString(node.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(reflect.ValueOf(b))
|
||||
case *bool:
|
||||
v, err := strconv.ParseBool(node.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(reflect.ValueOf(&v))
|
||||
case *int64:
|
||||
v, err := strconv.ParseInt(node.Text, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(reflect.ValueOf(&v))
|
||||
case *float64:
|
||||
v, err := strconv.ParseFloat(node.Text, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Set(reflect.ValueOf(&v))
|
||||
case *time.Time:
|
||||
const ISO8601UTC = "2006-01-02T15:04:05Z"
|
||||
t, err := time.Parse(ISO8601UTC, node.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
r.Set(reflect.ValueOf(&t))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported value: %v (%s)", r.Interface(), r.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
100
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil/xml_to_struct.go
сгенерированный
поставляемый
100
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/protocol/xml/xmlutil/xml_to_struct.go
сгенерированный
поставляемый
@@ -1,100 +0,0 @@
|
||||
package xmlutil
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type XMLNode struct {
|
||||
Name xml.Name `json:",omitempty"`
|
||||
Children map[string][]*XMLNode `json:",omitempty"`
|
||||
Text string `json:",omitempty"`
|
||||
Attr []xml.Attr `json:",omitempty"`
|
||||
}
|
||||
|
||||
func NewXMLElement(name xml.Name) *XMLNode {
|
||||
return &XMLNode{
|
||||
Name: name,
|
||||
Children: map[string][]*XMLNode{},
|
||||
Attr: []xml.Attr{},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *XMLNode) AddChild(child *XMLNode) {
|
||||
if _, ok := n.Children[child.Name.Local]; !ok {
|
||||
n.Children[child.Name.Local] = []*XMLNode{}
|
||||
}
|
||||
n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child)
|
||||
}
|
||||
|
||||
func XMLToStruct(d *xml.Decoder, s *xml.StartElement) (*XMLNode, error) {
|
||||
out := &XMLNode{}
|
||||
for {
|
||||
tok, err := d.Token()
|
||||
if tok == nil || err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
switch typed := tok.(type) {
|
||||
case xml.CharData:
|
||||
out.Text = string(typed.Copy())
|
||||
case xml.StartElement:
|
||||
el := typed.Copy()
|
||||
out.Attr = el.Attr
|
||||
if out.Children == nil {
|
||||
out.Children = map[string][]*XMLNode{}
|
||||
}
|
||||
|
||||
name := typed.Name.Local
|
||||
slice := out.Children[name]
|
||||
if slice == nil {
|
||||
slice = []*XMLNode{}
|
||||
}
|
||||
node, e := XMLToStruct(d, &el)
|
||||
if e != nil {
|
||||
return out, e
|
||||
}
|
||||
node.Name = typed.Name
|
||||
slice = append(slice, node)
|
||||
out.Children[name] = slice
|
||||
case xml.EndElement:
|
||||
if s != nil && s.Name.Local == typed.Name.Local { // matching end token
|
||||
return out, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error {
|
||||
e.EncodeToken(xml.StartElement{Name: node.Name, Attr: node.Attr})
|
||||
|
||||
if node.Text != "" {
|
||||
e.EncodeToken(xml.CharData([]byte(node.Text)))
|
||||
} else if sorted {
|
||||
sortedNames := []string{}
|
||||
for k, _ := range node.Children {
|
||||
sortedNames = append(sortedNames, k)
|
||||
}
|
||||
sort.Strings(sortedNames)
|
||||
|
||||
for _, k := range sortedNames {
|
||||
for _, v := range node.Children[k] {
|
||||
StructToXML(e, v, sorted)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, c := range node.Children {
|
||||
for _, v := range c {
|
||||
StructToXML(e, v, sorted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e.EncodeToken(xml.EndElement{Name: node.Name})
|
||||
return e.Flush()
|
||||
}
|
||||
296
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/signer/v4/v4.go
сгенерированный
поставляемый
296
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/signer/v4/v4.go
сгенерированный
поставляемый
@@ -1,296 +0,0 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
)
|
||||
|
||||
const (
|
||||
authHeaderPrefix = "AWS4-HMAC-SHA256"
|
||||
timeFormat = "20060102T150405Z"
|
||||
shortTimeFormat = "20060102"
|
||||
)
|
||||
|
||||
var ignoredHeaders = map[string]bool{
|
||||
"Authorization": true,
|
||||
"Content-Type": true,
|
||||
"Content-Length": true,
|
||||
"User-Agent": true,
|
||||
}
|
||||
|
||||
type signer struct {
|
||||
Request *http.Request
|
||||
Time time.Time
|
||||
ExpireTime time.Duration
|
||||
ServiceName string
|
||||
Region string
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
SessionToken string
|
||||
Query url.Values
|
||||
Body io.ReadSeeker
|
||||
Debug uint
|
||||
Logger io.Writer
|
||||
|
||||
isPresign bool
|
||||
formattedTime string
|
||||
formattedShortTime string
|
||||
|
||||
signedHeaders string
|
||||
canonicalHeaders string
|
||||
canonicalString string
|
||||
credentialString string
|
||||
stringToSign string
|
||||
signature string
|
||||
authorization string
|
||||
}
|
||||
|
||||
// Sign requests with signature version 4.
|
||||
func Sign(req *aws.Request) {
|
||||
creds, err := req.Service.Config.Credentials.Credentials()
|
||||
if err != nil {
|
||||
req.Error = err
|
||||
return
|
||||
}
|
||||
|
||||
s := signer{
|
||||
Request: req.HTTPRequest,
|
||||
Time: req.Time,
|
||||
ExpireTime: req.ExpireTime,
|
||||
Query: req.HTTPRequest.URL.Query(),
|
||||
Body: req.Body,
|
||||
ServiceName: req.Service.ServiceName,
|
||||
Region: req.Service.Config.Region,
|
||||
AccessKeyID: creds.AccessKeyID,
|
||||
SecretAccessKey: creds.SecretAccessKey,
|
||||
SessionToken: creds.SessionToken,
|
||||
Debug: req.Service.Config.LogLevel,
|
||||
Logger: req.Service.Config.Logger,
|
||||
}
|
||||
s.sign()
|
||||
return
|
||||
}
|
||||
|
||||
func (v4 *signer) sign() {
|
||||
if v4.ExpireTime != 0 {
|
||||
v4.isPresign = true
|
||||
}
|
||||
|
||||
if v4.isPresign {
|
||||
v4.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
|
||||
if v4.SessionToken != "" {
|
||||
v4.Query.Set("X-Amz-Security-Token", v4.SessionToken)
|
||||
} else {
|
||||
v4.Query.Del("X-Amz-Security-Token")
|
||||
}
|
||||
} else if v4.SessionToken != "" {
|
||||
v4.Request.Header.Set("X-Amz-Security-Token", v4.SessionToken)
|
||||
}
|
||||
|
||||
v4.build()
|
||||
|
||||
if v4.Debug > 0 {
|
||||
out := v4.Logger
|
||||
fmt.Fprintf(out, "---[ CANONICAL STRING ]-----------------------------\n")
|
||||
fmt.Fprintln(out, v4.canonicalString)
|
||||
fmt.Fprintf(out, "---[ STRING TO SIGN ]--------------------------------\n")
|
||||
fmt.Fprintln(out, v4.stringToSign)
|
||||
fmt.Fprintf(out, "---[ SIGNED URL ]--------------------------------\n")
|
||||
fmt.Fprintln(out, v4.Request.URL)
|
||||
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (v4 *signer) build() {
|
||||
v4.buildTime() // no depends
|
||||
v4.buildCredentialString() // no depends
|
||||
if v4.isPresign {
|
||||
v4.buildQuery() // no depends
|
||||
}
|
||||
v4.buildCanonicalHeaders() // depends on cred string
|
||||
v4.buildCanonicalString() // depends on canon headers / signed headers
|
||||
v4.buildStringToSign() // depends on canon string
|
||||
v4.buildSignature() // depends on string to sign
|
||||
|
||||
if v4.isPresign {
|
||||
v4.Request.URL.RawQuery += "&X-Amz-Signature=" + v4.signature
|
||||
} else {
|
||||
parts := []string{
|
||||
authHeaderPrefix + " Credential=" + v4.AccessKeyID + "/" + v4.credentialString,
|
||||
"SignedHeaders=" + v4.signedHeaders,
|
||||
"Signature=" + v4.signature,
|
||||
}
|
||||
v4.Request.Header.Set("Authorization", strings.Join(parts, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func (v4 *signer) buildTime() {
|
||||
v4.formattedTime = v4.Time.UTC().Format(timeFormat)
|
||||
v4.formattedShortTime = v4.Time.UTC().Format(shortTimeFormat)
|
||||
|
||||
if v4.isPresign {
|
||||
duration := int64(v4.ExpireTime / time.Second)
|
||||
v4.Query.Set("X-Amz-Date", v4.formattedTime)
|
||||
v4.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
|
||||
} else {
|
||||
v4.Request.Header.Set("X-Amz-Date", v4.formattedTime)
|
||||
}
|
||||
}
|
||||
|
||||
func (v4 *signer) buildCredentialString() {
|
||||
v4.credentialString = strings.Join([]string{
|
||||
v4.formattedShortTime,
|
||||
v4.Region,
|
||||
v4.ServiceName,
|
||||
"aws4_request",
|
||||
}, "/")
|
||||
|
||||
if v4.isPresign {
|
||||
v4.Query.Set("X-Amz-Credential", v4.AccessKeyID+"/"+v4.credentialString)
|
||||
}
|
||||
}
|
||||
|
||||
func (v4 *signer) buildQuery() {
|
||||
for k, h := range v4.Request.Header {
|
||||
if strings.HasPrefix(http.CanonicalHeaderKey(k), "X-Amz-") {
|
||||
continue // never hoist x-amz-* headers, they must be signed
|
||||
}
|
||||
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||
continue // never hoist ignored headers
|
||||
}
|
||||
|
||||
v4.Request.Header.Del(k)
|
||||
v4.Query.Del(k)
|
||||
for _, v := range h {
|
||||
v4.Query.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v4 *signer) buildCanonicalHeaders() {
|
||||
headers := make([]string, 0)
|
||||
headers = append(headers, "host")
|
||||
for k, _ := range v4.Request.Header {
|
||||
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||
continue // ignored header
|
||||
}
|
||||
headers = append(headers, strings.ToLower(k))
|
||||
}
|
||||
sort.Strings(headers)
|
||||
|
||||
v4.signedHeaders = strings.Join(headers, ";")
|
||||
|
||||
if v4.isPresign {
|
||||
v4.Query.Set("X-Amz-SignedHeaders", v4.signedHeaders)
|
||||
}
|
||||
|
||||
headerValues := make([]string, len(headers))
|
||||
for i, k := range headers {
|
||||
if k == "host" {
|
||||
headerValues[i] = "host:" + v4.Request.URL.Host
|
||||
} else {
|
||||
headerValues[i] = k + ":" +
|
||||
strings.Join(v4.Request.Header[http.CanonicalHeaderKey(k)], ",")
|
||||
}
|
||||
}
|
||||
|
||||
v4.canonicalHeaders = strings.Join(headerValues, "\n")
|
||||
}
|
||||
|
||||
func (v4 *signer) buildCanonicalString() {
|
||||
v4.Request.URL.RawQuery = v4.Query.Encode()
|
||||
uri := v4.Request.URL.Opaque
|
||||
if uri != "" {
|
||||
uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
|
||||
} else {
|
||||
uri = v4.Request.URL.Path
|
||||
}
|
||||
if uri == "" {
|
||||
uri = "/"
|
||||
}
|
||||
|
||||
v4.canonicalString = strings.Join([]string{
|
||||
v4.Request.Method,
|
||||
uri,
|
||||
v4.Request.URL.RawQuery,
|
||||
v4.canonicalHeaders + "\n",
|
||||
v4.signedHeaders,
|
||||
v4.bodyDigest(),
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func (v4 *signer) buildStringToSign() {
|
||||
v4.stringToSign = strings.Join([]string{
|
||||
authHeaderPrefix,
|
||||
v4.formattedTime,
|
||||
v4.credentialString,
|
||||
hex.EncodeToString(makeSha256([]byte(v4.canonicalString))),
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func (v4 *signer) buildSignature() {
|
||||
secret := v4.SecretAccessKey
|
||||
date := makeHmac([]byte("AWS4"+secret), []byte(v4.formattedShortTime))
|
||||
region := makeHmac(date, []byte(v4.Region))
|
||||
service := makeHmac(region, []byte(v4.ServiceName))
|
||||
credentials := makeHmac(service, []byte("aws4_request"))
|
||||
signature := makeHmac(credentials, []byte(v4.stringToSign))
|
||||
v4.signature = hex.EncodeToString(signature)
|
||||
}
|
||||
|
||||
func (v4 *signer) bodyDigest() string {
|
||||
hash := v4.Request.Header.Get("X-Amz-Content-Sha256")
|
||||
if hash == "" {
|
||||
if v4.isPresign && v4.ServiceName == "s3" {
|
||||
hash = "UNSIGNED-PAYLOAD"
|
||||
} else if v4.Body == nil {
|
||||
hash = hex.EncodeToString(makeSha256([]byte{}))
|
||||
} else {
|
||||
hash = hex.EncodeToString(makeSha256Reader(v4.Body))
|
||||
}
|
||||
v4.Request.Header.Add("X-Amz-Content-Sha256", hash)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
func makeHmac(key []byte, data []byte) []byte {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
func makeSha256(data []byte) []byte {
|
||||
hash := sha256.New()
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
func makeSha256Reader(reader io.ReadSeeker) []byte {
|
||||
packet := make([]byte, 4096)
|
||||
hash := sha256.New()
|
||||
|
||||
reader.Seek(0, 0)
|
||||
for {
|
||||
n, err := reader.Read(packet)
|
||||
if n > 0 {
|
||||
hash.Write(packet[0:n])
|
||||
}
|
||||
if err == io.EOF || n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
reader.Seek(0, 0)
|
||||
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
89
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/signer/v4/v4_test.go
сгенерированный
поставляемый
89
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/internal/signer/v4/v4_test.go
сгенерированный
поставляемый
@@ -1,89 +0,0 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func buildSigner(serviceName string, region string, signTime time.Time, expireTime time.Duration, body string) signer {
|
||||
endpoint := "https://" + serviceName + "." + region + ".amazonaws.com"
|
||||
reader := strings.NewReader(body)
|
||||
req, _ := http.NewRequest("POST", endpoint, reader)
|
||||
req.URL.Opaque = "//example.org/bucket/key-._~,!@#$%^&*()"
|
||||
req.Header.Add("X-Amz-Target", "prefix.Operation")
|
||||
req.Header.Add("Content-Type", "application/x-amz-json-1.0")
|
||||
req.Header.Add("Content-Length", string(len(body)))
|
||||
req.Header.Add("X-Amz-Meta-Other-Header", "some-value=!@#$%^&* ()")
|
||||
|
||||
return signer{
|
||||
Request: req,
|
||||
Time: signTime,
|
||||
ExpireTime: expireTime,
|
||||
Query: req.URL.Query(),
|
||||
Body: reader,
|
||||
ServiceName: serviceName,
|
||||
Region: region,
|
||||
AccessKeyID: "AKID",
|
||||
SecretAccessKey: "SECRET",
|
||||
SessionToken: "SESSION",
|
||||
}
|
||||
}
|
||||
|
||||
func removeWS(text string) string {
|
||||
text = strings.Replace(text, " ", "", -1)
|
||||
text = strings.Replace(text, "\n", "", -1)
|
||||
text = strings.Replace(text, "\t", "", -1)
|
||||
return text
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, expected, given string) {
|
||||
if removeWS(expected) != removeWS(given) {
|
||||
t.Errorf("\nExpected: %s\nGiven: %s", expected, given)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPresignRequest(t *testing.T) {
|
||||
signer := buildSigner("dynamodb", "us-east-1", time.Unix(0, 0), 300*time.Second, "{}")
|
||||
signer.sign()
|
||||
|
||||
expectedDate := "19700101T000000Z"
|
||||
expectedHeaders := "host;x-amz-meta-other-header;x-amz-target"
|
||||
expectedSig := "41c18d68f9191079dfeead4e3f034328f89d86c79f8e9d51dd48bb70eaf623fc"
|
||||
expectedCred := "AKID/19700101/us-east-1/dynamodb/aws4_request"
|
||||
|
||||
q := signer.Request.URL.Query()
|
||||
assert.Equal(t, expectedSig, q.Get("X-Amz-Signature"))
|
||||
assert.Equal(t, expectedCred, q.Get("X-Amz-Credential"))
|
||||
assert.Equal(t, expectedHeaders, q.Get("X-Amz-SignedHeaders"))
|
||||
assert.Equal(t, expectedDate, q.Get("X-Amz-Date"))
|
||||
}
|
||||
|
||||
func TestSignRequest(t *testing.T) {
|
||||
signer := buildSigner("dynamodb", "us-east-1", time.Unix(0, 0), 0, "{}")
|
||||
signer.sign()
|
||||
|
||||
expectedDate := "19700101T000000Z"
|
||||
expectedSig := "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-meta-other-header;x-amz-security-token;x-amz-target, Signature=0196959cabd964bd10c05217b40ed151882dd394190438bab0c658dafdbff7a1"
|
||||
|
||||
q := signer.Request.Header
|
||||
assert.Equal(t, expectedSig, q.Get("Authorization"))
|
||||
assert.Equal(t, expectedDate, q.Get("X-Amz-Date"))
|
||||
}
|
||||
|
||||
func BenchmarkPresignRequest(b *testing.B) {
|
||||
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 300*time.Second, "{}")
|
||||
for i := 0; i < b.N; i++ {
|
||||
signer.sign()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSignRequest(b *testing.B) {
|
||||
signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "{}")
|
||||
for i := 0; i < b.N; i++ {
|
||||
signer.sign()
|
||||
}
|
||||
}
|
||||
2738
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/api.go
сгенерированный
поставляемый
2738
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/api.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
20
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/customizations.go
сгенерированный
поставляемый
20
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/customizations.go
сгенерированный
поставляемый
@@ -1,20 +0,0 @@
|
||||
package route53
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initService = func(s *aws.Service) {
|
||||
s.Handlers.Build.PushBack(sanitizeURL)
|
||||
}
|
||||
}
|
||||
|
||||
var reSanitizeURL = regexp.MustCompile(`\/%2F\w+%2F`)
|
||||
|
||||
func sanitizeURL(r *aws.Request) {
|
||||
r.HTTPRequest.URL.Opaque =
|
||||
reSanitizeURL.ReplaceAllString(r.HTTPRequest.URL.Opaque, "/")
|
||||
}
|
||||
20
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/customizations_test.go
сгенерированный
поставляемый
20
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/customizations_test.go
сгенерированный
поставляемый
@@ -1,20 +0,0 @@
|
||||
package route53_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/internal/util/utilassert"
|
||||
"github.com/awslabs/aws-sdk-go/service/route53"
|
||||
)
|
||||
|
||||
func TestBuildCorrectURI(t *testing.T) {
|
||||
svc := route53.New(nil)
|
||||
req, _ := svc.GetHostedZoneRequest(&route53.GetHostedZoneInput{
|
||||
ID: aws.String("/hostedzone/ABCDEFG"),
|
||||
})
|
||||
|
||||
req.Build()
|
||||
|
||||
utilassert.Match(t, `\/hostedzone\/ABCDEFG$`, req.HTTPRequest.URL.String())
|
||||
}
|
||||
714
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/examples_test.go
сгенерированный
поставляемый
714
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/examples_test.go
сгенерированный
поставляемый
@@ -1,714 +0,0 @@
|
||||
package route53_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/aws/awsutil"
|
||||
"github.com/awslabs/aws-sdk-go/service/route53"
|
||||
)
|
||||
|
||||
var _ time.Duration
|
||||
var _ bytes.Buffer
|
||||
|
||||
func ExampleRoute53_AssociateVPCWithHostedZone() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.AssociateVPCWithHostedZoneInput{
|
||||
HostedZoneID: aws.String("ResourceId"), // Required
|
||||
VPC: &route53.VPC{ // Required
|
||||
VPCID: aws.String("VPCId"),
|
||||
VPCRegion: aws.String("VPCRegion"),
|
||||
},
|
||||
Comment: aws.String("AssociateVPCComment"),
|
||||
}
|
||||
resp, err := svc.AssociateVPCWithHostedZone(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ChangeResourceRecordSets() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ChangeResourceRecordSetsInput{
|
||||
ChangeBatch: &route53.ChangeBatch{ // Required
|
||||
Changes: []*route53.Change{ // Required
|
||||
&route53.Change{ // Required
|
||||
Action: aws.String("ChangeAction"), // Required
|
||||
ResourceRecordSet: &route53.ResourceRecordSet{ // Required
|
||||
Name: aws.String("DNSName"), // Required
|
||||
Type: aws.String("RRType"), // Required
|
||||
AliasTarget: &route53.AliasTarget{
|
||||
DNSName: aws.String("DNSName"), // Required
|
||||
EvaluateTargetHealth: aws.Boolean(true), // Required
|
||||
HostedZoneID: aws.String("ResourceId"), // Required
|
||||
},
|
||||
Failover: aws.String("ResourceRecordSetFailover"),
|
||||
GeoLocation: &route53.GeoLocation{
|
||||
ContinentCode: aws.String("GeoLocationContinentCode"),
|
||||
CountryCode: aws.String("GeoLocationCountryCode"),
|
||||
SubdivisionCode: aws.String("GeoLocationSubdivisionCode"),
|
||||
},
|
||||
HealthCheckID: aws.String("HealthCheckId"),
|
||||
Region: aws.String("ResourceRecordSetRegion"),
|
||||
ResourceRecords: []*route53.ResourceRecord{
|
||||
&route53.ResourceRecord{ // Required
|
||||
Value: aws.String("RData"), // Required
|
||||
},
|
||||
// More values...
|
||||
},
|
||||
SetIdentifier: aws.String("ResourceRecordSetIdentifier"),
|
||||
TTL: aws.Long(1),
|
||||
Weight: aws.Long(1),
|
||||
},
|
||||
},
|
||||
// More values...
|
||||
},
|
||||
Comment: aws.String("ResourceDescription"),
|
||||
},
|
||||
HostedZoneID: aws.String("ResourceId"), // Required
|
||||
}
|
||||
resp, err := svc.ChangeResourceRecordSets(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ChangeTagsForResource() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ChangeTagsForResourceInput{
|
||||
ResourceID: aws.String("TagResourceId"), // Required
|
||||
ResourceType: aws.String("TagResourceType"), // Required
|
||||
AddTags: []*route53.Tag{
|
||||
&route53.Tag{ // Required
|
||||
Key: aws.String("TagKey"),
|
||||
Value: aws.String("TagValue"),
|
||||
},
|
||||
// More values...
|
||||
},
|
||||
RemoveTagKeys: []*string{
|
||||
aws.String("TagKey"), // Required
|
||||
// More values...
|
||||
},
|
||||
}
|
||||
resp, err := svc.ChangeTagsForResource(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_CreateHealthCheck() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.CreateHealthCheckInput{
|
||||
CallerReference: aws.String("HealthCheckNonce"), // Required
|
||||
HealthCheckConfig: &route53.HealthCheckConfig{ // Required
|
||||
Type: aws.String("HealthCheckType"), // Required
|
||||
FailureThreshold: aws.Long(1),
|
||||
FullyQualifiedDomainName: aws.String("FullyQualifiedDomainName"),
|
||||
IPAddress: aws.String("IPAddress"),
|
||||
Port: aws.Long(1),
|
||||
RequestInterval: aws.Long(1),
|
||||
ResourcePath: aws.String("ResourcePath"),
|
||||
SearchString: aws.String("SearchString"),
|
||||
},
|
||||
}
|
||||
resp, err := svc.CreateHealthCheck(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_CreateHostedZone() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.CreateHostedZoneInput{
|
||||
CallerReference: aws.String("Nonce"), // Required
|
||||
Name: aws.String("DNSName"), // Required
|
||||
DelegationSetID: aws.String("ResourceId"),
|
||||
HostedZoneConfig: &route53.HostedZoneConfig{
|
||||
Comment: aws.String("ResourceDescription"),
|
||||
PrivateZone: aws.Boolean(true),
|
||||
},
|
||||
VPC: &route53.VPC{
|
||||
VPCID: aws.String("VPCId"),
|
||||
VPCRegion: aws.String("VPCRegion"),
|
||||
},
|
||||
}
|
||||
resp, err := svc.CreateHostedZone(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_CreateReusableDelegationSet() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.CreateReusableDelegationSetInput{
|
||||
CallerReference: aws.String("Nonce"), // Required
|
||||
HostedZoneID: aws.String("ResourceId"),
|
||||
}
|
||||
resp, err := svc.CreateReusableDelegationSet(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_DeleteHealthCheck() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.DeleteHealthCheckInput{
|
||||
HealthCheckID: aws.String("HealthCheckId"), // Required
|
||||
}
|
||||
resp, err := svc.DeleteHealthCheck(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_DeleteHostedZone() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.DeleteHostedZoneInput{
|
||||
ID: aws.String("ResourceId"), // Required
|
||||
}
|
||||
resp, err := svc.DeleteHostedZone(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_DeleteReusableDelegationSet() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.DeleteReusableDelegationSetInput{
|
||||
ID: aws.String("ResourceId"), // Required
|
||||
}
|
||||
resp, err := svc.DeleteReusableDelegationSet(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_DisassociateVPCFromHostedZone() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.DisassociateVPCFromHostedZoneInput{
|
||||
HostedZoneID: aws.String("ResourceId"), // Required
|
||||
VPC: &route53.VPC{ // Required
|
||||
VPCID: aws.String("VPCId"),
|
||||
VPCRegion: aws.String("VPCRegion"),
|
||||
},
|
||||
Comment: aws.String("DisassociateVPCComment"),
|
||||
}
|
||||
resp, err := svc.DisassociateVPCFromHostedZone(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetChange() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.GetChangeInput{
|
||||
ID: aws.String("ResourceId"), // Required
|
||||
}
|
||||
resp, err := svc.GetChange(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetCheckerIPRanges() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
var params *route53.GetCheckerIPRangesInput
|
||||
resp, err := svc.GetCheckerIPRanges(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetGeoLocation() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.GetGeoLocationInput{
|
||||
ContinentCode: aws.String("GeoLocationContinentCode"),
|
||||
CountryCode: aws.String("GeoLocationCountryCode"),
|
||||
SubdivisionCode: aws.String("GeoLocationSubdivisionCode"),
|
||||
}
|
||||
resp, err := svc.GetGeoLocation(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetHealthCheck() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.GetHealthCheckInput{
|
||||
HealthCheckID: aws.String("HealthCheckId"), // Required
|
||||
}
|
||||
resp, err := svc.GetHealthCheck(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetHealthCheckCount() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
var params *route53.GetHealthCheckCountInput
|
||||
resp, err := svc.GetHealthCheckCount(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetHealthCheckLastFailureReason() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.GetHealthCheckLastFailureReasonInput{
|
||||
HealthCheckID: aws.String("HealthCheckId"), // Required
|
||||
}
|
||||
resp, err := svc.GetHealthCheckLastFailureReason(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetHealthCheckStatus() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.GetHealthCheckStatusInput{
|
||||
HealthCheckID: aws.String("HealthCheckId"), // Required
|
||||
}
|
||||
resp, err := svc.GetHealthCheckStatus(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetHostedZone() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.GetHostedZoneInput{
|
||||
ID: aws.String("ResourceId"), // Required
|
||||
}
|
||||
resp, err := svc.GetHostedZone(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetHostedZoneCount() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
var params *route53.GetHostedZoneCountInput
|
||||
resp, err := svc.GetHostedZoneCount(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_GetReusableDelegationSet() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.GetReusableDelegationSetInput{
|
||||
ID: aws.String("ResourceId"), // Required
|
||||
}
|
||||
resp, err := svc.GetReusableDelegationSet(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListGeoLocations() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListGeoLocationsInput{
|
||||
MaxItems: aws.String("PageMaxItems"),
|
||||
StartContinentCode: aws.String("GeoLocationContinentCode"),
|
||||
StartCountryCode: aws.String("GeoLocationCountryCode"),
|
||||
StartSubdivisionCode: aws.String("GeoLocationSubdivisionCode"),
|
||||
}
|
||||
resp, err := svc.ListGeoLocations(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListHealthChecks() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListHealthChecksInput{
|
||||
Marker: aws.String("PageMarker"),
|
||||
MaxItems: aws.String("PageMaxItems"),
|
||||
}
|
||||
resp, err := svc.ListHealthChecks(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListHostedZones() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListHostedZonesInput{
|
||||
DelegationSetID: aws.String("ResourceId"),
|
||||
Marker: aws.String("PageMarker"),
|
||||
MaxItems: aws.String("PageMaxItems"),
|
||||
}
|
||||
resp, err := svc.ListHostedZones(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListHostedZonesByName() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListHostedZonesByNameInput{
|
||||
DNSName: aws.String("DNSName"),
|
||||
HostedZoneID: aws.String("ResourceId"),
|
||||
MaxItems: aws.String("PageMaxItems"),
|
||||
}
|
||||
resp, err := svc.ListHostedZonesByName(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListResourceRecordSets() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListResourceRecordSetsInput{
|
||||
HostedZoneID: aws.String("ResourceId"), // Required
|
||||
MaxItems: aws.String("PageMaxItems"),
|
||||
StartRecordIdentifier: aws.String("ResourceRecordSetIdentifier"),
|
||||
StartRecordName: aws.String("DNSName"),
|
||||
StartRecordType: aws.String("RRType"),
|
||||
}
|
||||
resp, err := svc.ListResourceRecordSets(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListReusableDelegationSets() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListReusableDelegationSetsInput{
|
||||
Marker: aws.String("PageMarker"),
|
||||
MaxItems: aws.String("PageMaxItems"),
|
||||
}
|
||||
resp, err := svc.ListReusableDelegationSets(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListTagsForResource() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListTagsForResourceInput{
|
||||
ResourceID: aws.String("TagResourceId"), // Required
|
||||
ResourceType: aws.String("TagResourceType"), // Required
|
||||
}
|
||||
resp, err := svc.ListTagsForResource(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_ListTagsForResources() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.ListTagsForResourcesInput{
|
||||
ResourceIDs: []*string{ // Required
|
||||
aws.String("TagResourceId"), // Required
|
||||
// More values...
|
||||
},
|
||||
ResourceType: aws.String("TagResourceType"), // Required
|
||||
}
|
||||
resp, err := svc.ListTagsForResources(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_UpdateHealthCheck() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.UpdateHealthCheckInput{
|
||||
HealthCheckID: aws.String("HealthCheckId"), // Required
|
||||
FailureThreshold: aws.Long(1),
|
||||
FullyQualifiedDomainName: aws.String("FullyQualifiedDomainName"),
|
||||
HealthCheckVersion: aws.Long(1),
|
||||
IPAddress: aws.String("IPAddress"),
|
||||
Port: aws.Long(1),
|
||||
ResourcePath: aws.String("ResourcePath"),
|
||||
SearchString: aws.String("SearchString"),
|
||||
}
|
||||
resp, err := svc.UpdateHealthCheck(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
|
||||
func ExampleRoute53_UpdateHostedZoneComment() {
|
||||
svc := route53.New(nil)
|
||||
|
||||
params := &route53.UpdateHostedZoneCommentInput{
|
||||
ID: aws.String("ResourceId"), // Required
|
||||
Comment: aws.String("ResourceDescription"),
|
||||
}
|
||||
resp, err := svc.UpdateHostedZoneComment(params)
|
||||
|
||||
if awserr := aws.Error(err); awserr != nil {
|
||||
// A service error occurred.
|
||||
fmt.Println("Error:", awserr.Code, awserr.Message)
|
||||
} else if err != nil {
|
||||
// A non-service error occurred.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Pretty-print the response data.
|
||||
fmt.Println(awsutil.StringValue(resp))
|
||||
}
|
||||
59
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/service.go
сгенерированный
поставляемый
59
Godeps/_workspace/src/github.com/awslabs/aws-sdk-go/service/route53/service.go
сгенерированный
поставляемый
@@ -1,59 +0,0 @@
|
||||
package route53
|
||||
|
||||
import (
|
||||
"github.com/awslabs/aws-sdk-go/aws"
|
||||
"github.com/awslabs/aws-sdk-go/internal/protocol/restxml"
|
||||
"github.com/awslabs/aws-sdk-go/internal/signer/v4"
|
||||
)
|
||||
|
||||
// Route53 is a client for Route 53.
|
||||
type Route53 struct {
|
||||
*aws.Service
|
||||
}
|
||||
|
||||
// Used for custom service initialization logic
|
||||
var initService func(*aws.Service)
|
||||
|
||||
// Used for custom request initialization logic
|
||||
var initRequest func(*aws.Request)
|
||||
|
||||
// New returns a new Route53 client.
|
||||
func New(config *aws.Config) *Route53 {
|
||||
if config == nil {
|
||||
config = &aws.Config{}
|
||||
}
|
||||
|
||||
service := &aws.Service{
|
||||
Config: aws.DefaultConfig.Merge(config),
|
||||
ServiceName: "route53",
|
||||
APIVersion: "2013-04-01",
|
||||
}
|
||||
service.Initialize()
|
||||
|
||||
// Handlers
|
||||
service.Handlers.Sign.PushBack(v4.Sign)
|
||||
service.Handlers.Build.PushBack(restxml.Build)
|
||||
service.Handlers.Unmarshal.PushBack(restxml.Unmarshal)
|
||||
service.Handlers.UnmarshalMeta.PushBack(restxml.UnmarshalMeta)
|
||||
service.Handlers.UnmarshalError.PushBack(restxml.UnmarshalError)
|
||||
|
||||
// Run custom service initialization if present
|
||||
if initService != nil {
|
||||
initService(service)
|
||||
}
|
||||
|
||||
return &Route53{service}
|
||||
}
|
||||
|
||||
// newRequest creates a new request for a Route53 operation and runs any
|
||||
// custom request initialization.
|
||||
func (c *Route53) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
|
||||
req := aws.NewRequest(c.Service, op, params, data)
|
||||
|
||||
// Run custom request initialization if present
|
||||
if initRequest != nil {
|
||||
initRequest(req)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
54
Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go
сгенерированный
поставляемый
Обычный файл
54
Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,54 @@
|
||||
// Copyright 2014 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
WatchState = 1 << iota
|
||||
MultiState
|
||||
SubscribeState
|
||||
MonitorState
|
||||
)
|
||||
|
||||
type CommandInfo struct {
|
||||
Set, Clear int
|
||||
}
|
||||
|
||||
var commandInfos = map[string]CommandInfo{
|
||||
"WATCH": {Set: WatchState},
|
||||
"UNWATCH": {Clear: WatchState},
|
||||
"MULTI": {Set: MultiState},
|
||||
"EXEC": {Clear: WatchState | MultiState},
|
||||
"DISCARD": {Clear: WatchState | MultiState},
|
||||
"PSUBSCRIBE": {Set: SubscribeState},
|
||||
"SUBSCRIBE": {Set: SubscribeState},
|
||||
"MONITOR": {Set: MonitorState},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for n, ci := range commandInfos {
|
||||
commandInfos[strings.ToLower(n)] = ci
|
||||
}
|
||||
}
|
||||
|
||||
func LookupCommandInfo(commandName string) CommandInfo {
|
||||
if ci, ok := commandInfos[commandName]; ok {
|
||||
return ci
|
||||
}
|
||||
return commandInfos[strings.ToUpper(commandName)]
|
||||
}
|
||||
27
Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo_test.go
сгенерированный
поставляемый
Обычный файл
27
Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,27 @@
|
||||
package internal
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLookupCommandInfo(t *testing.T) {
|
||||
for _, n := range []string{"watch", "WATCH", "wAtch"} {
|
||||
if LookupCommandInfo(n) == (CommandInfo{}) {
|
||||
t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, c := range names {
|
||||
LookupCommandInfo(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
|
||||
benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR")
|
||||
}
|
||||
|
||||
func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {
|
||||
benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR")
|
||||
}
|
||||
65
Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go
сгенерированный
поставляемый
Обычный файл
65
Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,65 @@
|
||||
// Copyright 2014 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package redistest contains utilities for writing Redigo tests.
|
||||
package redistest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type testConn struct {
|
||||
redis.Conn
|
||||
}
|
||||
|
||||
func (t testConn) Close() error {
|
||||
_, err := t.Conn.Do("SELECT", "9")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
_, err = t.Conn.Do("FLUSHDB")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return t.Conn.Close()
|
||||
}
|
||||
|
||||
// Dial dials the local Redis server and selects database 9. To prevent
|
||||
// stomping on real data, DialTestDB fails if database 9 contains data. The
|
||||
// returned connection flushes database 9 on close.
|
||||
func Dial() (redis.Conn, error) {
|
||||
c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = c.Do("SELECT", "9")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n, err := redis.Int(c.Do("DBSIZE"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n != 0 {
|
||||
return nil, errors.New("database #9 is not empty, test can not continue")
|
||||
}
|
||||
|
||||
return testConn{c}, nil
|
||||
}
|
||||
455
Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go
сгенерированный
поставляемый
Обычный файл
455
Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,455 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// conn is the low-level implementation of Conn
|
||||
type conn struct {
|
||||
|
||||
// Shared
|
||||
mu sync.Mutex
|
||||
pending int
|
||||
err error
|
||||
conn net.Conn
|
||||
|
||||
// Read
|
||||
readTimeout time.Duration
|
||||
br *bufio.Reader
|
||||
|
||||
// Write
|
||||
writeTimeout time.Duration
|
||||
bw *bufio.Writer
|
||||
|
||||
// Scratch space for formatting argument length.
|
||||
// '*' or '$', length, "\r\n"
|
||||
lenScratch [32]byte
|
||||
|
||||
// Scratch space for formatting integers and floats.
|
||||
numScratch [40]byte
|
||||
}
|
||||
|
||||
// Dial connects to the Redis server at the given network and address.
|
||||
func Dial(network, address string) (Conn, error) {
|
||||
dialer := xDialer{}
|
||||
return dialer.Dial(network, address)
|
||||
}
|
||||
|
||||
// DialTimeout acts like Dial but takes timeouts for establishing the
|
||||
// connection to the server, writing a command and reading a reply.
|
||||
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
|
||||
netDialer := net.Dialer{Timeout: connectTimeout}
|
||||
dialer := xDialer{
|
||||
NetDial: netDialer.Dial,
|
||||
ReadTimeout: readTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
}
|
||||
return dialer.Dial(network, address)
|
||||
}
|
||||
|
||||
// A Dialer specifies options for connecting to a Redis server.
|
||||
type xDialer struct {
|
||||
// NetDial specifies the dial function for creating TCP connections. If
|
||||
// NetDial is nil, then net.Dial is used.
|
||||
NetDial func(network, addr string) (net.Conn, error)
|
||||
|
||||
// ReadTimeout specifies the timeout for reading a single command
|
||||
// reply. If ReadTimeout is zero, then no timeout is used.
|
||||
ReadTimeout time.Duration
|
||||
|
||||
// WriteTimeout specifies the timeout for writing a single command. If
|
||||
// WriteTimeout is zero, then no timeout is used.
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
|
||||
// Dial connects to the Redis server at address on the named network.
|
||||
func (d *xDialer) Dial(network, address string) (Conn, error) {
|
||||
dial := d.NetDial
|
||||
if dial == nil {
|
||||
dial = net.Dial
|
||||
}
|
||||
netConn, err := dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &conn{
|
||||
conn: netConn,
|
||||
bw: bufio.NewWriter(netConn),
|
||||
br: bufio.NewReader(netConn),
|
||||
readTimeout: d.ReadTimeout,
|
||||
writeTimeout: d.WriteTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewConn returns a new Redigo connection for the given net connection.
|
||||
func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
|
||||
return &conn{
|
||||
conn: netConn,
|
||||
bw: bufio.NewWriter(netConn),
|
||||
br: bufio.NewReader(netConn),
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
c.mu.Lock()
|
||||
err := c.err
|
||||
if c.err == nil {
|
||||
c.err = errors.New("redigo: closed")
|
||||
err = c.conn.Close()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) fatal(err error) error {
|
||||
c.mu.Lock()
|
||||
if c.err == nil {
|
||||
c.err = err
|
||||
// Close connection to force errors on subsequent calls and to unblock
|
||||
// other reader or writer.
|
||||
c.conn.Close()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) Err() error {
|
||||
c.mu.Lock()
|
||||
err := c.err
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeLen(prefix byte, n int) error {
|
||||
c.lenScratch[len(c.lenScratch)-1] = '\n'
|
||||
c.lenScratch[len(c.lenScratch)-2] = '\r'
|
||||
i := len(c.lenScratch) - 3
|
||||
for {
|
||||
c.lenScratch[i] = byte('0' + n%10)
|
||||
i -= 1
|
||||
n = n / 10
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.lenScratch[i] = prefix
|
||||
_, err := c.bw.Write(c.lenScratch[i:])
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeString(s string) error {
|
||||
c.writeLen('$', len(s))
|
||||
c.bw.WriteString(s)
|
||||
_, err := c.bw.WriteString("\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeBytes(p []byte) error {
|
||||
c.writeLen('$', len(p))
|
||||
c.bw.Write(p)
|
||||
_, err := c.bw.WriteString("\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) writeInt64(n int64) error {
|
||||
return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
|
||||
}
|
||||
|
||||
func (c *conn) writeFloat64(n float64) error {
|
||||
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
||||
}
|
||||
|
||||
func (c *conn) writeCommand(cmd string, args []interface{}) (err error) {
|
||||
c.writeLen('*', 1+len(args))
|
||||
err = c.writeString(cmd)
|
||||
for _, arg := range args {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch arg := arg.(type) {
|
||||
case string:
|
||||
err = c.writeString(arg)
|
||||
case []byte:
|
||||
err = c.writeBytes(arg)
|
||||
case int:
|
||||
err = c.writeInt64(int64(arg))
|
||||
case int64:
|
||||
err = c.writeInt64(arg)
|
||||
case float64:
|
||||
err = c.writeFloat64(arg)
|
||||
case bool:
|
||||
if arg {
|
||||
err = c.writeString("1")
|
||||
} else {
|
||||
err = c.writeString("0")
|
||||
}
|
||||
case nil:
|
||||
err = c.writeString("")
|
||||
default:
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, arg)
|
||||
err = c.writeBytes(buf.Bytes())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type protocolError string
|
||||
|
||||
func (pe protocolError) Error() string {
|
||||
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
|
||||
}
|
||||
|
||||
func (c *conn) readLine() ([]byte, error) {
|
||||
p, err := c.br.ReadSlice('\n')
|
||||
if err == bufio.ErrBufferFull {
|
||||
return nil, protocolError("long response line")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := len(p) - 2
|
||||
if i < 0 || p[i] != '\r' {
|
||||
return nil, protocolError("bad response line terminator")
|
||||
}
|
||||
return p[:i], nil
|
||||
}
|
||||
|
||||
// parseLen parses bulk string and array lengths.
|
||||
func parseLen(p []byte) (int, error) {
|
||||
if len(p) == 0 {
|
||||
return -1, protocolError("malformed length")
|
||||
}
|
||||
|
||||
if p[0] == '-' && len(p) == 2 && p[1] == '1' {
|
||||
// handle $-1 and $-1 null replies.
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
var n int
|
||||
for _, b := range p {
|
||||
n *= 10
|
||||
if b < '0' || b > '9' {
|
||||
return -1, protocolError("illegal bytes in length")
|
||||
}
|
||||
n += int(b - '0')
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// parseInt parses an integer reply.
|
||||
func parseInt(p []byte) (interface{}, error) {
|
||||
if len(p) == 0 {
|
||||
return 0, protocolError("malformed integer")
|
||||
}
|
||||
|
||||
var negate bool
|
||||
if p[0] == '-' {
|
||||
negate = true
|
||||
p = p[1:]
|
||||
if len(p) == 0 {
|
||||
return 0, protocolError("malformed integer")
|
||||
}
|
||||
}
|
||||
|
||||
var n int64
|
||||
for _, b := range p {
|
||||
n *= 10
|
||||
if b < '0' || b > '9' {
|
||||
return 0, protocolError("illegal bytes in length")
|
||||
}
|
||||
n += int64(b - '0')
|
||||
}
|
||||
|
||||
if negate {
|
||||
n = -n
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
var (
|
||||
okReply interface{} = "OK"
|
||||
pongReply interface{} = "PONG"
|
||||
)
|
||||
|
||||
func (c *conn) readReply() (interface{}, error) {
|
||||
line, err := c.readLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(line) == 0 {
|
||||
return nil, protocolError("short response line")
|
||||
}
|
||||
switch line[0] {
|
||||
case '+':
|
||||
switch {
|
||||
case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
|
||||
// Avoid allocation for frequent "+OK" response.
|
||||
return okReply, nil
|
||||
case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
|
||||
// Avoid allocation in PING command benchmarks :)
|
||||
return pongReply, nil
|
||||
default:
|
||||
return string(line[1:]), nil
|
||||
}
|
||||
case '-':
|
||||
return Error(string(line[1:])), nil
|
||||
case ':':
|
||||
return parseInt(line[1:])
|
||||
case '$':
|
||||
n, err := parseLen(line[1:])
|
||||
if n < 0 || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := make([]byte, n)
|
||||
_, err = io.ReadFull(c.br, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if line, err := c.readLine(); err != nil {
|
||||
return nil, err
|
||||
} else if len(line) != 0 {
|
||||
return nil, protocolError("bad bulk string format")
|
||||
}
|
||||
return p, nil
|
||||
case '*':
|
||||
n, err := parseLen(line[1:])
|
||||
if n < 0 || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := make([]interface{}, n)
|
||||
for i := range r {
|
||||
r[i], err = c.readReply()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
return nil, protocolError("unexpected response line")
|
||||
}
|
||||
|
||||
func (c *conn) Send(cmd string, args ...interface{}) error {
|
||||
c.mu.Lock()
|
||||
c.pending += 1
|
||||
c.mu.Unlock()
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
if err := c.writeCommand(cmd, args); err != nil {
|
||||
return c.fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Flush() error {
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
if err := c.bw.Flush(); err != nil {
|
||||
return c.fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Receive() (reply interface{}, err error) {
|
||||
if c.readTimeout != 0 {
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||
}
|
||||
if reply, err = c.readReply(); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
// When using pub/sub, the number of receives can be greater than the
|
||||
// number of sends. To enable normal use of the connection after
|
||||
// unsubscribing from all channels, we do not decrement pending to a
|
||||
// negative value.
|
||||
//
|
||||
// The pending field is decremented after the reply is read to handle the
|
||||
// case where Receive is called before Send.
|
||||
c.mu.Lock()
|
||||
if c.pending > 0 {
|
||||
c.pending -= 1
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if err, ok := reply.(Error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
|
||||
c.mu.Lock()
|
||||
pending := c.pending
|
||||
c.pending = 0
|
||||
c.mu.Unlock()
|
||||
|
||||
if cmd == "" && pending == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if c.writeTimeout != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||
}
|
||||
|
||||
if cmd != "" {
|
||||
c.writeCommand(cmd, args)
|
||||
}
|
||||
|
||||
if err := c.bw.Flush(); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
|
||||
if c.readTimeout != 0 {
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||
}
|
||||
|
||||
if cmd == "" {
|
||||
reply := make([]interface{}, pending)
|
||||
for i := range reply {
|
||||
r, e := c.readReply()
|
||||
if e != nil {
|
||||
return nil, c.fatal(e)
|
||||
}
|
||||
reply[i] = r
|
||||
}
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var reply interface{}
|
||||
for i := 0; i <= pending; i++ {
|
||||
var e error
|
||||
if reply, e = c.readReply(); e != nil {
|
||||
return nil, c.fatal(e)
|
||||
}
|
||||
if e, ok := reply.(Error); ok && err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return reply, err
|
||||
}
|
||||
542
Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go
сгенерированный
поставляемый
Обычный файл
542
Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,542 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/internal/redistest"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
var writeTests = []struct {
|
||||
args []interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
[]interface{}{"SET", "key", "value"},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", "value"},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", byte(100)},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", 100},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", int64(math.MinInt64)},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", float64(1349673917.939762)},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", ""},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "key", nil},
|
||||
"*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
|
||||
},
|
||||
{
|
||||
[]interface{}{"ECHO", true, false},
|
||||
"*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
|
||||
},
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
for _, tt := range writeTests {
|
||||
var buf bytes.Buffer
|
||||
rw := bufio.ReadWriter{Writer: bufio.NewWriter(&buf)}
|
||||
c := redis.NewConnBufio(rw)
|
||||
err := c.Send(tt.args[0].(string), tt.args[1:]...)
|
||||
if err != nil {
|
||||
t.Errorf("Send(%v) returned error %v", tt.args, err)
|
||||
continue
|
||||
}
|
||||
rw.Flush()
|
||||
actual := buf.String()
|
||||
if actual != tt.expected {
|
||||
t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errorSentinel = &struct{}{}
|
||||
|
||||
var readTests = []struct {
|
||||
reply string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
"+OK\r\n",
|
||||
"OK",
|
||||
},
|
||||
{
|
||||
"+PONG\r\n",
|
||||
"PONG",
|
||||
},
|
||||
{
|
||||
"@OK\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
"$6\r\nfoobar\r\n",
|
||||
[]byte("foobar"),
|
||||
},
|
||||
{
|
||||
"$-1\r\n",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
":1\r\n",
|
||||
int64(1),
|
||||
},
|
||||
{
|
||||
":-2\r\n",
|
||||
int64(-2),
|
||||
},
|
||||
{
|
||||
"*0\r\n",
|
||||
[]interface{}{},
|
||||
},
|
||||
{
|
||||
"*-1\r\n",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n",
|
||||
[]interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")},
|
||||
},
|
||||
{
|
||||
"*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n",
|
||||
[]interface{}{[]byte("foo"), nil, []byte("bar")},
|
||||
},
|
||||
|
||||
{
|
||||
// "x" is not a valid length
|
||||
"$x\r\nfoobar\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// -2 is not a valid length
|
||||
"$-2\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// "x" is not a valid integer
|
||||
":x\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// missing \r\n following value
|
||||
"$6\r\nfoobar",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// short value
|
||||
"$6\r\nxx",
|
||||
errorSentinel,
|
||||
},
|
||||
{
|
||||
// long value
|
||||
"$6\r\nfoobarx\r\n",
|
||||
errorSentinel,
|
||||
},
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
for _, tt := range readTests {
|
||||
rw := bufio.ReadWriter{
|
||||
Reader: bufio.NewReader(strings.NewReader(tt.reply)),
|
||||
Writer: bufio.NewWriter(nil), // writer need to support Flush
|
||||
}
|
||||
c := redis.NewConnBufio(rw)
|
||||
actual, err := c.Receive()
|
||||
if tt.expected == errorSentinel {
|
||||
if err == nil {
|
||||
t.Errorf("Receive(%q) did not return expected error", tt.reply)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Receive(%q) returned error %v", tt.reply, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tt.expected) {
|
||||
t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testCommands = []struct {
|
||||
args []interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
[]interface{}{"PING"},
|
||||
"PONG",
|
||||
},
|
||||
{
|
||||
[]interface{}{"SET", "foo", "bar"},
|
||||
"OK",
|
||||
},
|
||||
{
|
||||
[]interface{}{"GET", "foo"},
|
||||
[]byte("bar"),
|
||||
},
|
||||
{
|
||||
[]interface{}{"GET", "nokey"},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]interface{}{"MGET", "nokey", "foo"},
|
||||
[]interface{}{nil, []byte("bar")},
|
||||
},
|
||||
{
|
||||
[]interface{}{"INCR", "mycounter"},
|
||||
int64(1),
|
||||
},
|
||||
{
|
||||
[]interface{}{"LPUSH", "mylist", "foo"},
|
||||
int64(1),
|
||||
},
|
||||
{
|
||||
[]interface{}{"LPUSH", "mylist", "bar"},
|
||||
int64(2),
|
||||
},
|
||||
{
|
||||
[]interface{}{"LRANGE", "mylist", 0, -1},
|
||||
[]interface{}{[]byte("bar"), []byte("foo")},
|
||||
},
|
||||
{
|
||||
[]interface{}{"MULTI"},
|
||||
"OK",
|
||||
},
|
||||
{
|
||||
[]interface{}{"LRANGE", "mylist", 0, -1},
|
||||
"QUEUED",
|
||||
},
|
||||
{
|
||||
[]interface{}{"PING"},
|
||||
"QUEUED",
|
||||
},
|
||||
{
|
||||
[]interface{}{"EXEC"},
|
||||
[]interface{}{
|
||||
[]interface{}{[]byte("bar"), []byte("foo")},
|
||||
"PONG",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDoCommands(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for _, cmd := range testCommands {
|
||||
actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...)
|
||||
if err != nil {
|
||||
t.Errorf("Do(%v) returned error %v", cmd.args, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(actual, cmd.expected) {
|
||||
t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPipelineCommands(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for _, cmd := range testCommands {
|
||||
if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
|
||||
t.Fatalf("Send(%v) returned error %v", cmd.args, err)
|
||||
}
|
||||
}
|
||||
if err := c.Flush(); err != nil {
|
||||
t.Errorf("Flush() returned error %v", err)
|
||||
}
|
||||
for _, cmd := range testCommands {
|
||||
actual, err := c.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive(%v) returned error %v", cmd.args, err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, cmd.expected) {
|
||||
t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlankCommmand(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for _, cmd := range testCommands {
|
||||
if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
|
||||
t.Fatalf("Send(%v) returned error %v", cmd.args, err)
|
||||
}
|
||||
}
|
||||
reply, err := redis.Values(c.Do(""))
|
||||
if err != nil {
|
||||
t.Fatalf("Do() returned error %v", err)
|
||||
}
|
||||
if len(reply) != len(testCommands) {
|
||||
t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands))
|
||||
}
|
||||
for i, cmd := range testCommands {
|
||||
actual := reply[i]
|
||||
if !reflect.DeepEqual(actual, cmd.expected) {
|
||||
t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecvBeforeSend(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
c.Receive()
|
||||
close(done)
|
||||
}()
|
||||
time.Sleep(time.Millisecond)
|
||||
c.Send("PING")
|
||||
c.Flush()
|
||||
<-done
|
||||
_, err = c.Do("")
|
||||
if err != nil {
|
||||
t.Fatalf("error=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "key", "val")
|
||||
_, err = c.Do("HSET", "key", "fld", "val")
|
||||
if err == nil {
|
||||
t.Errorf("Expected err for HSET on string key.")
|
||||
}
|
||||
if c.Err() != nil {
|
||||
t.Errorf("Conn has Err()=%v, expect nil", c.Err())
|
||||
}
|
||||
_, err = c.Do("SET", "key", "val")
|
||||
if err != nil {
|
||||
t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDeadline(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("net.Listen returned %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
c.Write([]byte("+OK\r\n"))
|
||||
c.Close()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
c1, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("redis.Dial returned %v", err)
|
||||
}
|
||||
defer c1.Close()
|
||||
|
||||
_, err = c1.Do("PING")
|
||||
if err == nil {
|
||||
t.Fatalf("c1.Do() returned nil, expect error")
|
||||
}
|
||||
if c1.Err() == nil {
|
||||
t.Fatalf("c1.Err() = nil, expect error")
|
||||
}
|
||||
|
||||
c2, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("redis.Dial returned %v", err)
|
||||
}
|
||||
defer c2.Close()
|
||||
|
||||
c2.Send("PING")
|
||||
c2.Flush()
|
||||
_, err = c2.Receive()
|
||||
if err == nil {
|
||||
t.Fatalf("c2.Receive() returned nil, expect error")
|
||||
}
|
||||
if c2.Err() == nil {
|
||||
t.Fatalf("c2.Err() = nil, expect error")
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to local instance of Redis running on the default port.
|
||||
func ExampleDial(x int) {
|
||||
c, err := redis.Dial("tcp", ":6379")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer c.Close()
|
||||
}
|
||||
|
||||
// TextExecError tests handling of errors in a transaction. See
|
||||
// http://redis.io/topics/transactions for information on how Redis handles
|
||||
// errors in a transaction.
|
||||
func TestExecError(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Execute commands that fail before EXEC is called.
|
||||
|
||||
c.Do("ZADD", "k0", 0, 0)
|
||||
c.Send("MULTI")
|
||||
c.Send("NOTACOMMAND", "k0", 0, 0)
|
||||
c.Send("ZINCRBY", "k0", 0, 0)
|
||||
v, err := c.Do("EXEC")
|
||||
if err == nil {
|
||||
t.Fatalf("EXEC returned values %v, expected error", v)
|
||||
}
|
||||
|
||||
// Execute commands that fail after EXEC is called. The first command
|
||||
// returns an error.
|
||||
|
||||
c.Do("ZADD", "k1", 0, 0)
|
||||
c.Send("MULTI")
|
||||
c.Send("HSET", "k1", 0, 0)
|
||||
c.Send("ZINCRBY", "k1", 0, 0)
|
||||
v, err = c.Do("EXEC")
|
||||
if err != nil {
|
||||
t.Fatalf("EXEC returned error %v", err)
|
||||
}
|
||||
|
||||
vs, err := redis.Values(v, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Values(v) returned error %v", err)
|
||||
}
|
||||
|
||||
if len(vs) != 2 {
|
||||
t.Fatalf("len(vs) == %d, want 2", len(vs))
|
||||
}
|
||||
|
||||
if _, ok := vs[0].(error); !ok {
|
||||
t.Fatalf("first result is type %T, expected error", vs[0])
|
||||
}
|
||||
|
||||
if _, ok := vs[1].([]byte); !ok {
|
||||
t.Fatalf("second result is type %T, expected []byte", vs[2])
|
||||
}
|
||||
|
||||
// Execute commands that fail after EXEC is called. The second command
|
||||
// returns an error.
|
||||
|
||||
c.Do("ZADD", "k2", 0, 0)
|
||||
c.Send("MULTI")
|
||||
c.Send("ZINCRBY", "k2", 0, 0)
|
||||
c.Send("HSET", "k2", 0, 0)
|
||||
v, err = c.Do("EXEC")
|
||||
if err != nil {
|
||||
t.Fatalf("EXEC returned error %v", err)
|
||||
}
|
||||
|
||||
vs, err = redis.Values(v, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Values(v) returned error %v", err)
|
||||
}
|
||||
|
||||
if len(vs) != 2 {
|
||||
t.Fatalf("len(vs) == %d, want 2", len(vs))
|
||||
}
|
||||
|
||||
if _, ok := vs[0].([]byte); !ok {
|
||||
t.Fatalf("first result is type %T, expected []byte", vs[0])
|
||||
}
|
||||
|
||||
if _, ok := vs[1].(error); !ok {
|
||||
t.Fatalf("second result is type %T, expected error", vs[2])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDoEmpty(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := c.Do(""); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDoPing(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
169
Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go
сгенерированный
поставляемый
Обычный файл
169
Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,169 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package redis is a client for the Redis database.
|
||||
//
|
||||
// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
|
||||
// documentation about this package.
|
||||
//
|
||||
// Connections
|
||||
//
|
||||
// The Conn interface is the primary interface for working with Redis.
|
||||
// Applications create connections by calling the Dial, DialWithTimeout or
|
||||
// NewConn functions. In the future, functions will be added for creating
|
||||
// sharded and other types of connections.
|
||||
//
|
||||
// The application must call the connection Close method when the application
|
||||
// is done with the connection.
|
||||
//
|
||||
// Executing Commands
|
||||
//
|
||||
// The Conn interface has a generic method for executing Redis commands:
|
||||
//
|
||||
// Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||
//
|
||||
// The Redis command reference (http://redis.io/commands) lists the available
|
||||
// commands. An example of using the Redis APPEND command is:
|
||||
//
|
||||
// n, err := conn.Do("APPEND", "key", "value")
|
||||
//
|
||||
// The Do method converts command arguments to binary strings for transmission
|
||||
// to the server as follows:
|
||||
//
|
||||
// Go Type Conversion
|
||||
// []byte Sent as is
|
||||
// string Sent as is
|
||||
// int, int64 strconv.FormatInt(v)
|
||||
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
||||
// bool true -> "1", false -> "0"
|
||||
// nil ""
|
||||
// all other types fmt.Print(v)
|
||||
//
|
||||
// Redis command reply types are represented using the following Go types:
|
||||
//
|
||||
// Redis type Go type
|
||||
// error redis.Error
|
||||
// integer int64
|
||||
// simple string string
|
||||
// bulk string []byte or nil if value not present.
|
||||
// array []interface{} or nil if value not present.
|
||||
//
|
||||
// Use type assertions or the reply helper functions to convert from
|
||||
// interface{} to the specific Go type for the command result.
|
||||
//
|
||||
// Pipelining
|
||||
//
|
||||
// Connections support pipelining using the Send, Flush and Receive methods.
|
||||
//
|
||||
// Send(commandName string, args ...interface{}) error
|
||||
// Flush() error
|
||||
// Receive() (reply interface{}, err error)
|
||||
//
|
||||
// Send writes the command to the connection's output buffer. Flush flushes the
|
||||
// connection's output buffer to the server. Receive reads a single reply from
|
||||
// the server. The following example shows a simple pipeline.
|
||||
//
|
||||
// c.Send("SET", "foo", "bar")
|
||||
// c.Send("GET", "foo")
|
||||
// c.Flush()
|
||||
// c.Receive() // reply from SET
|
||||
// v, err = c.Receive() // reply from GET
|
||||
//
|
||||
// The Do method combines the functionality of the Send, Flush and Receive
|
||||
// methods. The Do method starts by writing the command and flushing the output
|
||||
// buffer. Next, the Do method receives all pending replies including the reply
|
||||
// for the command just sent by Do. If any of the received replies is an error,
|
||||
// then Do returns the error. If there are no errors, then Do returns the last
|
||||
// reply. If the command argument to the Do method is "", then the Do method
|
||||
// will flush the output buffer and receive pending replies without sending a
|
||||
// command.
|
||||
//
|
||||
// Use the Send and Do methods to implement pipelined transactions.
|
||||
//
|
||||
// c.Send("MULTI")
|
||||
// c.Send("INCR", "foo")
|
||||
// c.Send("INCR", "bar")
|
||||
// r, err := c.Do("EXEC")
|
||||
// fmt.Println(r) // prints [1, 1]
|
||||
//
|
||||
// Concurrency
|
||||
//
|
||||
// Connections do not support concurrent calls to the write methods (Send,
|
||||
// Flush) or concurrent calls to the read method (Receive). Connections do
|
||||
// allow a concurrent reader and writer.
|
||||
//
|
||||
// Because the Do method combines the functionality of Send, Flush and Receive,
|
||||
// the Do method cannot be called concurrently with the other methods.
|
||||
//
|
||||
// For full concurrent access to Redis, use the thread-safe Pool to get and
|
||||
// release connections from within a goroutine.
|
||||
//
|
||||
// Publish and Subscribe
|
||||
//
|
||||
// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
|
||||
//
|
||||
// c.Send("SUBSCRIBE", "example")
|
||||
// c.Flush()
|
||||
// for {
|
||||
// reply, err := c.Receive()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// // process pushed message
|
||||
// }
|
||||
//
|
||||
// The PubSubConn type wraps a Conn with convenience methods for implementing
|
||||
// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
|
||||
// send and flush a subscription management command. The receive method
|
||||
// converts a pushed message to convenient types for use in a type switch.
|
||||
//
|
||||
// psc := redis.PubSubConn{c}
|
||||
// psc.Subscribe("example")
|
||||
// for {
|
||||
// switch v := psc.Receive().(type) {
|
||||
// case redis.Message:
|
||||
// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
|
||||
// case redis.Subscription:
|
||||
// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
|
||||
// case error:
|
||||
// return v
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Reply Helpers
|
||||
//
|
||||
// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
|
||||
// to a value of a specific type. To allow convenient wrapping of calls to the
|
||||
// connection Do and Receive methods, the functions take a second argument of
|
||||
// type error. If the error is non-nil, then the helper function returns the
|
||||
// error. If the error is nil, the function converts the reply to the specified
|
||||
// type:
|
||||
//
|
||||
// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
|
||||
// if err != nil {
|
||||
// // handle error return from c.Do or type conversion error.
|
||||
// }
|
||||
//
|
||||
// The Scan function converts elements of a array reply to Go types:
|
||||
//
|
||||
// var value1 int
|
||||
// var value2 string
|
||||
// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
package redis
|
||||
117
Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go
сгенерированный
поставляемый
Обычный файл
117
Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,117 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// NewLoggingConn returns a logging wrapper around a connection.
|
||||
func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
|
||||
if prefix != "" {
|
||||
prefix = prefix + "."
|
||||
}
|
||||
return &loggingConn{conn, logger, prefix}
|
||||
}
|
||||
|
||||
type loggingConn struct {
|
||||
Conn
|
||||
logger *log.Logger
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (c *loggingConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
|
||||
c.logger.Output(2, buf.String())
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
|
||||
const chop = 32
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case string:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case []interface{}:
|
||||
if len(v) == 0 {
|
||||
buf.WriteString("[]")
|
||||
} else {
|
||||
sep := "["
|
||||
fin := "]"
|
||||
if len(v) > chop {
|
||||
v = v[:chop]
|
||||
fin = "...]"
|
||||
}
|
||||
for _, vv := range v {
|
||||
buf.WriteString(sep)
|
||||
c.printValue(buf, vv)
|
||||
sep = ", "
|
||||
}
|
||||
buf.WriteString(fin)
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(buf, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
|
||||
if method != "Receive" {
|
||||
buf.WriteString(commandName)
|
||||
for _, arg := range args {
|
||||
buf.WriteString(", ")
|
||||
c.printValue(&buf, arg)
|
||||
}
|
||||
}
|
||||
buf.WriteString(") -> (")
|
||||
if method != "Send" {
|
||||
c.printValue(&buf, reply)
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&buf, "%v)", err)
|
||||
c.logger.Output(3, buf.String())
|
||||
}
|
||||
|
||||
func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
|
||||
reply, err := c.Conn.Do(commandName, args...)
|
||||
c.print("Do", commandName, args, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Send(commandName string, args ...interface{}) error {
|
||||
err := c.Conn.Send(commandName, args...)
|
||||
c.print("Send", commandName, args, nil, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Receive() (interface{}, error) {
|
||||
reply, err := c.Conn.Receive()
|
||||
c.print("Receive", "", nil, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
389
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go
сгенерированный
поставляемый
Обычный файл
389
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,389 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/internal"
|
||||
)
|
||||
|
||||
var nowFunc = time.Now // for testing
|
||||
|
||||
// ErrPoolExhausted is returned from a pool connection method (Do, Send,
|
||||
// Receive, Flush, Err) when the maximum number of database connections in the
|
||||
// pool has been reached.
|
||||
var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
|
||||
|
||||
var (
|
||||
errPoolClosed = errors.New("redigo: connection pool closed")
|
||||
errConnClosed = errors.New("redigo: connection closed")
|
||||
)
|
||||
|
||||
// Pool maintains a pool of connections. The application calls the Get method
|
||||
// to get a connection from the pool and the connection's Close method to
|
||||
// return the connection's resources to the pool.
|
||||
//
|
||||
// The following example shows how to use a pool in a web application. The
|
||||
// application creates a pool at application startup and makes it available to
|
||||
// request handlers using a global variable.
|
||||
//
|
||||
// func newPool(server, password string) *redis.Pool {
|
||||
// return &redis.Pool{
|
||||
// MaxIdle: 3,
|
||||
// IdleTimeout: 240 * time.Second,
|
||||
// Dial: func () (redis.Conn, error) {
|
||||
// c, err := redis.Dial("tcp", server)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if _, err := c.Do("AUTH", password); err != nil {
|
||||
// c.Close()
|
||||
// return nil, err
|
||||
// }
|
||||
// return c, err
|
||||
// },
|
||||
// TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
// _, err := c.Do("PING")
|
||||
// return err
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// pool *redis.Pool
|
||||
// redisServer = flag.String("redisServer", ":6379", "")
|
||||
// redisPassword = flag.String("redisPassword", "", "")
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// flag.Parse()
|
||||
// pool = newPool(*redisServer, *redisPassword)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// A request handler gets a connection from the pool and closes the connection
|
||||
// when the handler is done:
|
||||
//
|
||||
// func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||
// conn := pool.Get()
|
||||
// defer conn.Close()
|
||||
// ....
|
||||
// }
|
||||
//
|
||||
type Pool struct {
|
||||
|
||||
// Dial is an application supplied function for creating and configuring a
|
||||
// connection
|
||||
Dial func() (Conn, error)
|
||||
|
||||
// TestOnBorrow is an optional application supplied function for checking
|
||||
// the health of an idle connection before the connection is used again by
|
||||
// the application. Argument t is the time that the connection was returned
|
||||
// to the pool. If the function returns an error, then the connection is
|
||||
// closed.
|
||||
TestOnBorrow func(c Conn, t time.Time) error
|
||||
|
||||
// Maximum number of idle connections in the pool.
|
||||
MaxIdle int
|
||||
|
||||
// Maximum number of connections allocated by the pool at a given time.
|
||||
// When zero, there is no limit on the number of connections in the pool.
|
||||
MaxActive int
|
||||
|
||||
// Close connections after remaining idle for this duration. If the value
|
||||
// is zero, then idle connections are not closed. Applications should set
|
||||
// the timeout to a value less than the server's timeout.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// If Wait is true and the pool is at the MaxIdle limit, then Get() waits
|
||||
// for a connection to be returned to the pool before returning.
|
||||
Wait bool
|
||||
|
||||
// mu protects fields defined below.
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
closed bool
|
||||
active int
|
||||
|
||||
// Stack of idleConn with most recently used at the front.
|
||||
idle list.List
|
||||
}
|
||||
|
||||
type idleConn struct {
|
||||
c Conn
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// NewPool creates a new pool. This function is deprecated. Applications should
|
||||
// initialize the Pool fields directly as shown in example.
|
||||
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
|
||||
return &Pool{Dial: newFn, MaxIdle: maxIdle}
|
||||
}
|
||||
|
||||
// Get gets a connection. The application must close the returned connection.
|
||||
// This method always returns a valid connection so that applications can defer
|
||||
// error handling to the first use of the connection. If there is an error
|
||||
// getting an underlying connection, then the connection Err, Do, Send, Flush
|
||||
// and Receive methods return that error.
|
||||
func (p *Pool) Get() Conn {
|
||||
c, err := p.get()
|
||||
if err != nil {
|
||||
return errorConnection{err}
|
||||
}
|
||||
return &pooledConnection{p: p, c: c}
|
||||
}
|
||||
|
||||
// ActiveCount returns the number of active connections in the pool.
|
||||
func (p *Pool) ActiveCount() int {
|
||||
p.mu.Lock()
|
||||
active := p.active
|
||||
p.mu.Unlock()
|
||||
return active
|
||||
}
|
||||
|
||||
// Close releases the resources used by the pool.
|
||||
func (p *Pool) Close() error {
|
||||
p.mu.Lock()
|
||||
idle := p.idle
|
||||
p.idle.Init()
|
||||
p.closed = true
|
||||
p.active -= idle.Len()
|
||||
if p.cond != nil {
|
||||
p.cond.Broadcast()
|
||||
}
|
||||
p.mu.Unlock()
|
||||
for e := idle.Front(); e != nil; e = e.Next() {
|
||||
e.Value.(idleConn).c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// release decrements the active count and signals waiters. The caller must
|
||||
// hold p.mu during the call.
|
||||
func (p *Pool) release() {
|
||||
p.active -= 1
|
||||
if p.cond != nil {
|
||||
p.cond.Signal()
|
||||
}
|
||||
}
|
||||
|
||||
// get prunes stale connections and returns a connection from the idle list or
|
||||
// creates a new connection.
|
||||
func (p *Pool) get() (Conn, error) {
|
||||
p.mu.Lock()
|
||||
|
||||
// Prune stale connections.
|
||||
|
||||
if timeout := p.IdleTimeout; timeout > 0 {
|
||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
||||
e := p.idle.Back()
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
ic := e.Value.(idleConn)
|
||||
if ic.t.Add(timeout).After(nowFunc()) {
|
||||
break
|
||||
}
|
||||
p.idle.Remove(e)
|
||||
p.release()
|
||||
p.mu.Unlock()
|
||||
ic.c.Close()
|
||||
p.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
|
||||
// Get idle connection.
|
||||
|
||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
||||
e := p.idle.Front()
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
ic := e.Value.(idleConn)
|
||||
p.idle.Remove(e)
|
||||
test := p.TestOnBorrow
|
||||
p.mu.Unlock()
|
||||
if test == nil || test(ic.c, ic.t) == nil {
|
||||
return ic.c, nil
|
||||
}
|
||||
ic.c.Close()
|
||||
p.mu.Lock()
|
||||
p.release()
|
||||
}
|
||||
|
||||
// Check for pool closed before dialing a new connection.
|
||||
|
||||
if p.closed {
|
||||
p.mu.Unlock()
|
||||
return nil, errors.New("redigo: get on closed pool")
|
||||
}
|
||||
|
||||
// Dial new connection if under limit.
|
||||
|
||||
if p.MaxActive == 0 || p.active < p.MaxActive {
|
||||
dial := p.Dial
|
||||
p.active += 1
|
||||
p.mu.Unlock()
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
p.mu.Lock()
|
||||
p.release()
|
||||
p.mu.Unlock()
|
||||
c = nil
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
if !p.Wait {
|
||||
p.mu.Unlock()
|
||||
return nil, ErrPoolExhausted
|
||||
}
|
||||
|
||||
if p.cond == nil {
|
||||
p.cond = sync.NewCond(&p.mu)
|
||||
}
|
||||
p.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) put(c Conn, forceClose bool) error {
|
||||
err := c.Err()
|
||||
p.mu.Lock()
|
||||
if !p.closed && err == nil && !forceClose {
|
||||
p.idle.PushFront(idleConn{t: nowFunc(), c: c})
|
||||
if p.idle.Len() > p.MaxIdle {
|
||||
c = p.idle.Remove(p.idle.Back()).(idleConn).c
|
||||
} else {
|
||||
c = nil
|
||||
}
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
if p.cond != nil {
|
||||
p.cond.Signal()
|
||||
}
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
p.release()
|
||||
p.mu.Unlock()
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
type pooledConnection struct {
|
||||
p *Pool
|
||||
c Conn
|
||||
state int
|
||||
}
|
||||
|
||||
var (
|
||||
sentinel []byte
|
||||
sentinelOnce sync.Once
|
||||
)
|
||||
|
||||
func initSentinel() {
|
||||
p := make([]byte, 64)
|
||||
if _, err := rand.Read(p); err == nil {
|
||||
sentinel = p
|
||||
} else {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, "Oops, rand failed. Use time instead.")
|
||||
io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
sentinel = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Close() error {
|
||||
c := pc.c
|
||||
if _, ok := c.(errorConnection); ok {
|
||||
return nil
|
||||
}
|
||||
pc.c = errorConnection{errConnClosed}
|
||||
|
||||
if pc.state&internal.MultiState != 0 {
|
||||
c.Send("DISCARD")
|
||||
pc.state &^= (internal.MultiState | internal.WatchState)
|
||||
} else if pc.state&internal.WatchState != 0 {
|
||||
c.Send("UNWATCH")
|
||||
pc.state &^= internal.WatchState
|
||||
}
|
||||
if pc.state&internal.SubscribeState != 0 {
|
||||
c.Send("UNSUBSCRIBE")
|
||||
c.Send("PUNSUBSCRIBE")
|
||||
// To detect the end of the message stream, ask the server to echo
|
||||
// a sentinel value and read until we see that value.
|
||||
sentinelOnce.Do(initSentinel)
|
||||
c.Send("ECHO", sentinel)
|
||||
c.Flush()
|
||||
for {
|
||||
p, err := c.Receive()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
|
||||
pc.state &^= internal.SubscribeState
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Do("")
|
||||
pc.p.put(c, pc.state != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Err() error {
|
||||
return pc.c.Err()
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
ci := internal.LookupCommandInfo(commandName)
|
||||
pc.state = (pc.state | ci.Set) &^ ci.Clear
|
||||
return pc.c.Do(commandName, args...)
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Send(commandName string, args ...interface{}) error {
|
||||
ci := internal.LookupCommandInfo(commandName)
|
||||
pc.state = (pc.state | ci.Set) &^ ci.Clear
|
||||
return pc.c.Send(commandName, args...)
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Flush() error {
|
||||
return pc.c.Flush()
|
||||
}
|
||||
|
||||
func (pc *pooledConnection) Receive() (reply interface{}, err error) {
|
||||
return pc.c.Receive()
|
||||
}
|
||||
|
||||
type errorConnection struct{ err error }
|
||||
|
||||
func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
|
||||
func (ec errorConnection) Send(string, ...interface{}) error { return ec.err }
|
||||
func (ec errorConnection) Err() error { return ec.err }
|
||||
func (ec errorConnection) Close() error { return ec.err }
|
||||
func (ec errorConnection) Flush() error { return ec.err }
|
||||
func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err }
|
||||
674
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go
сгенерированный
поставляемый
Обычный файл
674
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,674 @@
|
||||
// Copyright 2011 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/internal/redistest"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type poolTestConn struct {
|
||||
d *poolDialer
|
||||
err error
|
||||
redis.Conn
|
||||
}
|
||||
|
||||
func (c *poolTestConn) Close() error { c.d.open -= 1; return nil }
|
||||
func (c *poolTestConn) Err() error { return c.err }
|
||||
|
||||
func (c *poolTestConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||
if commandName == "ERR" {
|
||||
c.err = args[0].(error)
|
||||
commandName = "PING"
|
||||
}
|
||||
if commandName != "" {
|
||||
c.d.commands = append(c.d.commands, commandName)
|
||||
}
|
||||
return c.Conn.Do(commandName, args...)
|
||||
}
|
||||
|
||||
func (c *poolTestConn) Send(commandName string, args ...interface{}) error {
|
||||
c.d.commands = append(c.d.commands, commandName)
|
||||
return c.Conn.Send(commandName, args...)
|
||||
}
|
||||
|
||||
type poolDialer struct {
|
||||
t *testing.T
|
||||
dialed int
|
||||
open int
|
||||
commands []string
|
||||
dialErr error
|
||||
}
|
||||
|
||||
func (d *poolDialer) dial() (redis.Conn, error) {
|
||||
d.dialed += 1
|
||||
if d.dialErr != nil {
|
||||
return nil, d.dialErr
|
||||
}
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.open += 1
|
||||
return &poolTestConn{d: d, Conn: c}, nil
|
||||
}
|
||||
|
||||
func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) {
|
||||
if d.dialed != dialed {
|
||||
d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed)
|
||||
}
|
||||
if d.open != open {
|
||||
d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
|
||||
}
|
||||
if active := p.ActiveCount(); active != open {
|
||||
d.t.Errorf("%s: active=%d, want %d", message, active, open)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolReuse(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
}
|
||||
|
||||
d.check("before close", p, 2, 2)
|
||||
p.Close()
|
||||
d.check("after close", p, 2, 0)
|
||||
}
|
||||
|
||||
func TestPoolMaxIdle(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
c3 := p.Get()
|
||||
c3.Do("PING")
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
c3.Close()
|
||||
}
|
||||
d.check("before close", p, 12, 2)
|
||||
p.Close()
|
||||
d.check("after close", p, 12, 0)
|
||||
}
|
||||
|
||||
func TestPoolError(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
|
||||
c := p.Get()
|
||||
c.Do("ERR", io.EOF)
|
||||
if c.Err() == nil {
|
||||
t.Errorf("expected c.Err() != nil")
|
||||
}
|
||||
c.Close()
|
||||
|
||||
c = p.Get()
|
||||
c.Do("ERR", io.EOF)
|
||||
c.Close()
|
||||
|
||||
d.check(".", p, 2, 0)
|
||||
}
|
||||
|
||||
func TestPoolClose(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
c3 := p.Get()
|
||||
c3.Do("PING")
|
||||
|
||||
c1.Close()
|
||||
if _, err := c1.Do("PING"); err == nil {
|
||||
t.Errorf("expected error after connection closed")
|
||||
}
|
||||
|
||||
c2.Close()
|
||||
c2.Close()
|
||||
|
||||
p.Close()
|
||||
|
||||
d.check("after pool close", p, 3, 1)
|
||||
|
||||
if _, err := c1.Do("PING"); err == nil {
|
||||
t.Errorf("expected error after connection and pool closed")
|
||||
}
|
||||
|
||||
c3.Close()
|
||||
|
||||
d.check("after conn close", p, 3, 0)
|
||||
|
||||
c1 = p.Get()
|
||||
if _, err := c1.Do("PING"); err == nil {
|
||||
t.Errorf("expected error after pool closed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolTimeout(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
IdleTimeout: 300 * time.Second,
|
||||
Dial: d.dial,
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
redis.SetNowFunc(func() time.Time { return now })
|
||||
defer redis.SetNowFunc(time.Now)
|
||||
|
||||
c := p.Get()
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
d.check("1", p, 1, 1)
|
||||
|
||||
now = now.Add(p.IdleTimeout)
|
||||
|
||||
c = p.Get()
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
d.check("2", p, 2, 1)
|
||||
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func TestPoolConcurrenSendReceive(t *testing.T) {
|
||||
p := &redis.Pool{
|
||||
Dial: redistest.Dial,
|
||||
}
|
||||
c := p.Get()
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := c.Receive()
|
||||
done <- err
|
||||
}()
|
||||
c.Send("PING")
|
||||
c.Flush()
|
||||
err := <-done
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() returned error %v", err)
|
||||
}
|
||||
_, err = c.Do("")
|
||||
if err != nil {
|
||||
t.Fatalf("Do() returned error %v", err)
|
||||
}
|
||||
c.Close()
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func TestPoolBorrowCheck(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
Dial: d.dial,
|
||||
TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") },
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c := p.Get()
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
}
|
||||
d.check("1", p, 10, 1)
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func TestPoolMaxActive(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
c1 := p.Get()
|
||||
c1.Do("PING")
|
||||
c2 := p.Get()
|
||||
c2.Do("PING")
|
||||
|
||||
d.check("1", p, 2, 2)
|
||||
|
||||
c3 := p.Get()
|
||||
if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted {
|
||||
t.Errorf("expected pool exhausted")
|
||||
}
|
||||
|
||||
c3.Close()
|
||||
d.check("2", p, 2, 2)
|
||||
c2.Close()
|
||||
d.check("3", p, 2, 2)
|
||||
|
||||
c3 = p.Get()
|
||||
if _, err := c3.Do("PING"); err != nil {
|
||||
t.Errorf("expected good channel, err=%v", err)
|
||||
}
|
||||
c3.Close()
|
||||
|
||||
d.check("4", p, 2, 2)
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func TestPoolMonitorCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
c := p.Get()
|
||||
c.Send("MONITOR")
|
||||
c.Close()
|
||||
|
||||
d.check("", p, 1, 0)
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func TestPoolPubSubCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
|
||||
c := p.Get()
|
||||
c.Send("SUBSCRIBE", "x")
|
||||
c.Close()
|
||||
|
||||
want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Send("PSUBSCRIBE", "x*")
|
||||
c.Close()
|
||||
|
||||
want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func TestPoolTransactionCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
|
||||
c := p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want := []string{"WATCH", "PING", "UNWATCH"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("UNWATCH")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "UNWATCH", "PING"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("MULTI")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "MULTI", "PING", "DISCARD"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("MULTI")
|
||||
c.Do("DISCARD")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "MULTI", "DISCARD", "PING"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
c = p.Get()
|
||||
c.Do("WATCH", "key")
|
||||
c.Do("MULTI")
|
||||
c.Do("EXEC")
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
|
||||
want = []string{"WATCH", "MULTI", "EXEC", "PING"}
|
||||
if !reflect.DeepEqual(d.commands, want) {
|
||||
t.Errorf("got commands %v, want %v", d.commands, want)
|
||||
}
|
||||
d.commands = nil
|
||||
|
||||
p.Close()
|
||||
}
|
||||
|
||||
func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error {
|
||||
errs := make(chan error, 10)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
go func() {
|
||||
c := p.Get()
|
||||
_, err := c.Do(cmd, args...)
|
||||
errs <- err
|
||||
c.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for goroutines to block.
|
||||
time.Sleep(time.Second / 4)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func TestWaitPool(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
defer p.Close()
|
||||
c := p.Get()
|
||||
errs := startGoroutines(p, "PING")
|
||||
d.check("before close", p, 1, 1)
|
||||
c.Close()
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for blocked goroutine %d", i)
|
||||
}
|
||||
}
|
||||
d.check("done", p, 1, 1)
|
||||
}
|
||||
|
||||
func TestWaitPoolClose(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
c := p.Get()
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
errs := startGoroutines(p, "PING")
|
||||
d.check("before close", p, 1, 1)
|
||||
p.Close()
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
switch err {
|
||||
case nil:
|
||||
t.Fatal("blocked goroutine did not get error")
|
||||
case redis.ErrPoolExhausted:
|
||||
t.Fatal("blocked goroutine got pool exhausted error")
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatal("timeout waiting for blocked goroutine")
|
||||
}
|
||||
}
|
||||
c.Close()
|
||||
d.check("done", p, 1, 0)
|
||||
}
|
||||
|
||||
func TestWaitPoolCommandError(t *testing.T) {
|
||||
testErr := errors.New("test")
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
defer p.Close()
|
||||
c := p.Get()
|
||||
errs := startGoroutines(p, "ERR", testErr)
|
||||
d.check("before close", p, 1, 1)
|
||||
c.Close()
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for blocked goroutine %d", i)
|
||||
}
|
||||
}
|
||||
d.check("done", p, cap(errs), 0)
|
||||
}
|
||||
|
||||
func TestWaitPoolDialError(t *testing.T) {
|
||||
testErr := errors.New("test")
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 1,
|
||||
MaxActive: 1,
|
||||
Dial: d.dial,
|
||||
Wait: true,
|
||||
}
|
||||
defer p.Close()
|
||||
c := p.Get()
|
||||
errs := startGoroutines(p, "ERR", testErr)
|
||||
d.check("before close", p, 1, 1)
|
||||
|
||||
d.dialErr = errors.New("dial")
|
||||
c.Close()
|
||||
|
||||
nilCount := 0
|
||||
errCount := 0
|
||||
timeout := time.After(2 * time.Second)
|
||||
for i := 0; i < cap(errs); i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
switch err {
|
||||
case nil:
|
||||
nilCount++
|
||||
case d.dialErr:
|
||||
errCount++
|
||||
default:
|
||||
t.Fatalf("expected dial error or nil, got %v", err)
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for blocked goroutine %d", i)
|
||||
}
|
||||
}
|
||||
if nilCount != 1 {
|
||||
t.Errorf("expected one nil error, got %d", nilCount)
|
||||
}
|
||||
if errCount != cap(errs)-1 {
|
||||
t.Errorf("expected %d dial erors, got %d", cap(errs)-1, errCount)
|
||||
}
|
||||
d.check("done", p, cap(errs), 0)
|
||||
}
|
||||
|
||||
// Borrowing requires us to iterate over the idle connections, unlock the pool,
|
||||
// and perform a blocking operation to check the connection still works. If
|
||||
// TestOnBorrow fails, we must reacquire the lock and continue iteration. This
|
||||
// test ensures that iteration will work correctly if multiple threads are
|
||||
// iterating simultaneously.
|
||||
func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) {
|
||||
count := 100
|
||||
|
||||
// First we'll Create a pool where the pilfering of idle connections fails.
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: count,
|
||||
MaxActive: count,
|
||||
Dial: d.dial,
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
return errors.New("No way back into the real world.")
|
||||
},
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
// Fill the pool with idle connections.
|
||||
b1 := sync.WaitGroup{}
|
||||
b1.Add(count)
|
||||
b2 := sync.WaitGroup{}
|
||||
b2.Add(count)
|
||||
for i := 0; i < count; i++ {
|
||||
go func() {
|
||||
c := p.Get()
|
||||
if c.Err() != nil {
|
||||
t.Errorf("pool get failed: %v", c.Err())
|
||||
}
|
||||
b1.Done()
|
||||
b1.Wait()
|
||||
c.Close()
|
||||
b2.Done()
|
||||
}()
|
||||
}
|
||||
b2.Wait()
|
||||
if d.dialed != count {
|
||||
t.Errorf("Expected %d dials, got %d", count, d.dialed)
|
||||
}
|
||||
|
||||
// Spawn a bunch of goroutines to thrash the pool.
|
||||
b2.Add(count)
|
||||
for i := 0; i < count; i++ {
|
||||
go func() {
|
||||
c := p.Get()
|
||||
if c.Err() != nil {
|
||||
t.Errorf("pool get failed: %v", c.Err())
|
||||
}
|
||||
c.Close()
|
||||
b2.Done()
|
||||
}()
|
||||
}
|
||||
b2.Wait()
|
||||
if d.dialed != count*2 {
|
||||
t.Errorf("Expected %d dials, got %d", count*2, d.dialed)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPoolGet(b *testing.B) {
|
||||
b.StopTimer()
|
||||
p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2}
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
defer p.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = p.Get()
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetErr(b *testing.B) {
|
||||
b.StopTimer()
|
||||
p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2}
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
defer p.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetPing(b *testing.B) {
|
||||
b.StopTimer()
|
||||
p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2}
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
defer p.Close()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = p.Get()
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
144
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go
сгенерированный
поставляемый
Обычный файл
144
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,144 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import "errors"
|
||||
|
||||
// Subscription represents a subscribe or unsubscribe notification.
|
||||
type Subscription struct {
|
||||
|
||||
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
||||
Kind string
|
||||
|
||||
// The channel that was changed.
|
||||
Channel string
|
||||
|
||||
// The current number of subscriptions for connection.
|
||||
Count int
|
||||
}
|
||||
|
||||
// Message represents a message notification.
|
||||
type Message struct {
|
||||
|
||||
// The originating channel.
|
||||
Channel string
|
||||
|
||||
// The message data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// PMessage represents a pmessage notification.
|
||||
type PMessage struct {
|
||||
|
||||
// The matched pattern.
|
||||
Pattern string
|
||||
|
||||
// The originating channel.
|
||||
Channel string
|
||||
|
||||
// The message data.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Pong represents a pubsub pong notification.
|
||||
type Pong struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
// PubSubConn wraps a Conn with convenience methods for subscribers.
|
||||
type PubSubConn struct {
|
||||
Conn Conn
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c PubSubConn) Close() error {
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// Subscribe subscribes the connection to the specified channels.
|
||||
func (c PubSubConn) Subscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("SUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// PSubscribe subscribes the connection to the given patterns.
|
||||
func (c PubSubConn) PSubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("PSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Unsubscribe unsubscribes the connection from the given channels, or from all
|
||||
// of them if none is given.
|
||||
func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("UNSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// PUnsubscribe unsubscribes the connection from the given patterns, or from all
|
||||
// of them if none is given.
|
||||
func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
|
||||
c.Conn.Send("PUNSUBSCRIBE", channel...)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Ping sends a PING to the server with the specified data.
|
||||
func (c PubSubConn) Ping(data string) error {
|
||||
c.Conn.Send("PING", data)
|
||||
return c.Conn.Flush()
|
||||
}
|
||||
|
||||
// Receive returns a pushed message as a Subscription, Message, PMessage, Pong
|
||||
// or error. The return value is intended to be used directly in a type switch
|
||||
// as illustrated in the PubSubConn example.
|
||||
func (c PubSubConn) Receive() interface{} {
|
||||
reply, err := Values(c.Conn.Receive())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var kind string
|
||||
reply, err = Scan(reply, &kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case "message":
|
||||
var m Message
|
||||
if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return m
|
||||
case "pmessage":
|
||||
var pm PMessage
|
||||
if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return pm
|
||||
case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
|
||||
s := Subscription{Kind: kind}
|
||||
if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
return s
|
||||
case "pong":
|
||||
var p Pong
|
||||
if _, err := Scan(reply, &p.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
return p
|
||||
}
|
||||
return errors.New("redigo: unknown pubsub notification")
|
||||
}
|
||||
150
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go
сгенерированный
поставляемый
Обычный файл
150
Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,150 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/internal/redistest"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
func publish(channel, value interface{}) {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
c.Do("PUBLISH", channel, value)
|
||||
}
|
||||
|
||||
// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine.
|
||||
func ExamplePubSubConn() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
psc := redis.PubSubConn{Conn: c}
|
||||
|
||||
// This goroutine receives and prints pushed notifications from the server.
|
||||
// The goroutine exits when the connection is unsubscribed from all
|
||||
// channels or there is an error.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
switch n := psc.Receive().(type) {
|
||||
case redis.Message:
|
||||
fmt.Printf("Message: %s %s\n", n.Channel, n.Data)
|
||||
case redis.PMessage:
|
||||
fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data)
|
||||
case redis.Subscription:
|
||||
fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count)
|
||||
if n.Count == 0 {
|
||||
return
|
||||
}
|
||||
case error:
|
||||
fmt.Printf("error: %v\n", n)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// This goroutine manages subscriptions for the connection.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
psc.Subscribe("example")
|
||||
psc.PSubscribe("p*")
|
||||
|
||||
// The following function calls publish a message using another
|
||||
// connection to the Redis server.
|
||||
publish("example", "hello")
|
||||
publish("example", "world")
|
||||
publish("pexample", "foo")
|
||||
publish("pexample", "bar")
|
||||
|
||||
// Unsubscribe from all connections. This will cause the receiving
|
||||
// goroutine to exit.
|
||||
psc.Unsubscribe()
|
||||
psc.PUnsubscribe()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Output:
|
||||
// Subscription: subscribe example 1
|
||||
// Subscription: psubscribe p* 2
|
||||
// Message: example hello
|
||||
// Message: example world
|
||||
// PMessage: p* pexample foo
|
||||
// PMessage: p* pexample bar
|
||||
// Subscription: unsubscribe example 1
|
||||
// Subscription: punsubscribe p* 0
|
||||
}
|
||||
|
||||
func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) {
|
||||
actual := c.Receive()
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("%s = %v, want %v", message, actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushed(t *testing.T) {
|
||||
pc, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer pc.Close()
|
||||
|
||||
nc, err := net.Dial("tcp", ":6379")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nc.Close()
|
||||
nc.SetReadDeadline(time.Now().Add(4 * time.Second))
|
||||
|
||||
c := redis.PubSubConn{Conn: redis.NewConn(nc, 0, 0)}
|
||||
|
||||
c.Subscribe("c1")
|
||||
expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1})
|
||||
c.Subscribe("c2")
|
||||
expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2})
|
||||
c.PSubscribe("p1")
|
||||
expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3})
|
||||
c.PSubscribe("p2")
|
||||
expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4})
|
||||
c.PUnsubscribe()
|
||||
expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3})
|
||||
expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2})
|
||||
|
||||
pc.Do("PUBLISH", "c1", "hello")
|
||||
expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")})
|
||||
|
||||
c.Ping("hello")
|
||||
expectPushed(t, c, `Ping("hello")`, redis.Pong{"hello"})
|
||||
|
||||
c.Conn.Send("PING")
|
||||
c.Conn.Flush()
|
||||
expectPushed(t, c, `Send("PING")`, redis.Pong{})
|
||||
}
|
||||
44
Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go
сгенерированный
поставляемый
Обычный файл
44
Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,44 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
// Error represents an error returned in a command reply.
|
||||
type Error string
|
||||
|
||||
func (err Error) Error() string { return string(err) }
|
||||
|
||||
// Conn represents a connection to a Redis server.
|
||||
type Conn interface {
|
||||
// Close closes the connection.
|
||||
Close() error
|
||||
|
||||
// Err returns a non-nil value if the connection is broken. The returned
|
||||
// value is either the first non-nil value returned from the underlying
|
||||
// network connection or a protocol parsing error. Applications should
|
||||
// close broken connections.
|
||||
Err() error
|
||||
|
||||
// Do sends a command to the server and returns the received reply.
|
||||
Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
||||
|
||||
// Send writes the command to the client's output buffer.
|
||||
Send(commandName string, args ...interface{}) error
|
||||
|
||||
// Flush flushes the output buffer to the Redis server.
|
||||
Flush() error
|
||||
|
||||
// Receive receives a single reply from the Redis server
|
||||
Receive() (reply interface{}, err error)
|
||||
}
|
||||
364
Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go
сгенерированный
поставляемый
Обычный файл
364
Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,364 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ErrNil indicates that a reply value is nil.
|
||||
var ErrNil = errors.New("redigo: nil returned")
|
||||
|
||||
// Int is a helper that converts a command reply to an integer. If err is not
|
||||
// equal to nil, then Int returns 0, err. Otherwise, Int converts the
|
||||
// reply to an int as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer int(reply), nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Int(reply interface{}, err error) (int, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
x := int(reply)
|
||||
if int64(x) != reply {
|
||||
return 0, strconv.ErrRange
|
||||
}
|
||||
return x, nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(reply), 10, 0)
|
||||
return int(n), err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
|
||||
}
|
||||
|
||||
// Int64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||
// reply to an int64 as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer reply, nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Int64(reply interface{}, err error) (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
return reply, nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseInt(string(reply), 10, 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
|
||||
}
|
||||
|
||||
var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
|
||||
|
||||
// Uint64 is a helper that converts a command reply to 64 bit integer. If err is
|
||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
||||
// reply to an int64 as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer reply, nil
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Uint64(reply interface{}, err error) (uint64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
if reply < 0 {
|
||||
return 0, errNegativeInt
|
||||
}
|
||||
return uint64(reply), nil
|
||||
case []byte:
|
||||
n, err := strconv.ParseUint(string(reply), 10, 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
|
||||
}
|
||||
|
||||
// Float64 is a helper that converts a command reply to 64 bit float. If err is
|
||||
// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
|
||||
// the reply to an int as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string parsed reply, nil
|
||||
// nil 0, ErrNil
|
||||
// other 0, error
|
||||
func Float64(reply interface{}, err error) (float64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
n, err := strconv.ParseFloat(string(reply), 64)
|
||||
return n, err
|
||||
case nil:
|
||||
return 0, ErrNil
|
||||
case Error:
|
||||
return 0, reply
|
||||
}
|
||||
return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
|
||||
}
|
||||
|
||||
// String is a helper that converts a command reply to a string. If err is not
|
||||
// equal to nil, then String returns "", err. Otherwise String converts the
|
||||
// reply to a string as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string string(reply), nil
|
||||
// simple string reply, nil
|
||||
// nil "", ErrNil
|
||||
// other "", error
|
||||
func String(reply interface{}, err error) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
return string(reply), nil
|
||||
case string:
|
||||
return reply, nil
|
||||
case nil:
|
||||
return "", ErrNil
|
||||
case Error:
|
||||
return "", reply
|
||||
}
|
||||
return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
|
||||
}
|
||||
|
||||
// Bytes is a helper that converts a command reply to a slice of bytes. If err
|
||||
// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
|
||||
// the reply to a slice of bytes as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// bulk string reply, nil
|
||||
// simple string []byte(reply), nil
|
||||
// nil nil, ErrNil
|
||||
// other nil, error
|
||||
func Bytes(reply interface{}, err error) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []byte:
|
||||
return reply, nil
|
||||
case string:
|
||||
return []byte(reply), nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
|
||||
}
|
||||
|
||||
// Bool is a helper that converts a command reply to a boolean. If err is not
|
||||
// equal to nil, then Bool returns false, err. Otherwise Bool converts the
|
||||
// reply to boolean as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// integer value != 0, nil
|
||||
// bulk string strconv.ParseBool(reply)
|
||||
// nil false, ErrNil
|
||||
// other false, error
|
||||
func Bool(reply interface{}, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case int64:
|
||||
return reply != 0, nil
|
||||
case []byte:
|
||||
return strconv.ParseBool(string(reply))
|
||||
case nil:
|
||||
return false, ErrNil
|
||||
case Error:
|
||||
return false, reply
|
||||
}
|
||||
return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
|
||||
}
|
||||
|
||||
// MultiBulk is deprecated. Use Values.
|
||||
func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }
|
||||
|
||||
// Values is a helper that converts an array command reply to a []interface{}.
|
||||
// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
|
||||
// converts the reply as follows:
|
||||
//
|
||||
// Reply type Result
|
||||
// array reply, nil
|
||||
// nil nil, ErrNil
|
||||
// other nil, error
|
||||
func Values(reply interface{}, err error) ([]interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
return reply, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
||||
}
|
||||
|
||||
// Strings is a helper that converts an array command reply to a []string. If
|
||||
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
||||
// converted to "" in the output slice. Strings returns an error if an array
|
||||
// item is not a bulk string or nil.
|
||||
func Strings(reply interface{}, err error) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
result := make([]string, len(reply))
|
||||
for i := range reply {
|
||||
if reply[i] == nil {
|
||||
continue
|
||||
}
|
||||
p, ok := reply[i].([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
|
||||
}
|
||||
result[i] = string(p)
|
||||
}
|
||||
return result, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
|
||||
}
|
||||
|
||||
// Ints is a helper that converts an array command reply to a []int. If
|
||||
// err is not equal to nil, then Ints returns nil, err.
|
||||
func Ints(reply interface{}, err error) ([]int, error) {
|
||||
var ints []int
|
||||
if reply == nil {
|
||||
return ints, ErrNil
|
||||
}
|
||||
values, err := Values(reply, err)
|
||||
if err != nil {
|
||||
return ints, err
|
||||
}
|
||||
if err := ScanSlice(values, &ints); err != nil {
|
||||
return ints, err
|
||||
}
|
||||
return ints, nil
|
||||
}
|
||||
|
||||
// StringMap is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func StringMap(result interface{}, err error) (map[string]string, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: StringMap expects even number of values result")
|
||||
}
|
||||
m := make(map[string]string, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, okKey := values[i].([]byte)
|
||||
value, okValue := values[i+1].([]byte)
|
||||
if !okKey || !okValue {
|
||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
||||
}
|
||||
m[string(key)] = string(value)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// IntMap is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]int. The HGETALL commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func IntMap(result interface{}, err error) (map[string]int, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: IntMap expects even number of values result")
|
||||
}
|
||||
m := make(map[string]int, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].([]byte)
|
||||
if !ok {
|
||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
||||
}
|
||||
value, err := Int(values[i+1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(key)] = value
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Int64Map is a helper that converts an array of strings (alternating key, value)
|
||||
// into a map[string]int64. The HGETALL commands return replies in this format.
|
||||
// Requires an even number of values in result.
|
||||
func Int64Map(result interface{}, err error) (map[string]int64, error) {
|
||||
values, err := Values(result, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("redigo: Int64Map expects even number of values result")
|
||||
}
|
||||
m := make(map[string]int64, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].([]byte)
|
||||
if !ok {
|
||||
return nil, errors.New("redigo: ScanMap key not a bulk string value")
|
||||
}
|
||||
value, err := Int64(values[i+1], nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[string(key)] = value
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
166
Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go
сгенерированный
поставляемый
Обычный файл
166
Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,166 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/garyburd/redigo/internal/redistest"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type valueError struct {
|
||||
v interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
func ve(v interface{}, err error) valueError {
|
||||
return valueError{v, err}
|
||||
}
|
||||
|
||||
var replyTests = []struct {
|
||||
name interface{}
|
||||
actual valueError
|
||||
expected valueError
|
||||
}{
|
||||
{
|
||||
"ints([v1, v2])",
|
||||
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
|
||||
ve([]int{4, 5}, nil),
|
||||
},
|
||||
{
|
||||
"ints(nil)",
|
||||
ve(redis.Ints(nil, nil)),
|
||||
ve([]int(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"strings([v1, v2])",
|
||||
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||
ve([]string{"v1", "v2"}, nil),
|
||||
},
|
||||
{
|
||||
"strings(nil)",
|
||||
ve(redis.Strings(nil, nil)),
|
||||
ve([]string(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"values([v1, v2])",
|
||||
ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||
ve([]interface{}{[]byte("v1"), []byte("v2")}, nil),
|
||||
},
|
||||
{
|
||||
"values(nil)",
|
||||
ve(redis.Values(nil, nil)),
|
||||
ve([]interface{}(nil), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"float64(1.0)",
|
||||
ve(redis.Float64([]byte("1.0"), nil)),
|
||||
ve(float64(1.0), nil),
|
||||
},
|
||||
{
|
||||
"float64(nil)",
|
||||
ve(redis.Float64(nil, nil)),
|
||||
ve(float64(0.0), redis.ErrNil),
|
||||
},
|
||||
{
|
||||
"uint64(1)",
|
||||
ve(redis.Uint64(int64(1), nil)),
|
||||
ve(uint64(1), nil),
|
||||
},
|
||||
{
|
||||
"uint64(-1)",
|
||||
ve(redis.Uint64(int64(-1), nil)),
|
||||
ve(uint64(0), redis.ErrNegativeInt),
|
||||
},
|
||||
}
|
||||
|
||||
func TestReply(t *testing.T) {
|
||||
for _, rt := range replyTests {
|
||||
if rt.actual.err != rt.expected.err {
|
||||
t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(rt.actual.v, rt.expected.v) {
|
||||
t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dial wraps DialTestDB() with a more suitable function name for examples.
|
||||
func dial() (redis.Conn, error) {
|
||||
return redistest.Dial()
|
||||
}
|
||||
|
||||
func ExampleBool() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "foo", 1)
|
||||
exists, _ := redis.Bool(c.Do("EXISTS", "foo"))
|
||||
fmt.Printf("%#v\n", exists)
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleInt() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "k1", 1)
|
||||
n, _ := redis.Int(c.Do("GET", "k1"))
|
||||
fmt.Printf("%#v\n", n)
|
||||
n, _ = redis.Int(c.Do("INCR", "k1"))
|
||||
fmt.Printf("%#v\n", n)
|
||||
// Output:
|
||||
// 1
|
||||
// 2
|
||||
}
|
||||
|
||||
func ExampleInts() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SADD", "set_with_integers", 4, 5, 6)
|
||||
ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers"))
|
||||
fmt.Printf("%#v\n", ints)
|
||||
// Output:
|
||||
// []int{4, 5, 6}
|
||||
}
|
||||
|
||||
func ExampleString() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "hello", "world")
|
||||
s, err := redis.String(c.Do("GET", "hello"))
|
||||
fmt.Printf("%#v\n", s)
|
||||
// Output:
|
||||
// "world"
|
||||
}
|
||||
513
Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go
сгенерированный
поставляемый
Обычный файл
513
Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,513 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func ensureLen(d reflect.Value, n int) {
|
||||
if n > d.Cap() {
|
||||
d.Set(reflect.MakeSlice(d.Type(), n, n))
|
||||
} else {
|
||||
d.SetLen(n)
|
||||
}
|
||||
}
|
||||
|
||||
func cannotConvert(d reflect.Value, s interface{}) error {
|
||||
return fmt.Errorf("redigo: Scan cannot convert from %s to %s",
|
||||
reflect.TypeOf(s), d.Type())
|
||||
}
|
||||
|
||||
func convertAssignBytes(d reflect.Value, s []byte) (err error) {
|
||||
switch d.Type().Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
var x float64
|
||||
x, err = strconv.ParseFloat(string(s), d.Type().Bits())
|
||||
d.SetFloat(x)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var x int64
|
||||
x, err = strconv.ParseInt(string(s), 10, d.Type().Bits())
|
||||
d.SetInt(x)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
var x uint64
|
||||
x, err = strconv.ParseUint(string(s), 10, d.Type().Bits())
|
||||
d.SetUint(x)
|
||||
case reflect.Bool:
|
||||
var x bool
|
||||
x, err = strconv.ParseBool(string(s))
|
||||
d.SetBool(x)
|
||||
case reflect.String:
|
||||
d.SetString(string(s))
|
||||
case reflect.Slice:
|
||||
if d.Type().Elem().Kind() != reflect.Uint8 {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
d.SetBytes(s)
|
||||
}
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertAssignInt(d reflect.Value, s int64) (err error) {
|
||||
switch d.Type().Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
d.SetInt(s)
|
||||
if d.Int() != s {
|
||||
err = strconv.ErrRange
|
||||
d.SetInt(0)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if s < 0 {
|
||||
err = strconv.ErrRange
|
||||
} else {
|
||||
x := uint64(s)
|
||||
d.SetUint(x)
|
||||
if d.Uint() != x {
|
||||
err = strconv.ErrRange
|
||||
d.SetUint(0)
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
d.SetBool(s != 0)
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertAssignValue(d reflect.Value, s interface{}) (err error) {
|
||||
switch s := s.(type) {
|
||||
case []byte:
|
||||
err = convertAssignBytes(d, s)
|
||||
case int64:
|
||||
err = convertAssignInt(d, s)
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func convertAssignValues(d reflect.Value, s []interface{}) error {
|
||||
if d.Type().Kind() != reflect.Slice {
|
||||
return cannotConvert(d, s)
|
||||
}
|
||||
ensureLen(d, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
if err := convertAssignValue(d.Index(i), s[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertAssign(d interface{}, s interface{}) (err error) {
|
||||
// Handle the most common destination types using type switches and
|
||||
// fall back to reflection for all other types.
|
||||
switch s := s.(type) {
|
||||
case nil:
|
||||
// ingore
|
||||
case []byte:
|
||||
switch d := d.(type) {
|
||||
case *string:
|
||||
*d = string(s)
|
||||
case *int:
|
||||
*d, err = strconv.Atoi(string(s))
|
||||
case *bool:
|
||||
*d, err = strconv.ParseBool(string(s))
|
||||
case *[]byte:
|
||||
*d = s
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignBytes(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case int64:
|
||||
switch d := d.(type) {
|
||||
case *int:
|
||||
x := int(s)
|
||||
if int64(x) != s {
|
||||
err = strconv.ErrRange
|
||||
x = 0
|
||||
}
|
||||
*d = x
|
||||
case *bool:
|
||||
*d = s != 0
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignInt(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
switch d := d.(type) {
|
||||
case *[]interface{}:
|
||||
*d = s
|
||||
case *interface{}:
|
||||
*d = s
|
||||
case nil:
|
||||
// skip value
|
||||
default:
|
||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
||||
err = cannotConvert(d, s)
|
||||
} else {
|
||||
err = convertAssignValues(d.Elem(), s)
|
||||
}
|
||||
}
|
||||
case Error:
|
||||
err = s
|
||||
default:
|
||||
err = cannotConvert(reflect.ValueOf(d), s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Scan copies from src to the values pointed at by dest.
|
||||
//
|
||||
// The values pointed at by dest must be an integer, float, boolean, string,
|
||||
// []byte, interface{} or slices of these types. Scan uses the standard strconv
|
||||
// package to convert bulk strings to numeric and boolean types.
|
||||
//
|
||||
// If a dest value is nil, then the corresponding src value is skipped.
|
||||
//
|
||||
// If a src element is nil, then the corresponding dest value is not modified.
|
||||
//
|
||||
// To enable easy use of Scan in a loop, Scan returns the slice of src
|
||||
// following the copied values.
|
||||
func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
|
||||
if len(src) < len(dest) {
|
||||
return nil, errors.New("redigo: Scan array short")
|
||||
}
|
||||
var err error
|
||||
for i, d := range dest {
|
||||
err = convertAssign(d, src[i])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return src[len(dest):], err
|
||||
}
|
||||
|
||||
type fieldSpec struct {
|
||||
name string
|
||||
index []int
|
||||
//omitEmpty bool
|
||||
}
|
||||
|
||||
type structSpec struct {
|
||||
m map[string]*fieldSpec
|
||||
l []*fieldSpec
|
||||
}
|
||||
|
||||
func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
|
||||
return ss.m[string(name)]
|
||||
}
|
||||
|
||||
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
switch {
|
||||
case f.PkgPath != "":
|
||||
// Ignore unexported fields.
|
||||
case f.Anonymous:
|
||||
// TODO: Handle pointers. Requires change to decoder and
|
||||
// protection against infinite recursion.
|
||||
if f.Type.Kind() == reflect.Struct {
|
||||
compileStructSpec(f.Type, depth, append(index, i), ss)
|
||||
}
|
||||
default:
|
||||
fs := &fieldSpec{name: f.Name}
|
||||
tag := f.Tag.Get("redis")
|
||||
p := strings.Split(tag, ",")
|
||||
if len(p) > 0 {
|
||||
if p[0] == "-" {
|
||||
continue
|
||||
}
|
||||
if len(p[0]) > 0 {
|
||||
fs.name = p[0]
|
||||
}
|
||||
for _, s := range p[1:] {
|
||||
switch s {
|
||||
//case "omitempty":
|
||||
// fs.omitempty = true
|
||||
default:
|
||||
panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
d, found := depth[fs.name]
|
||||
if !found {
|
||||
d = 1 << 30
|
||||
}
|
||||
switch {
|
||||
case len(index) == d:
|
||||
// At same depth, remove from result.
|
||||
delete(ss.m, fs.name)
|
||||
j := 0
|
||||
for i := 0; i < len(ss.l); i++ {
|
||||
if fs.name != ss.l[i].name {
|
||||
ss.l[j] = ss.l[i]
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
ss.l = ss.l[:j]
|
||||
case len(index) < d:
|
||||
fs.index = make([]int, len(index)+1)
|
||||
copy(fs.index, index)
|
||||
fs.index[len(index)] = i
|
||||
depth[fs.name] = len(index)
|
||||
ss.m[fs.name] = fs
|
||||
ss.l = append(ss.l, fs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
structSpecMutex sync.RWMutex
|
||||
structSpecCache = make(map[reflect.Type]*structSpec)
|
||||
defaultFieldSpec = &fieldSpec{}
|
||||
)
|
||||
|
||||
func structSpecForType(t reflect.Type) *structSpec {
|
||||
|
||||
structSpecMutex.RLock()
|
||||
ss, found := structSpecCache[t]
|
||||
structSpecMutex.RUnlock()
|
||||
if found {
|
||||
return ss
|
||||
}
|
||||
|
||||
structSpecMutex.Lock()
|
||||
defer structSpecMutex.Unlock()
|
||||
ss, found = structSpecCache[t]
|
||||
if found {
|
||||
return ss
|
||||
}
|
||||
|
||||
ss = &structSpec{m: make(map[string]*fieldSpec)}
|
||||
compileStructSpec(t, make(map[string]int), nil, ss)
|
||||
structSpecCache[t] = ss
|
||||
return ss
|
||||
}
|
||||
|
||||
var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct")
|
||||
|
||||
// ScanStruct scans alternating names and values from src to a struct. The
|
||||
// HGETALL and CONFIG GET commands return replies in this format.
|
||||
//
|
||||
// ScanStruct uses exported field names to match values in the response. Use
|
||||
// 'redis' field tag to override the name:
|
||||
//
|
||||
// Field int `redis:"myName"`
|
||||
//
|
||||
// Fields with the tag redis:"-" are ignored.
|
||||
//
|
||||
// Integer, float, boolean, string and []byte fields are supported. Scan uses the
|
||||
// standard strconv package to convert bulk string values to numeric and
|
||||
// boolean types.
|
||||
//
|
||||
// If a src element is nil, then the corresponding field is not modified.
|
||||
func ScanStruct(src []interface{}, dest interface{}) error {
|
||||
d := reflect.ValueOf(dest)
|
||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||
return errScanStructValue
|
||||
}
|
||||
d = d.Elem()
|
||||
if d.Kind() != reflect.Struct {
|
||||
return errScanStructValue
|
||||
}
|
||||
ss := structSpecForType(d.Type())
|
||||
|
||||
if len(src)%2 != 0 {
|
||||
return errors.New("redigo: ScanStruct expects even number of values in values")
|
||||
}
|
||||
|
||||
for i := 0; i < len(src); i += 2 {
|
||||
s := src[i+1]
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
name, ok := src[i].([]byte)
|
||||
if !ok {
|
||||
return errors.New("redigo: ScanStruct key not a bulk string value")
|
||||
}
|
||||
fs := ss.fieldSpec(name)
|
||||
if fs == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct")
|
||||
)
|
||||
|
||||
// ScanSlice scans src to the slice pointed to by dest. The elements the dest
|
||||
// slice must be integer, float, boolean, string, struct or pointer to struct
|
||||
// values.
|
||||
//
|
||||
// Struct fields must be integer, float, boolean or string values. All struct
|
||||
// fields are used unless a subset is specified using fieldNames.
|
||||
func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {
|
||||
d := reflect.ValueOf(dest)
|
||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
||||
return errScanSliceValue
|
||||
}
|
||||
d = d.Elem()
|
||||
if d.Kind() != reflect.Slice {
|
||||
return errScanSliceValue
|
||||
}
|
||||
|
||||
isPtr := false
|
||||
t := d.Type().Elem()
|
||||
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
||||
isPtr = true
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
ensureLen(d, len(src))
|
||||
for i, s := range src {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.Index(i), s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ss := structSpecForType(t)
|
||||
fss := ss.l
|
||||
if len(fieldNames) > 0 {
|
||||
fss = make([]*fieldSpec, len(fieldNames))
|
||||
for i, name := range fieldNames {
|
||||
fss[i] = ss.m[name]
|
||||
if fss[i] == nil {
|
||||
return errors.New("redigo: ScanSlice bad field name " + name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(fss) == 0 {
|
||||
return errors.New("redigo: ScanSlice no struct fields")
|
||||
}
|
||||
|
||||
n := len(src) / len(fss)
|
||||
if n*len(fss) != len(src) {
|
||||
return errors.New("redigo: ScanSlice length not a multiple of struct field count")
|
||||
}
|
||||
|
||||
ensureLen(d, n)
|
||||
for i := 0; i < n; i++ {
|
||||
d := d.Index(i)
|
||||
if isPtr {
|
||||
if d.IsNil() {
|
||||
d.Set(reflect.New(t))
|
||||
}
|
||||
d = d.Elem()
|
||||
}
|
||||
for j, fs := range fss {
|
||||
s := src[i*len(fss)+j]
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args is a helper for constructing command arguments from structured values.
|
||||
type Args []interface{}
|
||||
|
||||
// Add returns the result of appending value to args.
|
||||
func (args Args) Add(value ...interface{}) Args {
|
||||
return append(args, value...)
|
||||
}
|
||||
|
||||
// AddFlat returns the result of appending the flattened value of v to args.
|
||||
//
|
||||
// Maps are flattened by appending the alternating keys and map values to args.
|
||||
//
|
||||
// Slices are flattened by appending the slice elements to args.
|
||||
//
|
||||
// Structs are flattened by appending the alternating names and values of
|
||||
// exported fields to args. If v is a nil struct pointer, then nothing is
|
||||
// appended. The 'redis' field tag overrides struct field names. See ScanStruct
|
||||
// for more information on the use of the 'redis' field tag.
|
||||
//
|
||||
// Other types are appended to args as is.
|
||||
func (args Args) AddFlat(v interface{}) Args {
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Struct:
|
||||
args = flattenStruct(args, rv)
|
||||
case reflect.Slice:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
args = append(args, rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, k := range rv.MapKeys() {
|
||||
args = append(args, k.Interface(), rv.MapIndex(k).Interface())
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if rv.Type().Elem().Kind() == reflect.Struct {
|
||||
if !rv.IsNil() {
|
||||
args = flattenStruct(args, rv.Elem())
|
||||
}
|
||||
} else {
|
||||
args = append(args, v)
|
||||
}
|
||||
default:
|
||||
args = append(args, v)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func flattenStruct(args Args, v reflect.Value) Args {
|
||||
ss := structSpecForType(v.Type())
|
||||
for _, fs := range ss.l {
|
||||
fv := v.FieldByIndex(fs.index)
|
||||
args = append(args, fs.name, fv.Interface())
|
||||
}
|
||||
return args
|
||||
}
|
||||
412
Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go
сгенерированный
поставляемый
Обычный файл
412
Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,412 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var scanConversionTests = []struct {
|
||||
src interface{}
|
||||
dest interface{}
|
||||
}{
|
||||
{[]byte("-inf"), math.Inf(-1)},
|
||||
{[]byte("+inf"), math.Inf(1)},
|
||||
{[]byte("0"), float64(0)},
|
||||
{[]byte("3.14159"), float64(3.14159)},
|
||||
{[]byte("3.14"), float32(3.14)},
|
||||
{[]byte("-100"), int(-100)},
|
||||
{[]byte("101"), int(101)},
|
||||
{int64(102), int(102)},
|
||||
{[]byte("103"), uint(103)},
|
||||
{int64(104), uint(104)},
|
||||
{[]byte("105"), int8(105)},
|
||||
{int64(106), int8(106)},
|
||||
{[]byte("107"), uint8(107)},
|
||||
{int64(108), uint8(108)},
|
||||
{[]byte("0"), false},
|
||||
{int64(0), false},
|
||||
{[]byte("f"), false},
|
||||
{[]byte("1"), true},
|
||||
{int64(1), true},
|
||||
{[]byte("t"), true},
|
||||
{[]byte("hello"), "hello"},
|
||||
{[]byte("world"), []byte("world")},
|
||||
{[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}},
|
||||
{[]interface{}{[]byte("foo")}, []string{"foo"}},
|
||||
{[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}},
|
||||
{[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}},
|
||||
{[]interface{}{[]byte("1")}, []int{1}},
|
||||
{[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}},
|
||||
{[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
|
||||
{[]interface{}{[]byte("1")}, []byte{1}},
|
||||
{[]interface{}{[]byte("1")}, []bool{true}},
|
||||
}
|
||||
|
||||
func TestScanConversion(t *testing.T) {
|
||||
for _, tt := range scanConversionTests {
|
||||
values := []interface{}{tt.src}
|
||||
dest := reflect.New(reflect.TypeOf(tt.dest))
|
||||
values, err := redis.Scan(values, dest.Interface())
|
||||
if err != nil {
|
||||
t.Errorf("Scan(%v) returned error %v", tt, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {
|
||||
t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var scanConversionErrorTests = []struct {
|
||||
src interface{}
|
||||
dest interface{}
|
||||
}{
|
||||
{[]byte("1234"), byte(0)},
|
||||
{int64(1234), byte(0)},
|
||||
{[]byte("-1"), byte(0)},
|
||||
{int64(-1), byte(0)},
|
||||
{[]byte("junk"), false},
|
||||
{redis.Error("blah"), false},
|
||||
}
|
||||
|
||||
func TestScanConversionError(t *testing.T) {
|
||||
for _, tt := range scanConversionErrorTests {
|
||||
values := []interface{}{tt.src}
|
||||
dest := reflect.New(reflect.TypeOf(tt.dest))
|
||||
values, err := redis.Scan(values, dest.Interface())
|
||||
if err == nil {
|
||||
t.Errorf("Scan(%v) did not return error", tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleScan() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
|
||||
c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
|
||||
c.Send("HMSET", "album:3", "title", "Beat")
|
||||
c.Send("LPUSH", "albums", "1")
|
||||
c.Send("LPUSH", "albums", "2")
|
||||
c.Send("LPUSH", "albums", "3")
|
||||
values, err := redis.Values(c.Do("SORT", "albums",
|
||||
"BY", "album:*->rating",
|
||||
"GET", "album:*->title",
|
||||
"GET", "album:*->rating"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for len(values) > 0 {
|
||||
var title string
|
||||
rating := -1 // initialize to illegal value to detect nil.
|
||||
values, err = redis.Scan(values, &title, &rating)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if rating == -1 {
|
||||
fmt.Println(title, "not-rated")
|
||||
} else {
|
||||
fmt.Println(title, rating)
|
||||
}
|
||||
}
|
||||
// Output:
|
||||
// Beat not-rated
|
||||
// Earthbound 1
|
||||
// Red 5
|
||||
}
|
||||
|
||||
type s0 struct {
|
||||
X int
|
||||
Y int `redis:"y"`
|
||||
Bt bool
|
||||
}
|
||||
|
||||
type s1 struct {
|
||||
X int `redis:"-"`
|
||||
I int `redis:"i"`
|
||||
U uint `redis:"u"`
|
||||
S string `redis:"s"`
|
||||
P []byte `redis:"p"`
|
||||
B bool `redis:"b"`
|
||||
Bt bool
|
||||
Bf bool
|
||||
s0
|
||||
}
|
||||
|
||||
var scanStructTests = []struct {
|
||||
title string
|
||||
reply []string
|
||||
value interface{}
|
||||
}{
|
||||
{"basic",
|
||||
[]string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"},
|
||||
&s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}},
|
||||
},
|
||||
}
|
||||
|
||||
func TestScanStruct(t *testing.T) {
|
||||
for _, tt := range scanStructTests {
|
||||
|
||||
var reply []interface{}
|
||||
for _, v := range tt.reply {
|
||||
reply = append(reply, []byte(v))
|
||||
}
|
||||
|
||||
value := reflect.New(reflect.ValueOf(tt.value).Type().Elem())
|
||||
|
||||
if err := redis.ScanStruct(reply, value.Interface()); err != nil {
|
||||
t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(value.Interface(), tt.value) {
|
||||
t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadScanStructArgs(t *testing.T) {
|
||||
x := []interface{}{"A", "b"}
|
||||
test := func(v interface{}) {
|
||||
if err := redis.ScanStruct(x, v); err == nil {
|
||||
t.Errorf("Expect error for ScanStruct(%T, %T)", x, v)
|
||||
}
|
||||
}
|
||||
|
||||
test(nil)
|
||||
|
||||
var v0 *struct{}
|
||||
test(v0)
|
||||
|
||||
var v1 int
|
||||
test(&v1)
|
||||
|
||||
x = x[:1]
|
||||
v2 := struct{ A string }{}
|
||||
test(&v2)
|
||||
}
|
||||
|
||||
var scanSliceTests = []struct {
|
||||
src []interface{}
|
||||
fieldNames []string
|
||||
ok bool
|
||||
dest interface{}
|
||||
}{
|
||||
{
|
||||
[]interface{}{[]byte("1"), nil, []byte("-1")},
|
||||
nil,
|
||||
true,
|
||||
[]int{1, 0, -1},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("1"), nil, []byte("2")},
|
||||
nil,
|
||||
true,
|
||||
[]uint{1, 0, 2},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("-1")},
|
||||
nil,
|
||||
false,
|
||||
[]uint{1},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("hello"), nil, []byte("world")},
|
||||
nil,
|
||||
true,
|
||||
[][]byte{[]byte("hello"), nil, []byte("world")},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("hello"), nil, []byte("world")},
|
||||
nil,
|
||||
true,
|
||||
[]string{"hello", "", "world"},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
nil,
|
||||
true,
|
||||
[]struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1")},
|
||||
nil,
|
||||
false,
|
||||
[]struct{ A, B, C string }{{"a1", "b1", ""}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
nil,
|
||||
true,
|
||||
[]*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
[]string{"A", "B"},
|
||||
true,
|
||||
[]struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
|
||||
},
|
||||
{
|
||||
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
||||
nil,
|
||||
false,
|
||||
[]struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
func TestScanSlice(t *testing.T) {
|
||||
for _, tt := range scanSliceTests {
|
||||
|
||||
typ := reflect.ValueOf(tt.dest).Type()
|
||||
dest := reflect.New(typ)
|
||||
|
||||
err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
|
||||
if tt.ok != (err == nil) {
|
||||
t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
|
||||
continue
|
||||
}
|
||||
if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
|
||||
t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleScanSlice() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
|
||||
c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
|
||||
c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
|
||||
c.Send("LPUSH", "albums", "1")
|
||||
c.Send("LPUSH", "albums", "2")
|
||||
c.Send("LPUSH", "albums", "3")
|
||||
values, err := redis.Values(c.Do("SORT", "albums",
|
||||
"BY", "album:*->rating",
|
||||
"GET", "album:*->title",
|
||||
"GET", "album:*->rating"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var albums []struct {
|
||||
Title string
|
||||
Rating int
|
||||
}
|
||||
if err := redis.ScanSlice(values, &albums); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("%v\n", albums)
|
||||
// Output:
|
||||
// [{Earthbound 1} {Beat 4} {Red 5}]
|
||||
}
|
||||
|
||||
var argsTests = []struct {
|
||||
title string
|
||||
actual redis.Args
|
||||
expected redis.Args
|
||||
}{
|
||||
{"struct ptr",
|
||||
redis.Args{}.AddFlat(&struct {
|
||||
I int `redis:"i"`
|
||||
U uint `redis:"u"`
|
||||
S string `redis:"s"`
|
||||
P []byte `redis:"p"`
|
||||
Bt bool
|
||||
Bf bool
|
||||
}{
|
||||
-1234, 5678, "hello", []byte("world"), true, false,
|
||||
}),
|
||||
redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "Bt", true, "Bf", false},
|
||||
},
|
||||
{"struct",
|
||||
redis.Args{}.AddFlat(struct{ I int }{123}),
|
||||
redis.Args{"I", 123},
|
||||
},
|
||||
{"slice",
|
||||
redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
|
||||
redis.Args{1, "a", "b", "c", 2},
|
||||
},
|
||||
}
|
||||
|
||||
func TestArgs(t *testing.T) {
|
||||
for _, tt := range argsTests {
|
||||
if !reflect.DeepEqual(tt.actual, tt.expected) {
|
||||
t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleArgs() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
var p1, p2 struct {
|
||||
Title string `redis:"title"`
|
||||
Author string `redis:"author"`
|
||||
Body string `redis:"body"`
|
||||
}
|
||||
|
||||
p1.Title = "Example"
|
||||
p1.Author = "Gary"
|
||||
p1.Body = "Hello"
|
||||
|
||||
if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
m := map[string]string{
|
||||
"title": "Example2",
|
||||
"author": "Steve",
|
||||
"body": "Map",
|
||||
}
|
||||
|
||||
if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, id := range []string{"id1", "id2"} {
|
||||
|
||||
v, err := redis.Values(c.Do("HGETALL", id))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := redis.ScanStruct(v, &p2); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", p2)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {Title:Example Author:Gary Body:Hello}
|
||||
// {Title:Example2 Author:Steve Body:Map}
|
||||
}
|
||||
86
Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go
сгенерированный
поставляемый
Обычный файл
86
Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,86 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Script encapsulates the source, hash and key count for a Lua script. See
|
||||
// http://redis.io/commands/eval for information on scripts in Redis.
|
||||
type Script struct {
|
||||
keyCount int
|
||||
src string
|
||||
hash string
|
||||
}
|
||||
|
||||
// NewScript returns a new script object. If keyCount is greater than or equal
|
||||
// to zero, then the count is automatically inserted in the EVAL command
|
||||
// argument list. If keyCount is less than zero, then the application supplies
|
||||
// the count as the first value in the keysAndArgs argument to the Do, Send and
|
||||
// SendHash methods.
|
||||
func NewScript(keyCount int, src string) *Script {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, src)
|
||||
return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
|
||||
}
|
||||
|
||||
func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
|
||||
var args []interface{}
|
||||
if s.keyCount < 0 {
|
||||
args = make([]interface{}, 1+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
copy(args[1:], keysAndArgs)
|
||||
} else {
|
||||
args = make([]interface{}, 2+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
args[1] = s.keyCount
|
||||
copy(args[2:], keysAndArgs)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Do evaluates the script. Under the covers, Do optimistically evaluates the
|
||||
// script using the EVALSHA command. If the command fails because the script is
|
||||
// not loaded, then Do evaluates the script using the EVAL command (thus
|
||||
// causing the script to load).
|
||||
func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
|
||||
v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
|
||||
v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// SendHash evaluates the script without waiting for the reply. The script is
|
||||
// evaluated with the EVALSHA command. The application must ensure that the
|
||||
// script is loaded by a previous call to Send, Do or Load methods.
|
||||
func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Send evaluates the script without waiting for the reply.
|
||||
func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Load loads the script without evaluating it.
|
||||
func (s *Script) Load(c Conn) error {
|
||||
_, err := c.Do("SCRIPT", "LOAD", s.src)
|
||||
return err
|
||||
}
|
||||
93
Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go
сгенерированный
поставляемый
Обычный файл
93
Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,93 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/internal/redistest"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
func ExampleScript(c redis.Conn, reply interface{}, err error) {
|
||||
// Initialize a package-level variable with a script.
|
||||
var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`)
|
||||
|
||||
// In a function, use the script Do method to evaluate the script. The Do
|
||||
// method optimistically uses the EVALSHA command. If the script is not
|
||||
// loaded, then the Do method falls back to the EVAL command.
|
||||
reply, err = getScript.Do(c, "foo")
|
||||
}
|
||||
|
||||
func TestScript(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// To test fall back in Do, we make script unique by adding comment with current time.
|
||||
script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano())
|
||||
s := redis.NewScript(2, script)
|
||||
reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")}
|
||||
|
||||
v, err := s.Do(c, "key1", "key2", "arg1", "arg2")
|
||||
if err != nil {
|
||||
t.Errorf("s.Do(c, ...) returned %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(v, reply) {
|
||||
t.Errorf("s.Do(c, ..); = %v, want %v", v, reply)
|
||||
}
|
||||
|
||||
err = s.Load(c)
|
||||
if err != nil {
|
||||
t.Errorf("s.Load(c) returned %v", err)
|
||||
}
|
||||
|
||||
err = s.SendHash(c, "key1", "key2", "arg1", "arg2")
|
||||
if err != nil {
|
||||
t.Errorf("s.SendHash(c, ...) returned %v", err)
|
||||
}
|
||||
|
||||
err = c.Flush()
|
||||
if err != nil {
|
||||
t.Errorf("c.Flush() returned %v", err)
|
||||
}
|
||||
|
||||
v, err = c.Receive()
|
||||
if !reflect.DeepEqual(v, reply) {
|
||||
t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply)
|
||||
}
|
||||
|
||||
err = s.Send(c, "key1", "key2", "arg1", "arg2")
|
||||
if err != nil {
|
||||
t.Errorf("s.Send(c, ...) returned %v", err)
|
||||
}
|
||||
|
||||
err = c.Flush()
|
||||
if err != nil {
|
||||
t.Errorf("c.Flush() returned %v", err)
|
||||
}
|
||||
|
||||
v, err = c.Receive()
|
||||
if !reflect.DeepEqual(v, reply) {
|
||||
t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply)
|
||||
}
|
||||
|
||||
}
|
||||
38
Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go
сгенерированный
поставляемый
Обычный файл
38
Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,38 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetNowFunc(f func() time.Time) {
|
||||
nowFunc = f
|
||||
}
|
||||
|
||||
type nopCloser struct{ net.Conn }
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
// NewConnBufio is a hook for tests.
|
||||
func NewConnBufio(rw bufio.ReadWriter) Conn {
|
||||
return &conn{br: rw.Reader, bw: rw.Writer, conn: nopCloser{}}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNegativeInt = errNegativeInt
|
||||
)
|
||||
113
Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go
сгенерированный
поставляемый
Обычный файл
113
Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,113 @@
|
||||
// Copyright 2013 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.
|
||||
func zpop(c redis.Conn, key string) (result string, err error) {
|
||||
|
||||
defer func() {
|
||||
// Return connection to normal state on error.
|
||||
if err != nil {
|
||||
c.Do("DISCARD")
|
||||
}
|
||||
}()
|
||||
|
||||
// Loop until transaction is successful.
|
||||
for {
|
||||
if _, err := c.Do("WATCH", key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(members) != 1 {
|
||||
return "", redis.ErrNil
|
||||
}
|
||||
|
||||
c.Send("MULTI")
|
||||
c.Send("ZREM", key, members[0])
|
||||
queued, err := c.Do("EXEC")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if queued != nil {
|
||||
result = members[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// zpopScript pops a value from a ZSET.
|
||||
var zpopScript = redis.NewScript(1, `
|
||||
local r = redis.call('ZRANGE', KEYS[1], 0, 0)
|
||||
if r ~= nil then
|
||||
r = r[1]
|
||||
redis.call('ZREM', KEYS[1], r)
|
||||
end
|
||||
return r
|
||||
`)
|
||||
|
||||
// This example implements ZPOP as described at
|
||||
// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.
|
||||
func Example_zpop() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Add test data using a pipeline.
|
||||
|
||||
for i, member := range []string{"red", "blue", "green"} {
|
||||
c.Send("ZADD", "zset", i, member)
|
||||
}
|
||||
if _, err := c.Do(""); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Pop using WATCH/MULTI/EXEC
|
||||
|
||||
v, err := zpop(c, "zset")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(v)
|
||||
|
||||
// Pop using a script.
|
||||
|
||||
v, err = redis.String(zpopScript.Do(c, "zset"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(v)
|
||||
|
||||
// Output:
|
||||
// red
|
||||
// blue
|
||||
}
|
||||
121
Godeps/_workspace/src/github.com/golang/groupcache/lru/lru.go
сгенерированный
поставляемый
Обычный файл
121
Godeps/_workspace/src/github.com/golang/groupcache/lru/lru.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright 2013 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package lru implements an LRU cache.
|
||||
package lru
|
||||
|
||||
import "container/list"
|
||||
|
||||
// Cache is an LRU cache. It is not safe for concurrent access.
|
||||
type Cache struct {
|
||||
// MaxEntries is the maximum number of cache entries before
|
||||
// an item is evicted. Zero means no limit.
|
||||
MaxEntries int
|
||||
|
||||
// OnEvicted optionally specificies a callback function to be
|
||||
// executed when an entry is purged from the cache.
|
||||
OnEvicted func(key Key, value interface{})
|
||||
|
||||
ll *list.List
|
||||
cache map[interface{}]*list.Element
|
||||
}
|
||||
|
||||
// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators
|
||||
type Key interface{}
|
||||
|
||||
type entry struct {
|
||||
key Key
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// New creates a new Cache.
|
||||
// If maxEntries is zero, the cache has no limit and it's assumed
|
||||
// that eviction is done by the caller.
|
||||
func New(maxEntries int) *Cache {
|
||||
return &Cache{
|
||||
MaxEntries: maxEntries,
|
||||
ll: list.New(),
|
||||
cache: make(map[interface{}]*list.Element),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a value to the cache.
|
||||
func (c *Cache) Add(key Key, value interface{}) {
|
||||
if c.cache == nil {
|
||||
c.cache = make(map[interface{}]*list.Element)
|
||||
c.ll = list.New()
|
||||
}
|
||||
if ee, ok := c.cache[key]; ok {
|
||||
c.ll.MoveToFront(ee)
|
||||
ee.Value.(*entry).value = value
|
||||
return
|
||||
}
|
||||
ele := c.ll.PushFront(&entry{key, value})
|
||||
c.cache[key] = ele
|
||||
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
|
||||
c.RemoveOldest()
|
||||
}
|
||||
}
|
||||
|
||||
// Get looks up a key's value from the cache.
|
||||
func (c *Cache) Get(key Key) (value interface{}, ok bool) {
|
||||
if c.cache == nil {
|
||||
return
|
||||
}
|
||||
if ele, hit := c.cache[key]; hit {
|
||||
c.ll.MoveToFront(ele)
|
||||
return ele.Value.(*entry).value, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *Cache) Remove(key Key) {
|
||||
if c.cache == nil {
|
||||
return
|
||||
}
|
||||
if ele, hit := c.cache[key]; hit {
|
||||
c.removeElement(ele)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveOldest removes the oldest item from the cache.
|
||||
func (c *Cache) RemoveOldest() {
|
||||
if c.cache == nil {
|
||||
return
|
||||
}
|
||||
ele := c.ll.Back()
|
||||
if ele != nil {
|
||||
c.removeElement(ele)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) removeElement(e *list.Element) {
|
||||
c.ll.Remove(e)
|
||||
kv := e.Value.(*entry)
|
||||
delete(c.cache, kv.key)
|
||||
if c.OnEvicted != nil {
|
||||
c.OnEvicted(kv.key, kv.value)
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *Cache) Len() int {
|
||||
if c.cache == nil {
|
||||
return 0
|
||||
}
|
||||
return c.ll.Len()
|
||||
}
|
||||
73
Godeps/_workspace/src/github.com/golang/groupcache/lru/lru_test.go
сгенерированный
поставляемый
Обычный файл
73
Godeps/_workspace/src/github.com/golang/groupcache/lru/lru_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2013 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lru
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type simpleStruct struct {
|
||||
int
|
||||
string
|
||||
}
|
||||
|
||||
type complexStruct struct {
|
||||
int
|
||||
simpleStruct
|
||||
}
|
||||
|
||||
var getTests = []struct {
|
||||
name string
|
||||
keyToAdd interface{}
|
||||
keyToGet interface{}
|
||||
expectedOk bool
|
||||
}{
|
||||
{"string_hit", "myKey", "myKey", true},
|
||||
{"string_miss", "myKey", "nonsense", false},
|
||||
{"simple_struct_hit", simpleStruct{1, "two"}, simpleStruct{1, "two"}, true},
|
||||
{"simeple_struct_miss", simpleStruct{1, "two"}, simpleStruct{0, "noway"}, false},
|
||||
{"complex_struct_hit", complexStruct{1, simpleStruct{2, "three"}},
|
||||
complexStruct{1, simpleStruct{2, "three"}}, true},
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
for _, tt := range getTests {
|
||||
lru := New(0)
|
||||
lru.Add(tt.keyToAdd, 1234)
|
||||
val, ok := lru.Get(tt.keyToGet)
|
||||
if ok != tt.expectedOk {
|
||||
t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok)
|
||||
} else if ok && val != 1234 {
|
||||
t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
lru := New(0)
|
||||
lru.Add("myKey", 1234)
|
||||
if val, ok := lru.Get("myKey"); !ok {
|
||||
t.Fatal("TestRemove returned no match")
|
||||
} else if val != 1234 {
|
||||
t.Fatalf("TestRemove failed. Expected %d, got %v", 1234, val)
|
||||
}
|
||||
|
||||
lru.Remove("myKey")
|
||||
if _, ok := lru.Get("myKey"); ok {
|
||||
t.Fatal("TestRemove returned a removed entry")
|
||||
}
|
||||
}
|
||||
10
Godeps/_workspace/src/github.com/throttled/throttled/.gitignore
сгенерированный
поставляемый
Обычный файл
10
Godeps/_workspace/src/github.com/throttled/throttled/.gitignore
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
*.test
|
||||
examples/interval/interval
|
||||
examples/interval-vary/interval-vary
|
||||
examples/interval-many/interval-many
|
||||
examples/memstats/memstats
|
||||
examples/rate-limit/rate-limit
|
||||
examples/custom/custom
|
||||
10
Godeps/_workspace/src/github.com/throttled/throttled/.travis.yml
сгенерированный
поставляемый
Обычный файл
10
Godeps/_workspace/src/github.com/throttled/throttled/.travis.yml
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,10 @@
|
||||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- tip
|
||||
|
||||
install: go get -t ./...
|
||||
|
||||
script: go test -v -short ./...
|
||||
12
Godeps/_workspace/src/github.com/throttled/throttled/LICENSE
сгенерированный
поставляемый
Обычный файл
12
Godeps/_workspace/src/github.com/throttled/throttled/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2014, Martin Angers and Contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of 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.
|
||||
|
||||
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.
|
||||
80
Godeps/_workspace/src/github.com/throttled/throttled/README.md
сгенерированный
поставляемый
Обычный файл
80
Godeps/_workspace/src/github.com/throttled/throttled/README.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,80 @@
|
||||
# Throttled [](http://travis-ci.org/throttled/throttled) [](http://godoc.org/github.com/throttled/throttled)
|
||||
|
||||
Package throttled implements different throttling strategies for controlling
|
||||
access to HTTP handlers.
|
||||
|
||||
*As of July 27, 2015, the package is now located under its own GitHub organization, please adjust your imports to `github.com/throttled/throttled`.*
|
||||
|
||||
## Installation
|
||||
|
||||
`go get github.com/throttled/throttled/...`
|
||||
|
||||
## Interval
|
||||
|
||||
The Interval function creates a throttler that allows requests to go through at
|
||||
a controlled, constant interval. The interval may be applied to all requests
|
||||
(vary argument == nil) or independently based on vary-by criteria.
|
||||
|
||||
For example:
|
||||
|
||||
th := throttled.Interval(throttled.PerSec(10), 100, &throttled.VaryBy{Path: true}, 50)
|
||||
h := th.Throttle(myHandler)
|
||||
http.ListenAndServe(":9000", h)
|
||||
|
||||
Creates a throttler that will allow a request each 100ms (10 requests per second), with
|
||||
a buffer of 100 exceeding requests before dropping requests with a status code 429 (by
|
||||
default, configurable using th.DeniedHandler or the package-global DefaultDeniedHandler
|
||||
variable). Different paths will be throttled independently, so that /path_a and /path_b
|
||||
both can serve 10 requests per second. The last argument, 50, indicates the maximum number
|
||||
of keys that the throttler will keep in memory.
|
||||
|
||||
## MemStats
|
||||
|
||||
The MemStats function creates a throttler that allows requests to go through only if
|
||||
the memory statistics of the current process are below specified thresholds.
|
||||
|
||||
For example:
|
||||
|
||||
th := throttled.MemStats(throttled.MemThresholds(&runtime.MemStats{NumGC: 10}, 10*time.Millisecond)
|
||||
h := th.Throttle(myHandler)
|
||||
http.ListenAndServe(":9000", h)
|
||||
|
||||
Creates a throttler that will allow requests to go through until the number of garbage
|
||||
collections reaches the initial number + 10 (the MemThresholds function creates absolute
|
||||
memory stats thresholds from offsets). The second argument, 10ms, indicates the refresh
|
||||
rate of the memory stats.
|
||||
|
||||
## RateLimit
|
||||
|
||||
The RateLimit function creates a throttler that allows a certain number of requests in
|
||||
a given time window, as is often implemented in public RESTful APIs.
|
||||
|
||||
For example:
|
||||
|
||||
th := throttled.RateLimit(throttled.PerMin(30), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(1000))
|
||||
h := th.Throttle(myHandler)
|
||||
http.ListenAndServe(":9000", h)
|
||||
|
||||
Creates a throttler that will limit requests to 30 per minute, based on the remote address
|
||||
of the client, and will store the counter and remaining time of the current window in the
|
||||
provided memory store, limiting the number of keys to keep in memory to 1000. The store
|
||||
sub-package also provides a Redis-based Store implementations.
|
||||
|
||||
The RateLimit throttler sets the expected X-RateLimit-* headers on the response, and
|
||||
also sets a Retry-After header when the limit is exceeded.
|
||||
|
||||
## Documentation
|
||||
|
||||
The API documentation is available as usual on [godoc.org][doc].
|
||||
|
||||
There is also a [blog post explaining the package's usage on 0value.com][blog].
|
||||
|
||||
Finally, many examples are provided in the /examples sub-folder of the repository.
|
||||
|
||||
## License
|
||||
|
||||
The [BSD 3-clause license][bsd]. Copyright (c) 2014 Martin Angers and Contributors.
|
||||
|
||||
[doc]: http://godoc.org/github.com/throttled/throttled
|
||||
[blog]: http://0value.com/throttled--guardian-of-the-web-server
|
||||
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
||||
65
Godeps/_workspace/src/github.com/throttled/throttled/common_test.go
сгенерированный
поставляемый
Обычный файл
65
Godeps/_workspace/src/github.com/throttled/throttled/common_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,65 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/boom/commands"
|
||||
)
|
||||
|
||||
type stats struct {
|
||||
sync.Mutex
|
||||
ok int
|
||||
dropped int
|
||||
ts []time.Time
|
||||
|
||||
body func()
|
||||
}
|
||||
|
||||
func (s *stats) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if s.body != nil {
|
||||
s.body()
|
||||
}
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.ts = append(s.ts, time.Now())
|
||||
s.ok++
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
func (s *stats) DeniedHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.dropped++
|
||||
w.WriteHeader(deniedStatus)
|
||||
}
|
||||
|
||||
func (s *stats) Stats() (int, int, []time.Time) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
return s.ok, s.dropped, s.ts
|
||||
}
|
||||
|
||||
func runTest(h http.Handler, b ...commands.Boom) []*commands.Report {
|
||||
srv := httptest.NewServer(h)
|
||||
defer srv.Close()
|
||||
|
||||
var rpts []*commands.Report
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
wg.Add(len(b))
|
||||
for i, bo := range b {
|
||||
bo.Req.Url = srv.URL + fmt.Sprintf("/%d", i)
|
||||
go func(bo commands.Boom) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
rpts = append(rpts, bo.Run())
|
||||
wg.Done()
|
||||
}(bo)
|
||||
}
|
||||
wg.Wait()
|
||||
return rpts
|
||||
}
|
||||
109
Godeps/_workspace/src/github.com/throttled/throttled/delayer.go
сгенерированный
поставляемый
Обычный файл
109
Godeps/_workspace/src/github.com/throttled/throttled/delayer.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,109 @@
|
||||
package throttled
|
||||
|
||||
import "time"
|
||||
|
||||
// The Quota interface defines the method to implement to describe
|
||||
// a time-window quota, as required by the RateLimit throttler.
|
||||
type Quota interface {
|
||||
// Quota returns a number of requests allowed, and a duration.
|
||||
Quota() (int, time.Duration)
|
||||
}
|
||||
|
||||
// The Delayer interface defines the method to implement to describe
|
||||
// a delay as required by the Interval throttler.
|
||||
type Delayer interface {
|
||||
// Delay returns a duration.
|
||||
Delay() time.Duration
|
||||
}
|
||||
|
||||
// PerSec represents a number of requests per second.
|
||||
type PerSec int
|
||||
|
||||
// Delay returns the duration to wait before the next request can go through,
|
||||
// so that PerSec(n) == n requests per second at regular intervals.
|
||||
func (ps PerSec) Delay() time.Duration {
|
||||
if ps <= 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(1.0 / float64(ps) * float64(time.Second))
|
||||
}
|
||||
|
||||
// Quota returns the number of requests allowed in a 1 second time window,
|
||||
// so that PerSec(n) == n requests allowed per second.
|
||||
func (ps PerSec) Quota() (int, time.Duration) {
|
||||
return int(ps), time.Second
|
||||
}
|
||||
|
||||
// PerMin represents a number of requests per minute.
|
||||
type PerMin int
|
||||
|
||||
// Delay returns the duration to wait before the next request can go through,
|
||||
// so that PerMin(n) == n requests per minute at regular intervals.
|
||||
func (pm PerMin) Delay() time.Duration {
|
||||
if pm <= 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(1.0 / float64(pm) * float64(time.Minute))
|
||||
}
|
||||
|
||||
// Quota returns the number of requests allowed in a 1 minute time window,
|
||||
// so that PerMin(n) == n requests allowed per minute.
|
||||
func (pm PerMin) Quota() (int, time.Duration) {
|
||||
return int(pm), time.Minute
|
||||
}
|
||||
|
||||
// PerHour represents a number of requests per hour.
|
||||
type PerHour int
|
||||
|
||||
// Delay returns the duration to wait before the next request can go through,
|
||||
// so that PerHour(n) == n requests per hour at regular intervals.
|
||||
func (ph PerHour) Delay() time.Duration {
|
||||
if ph <= 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(1.0 / float64(ph) * float64(time.Hour))
|
||||
}
|
||||
|
||||
// Quota returns the number of requests allowed in a 1 hour time window,
|
||||
// so that PerHour(n) == n requests allowed per hour.
|
||||
func (ph PerHour) Quota() (int, time.Duration) {
|
||||
return int(ph), time.Hour
|
||||
}
|
||||
|
||||
// PerDay represents a number of requests per day.
|
||||
type PerDay int
|
||||
|
||||
// Delay returns the duration to wait before the next request can go through,
|
||||
// so that PerDay(n) == n requests per day at regular intervals.
|
||||
func (pd PerDay) Delay() time.Duration {
|
||||
if pd <= 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(1.0 / float64(pd) * float64(24*time.Hour))
|
||||
}
|
||||
|
||||
// Quota returns the number of requests allowed in a 1 day time window,
|
||||
// so that PerDay(n) == n requests allowed per day.
|
||||
func (pd PerDay) Quota() (int, time.Duration) {
|
||||
return int(pd), 24 * time.Hour
|
||||
}
|
||||
|
||||
// D represents a custom delay.
|
||||
type D time.Duration
|
||||
|
||||
// Delay returns the duration to wait before the next request can go through,
|
||||
// which is the custom duration represented by the D value.
|
||||
func (d D) Delay() time.Duration {
|
||||
return time.Duration(d)
|
||||
}
|
||||
|
||||
// Q represents a custom quota.
|
||||
type Q struct {
|
||||
Requests int
|
||||
Window time.Duration
|
||||
}
|
||||
|
||||
// Quota returns the number of requests allowed and the custom time window.
|
||||
func (q Q) Quota() (int, time.Duration) {
|
||||
return q.Requests, q.Window
|
||||
}
|
||||
65
Godeps/_workspace/src/github.com/throttled/throttled/delayer_test.go
сгенерированный
поставляемый
Обычный файл
65
Godeps/_workspace/src/github.com/throttled/throttled/delayer_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,65 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDelayer(t *testing.T) {
|
||||
cases := []struct {
|
||||
in Delayer
|
||||
out time.Duration
|
||||
}{
|
||||
0: {PerSec(1), time.Second},
|
||||
1: {PerSec(2), 500 * time.Millisecond},
|
||||
2: {PerSec(4), 250 * time.Millisecond},
|
||||
3: {PerSec(5), 200 * time.Millisecond},
|
||||
4: {PerSec(10), 100 * time.Millisecond},
|
||||
5: {PerSec(100), 10 * time.Millisecond},
|
||||
6: {PerSec(3), 333333333 * time.Nanosecond},
|
||||
7: {PerMin(1), time.Minute},
|
||||
8: {PerMin(2), 30 * time.Second},
|
||||
9: {PerMin(4), 15 * time.Second},
|
||||
10: {PerMin(5), 12 * time.Second},
|
||||
11: {PerMin(10), 6 * time.Second},
|
||||
12: {PerMin(60), time.Second},
|
||||
13: {PerHour(1), time.Hour},
|
||||
14: {PerHour(2), 30 * time.Minute},
|
||||
15: {PerHour(4), 15 * time.Minute},
|
||||
16: {PerHour(60), time.Minute},
|
||||
17: {PerHour(120), 30 * time.Second},
|
||||
18: {D(time.Second), time.Second},
|
||||
19: {D(5 * time.Minute), 5 * time.Minute},
|
||||
20: {PerSec(200), 5 * time.Millisecond},
|
||||
21: {PerDay(24), time.Hour},
|
||||
}
|
||||
for i, c := range cases {
|
||||
got := c.in.Delay()
|
||||
if got != c.out {
|
||||
t.Errorf("%d: expected %s, got %s", i, c.out, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuota(t *testing.T) {
|
||||
cases := []struct {
|
||||
q Quota
|
||||
reqs int
|
||||
win time.Duration
|
||||
}{
|
||||
0: {PerSec(10), 10, time.Second},
|
||||
1: {PerMin(30), 30, time.Minute},
|
||||
2: {PerHour(124), 124, time.Hour},
|
||||
3: {PerDay(1), 1, 24 * time.Hour},
|
||||
4: {Q{148, 17 * time.Second}, 148, 17 * time.Second},
|
||||
}
|
||||
for i, c := range cases {
|
||||
r, w := c.q.Quota()
|
||||
if r != c.reqs {
|
||||
t.Errorf("%d: expected %d requests, got %d", i, c.reqs, r)
|
||||
}
|
||||
if w != c.win {
|
||||
t.Errorf("%d: expected %s window, got %s", i, c.win, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Godeps/_workspace/src/github.com/throttled/throttled/doc.go
сгенерированный
поставляемый
Обычный файл
77
Godeps/_workspace/src/github.com/throttled/throttled/doc.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,77 @@
|
||||
// Package throttled implements different throttling strategies for controlling
|
||||
// access to HTTP handlers.
|
||||
//
|
||||
// Installation
|
||||
//
|
||||
// go get github.com/throttled/throttled/...
|
||||
//
|
||||
// Inverval
|
||||
//
|
||||
// The Interval function creates a throttler that allows requests to go through at
|
||||
// a controlled, constant interval. The interval may be applied to all requests
|
||||
// (vary argument == nil) or independently based on vary-by criteria.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// th := throttled.Interval(throttled.PerSec(10), 100, &throttled.VaryBy{Path: true}, 50)
|
||||
// h := th.Throttle(myHandler)
|
||||
// http.ListenAndServe(":9000", h)
|
||||
//
|
||||
// Creates a throttler that will allow a request each 100ms (10 requests per second), with
|
||||
// a buffer of 100 exceeding requests before dropping requests with a status code 429 (by
|
||||
// default, configurable using th.DeniedHandler or the package-global DefaultDeniedHandler
|
||||
// variable). Different paths will be throttled independently, so that /path_a and /path_b
|
||||
// both can serve 10 requests per second. The last argument, 50, indicates the maximum number
|
||||
// of keys that the throttler will keep in memory.
|
||||
//
|
||||
// MemStats
|
||||
//
|
||||
// The MemStats function creates a throttler that allows requests to go through only if
|
||||
// the memory statistics of the current process are below specified thresholds.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// th := throttled.MemStats(throttled.MemThresholds(&runtime.MemStats{NumGC: 10}, 10*time.Millisecond)
|
||||
// h := th.Throttle(myHandler)
|
||||
// http.ListenAndServe(":9000", h)
|
||||
//
|
||||
// Creates a throttler that will allow requests to go through until the number of garbage
|
||||
// collections reaches the initial number + 10 (the MemThresholds function creates absolute
|
||||
// memory stats thresholds from offsets). The second argument, 10ms, indicates the refresh
|
||||
// rate of the memory stats.
|
||||
//
|
||||
// RateLimit
|
||||
//
|
||||
// The RateLimit function creates a throttler that allows a certain number of requests in
|
||||
// a given time window, as is often implemented in public RESTful APIs.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// th := throttled.RateLimit(throttled.PerMin(30), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(1000))
|
||||
// h := th.Throttle(myHandler)
|
||||
// http.ListenAndServe(":9000", h)
|
||||
//
|
||||
// Creates a throttler that will limit requests to 30 per minute, based on the remote address
|
||||
// of the client, and will store the counter and remaining time of the current window in the
|
||||
// provided memory store, limiting the number of keys to keep in memory to 1000. The store
|
||||
// sub-package also provides a Redis-based Store implementations.
|
||||
//
|
||||
// The RateLimit throttler sets the expected X-RateLimit-* headers on the response, and
|
||||
// also sets a Retry-After header when the limit is exceeded.
|
||||
//
|
||||
// Documentation
|
||||
//
|
||||
// The API documentation is available as usual on godoc.org:
|
||||
// http://godoc.org/github.com/throttled/throttled
|
||||
//
|
||||
// There is also a blog post explaining the package's usage on 0value.com:
|
||||
// http://0value.com/throttled--guardian-of-the-web-server
|
||||
//
|
||||
// Finally, many examples are provided in the /examples sub-folder of the repository.
|
||||
//
|
||||
// License
|
||||
//
|
||||
// The BSD 3-clause license. Copyright (c) 2014 Martin Angers and Contributors.
|
||||
// http://opensource.org/licenses/BSD-3-Clause
|
||||
//
|
||||
package throttled
|
||||
12
Godeps/_workspace/src/github.com/throttled/throttled/examples/README.md
сгенерированный
поставляемый
Обычный файл
12
Godeps/_workspace/src/github.com/throttled/throttled/examples/README.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,12 @@
|
||||
# Examples
|
||||
|
||||
This directory contains examples for all the throttlers implemented by the throttled package, as well as an example of a custom limiter.
|
||||
|
||||
* custom/ : implements a custom limiter that allows requests to path /a on even seconds, and on path /b on odd seconds.
|
||||
* interval-many/ : implements a common interval throttler to control two different handlers, one for path /a and another for path /b, so that requests to any one of the handlers go through at the specified interval.
|
||||
* interval-vary/ : implements an interval throttler that varies by path, so that requests to each different path goes through at the specified interval.
|
||||
* interval/ : implements an interval throttler so that any request goes through at the specified interval, regardless of path or any other criteria.
|
||||
* memstats/ : implements a memory-usage throttler that limits access based on current memory statistics.
|
||||
* rate-limit/ : implements a rate-limiter throttler that varies by path, so that the number of requests allowed are counted based on the requested path.
|
||||
|
||||
Each example app supports a number of command-line flags. Run the example with the -h flag to display usage and defaults.
|
||||
90
Godeps/_workspace/src/github.com/throttled/throttled/examples/custom/main.go
сгенерированный
поставляемый
Обычный файл
90
Godeps/_workspace/src/github.com/throttled/throttled/examples/custom/main.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/throttled/throttled"
|
||||
)
|
||||
|
||||
var (
|
||||
delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value")
|
||||
output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only")
|
||||
)
|
||||
|
||||
// Custom limiter: allow requests to the /a path on even seconds only, and
|
||||
// allow access to the /b path on odd seconds only.
|
||||
//
|
||||
// Yes this is absurd. A more realistic case could be to allow requests to some
|
||||
// contest page only during a limited time window.
|
||||
type customLimiter struct {
|
||||
}
|
||||
|
||||
func (c *customLimiter) Start() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
func (c *customLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) {
|
||||
s := time.Now().Second()
|
||||
ch := make(chan bool, 1)
|
||||
ok := (r.URL.Path == "/a" && s%2 == 0) || (r.URL.Path == "/b" && s%2 != 0)
|
||||
ch <- ok
|
||||
if *output == "v" {
|
||||
log.Printf("Custom Limiter: Path=%s, Second=%d; ok? %v", r.URL.Path, s, ok)
|
||||
}
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var h http.Handler
|
||||
var ok, ko int
|
||||
var mu sync.Mutex
|
||||
|
||||
// Keep the start time to print since-time
|
||||
start := time.Now()
|
||||
// Create the custom throttler using our custom limiter
|
||||
t := throttled.Custom(&customLimiter{})
|
||||
// Set its denied handler
|
||||
t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ko" {
|
||||
log.Printf("KO: %s", time.Since(start))
|
||||
}
|
||||
throttled.DefaultDeniedHandler.ServeHTTP(w, r)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ko++
|
||||
})
|
||||
// Throttle the OK handler
|
||||
rand.Seed(time.Now().Unix())
|
||||
h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ok" {
|
||||
log.Printf("ok: %s", time.Since(start))
|
||||
}
|
||||
if *delayRes > 0 {
|
||||
wait := time.Duration(rand.Intn(int(*delayRes)))
|
||||
time.Sleep(wait)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ok++
|
||||
}))
|
||||
|
||||
// Print stats once in a while
|
||||
go func() {
|
||||
for _ = range time.Tick(10 * time.Second) {
|
||||
mu.Lock()
|
||||
log.Printf("ok: %d, ko: %d", ok, ko)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
fmt.Println("server listening on port 9000")
|
||||
http.ListenAndServe(":9000", h)
|
||||
}
|
||||
79
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval-many/main.go
сгенерированный
поставляемый
Обычный файл
79
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval-many/main.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/throttled/throttled"
|
||||
)
|
||||
|
||||
var (
|
||||
delay = flag.Duration("delay", 200*time.Millisecond, "delay between calls")
|
||||
bursts = flag.Int("bursts", 10, "number of bursts allowed")
|
||||
delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value")
|
||||
output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var ok, ko int
|
||||
var mu sync.Mutex
|
||||
|
||||
// Keep start time to log since-time
|
||||
start := time.Now()
|
||||
|
||||
// Create the interval throttle
|
||||
t := throttled.Interval(throttled.D(*delay), *bursts, nil, 0)
|
||||
// Set its denied handler
|
||||
t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ko" {
|
||||
log.Printf("%s: KO: %s", r.URL.Path, time.Since(start))
|
||||
}
|
||||
throttled.DefaultDeniedHandler.ServeHTTP(w, r)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ko++
|
||||
})
|
||||
// Create OK handlers
|
||||
rand.Seed(time.Now().Unix())
|
||||
makeHandler := func(ix int) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ok" {
|
||||
log.Printf("handler %d: %s: ok: %s", ix, r.URL.Path, time.Since(start))
|
||||
}
|
||||
if *delayRes > 0 {
|
||||
wait := time.Duration(rand.Intn(int(*delayRes)))
|
||||
time.Sleep(wait)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ok++
|
||||
})
|
||||
}
|
||||
// Throttle them using the same interval throttler
|
||||
h1 := t.Throttle(makeHandler(1))
|
||||
h2 := t.Throttle(makeHandler(2))
|
||||
|
||||
// Handle two paths
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/a", h1)
|
||||
mux.Handle("/b", h2)
|
||||
|
||||
// Print stats once in a while
|
||||
go func() {
|
||||
for _ = range time.Tick(10 * time.Second) {
|
||||
mu.Lock()
|
||||
log.Printf("ok: %d, ko: %d", ok, ko)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
fmt.Println("server listening on port 9000")
|
||||
http.ListenAndServe(":9000", mux)
|
||||
}
|
||||
74
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval-vary/main.go
сгенерированный
поставляемый
Обычный файл
74
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval-vary/main.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/throttled/throttled"
|
||||
)
|
||||
|
||||
var (
|
||||
delay = flag.Duration("delay", 200*time.Millisecond, "delay between calls")
|
||||
bursts = flag.Int("bursts", 10, "number of bursts allowed")
|
||||
maxkeys = flag.Int("max-keys", 1000, "maximum number of keys")
|
||||
delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value")
|
||||
output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var h http.Handler
|
||||
var ok, ko int
|
||||
var mu sync.Mutex
|
||||
|
||||
// Keep the start time to print since-time
|
||||
start := time.Now()
|
||||
|
||||
// Create the interval throttler
|
||||
t := throttled.Interval(throttled.D(*delay), *bursts, &throttled.VaryBy{
|
||||
Path: true,
|
||||
}, *maxkeys)
|
||||
// Set the denied handler
|
||||
t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ko" {
|
||||
log.Printf("KO: %s", time.Since(start))
|
||||
}
|
||||
throttled.DefaultDeniedHandler.ServeHTTP(w, r)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ko++
|
||||
})
|
||||
|
||||
// Throttle the OK handler
|
||||
rand.Seed(time.Now().Unix())
|
||||
h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ok" {
|
||||
log.Printf("%s: ok: %s", r.URL.Path, time.Since(start))
|
||||
}
|
||||
if *delayRes > 0 {
|
||||
wait := time.Duration(rand.Intn(int(*delayRes)))
|
||||
time.Sleep(wait)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ok++
|
||||
}))
|
||||
|
||||
// Print stats once in a while
|
||||
go func() {
|
||||
for _ = range time.Tick(10 * time.Second) {
|
||||
mu.Lock()
|
||||
log.Printf("ok: %d, ko: %d", ok, ko)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
fmt.Println("server listening on port 9000")
|
||||
http.ListenAndServe(":9000", h)
|
||||
}
|
||||
4
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval-vary/siege-urls
сгенерированный
поставляемый
Обычный файл
4
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval-vary/siege-urls
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,4 @@
|
||||
http://localhost:9000/a
|
||||
http://localhost:9000/b
|
||||
http://localhost:9000/c
|
||||
|
||||
69
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval/main.go
сгенерированный
поставляемый
Обычный файл
69
Godeps/_workspace/src/github.com/throttled/throttled/examples/interval/main.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/throttled/throttled"
|
||||
)
|
||||
|
||||
var (
|
||||
delay = flag.Duration("delay", 200*time.Millisecond, "delay between calls")
|
||||
bursts = flag.Int("bursts", 10, "number of bursts allowed")
|
||||
delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value")
|
||||
output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var h http.Handler
|
||||
var ok, ko int
|
||||
var mu sync.Mutex
|
||||
|
||||
// Keep the start time to print since-time
|
||||
start := time.Now()
|
||||
// Create the interval throttler
|
||||
t := throttled.Interval(throttled.D(*delay), *bursts, nil, 0)
|
||||
// Set its denied handler
|
||||
t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ko" {
|
||||
log.Printf("KO: %s", time.Since(start))
|
||||
}
|
||||
throttled.DefaultDeniedHandler.ServeHTTP(w, r)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ko++
|
||||
})
|
||||
// Throttle the OK handler
|
||||
rand.Seed(time.Now().Unix())
|
||||
h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ok" {
|
||||
log.Printf("ok: %s", time.Since(start))
|
||||
}
|
||||
if *delayRes > 0 {
|
||||
wait := time.Duration(rand.Intn(int(*delayRes)))
|
||||
time.Sleep(wait)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ok++
|
||||
}))
|
||||
|
||||
// Print stats once in a while
|
||||
go func() {
|
||||
for _ = range time.Tick(10 * time.Second) {
|
||||
mu.Lock()
|
||||
log.Printf("ok: %d, ko: %d", ok, ko)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
fmt.Println("server listening on port 9000")
|
||||
http.ListenAndServe(":9000", h)
|
||||
}
|
||||
97
Godeps/_workspace/src/github.com/throttled/throttled/examples/memstats/main.go
сгенерированный
поставляемый
Обычный файл
97
Godeps/_workspace/src/github.com/throttled/throttled/examples/memstats/main.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/throttled/throttled"
|
||||
)
|
||||
|
||||
var (
|
||||
numgc = flag.Int("gc", 0, "number of GC runs")
|
||||
mallocs = flag.Int("mallocs", 0, "number of mallocs")
|
||||
total = flag.Int("total", 0, "total number of bytes allocated")
|
||||
allocs = flag.Int("allocs", 0, "number of bytes allocated")
|
||||
refrate = flag.Duration("refresh", 0, "refresh rate of the memory stats")
|
||||
delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value")
|
||||
output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var h http.Handler
|
||||
var ok, ko int
|
||||
var mu sync.Mutex
|
||||
|
||||
// Keep the start time to print since-time
|
||||
start := time.Now()
|
||||
// Create the thresholds struct
|
||||
thresh := throttled.MemThresholds(&runtime.MemStats{
|
||||
NumGC: uint32(*numgc),
|
||||
Mallocs: uint64(*mallocs),
|
||||
TotalAlloc: uint64(*total),
|
||||
Alloc: uint64(*allocs),
|
||||
})
|
||||
if *output != "q" {
|
||||
log.Printf("thresholds: NumGC: %d, Mallocs: %d, Alloc: %dKb, Total: %dKb", thresh.NumGC, thresh.Mallocs, thresh.Alloc/1024, thresh.TotalAlloc/1024)
|
||||
}
|
||||
// Create the MemStats throttler
|
||||
t := throttled.MemStats(thresh, *refrate)
|
||||
// Set its denied handler
|
||||
t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ko" {
|
||||
log.Printf("KO: %s", time.Since(start))
|
||||
}
|
||||
throttled.DefaultDeniedHandler.ServeHTTP(w, r)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ko++
|
||||
})
|
||||
|
||||
// Throttle the OK handler
|
||||
rand.Seed(time.Now().Unix())
|
||||
h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ok" {
|
||||
log.Printf("ok: %s", time.Since(start))
|
||||
}
|
||||
if *delayRes > 0 {
|
||||
wait := time.Duration(rand.Intn(int(*delayRes)))
|
||||
time.Sleep(wait)
|
||||
}
|
||||
// Read the whole file in memory, to actually use 64Kb (instead of streaming to w)
|
||||
b, err := ioutil.ReadFile("test-file")
|
||||
if err != nil {
|
||||
throttled.Error(w, r, err)
|
||||
return
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
throttled.Error(w, r, err)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ok++
|
||||
}))
|
||||
|
||||
// Print stats once in a while
|
||||
go func() {
|
||||
var mem runtime.MemStats
|
||||
for _ = range time.Tick(10 * time.Second) {
|
||||
mu.Lock()
|
||||
runtime.ReadMemStats(&mem)
|
||||
log.Printf("ok: %d, ko: %d", ok, ko)
|
||||
log.Printf("TotalAllocs: %d Kb, Allocs: %d Kb, Mallocs: %d, NumGC: %d", mem.TotalAlloc/1024, mem.Alloc/1024, mem.Mallocs, mem.NumGC)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
fmt.Println("server listening on port 9000")
|
||||
http.ListenAndServe(":9000", h)
|
||||
}
|
||||
Двоичные данные
Godeps/_workspace/src/github.com/throttled/throttled/examples/memstats/test-file
сгенерированный
поставляемый
Обычный файл
Двоичные данные
Godeps/_workspace/src/github.com/throttled/throttled/examples/memstats/test-file
сгенерированный
поставляемый
Обычный файл
Двоичный файл не отображается.
101
Godeps/_workspace/src/github.com/throttled/throttled/examples/rate-limit/main.go
сгенерированный
поставляемый
Обычный файл
101
Godeps/_workspace/src/github.com/throttled/throttled/examples/rate-limit/main.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,101 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/throttled/throttled"
|
||||
"github.com/throttled/throttled/store"
|
||||
)
|
||||
|
||||
var (
|
||||
requests = flag.Int("requests", 10, "number of requests allowed in the time window")
|
||||
window = flag.Duration("window", time.Minute, "time window for the limit of requests")
|
||||
storeType = flag.String("store", "mem", "store to use, one of `mem` or `redis` (on default localhost port)")
|
||||
delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value")
|
||||
output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var h http.Handler
|
||||
var ok, ko int
|
||||
var mu sync.Mutex
|
||||
var st throttled.Store
|
||||
|
||||
// Keep the start time to print since-time
|
||||
start := time.Now()
|
||||
// Create the rate-limit store
|
||||
switch *storeType {
|
||||
case "mem":
|
||||
st = store.NewMemStore(0)
|
||||
case "redis":
|
||||
st = store.NewRedisStore(setupRedis(), "throttled:", 0)
|
||||
default:
|
||||
log.Fatalf("unsupported store: %s", *storeType)
|
||||
}
|
||||
// Create the rate-limit throttler, varying on path
|
||||
t := throttled.RateLimit(throttled.Q{Requests: *requests, Window: *window}, &throttled.VaryBy{
|
||||
Path: true,
|
||||
}, st)
|
||||
|
||||
// Set its denied handler
|
||||
t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ko" {
|
||||
log.Printf("KO: %s", time.Since(start))
|
||||
}
|
||||
throttled.DefaultDeniedHandler.ServeHTTP(w, r)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ko++
|
||||
})
|
||||
|
||||
// Throttle the OK handler
|
||||
rand.Seed(time.Now().Unix())
|
||||
h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if *output == "v" || *output == "ok" {
|
||||
log.Printf("ok: %s", time.Since(start))
|
||||
}
|
||||
if *delayRes > 0 {
|
||||
wait := time.Duration(rand.Intn(int(*delayRes)))
|
||||
time.Sleep(wait)
|
||||
}
|
||||
w.WriteHeader(200)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
ok++
|
||||
}))
|
||||
|
||||
// Print stats once in a while
|
||||
go func() {
|
||||
for _ = range time.Tick(10 * time.Second) {
|
||||
mu.Lock()
|
||||
log.Printf("ok: %d, ko: %d", ok, ko)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
fmt.Println("server listening on port 9000")
|
||||
http.ListenAndServe(":9000", h)
|
||||
}
|
||||
|
||||
func setupRedis() *redis.Pool {
|
||||
pool := &redis.Pool{
|
||||
MaxIdle: 3,
|
||||
IdleTimeout: 30 * time.Second,
|
||||
Dial: func() (redis.Conn, error) {
|
||||
return redis.Dial("tcp", ":6379")
|
||||
},
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
return pool
|
||||
}
|
||||
164
Godeps/_workspace/src/github.com/throttled/throttled/interval.go
сгенерированный
поставляемый
Обычный файл
164
Godeps/_workspace/src/github.com/throttled/throttled/interval.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,164 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/groupcache/lru"
|
||||
)
|
||||
|
||||
// Static check to ensure that the interval limiters implement the Limiter interface.
|
||||
var _ Limiter = (*intervalVaryByLimiter)(nil)
|
||||
var _ Limiter = (*intervalLimiter)(nil)
|
||||
|
||||
// Interval creates a throttler that controls the requests so that they
|
||||
// go through at a constant interval. The interval is specified by the
|
||||
// delay argument, and convenience types such as PerSec can be used to
|
||||
// express the interval in a more expressive way, i.e. PerSec(10) means
|
||||
// 10 requests per second or one request each 100ms, PerMin(30) means
|
||||
// 30 requests per minute or on request each 2s, etc.
|
||||
//
|
||||
// The bursts argument indicates the number of exceeding requests that may
|
||||
// be queued up waiting to be processed. Requests that overflow the queue
|
||||
// are dropped and go through the denied handler, which may be specified
|
||||
// on the Throttler and that defaults to the package-global variable
|
||||
// DefaultDeniedHandler.
|
||||
//
|
||||
// The vary argument indicates the criteria to use to group the requests,
|
||||
// so that the interval applies to the requests in the same group (e.g. based on
|
||||
// the path, or the remote IP address, etc.). If this argument is nil, the
|
||||
// interval applies to all requests going through this throttler.
|
||||
//
|
||||
// The maxKeys indicates the maximum number of keys to keep in memory to apply the interval,
|
||||
// when a vary argument is specified. A LRU algorithm is used to remove older keys.
|
||||
//
|
||||
func Interval(delay Delayer, bursts int, vary *VaryBy, maxKeys int) *Throttler {
|
||||
var l Limiter
|
||||
if vary != nil {
|
||||
if maxKeys < 1 {
|
||||
maxKeys = 1
|
||||
}
|
||||
l = &intervalVaryByLimiter{
|
||||
delay: delay.Delay(),
|
||||
bursts: bursts,
|
||||
vary: vary,
|
||||
maxKeys: maxKeys,
|
||||
}
|
||||
} else {
|
||||
l = &intervalLimiter{
|
||||
delay: delay.Delay(),
|
||||
bursts: bursts,
|
||||
}
|
||||
}
|
||||
return &Throttler{
|
||||
limiter: l,
|
||||
}
|
||||
}
|
||||
|
||||
// The intervalLimiter struct implements an interval limiter with no vary-by
|
||||
// criteria.
|
||||
type intervalLimiter struct {
|
||||
delay time.Duration
|
||||
bursts int
|
||||
|
||||
bucket chan chan bool
|
||||
}
|
||||
|
||||
// Start initializes the limiter for execution.
|
||||
func (il *intervalLimiter) Start() {
|
||||
if il.bursts < 0 {
|
||||
il.bursts = 0
|
||||
}
|
||||
il.bucket = make(chan chan bool, il.bursts)
|
||||
go process(il.bucket, il.delay)
|
||||
}
|
||||
|
||||
// Limit is called for each request to the throttled handler. It tries to
|
||||
// queue the request to allow it to run at the given interval, but if the
|
||||
// queue is full, the request is denied access.
|
||||
func (il *intervalLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) {
|
||||
ch := make(chan bool, 1)
|
||||
select {
|
||||
case il.bucket <- ch:
|
||||
return ch, nil
|
||||
default:
|
||||
ch <- false
|
||||
return ch, nil
|
||||
}
|
||||
}
|
||||
|
||||
// The intervalVaryByLimiter struct implements an interval limiter with a vary-by
|
||||
// criteria.
|
||||
type intervalVaryByLimiter struct {
|
||||
delay time.Duration
|
||||
bursts int
|
||||
vary *VaryBy
|
||||
|
||||
lock sync.RWMutex
|
||||
keys *lru.Cache
|
||||
maxKeys int
|
||||
}
|
||||
|
||||
// Start initializes the limiter for execution.
|
||||
func (il *intervalVaryByLimiter) Start() {
|
||||
if il.bursts < 0 {
|
||||
il.bursts = 0
|
||||
}
|
||||
il.keys = lru.New(il.maxKeys)
|
||||
il.keys.OnEvicted = il.stopProcess
|
||||
}
|
||||
|
||||
// Limit is called for each request to the throttled handler. It tries to
|
||||
// queue the request for the vary-by key to allow it to run at the given interval,
|
||||
// but if the queue is full, the request is denied access.
|
||||
func (il *intervalVaryByLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) {
|
||||
ch := make(chan bool, 1)
|
||||
key := il.vary.Key(r)
|
||||
|
||||
il.lock.RLock()
|
||||
item, ok := il.keys.Get(key)
|
||||
if !ok {
|
||||
// Create the key, bucket, start goroutine
|
||||
// First release the read lock and acquire a write lock
|
||||
il.lock.RUnlock()
|
||||
il.lock.Lock()
|
||||
// Create the bucket, add the key
|
||||
bucket := make(chan chan bool, il.bursts)
|
||||
il.keys.Add(key, bucket)
|
||||
// Start the goroutine to process this bucket
|
||||
go process(bucket, il.delay)
|
||||
item = bucket
|
||||
// Release the write lock, acquire the read lock
|
||||
il.lock.Unlock()
|
||||
il.lock.RLock()
|
||||
}
|
||||
defer il.lock.RUnlock()
|
||||
bucket := item.(chan chan bool)
|
||||
select {
|
||||
case bucket <- ch:
|
||||
return ch, nil
|
||||
default:
|
||||
ch <- false
|
||||
return ch, nil
|
||||
}
|
||||
}
|
||||
|
||||
// process loops through the queued requests for a key's bucket, and sends
|
||||
// requests through at the given interval.
|
||||
func process(bucket chan chan bool, delay time.Duration) {
|
||||
after := time.After(0)
|
||||
for v := range bucket {
|
||||
<-after
|
||||
// Let the request go through
|
||||
v <- true
|
||||
// Wait the required duration
|
||||
after = time.After(delay)
|
||||
}
|
||||
}
|
||||
|
||||
// stopProcess is called when a key is removed from the LRU cache so that its
|
||||
// accompanying goroutine is correctly released.
|
||||
func (il *intervalVaryByLimiter) stopProcess(key lru.Key, value interface{}) {
|
||||
close(value.(chan chan bool))
|
||||
}
|
||||
114
Godeps/_workspace/src/github.com/throttled/throttled/interval_test.go
сгенерированный
поставляемый
Обычный файл
114
Godeps/_workspace/src/github.com/throttled/throttled/interval_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,114 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/PuerkitoBio/boom/commands"
|
||||
)
|
||||
|
||||
func TestInterval(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
cases := []struct {
|
||||
n int
|
||||
c int
|
||||
rps int
|
||||
bursts int
|
||||
}{
|
||||
0: {60, 10, 20, 100},
|
||||
1: {300, 20, 100, 100},
|
||||
2: {10, 10, 1, 10},
|
||||
3: {1000, 100, 1000, 100},
|
||||
}
|
||||
for i, c := range cases {
|
||||
// Setup the stats handler
|
||||
st := &stats{}
|
||||
// Create the throttler
|
||||
th := Interval(PerSec(c.rps), c.bursts, nil, 0)
|
||||
th.DeniedHandler = http.HandlerFunc(st.DeniedHTTP)
|
||||
b := commands.Boom{
|
||||
Req: &commands.ReqOpts{},
|
||||
N: c.n,
|
||||
C: c.c,
|
||||
Output: "quiet",
|
||||
}
|
||||
// Run the test
|
||||
rpts := runTest(th.Throttle(st), b)
|
||||
// Assert results
|
||||
for _, rpt := range rpts {
|
||||
assertRPS(t, i, c.rps, rpt)
|
||||
}
|
||||
assertStats(t, i, st, rpts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntervalVary(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
cases := []struct {
|
||||
n int
|
||||
c int
|
||||
urls int
|
||||
rps int
|
||||
bursts int
|
||||
}{
|
||||
0: {60, 10, 3, 20, 100},
|
||||
1: {300, 20, 3, 100, 100},
|
||||
2: {10, 10, 3, 1, 10},
|
||||
3: {500, 10, 2, 1000, 100},
|
||||
}
|
||||
for i, c := range cases {
|
||||
// Setup the stats handler
|
||||
st := &stats{}
|
||||
// Create the throttler
|
||||
th := Interval(PerSec(c.rps), c.bursts, nil, 0)
|
||||
th.DeniedHandler = http.HandlerFunc(st.DeniedHTTP)
|
||||
var booms []commands.Boom
|
||||
for j := 0; j < c.urls; j++ {
|
||||
booms = append(booms, commands.Boom{
|
||||
Req: &commands.ReqOpts{},
|
||||
N: c.n,
|
||||
C: c.c,
|
||||
Output: "quiet",
|
||||
})
|
||||
}
|
||||
// Run the test
|
||||
rpts := runTest(th.Throttle(st), booms...)
|
||||
// Assert results
|
||||
for _, rpt := range rpts {
|
||||
assertRPS(t, i, c.rps, rpt)
|
||||
}
|
||||
assertStats(t, i, st, rpts)
|
||||
}
|
||||
}
|
||||
|
||||
func assertRPS(t *testing.T, ix int, exp int, rpt *commands.Report) {
|
||||
wigglef := 0.2 * float64(exp)
|
||||
if rpt.SuccessRPS < float64(exp)-wigglef || rpt.SuccessRPS > float64(exp)+wigglef {
|
||||
t.Errorf("%d: expected RPS to be around %d, got %f", ix, exp, rpt.SuccessRPS)
|
||||
}
|
||||
}
|
||||
|
||||
func assertStats(t *testing.T, ix int, st *stats, rpts []*commands.Report) {
|
||||
ok, ko, _ := st.Stats()
|
||||
var twos, fives, max int
|
||||
for _, rpt := range rpts {
|
||||
twos += rpt.StatusCodeDist[200]
|
||||
fives += rpt.StatusCodeDist[deniedStatus]
|
||||
if len(rpt.StatusCodeDist) > max {
|
||||
max = len(rpt.StatusCodeDist)
|
||||
}
|
||||
}
|
||||
if ok != twos {
|
||||
t.Errorf("%d: expected %d status 200, got %d", ix, twos, ok)
|
||||
}
|
||||
if ko != fives {
|
||||
t.Errorf("%d: expected %d status 429, got %d", ix, fives, ok)
|
||||
}
|
||||
if max > 2 {
|
||||
t.Errorf("%d: expected at most 2 different status codes, got %d", ix, max)
|
||||
}
|
||||
}
|
||||
214
Godeps/_workspace/src/github.com/throttled/throttled/memstats.go
сгенерированный
поставляемый
Обычный файл
214
Godeps/_workspace/src/github.com/throttled/throttled/memstats.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,214 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Static check to ensure that memStatsLimiter implements Limiter.
|
||||
var _ Limiter = (*memStatsLimiter)(nil)
|
||||
|
||||
// The memStatsLimiter struct implements a limiter based on the memory statistics
|
||||
// of the current process.
|
||||
type memStatsLimiter struct {
|
||||
thresholds *runtime.MemStats
|
||||
refreshRate time.Duration
|
||||
|
||||
lockStats sync.RWMutex
|
||||
stats runtime.MemStats
|
||||
}
|
||||
|
||||
// MemStats creates a Throttler based on the memory statistics of the current process.
|
||||
// Any combination of any (non-array) integer field of Go's runtime.MemStats structure
|
||||
// can be used as thresholds to deny a request.
|
||||
//
|
||||
// As soon as one threshold value is reached, the access is denied. If the value can
|
||||
// decrease, access will be allowed again once it gets back under the threshold value.
|
||||
// Denied requests go through the denied handler, which may be specified on the Throttler
|
||||
// and that defaults to the package-global variable DefaultDeniedHandler.
|
||||
//
|
||||
// Thresholds must be specified in absolute numbers (i.e. NumGC = 10 means stop once the
|
||||
// NumGC reaches 10, not when the current value increments by 10), and zero values are
|
||||
// ignored.
|
||||
//
|
||||
// The refreshRate indicates the frequency at which the process' memory stats are refreshed,
|
||||
// and 0 means on each request.
|
||||
//
|
||||
func MemStats(thresholds *runtime.MemStats, refreshRate time.Duration) *Throttler {
|
||||
return &Throttler{
|
||||
limiter: &memStatsLimiter{
|
||||
thresholds: thresholds,
|
||||
refreshRate: refreshRate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start initialized the limiter for execution.
|
||||
func (m *memStatsLimiter) Start() {
|
||||
// Make sure there is an initial MemStats reading
|
||||
runtime.ReadMemStats(&m.stats)
|
||||
if m.refreshRate > 0 {
|
||||
go m.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
// refresh runs in a separate goroutine and refreshes the memory statistics
|
||||
// at regular intervals.
|
||||
func (m *memStatsLimiter) refresh() {
|
||||
c := time.Tick(m.refreshRate)
|
||||
for _ = range c {
|
||||
m.lockStats.Lock()
|
||||
runtime.ReadMemStats(&m.stats)
|
||||
m.lockStats.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Limit is called for each request to the throttled handler. It checks if
|
||||
// the request can go through by checking the memory thresholds, and signals it
|
||||
// via the returned channel.
|
||||
func (m *memStatsLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) {
|
||||
ch := make(chan bool, 1)
|
||||
// Check if memory thresholds are reached
|
||||
ch <- m.allow()
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// allow compares the current memory stats with the thresholds, and returns
|
||||
// false if any threshold is reached.
|
||||
func (m *memStatsLimiter) allow() bool {
|
||||
m.lockStats.RLock()
|
||||
mem := m.stats
|
||||
m.lockStats.RUnlock()
|
||||
// If refreshRate == 0, then read on every request.
|
||||
if m.refreshRate == 0 {
|
||||
runtime.ReadMemStats(&mem)
|
||||
}
|
||||
ok := true
|
||||
checkStat(m.thresholds.Alloc, mem.Alloc, &ok)
|
||||
checkStat(m.thresholds.BuckHashSys, mem.BuckHashSys, &ok)
|
||||
checkStat(m.thresholds.Frees, mem.Frees, &ok)
|
||||
checkStat(m.thresholds.GCSys, mem.GCSys, &ok)
|
||||
checkStat(m.thresholds.HeapAlloc, mem.HeapAlloc, &ok)
|
||||
checkStat(m.thresholds.HeapIdle, mem.HeapIdle, &ok)
|
||||
checkStat(m.thresholds.HeapInuse, mem.HeapInuse, &ok)
|
||||
checkStat(m.thresholds.HeapObjects, mem.HeapObjects, &ok)
|
||||
checkStat(m.thresholds.HeapReleased, mem.HeapReleased, &ok)
|
||||
checkStat(m.thresholds.HeapSys, mem.HeapSys, &ok)
|
||||
checkStat(m.thresholds.LastGC, mem.LastGC, &ok)
|
||||
checkStat(m.thresholds.Lookups, mem.Lookups, &ok)
|
||||
checkStat(m.thresholds.MCacheInuse, mem.MCacheInuse, &ok)
|
||||
checkStat(m.thresholds.MCacheSys, mem.MCacheSys, &ok)
|
||||
checkStat(m.thresholds.MSpanInuse, mem.MSpanInuse, &ok)
|
||||
checkStat(m.thresholds.MSpanSys, mem.MSpanSys, &ok)
|
||||
checkStat(m.thresholds.Mallocs, mem.Mallocs, &ok)
|
||||
checkStat(m.thresholds.NextGC, mem.NextGC, &ok)
|
||||
checkStat(uint64(m.thresholds.NumGC), uint64(mem.NumGC), &ok)
|
||||
checkStat(m.thresholds.OtherSys, mem.OtherSys, &ok)
|
||||
checkStat(m.thresholds.PauseTotalNs, mem.PauseTotalNs, &ok)
|
||||
checkStat(m.thresholds.StackInuse, mem.StackInuse, &ok)
|
||||
checkStat(m.thresholds.StackSys, mem.StackSys, &ok)
|
||||
checkStat(m.thresholds.Sys, mem.Sys, &ok)
|
||||
checkStat(m.thresholds.TotalAlloc, mem.TotalAlloc, &ok)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Checks the threshold value against the actual value, and assigns false
|
||||
// to the boolean pointer if the threshold is reached.
|
||||
func checkStat(threshold, actual uint64, ok *bool) {
|
||||
if !*ok {
|
||||
return
|
||||
}
|
||||
if threshold > 0 {
|
||||
if actual >= threshold {
|
||||
*ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MemThresholds is a convenience function to create a thresholds memory stats from
|
||||
// offsets to apply to the current memory stats. Zero values in the offset stats
|
||||
// are left to 0 in the resulting thresholds memory stats value.
|
||||
//
|
||||
// The return value may be used as thresholds argument to the MemStats function.
|
||||
func MemThresholds(offset *runtime.MemStats) *runtime.MemStats {
|
||||
var mem, thr runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
if offset.Alloc > 0 {
|
||||
thr.Alloc = mem.Alloc + offset.Alloc
|
||||
}
|
||||
if offset.BuckHashSys > 0 {
|
||||
thr.BuckHashSys = mem.BuckHashSys + offset.BuckHashSys
|
||||
}
|
||||
if offset.Frees > 0 {
|
||||
thr.Frees = mem.Frees + offset.Frees
|
||||
}
|
||||
if offset.GCSys > 0 {
|
||||
thr.GCSys = mem.GCSys + offset.GCSys
|
||||
}
|
||||
if offset.HeapAlloc > 0 {
|
||||
thr.HeapAlloc = mem.HeapAlloc + offset.HeapAlloc
|
||||
}
|
||||
if offset.HeapIdle > 0 {
|
||||
thr.HeapIdle = mem.HeapIdle + offset.HeapIdle
|
||||
}
|
||||
if offset.HeapInuse > 0 {
|
||||
thr.HeapInuse = mem.HeapInuse + offset.HeapInuse
|
||||
}
|
||||
if offset.HeapObjects > 0 {
|
||||
thr.HeapObjects = mem.HeapObjects + offset.HeapObjects
|
||||
}
|
||||
if offset.HeapReleased > 0 {
|
||||
thr.HeapReleased = mem.HeapReleased + offset.HeapReleased
|
||||
}
|
||||
if offset.HeapSys > 0 {
|
||||
thr.HeapSys = mem.HeapSys + offset.HeapSys
|
||||
}
|
||||
if offset.LastGC > 0 {
|
||||
thr.LastGC = mem.LastGC + offset.LastGC
|
||||
}
|
||||
if offset.Lookups > 0 {
|
||||
thr.Lookups = mem.Lookups + offset.Lookups
|
||||
}
|
||||
if offset.MCacheInuse > 0 {
|
||||
thr.MCacheInuse = mem.MCacheInuse + offset.MCacheInuse
|
||||
}
|
||||
if offset.MCacheSys > 0 {
|
||||
thr.MCacheSys = mem.MCacheSys + offset.MCacheSys
|
||||
}
|
||||
if offset.MSpanInuse > 0 {
|
||||
thr.MSpanInuse = mem.MSpanInuse + offset.MSpanInuse
|
||||
}
|
||||
if offset.MSpanSys > 0 {
|
||||
thr.MSpanSys = mem.MSpanSys + offset.MSpanSys
|
||||
}
|
||||
if offset.Mallocs > 0 {
|
||||
thr.Mallocs = mem.Mallocs + offset.Mallocs
|
||||
}
|
||||
if offset.NextGC > 0 {
|
||||
thr.NextGC = mem.NextGC + offset.NextGC
|
||||
}
|
||||
if offset.NumGC > 0 {
|
||||
thr.NumGC = mem.NumGC + offset.NumGC
|
||||
}
|
||||
if offset.OtherSys > 0 {
|
||||
thr.OtherSys = mem.OtherSys + offset.OtherSys
|
||||
}
|
||||
if offset.PauseTotalNs > 0 {
|
||||
thr.PauseTotalNs = mem.PauseTotalNs + offset.PauseTotalNs
|
||||
}
|
||||
if offset.StackInuse > 0 {
|
||||
thr.StackInuse = mem.StackInuse + offset.StackInuse
|
||||
}
|
||||
if offset.StackSys > 0 {
|
||||
thr.StackSys = mem.StackSys + offset.StackSys
|
||||
}
|
||||
if offset.Sys > 0 {
|
||||
thr.Sys = mem.Sys + offset.Sys
|
||||
}
|
||||
if offset.TotalAlloc > 0 {
|
||||
thr.TotalAlloc = mem.TotalAlloc + offset.TotalAlloc
|
||||
}
|
||||
return &thr
|
||||
}
|
||||
64
Godeps/_workspace/src/github.com/throttled/throttled/memstats_test.go
сгенерированный
поставляемый
Обычный файл
64
Godeps/_workspace/src/github.com/throttled/throttled/memstats_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,64 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/boom/commands"
|
||||
)
|
||||
|
||||
func TestMemStats(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
cases := []struct {
|
||||
n int
|
||||
c int
|
||||
gc uint32
|
||||
total uint64
|
||||
rate time.Duration
|
||||
}{
|
||||
0: {1000, 10, 3, 0, 0},
|
||||
1: {200, 10, 0, 600000, 0},
|
||||
2: {500, 10, 2, 555555, 10 * time.Millisecond},
|
||||
}
|
||||
for i, c := range cases {
|
||||
// Setup the stats handler
|
||||
st := &stats{}
|
||||
// Create the throttler
|
||||
limit := MemThresholds(&runtime.MemStats{NumGC: c.gc, TotalAlloc: c.total})
|
||||
th := MemStats(limit, c.rate)
|
||||
th.DeniedHandler = http.HandlerFunc(st.DeniedHTTP)
|
||||
// Run the test
|
||||
b := commands.Boom{
|
||||
Req: &commands.ReqOpts{},
|
||||
N: c.n,
|
||||
C: c.c,
|
||||
Output: "quiet",
|
||||
}
|
||||
rpts := runTest(th.Throttle(st), b)
|
||||
// Assert results
|
||||
assertStats(t, i, st, rpts)
|
||||
assertMem(t, i, limit)
|
||||
}
|
||||
}
|
||||
|
||||
func assertMem(t *testing.T, ix int, limit *runtime.MemStats) {
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
if mem.NumGC < limit.NumGC {
|
||||
t.Errorf("%d: expected gc to be at least %d, got %d", ix, limit.NumGC, mem.NumGC)
|
||||
}
|
||||
if mem.TotalAlloc < limit.TotalAlloc {
|
||||
t.Errorf("%d: expected total alloc to be at least %dKb, got %dKb", ix, limit.TotalAlloc/1024, mem.TotalAlloc/1024)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReadMemStats(b *testing.B) {
|
||||
var mem runtime.MemStats
|
||||
for i := 0; i < b.N; i++ {
|
||||
runtime.ReadMemStats(&mem)
|
||||
}
|
||||
}
|
||||
38
Godeps/_workspace/src/github.com/throttled/throttled/misc/pre-commit
сгенерированный
поставляемый
Обычный файл
38
Godeps/_workspace/src/github.com/throttled/throttled/misc/pre-commit
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2012 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# git gofmt pre-commit hook
|
||||
#
|
||||
# To use, store as .git/hooks/pre-commit inside your repository and make sure
|
||||
# it has execute permissions.
|
||||
#
|
||||
# This script does not handle file names that contain spaces.
|
||||
|
||||
# golint is purely informational, it doesn't fail with exit code != 0 if it finds something,
|
||||
# because it may find a lot of false positives. Just print out its result for information.
|
||||
echo "lint result (informational only):"
|
||||
echo
|
||||
golint .
|
||||
|
||||
# go vet returns 1 if an error was found. Exit the hook with this exit code.
|
||||
go vet ./...
|
||||
vetres=$?
|
||||
|
||||
# Check for gofmt problems and report if any.
|
||||
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$')
|
||||
[ -z "$gofiles" ] && echo "EXIT $vetres" && exit $vetres
|
||||
|
||||
unformatted=$(gofmt -l $gofiles)
|
||||
[ -z "$unformatted" ] && echo "EXIT $vetres" && exit $vetres
|
||||
|
||||
# Some files are not gofmt'd. Print message and fail.
|
||||
|
||||
echo >&2 "Go files must be formatted with gofmt. Please run:"
|
||||
for fn in $unformatted; do
|
||||
echo >&2 " gofmt -w $PWD/$fn"
|
||||
done
|
||||
|
||||
echo "EXIT 1"
|
||||
exit 1
|
||||
116
Godeps/_workspace/src/github.com/throttled/throttled/rate.go
сгенерированный
поставляемый
Обычный файл
116
Godeps/_workspace/src/github.com/throttled/throttled/rate.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,116 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Static check to ensure that rateLimiter implements Limiter.
|
||||
var _ Limiter = (*rateLimiter)(nil)
|
||||
|
||||
// RateLimit creates a throttler that limits the number of requests allowed
|
||||
// in a certain time window defined by the Quota q. The q parameter specifies
|
||||
// the requests per time window, and it is silently set to at least 1 request
|
||||
// and at least a 1 second window if it is less than that. The time window
|
||||
// starts when the first request is made outside an existing window. Fractions
|
||||
// of seconds are not supported, they are truncated.
|
||||
//
|
||||
// The vary parameter indicates what criteria should be used to group requests
|
||||
// for which the limit must be applied (ex.: rate limit based on the remote address).
|
||||
// See varyby.go for the various options.
|
||||
//
|
||||
// The specified store is used to keep track of the request count and the
|
||||
// time remaining in the window. The throttled package comes with some stores
|
||||
// in the throttled/store package. Custom stores can be created too, by implementing
|
||||
// the Store interface.
|
||||
//
|
||||
// Requests that bust the rate limit are denied access and go through the denied handler,
|
||||
// which may be specified on the Throttler and that defaults to the package-global
|
||||
// variable DefaultDeniedHandler.
|
||||
//
|
||||
// The rate limit throttler sets the following headers on the response:
|
||||
//
|
||||
// X-RateLimit-Limit : quota
|
||||
// X-RateLimit-Remaining : number of requests remaining in the current window
|
||||
// X-RateLimit-Reset : seconds before a new window
|
||||
//
|
||||
// Additionally, if the request was denied access, the following header is added:
|
||||
//
|
||||
// Retry-After : seconds before the caller should retry
|
||||
//
|
||||
func RateLimit(q Quota, vary *VaryBy, store Store) *Throttler {
|
||||
// Extract requests and window
|
||||
reqs, win := q.Quota()
|
||||
|
||||
// Create and return the throttler
|
||||
return &Throttler{
|
||||
limiter: &rateLimiter{
|
||||
reqs: reqs,
|
||||
window: win,
|
||||
vary: vary,
|
||||
store: store,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// The rate limiter implements limiting the request to a certain quota
|
||||
// based on the vary-by criteria. State is saved in the store.
|
||||
type rateLimiter struct {
|
||||
reqs int
|
||||
window time.Duration
|
||||
vary *VaryBy
|
||||
store Store
|
||||
}
|
||||
|
||||
// Start initializes the limiter for execution.
|
||||
func (r *rateLimiter) Start() {
|
||||
if r.reqs < 1 {
|
||||
r.reqs = 1
|
||||
}
|
||||
if r.window < time.Second {
|
||||
r.window = time.Second
|
||||
}
|
||||
}
|
||||
|
||||
// Limit is called for each request to the throttled handler. It checks if
|
||||
// the request can go through and signals it via the returned channel.
|
||||
// It returns an error if the operation fails.
|
||||
func (r *rateLimiter) Limit(w http.ResponseWriter, req *http.Request) (<-chan bool, error) {
|
||||
// Create return channel and initialize
|
||||
ch := make(chan bool, 1)
|
||||
ok := true
|
||||
key := r.vary.Key(req)
|
||||
|
||||
// Get the current count and remaining seconds
|
||||
cnt, secs, err := r.store.Incr(key, r.window)
|
||||
// Handle the possible situations: error, begin new window, or increment current window.
|
||||
switch {
|
||||
case err != nil && err != ErrNoSuchKey:
|
||||
// An unexpected error occurred
|
||||
return nil, err
|
||||
case err == ErrNoSuchKey || secs <= 0:
|
||||
// Reset counter
|
||||
if err := r.store.Reset(key, r.window); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cnt = 1
|
||||
secs = int(r.window.Seconds())
|
||||
default:
|
||||
// If the limit is reached, deny access
|
||||
if cnt > r.reqs {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
// Set rate-limit headers
|
||||
w.Header().Add("X-RateLimit-Limit", strconv.Itoa(r.reqs))
|
||||
w.Header().Add("X-RateLimit-Remaining", strconv.Itoa(int(math.Max(float64(r.reqs-cnt), 0))))
|
||||
w.Header().Add("X-RateLimit-Reset", strconv.Itoa(secs))
|
||||
if !ok {
|
||||
w.Header().Add("Retry-After", strconv.Itoa(secs))
|
||||
}
|
||||
// Send response via the return channel
|
||||
ch <- ok
|
||||
return ch, nil
|
||||
}
|
||||
101
Godeps/_workspace/src/github.com/throttled/throttled/rate_test.go
сгенерированный
поставляемый
Обычный файл
101
Godeps/_workspace/src/github.com/throttled/throttled/rate_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,101 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const deniedStatus = 429
|
||||
|
||||
// Simple memory store for tests, unsafe for concurrent access
|
||||
type mapStore struct {
|
||||
cnt map[string]int
|
||||
ts map[string]time.Time
|
||||
}
|
||||
|
||||
func newMapStore() *mapStore {
|
||||
return &mapStore{
|
||||
make(map[string]int),
|
||||
make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
func (ms *mapStore) Incr(key string, window time.Duration) (int, int, error) {
|
||||
if _, ok := ms.cnt[key]; !ok {
|
||||
return 0, 0, ErrNoSuchKey
|
||||
}
|
||||
ms.cnt[key]++
|
||||
ts := ms.ts[key]
|
||||
return ms.cnt[key], RemainingSeconds(ts, window), nil
|
||||
}
|
||||
func (ms *mapStore) Reset(key string, win time.Duration) error {
|
||||
ms.cnt[key] = 1
|
||||
ms.ts[key] = time.Now().UTC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestRateLimit(t *testing.T) {
|
||||
quota := Q{5, 5 * time.Second}
|
||||
cases := []struct {
|
||||
limit, remain, reset, status int
|
||||
}{
|
||||
0: {5, 4, 5, 200},
|
||||
1: {5, 3, 4, 200},
|
||||
2: {5, 2, 4, 200},
|
||||
3: {5, 1, 3, 200},
|
||||
4: {5, 0, 3, 200},
|
||||
5: {5, 0, 2, deniedStatus},
|
||||
}
|
||||
// Limit the requests to 2 per second
|
||||
th := Interval(PerSec(2), 0, nil, 0)
|
||||
// Rate limit
|
||||
rl := RateLimit(quota, nil, newMapStore())
|
||||
// Create the stats
|
||||
st := &stats{}
|
||||
// Create the handler
|
||||
h := th.Throttle(rl.Throttle(st))
|
||||
|
||||
// Start the server
|
||||
srv := httptest.NewServer(h)
|
||||
defer srv.Close()
|
||||
for i, c := range cases {
|
||||
callRateLimited(t, i, c.limit, c.remain, c.reset, c.status, srv.URL)
|
||||
}
|
||||
// Wait 3 seconds and call again, should start a new window
|
||||
time.Sleep(3 * time.Second)
|
||||
callRateLimited(t, len(cases), 5, 4, 5, 200, srv.URL)
|
||||
}
|
||||
|
||||
func callRateLimited(t *testing.T, i, limit, remain, reset, status int, url string) {
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// Assert status code
|
||||
if status != res.StatusCode {
|
||||
t.Errorf("%d: expected status %d, got %d", i, status, res.StatusCode)
|
||||
}
|
||||
// Assert headers
|
||||
if v := res.Header.Get("X-RateLimit-Limit"); v != strconv.Itoa(limit) {
|
||||
t.Errorf("%d: expected limit header to be %d, got %s", i, limit, v)
|
||||
}
|
||||
if v := res.Header.Get("X-RateLimit-Remaining"); v != strconv.Itoa(remain) {
|
||||
t.Errorf("%d: expected remain header to be %d, got %s", i, remain, v)
|
||||
}
|
||||
// Allow 1 second wiggle room
|
||||
v := res.Header.Get("X-RateLimit-Reset")
|
||||
vi, _ := strconv.Atoi(v)
|
||||
if vi < reset-1 || vi > reset+1 {
|
||||
t.Errorf("%d: expected reset header to be close to %d, got %d", i, reset, vi)
|
||||
}
|
||||
if status == deniedStatus {
|
||||
v := res.Header.Get("Retry-After")
|
||||
vi, _ := strconv.Atoi(v)
|
||||
if vi < reset-1 || vi > reset+1 {
|
||||
t.Errorf("%d: expected retry after header to be close to %d, got %d", i, reset, vi)
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Godeps/_workspace/src/github.com/throttled/throttled/store.go
сгенерированный
поставляемый
Обычный файл
31
Godeps/_workspace/src/github.com/throttled/throttled/store.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,31 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The error returned if the key does not exist in the Store.
|
||||
var ErrNoSuchKey = errors.New("throttled: no such key")
|
||||
|
||||
// Store is the interface to implement to store the RateLimit state (number
|
||||
// of requests per key, time-to-live or creation timestamp).
|
||||
type Store interface {
|
||||
// Incr increments the count for the specified key and returns the new value along
|
||||
// with the number of seconds remaining. It may return an error
|
||||
// if the operation fails.
|
||||
//
|
||||
// The method may return ErrNoSuchKey if the key to increment does not exist,
|
||||
// in which case Reset will be called to initialize the value.
|
||||
Incr(string, time.Duration) (int, int, error)
|
||||
|
||||
// Reset resets the key to 1 with the specified window duration. It must create the
|
||||
// key if it doesn't exist. It returns an error if it fails.
|
||||
Reset(string, time.Duration) error
|
||||
}
|
||||
|
||||
// RemainingSeconds is a helper function that returns the number of seconds
|
||||
// remaining from an absolute timestamp in UTC.
|
||||
func RemainingSeconds(ts time.Time, window time.Duration) int {
|
||||
return int((window - time.Now().UTC().Sub(ts)).Seconds())
|
||||
}
|
||||
2
Godeps/_workspace/src/github.com/throttled/throttled/store/doc.go
сгенерированный
поставляемый
Обычный файл
2
Godeps/_workspace/src/github.com/throttled/throttled/store/doc.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,2 @@
|
||||
// Package store offers a memory-based and a Redis-based throttled.Store implementation.
|
||||
package store
|
||||
90
Godeps/_workspace/src/github.com/throttled/throttled/store/mem.go
сгенерированный
поставляемый
Обычный файл
90
Godeps/_workspace/src/github.com/throttled/throttled/store/mem.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,90 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/groupcache/lru"
|
||||
"github.com/throttled/throttled"
|
||||
)
|
||||
|
||||
// memStore implements an in-memory Store.
|
||||
type memStore struct {
|
||||
sync.Mutex
|
||||
keys *lru.Cache
|
||||
m map[string]*counter
|
||||
}
|
||||
|
||||
// NewMemStore creates a new MemStore. If maxKeys > 0, the number of different keys
|
||||
// is restricted to the specified amount. In this case, it uses an LRU algorithm to
|
||||
// evict older keys to make room for newer ones. If a request is made for a key that
|
||||
// has been evicted, it will be processed as if its count was 0, possibly allowing requests
|
||||
// that should be denied.
|
||||
//
|
||||
// If maxKeys <= 0, there is no limit on the number of keys, which may use an unbounded amount of
|
||||
// memory depending on the server's load.
|
||||
//
|
||||
// The MemStore is only for single-process rate-limiting. To share the rate limit state
|
||||
// among multiple instances of the web server, use a database- or key-value-based
|
||||
// store.
|
||||
//
|
||||
func NewMemStore(maxKeys int) throttled.Store {
|
||||
var m *memStore
|
||||
if maxKeys > 0 {
|
||||
m = &memStore{
|
||||
keys: lru.New(maxKeys),
|
||||
}
|
||||
} else {
|
||||
m = &memStore{
|
||||
m: make(map[string]*counter),
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// A counter represents a single entry in the MemStore.
|
||||
type counter struct {
|
||||
n int
|
||||
ts time.Time
|
||||
}
|
||||
|
||||
// Incr increments the counter for the specified key. It returns the new
|
||||
// count value and the remaining number of seconds, or an error.
|
||||
func (ms *memStore) Incr(key string, window time.Duration) (int, int, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
var c *counter
|
||||
if ms.keys != nil {
|
||||
v, _ := ms.keys.Get(key)
|
||||
if v != nil {
|
||||
c = v.(*counter)
|
||||
}
|
||||
} else {
|
||||
c = ms.m[key]
|
||||
}
|
||||
if c == nil {
|
||||
c = &counter{0, time.Now().UTC()}
|
||||
}
|
||||
c.n++
|
||||
if ms.keys != nil {
|
||||
ms.keys.Add(key, c)
|
||||
} else {
|
||||
ms.m[key] = c
|
||||
}
|
||||
return c.n, throttled.RemainingSeconds(c.ts, window), nil
|
||||
}
|
||||
|
||||
// Reset resets the counter for the specified key. It sets the count
|
||||
// to 1 and initializes the timestamp with the current time, in UTC.
|
||||
// It returns an error if the operation fails.
|
||||
func (ms *memStore) Reset(key string, win time.Duration) error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
c := &counter{1, time.Now().UTC()}
|
||||
if ms.keys != nil {
|
||||
ms.keys.Add(key, c)
|
||||
} else {
|
||||
ms.m[key] = c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
43
Godeps/_workspace/src/github.com/throttled/throttled/store/mem_test.go
сгенерированный
поставляемый
Обычный файл
43
Godeps/_workspace/src/github.com/throttled/throttled/store/mem_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMemStore(t *testing.T) {
|
||||
st := NewMemStore(0)
|
||||
win := time.Second
|
||||
|
||||
// Reset stores a key with count of 1, current timestamp
|
||||
err := st.Reset("k", time.Second)
|
||||
if err != nil {
|
||||
t.Errorf("expected reset to return nil, got %s", err)
|
||||
}
|
||||
cnt, sec1, _ := st.Incr("k", win)
|
||||
if cnt != 2 {
|
||||
t.Errorf("expected reset+incr to set count to 2, got %d", cnt)
|
||||
}
|
||||
|
||||
// Incr increments the key, keeps same timestamp
|
||||
cnt, sec2, err := st.Incr("k", win)
|
||||
if err != nil {
|
||||
t.Errorf("expected 2nd incr to return nil error, got %s", err)
|
||||
}
|
||||
if cnt != 3 {
|
||||
t.Errorf("expected 2nd incr to return 3, got %d", cnt)
|
||||
}
|
||||
if sec1 != sec2 {
|
||||
t.Errorf("expected 2nd incr to return %d secs, got %d", sec1, sec2)
|
||||
}
|
||||
|
||||
// Reset on existing key brings it back to 1, new timestamp
|
||||
err = st.Reset("k", win)
|
||||
if err != nil {
|
||||
t.Errorf("expected reset on existing key to return nil, got %s", err)
|
||||
}
|
||||
cnt, _, _ = st.Incr("k", win)
|
||||
if cnt != 2 {
|
||||
t.Errorf("expected last reset+incr to return 2, got %d", cnt)
|
||||
}
|
||||
}
|
||||
85
Godeps/_workspace/src/github.com/throttled/throttled/store/redis.go
сгенерированный
поставляемый
Обычный файл
85
Godeps/_workspace/src/github.com/throttled/throttled/store/redis.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,85 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/throttled/throttled"
|
||||
)
|
||||
|
||||
// redisStore implements a Redis-based store.
|
||||
type redisStore struct {
|
||||
pool *redis.Pool
|
||||
prefix string
|
||||
db int
|
||||
}
|
||||
|
||||
// NewRedisStore creates a new Redis-based store, using the provided pool to get its
|
||||
// connections. The keys will have the specified keyPrefix, which may be an empty string,
|
||||
// and the database index specified by db will be selected to store the keys.
|
||||
//
|
||||
func NewRedisStore(pool *redis.Pool, keyPrefix string, db int) throttled.Store {
|
||||
return &redisStore{
|
||||
pool: pool,
|
||||
prefix: keyPrefix,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Incr increments the specified key. If the key did not exist, it sets it to 1
|
||||
// and sets it to expire after the number of seconds specified by window.
|
||||
//
|
||||
// It returns the new count value and the number of remaining seconds, or an error
|
||||
// if the operation fails.
|
||||
func (r *redisStore) Incr(key string, window time.Duration) (int, int, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
if err := selectDB(r.db, conn); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// Atomically increment and read the TTL.
|
||||
conn.Send("MULTI")
|
||||
conn.Send("INCR", r.prefix+key)
|
||||
conn.Send("TTL", r.prefix+key)
|
||||
vals, err := redis.Values(conn.Do("EXEC"))
|
||||
if err != nil {
|
||||
conn.Do("DISCARD")
|
||||
return 0, 0, err
|
||||
}
|
||||
var cnt, ttl int
|
||||
if _, err = redis.Scan(vals, &cnt, &ttl); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// If there was no TTL set, then this is a newly created key (INCR creates the key
|
||||
// if it didn't exist), so set it to expire.
|
||||
if ttl == -1 {
|
||||
ttl = int(window.Seconds())
|
||||
_, err = conn.Do("EXPIRE", r.prefix+key, ttl)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
return cnt, ttl, nil
|
||||
}
|
||||
|
||||
// Reset sets the value of the key to 1, and resets its time window.
|
||||
func (r *redisStore) Reset(key string, window time.Duration) error {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
if err := selectDB(r.db, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := redis.String(conn.Do("SET", r.prefix+key, "1", "EX", int(window.Seconds()), "NX"))
|
||||
return err
|
||||
}
|
||||
|
||||
// Select the specified database index.
|
||||
func selectDB(db int, conn redis.Conn) error {
|
||||
// Select the specified database
|
||||
if db > 0 {
|
||||
if _, err := redis.String(conn.Do("SELECT", db)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
66
Godeps/_workspace/src/github.com/throttled/throttled/store/redis_test.go
сгенерированный
поставляемый
Обычный файл
66
Godeps/_workspace/src/github.com/throttled/throttled/store/redis_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,66 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
func getPool() *redis.Pool {
|
||||
pool := &redis.Pool{
|
||||
MaxIdle: 3,
|
||||
IdleTimeout: 30 * time.Second,
|
||||
Dial: func() (redis.Conn, error) {
|
||||
return redis.Dial("tcp", ":6379")
|
||||
},
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
return pool
|
||||
}
|
||||
|
||||
func TestRedisStore(t *testing.T) {
|
||||
pool := getPool()
|
||||
c := pool.Get()
|
||||
if _, err := redis.String(c.Do("PING")); err != nil {
|
||||
c.Close()
|
||||
t.Skip("redis server not available on localhost port 6379")
|
||||
}
|
||||
st := NewRedisStore(pool, "throttled:", 1)
|
||||
win := 2 * time.Second
|
||||
|
||||
// Incr increments the key, even if it does not exist
|
||||
cnt, secs, err := st.Incr("k", win)
|
||||
if err != nil {
|
||||
t.Errorf("expected initial incr to return nil error, got %s", err)
|
||||
}
|
||||
if cnt != 1 {
|
||||
t.Errorf("expected initial incr to return 1, got %d", cnt)
|
||||
}
|
||||
if secs != int(win.Seconds()) {
|
||||
t.Errorf("expected initial incr to return %d secs, got %d", int(win.Seconds()), secs)
|
||||
}
|
||||
|
||||
// Waiting a second diminishes the remaining seconds
|
||||
time.Sleep(time.Second)
|
||||
_, sec2, _ := st.Incr("k", win)
|
||||
if sec2 != secs-1 {
|
||||
t.Errorf("expected 2nd incr after a 1s sleep to return %d secs, got %d", secs-1, sec2)
|
||||
}
|
||||
|
||||
// Waiting a second so the key expires, Incr should set back to 1, initial secs
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
cnt, sec3, err := st.Incr("k", win)
|
||||
if err != nil {
|
||||
t.Errorf("expected last incr to return nil error, got %s", err)
|
||||
}
|
||||
if cnt != 1 {
|
||||
t.Errorf("expected last incr to return 1, got %d", cnt)
|
||||
}
|
||||
if sec3 != int(win.Seconds()) {
|
||||
t.Errorf("expected last incr to return %d secs, got %d", int(win.Seconds()), sec3)
|
||||
}
|
||||
}
|
||||
86
Godeps/_workspace/src/github.com/throttled/throttled/throttler.go
сгенерированный
поставляемый
Обычный файл
86
Godeps/_workspace/src/github.com/throttled/throttled/throttler.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,86 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultDeniedHandler handles the requests that were denied access because
|
||||
// of a throttler. By default, returns a 429 status code with a
|
||||
// generic message.
|
||||
DefaultDeniedHandler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "limit exceeded", 429)
|
||||
}))
|
||||
|
||||
// Error is the function to call when an error occurs on a throttled handler.
|
||||
// By default, returns a 500 status code with a generic message.
|
||||
Error = ErrorFunc(func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
})
|
||||
)
|
||||
|
||||
// ErrorFunc defines the function type for the Error variable.
|
||||
type ErrorFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||
|
||||
// The Limiter interface defines the methods required to control access to a
|
||||
// throttled handler.
|
||||
type Limiter interface {
|
||||
Start()
|
||||
Limit(http.ResponseWriter, *http.Request) (<-chan bool, error)
|
||||
}
|
||||
|
||||
// Custom creates a Throttler using the provided Limiter implementation.
|
||||
func Custom(l Limiter) *Throttler {
|
||||
return &Throttler{
|
||||
limiter: l,
|
||||
}
|
||||
}
|
||||
|
||||
// A Throttler controls access to HTTP handlers using a Limiter.
|
||||
type Throttler struct {
|
||||
// DeniedHandler is called if the request is disallowed. If it is nil,
|
||||
// the DefaultDeniedHandler variable is used.
|
||||
DeniedHandler http.Handler
|
||||
|
||||
limiter Limiter
|
||||
// The mutex protects the started flag
|
||||
mu sync.Mutex
|
||||
started bool
|
||||
}
|
||||
|
||||
// Throttle wraps a HTTP handler so that its access is controlled by
|
||||
// the Throttler. It returns the Handler with the throttling logic.
|
||||
func (t *Throttler) Throttle(h http.Handler) http.Handler {
|
||||
dh := t.start()
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ch, err := t.limiter.Limit(w, r)
|
||||
if err != nil {
|
||||
Error(w, r, err)
|
||||
return
|
||||
}
|
||||
ok := <-ch
|
||||
if ok {
|
||||
h.ServeHTTP(w, r)
|
||||
} else {
|
||||
dh.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// start starts the throttling and returns the effective denied handler to
|
||||
// use for requests that were denied access.
|
||||
func (t *Throttler) start() http.Handler {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
// Get the effective denied handler
|
||||
dh := t.DeniedHandler
|
||||
if dh == nil {
|
||||
dh = DefaultDeniedHandler
|
||||
}
|
||||
if !t.started {
|
||||
t.limiter.Start()
|
||||
t.started = true
|
||||
}
|
||||
return dh
|
||||
}
|
||||
78
Godeps/_workspace/src/github.com/throttled/throttled/varyby.go
сгенерированный
поставляемый
Обычный файл
78
Godeps/_workspace/src/github.com/throttled/throttled/varyby.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,78 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VaryBy defines the criteria to use to group requests.
|
||||
type VaryBy struct {
|
||||
// Vary by the RemoteAddr as specified by the net/http.Request field.
|
||||
RemoteAddr bool
|
||||
|
||||
// Vary by the HTTP Method as specified by the net/http.Request field.
|
||||
Method bool
|
||||
|
||||
// Vary by the URL's Path as specified by the Path field of the net/http.Request
|
||||
// URL field.
|
||||
Path bool
|
||||
|
||||
// Vary by this list of header names, read from the net/http.Request Header field.
|
||||
Headers []string
|
||||
|
||||
// Vary by this list of parameters, read from the net/http.Request FormValue method.
|
||||
Params []string
|
||||
|
||||
// Vary by this list of cookie names, read from the net/http.Request Cookie method.
|
||||
Cookies []string
|
||||
|
||||
// Use this separator string to concatenate the various criteria of the VaryBy struct.
|
||||
// Defaults to a newline character if empty (\n).
|
||||
Separator string
|
||||
|
||||
// Custom specifies the custom-generated key to use for this request.
|
||||
// If not nil, the value returned by this function is used instead of any
|
||||
// VaryBy criteria.
|
||||
Custom func(r *http.Request) string
|
||||
}
|
||||
|
||||
// Key returns the key for this request based on the criteria defined by the VaryBy struct.
|
||||
func (vb *VaryBy) Key(r *http.Request) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if vb == nil {
|
||||
return "" // Special case for no vary-by option
|
||||
}
|
||||
if vb.Custom != nil {
|
||||
// A custom key generator is specified
|
||||
return vb.Custom(r)
|
||||
}
|
||||
sep := vb.Separator
|
||||
if sep == "" {
|
||||
sep = "\n" // Separator defaults to newline
|
||||
}
|
||||
if vb.RemoteAddr {
|
||||
buf.WriteString(strings.ToLower(r.RemoteAddr) + sep)
|
||||
}
|
||||
if vb.Method {
|
||||
buf.WriteString(strings.ToLower(r.Method) + sep)
|
||||
}
|
||||
for _, h := range vb.Headers {
|
||||
buf.WriteString(strings.ToLower(r.Header.Get(h)) + sep)
|
||||
}
|
||||
if vb.Path {
|
||||
buf.WriteString(r.URL.Path + sep)
|
||||
}
|
||||
for _, p := range vb.Params {
|
||||
buf.WriteString(r.FormValue(p) + sep)
|
||||
}
|
||||
for _, c := range vb.Cookies {
|
||||
ck, err := r.Cookie(c)
|
||||
if err == nil {
|
||||
buf.WriteString(ck.Value)
|
||||
}
|
||||
buf.WriteString(sep) // Write the separator anyway, whether or not the cookie exists
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
56
Godeps/_workspace/src/github.com/throttled/throttled/varyby_test.go
сгенерированный
поставляемый
Обычный файл
56
Godeps/_workspace/src/github.com/throttled/throttled/varyby_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,56 @@
|
||||
package throttled
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVaryBy(t *testing.T) {
|
||||
u, err := url.Parse("http://localhost/test/path?q=s")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ck := &http.Cookie{Name: "ssn", Value: "test"}
|
||||
cases := []struct {
|
||||
vb *VaryBy
|
||||
r *http.Request
|
||||
k string
|
||||
}{
|
||||
0: {nil, &http.Request{}, ""},
|
||||
1: {&VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"},
|
||||
2: {
|
||||
&VaryBy{Method: true, Path: true},
|
||||
&http.Request{Method: "POST", URL: u},
|
||||
"post\n/test/path\n",
|
||||
},
|
||||
3: {
|
||||
&VaryBy{Headers: []string{"Content-length"}},
|
||||
&http.Request{Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}},
|
||||
"123\n",
|
||||
},
|
||||
4: {
|
||||
&VaryBy{Separator: ",", Method: true, Headers: []string{"Content-length"}, Params: []string{"q", "user"}},
|
||||
&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"}}},
|
||||
"get,123,s,test,",
|
||||
},
|
||||
5: {
|
||||
&VaryBy{Cookies: []string{"ssn"}},
|
||||
&http.Request{Header: http.Header{"Cookie": []string{ck.String()}}},
|
||||
"test\n",
|
||||
},
|
||||
6: {
|
||||
&VaryBy{Cookies: []string{"ssn"}, RemoteAddr: true, Custom: func(r *http.Request) string {
|
||||
return "blah"
|
||||
}},
|
||||
&http.Request{Header: http.Header{"Cookie": []string{ck.String()}}},
|
||||
"blah",
|
||||
},
|
||||
}
|
||||
for i, c := range cases {
|
||||
got := c.vb.Key(c.r)
|
||||
if got != c.k {
|
||||
t.Errorf("%d: expected '%s' (%d), got '%s' (%d)", i, c.k, len(c.k), got, len(got))
|
||||
}
|
||||
}
|
||||
}
|
||||
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Ссылка в новой задаче
Block a user