mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-04 13:52:26 +08:00
add vendor
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,5 @@
|
||||
*.DS_Store
|
||||
*.exe
|
||||
vendor
|
||||
vendor/**
|
||||
conf/ssl/domain.*
|
||||
eiblog
|
||||
static/*.*
|
||||
|
||||
2
vendor/github.com/boj/redistore/.gitignore
generated
vendored
Normal file
2
vendor/github.com/boj/redistore/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
|
||||
19
vendor/github.com/boj/redistore/LICENSE
generated
vendored
Normal file
19
vendor/github.com/boj/redistore/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2013 Brian Jones
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
52
vendor/github.com/boj/redistore/README.md
generated
vendored
Normal file
52
vendor/github.com/boj/redistore/README.md
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# redistore
|
||||
|
||||
[](https://drone.io/github.com/boj/redistore/latest)
|
||||
|
||||
A session store backend for [gorilla/sessions](http://www.gorillatoolkit.org/pkg/sessions) - [src](https://github.com/gorilla/sessions).
|
||||
|
||||
## Requirements
|
||||
|
||||
Depends on the [Redigo](https://github.com/garyburd/redigo) Redis library.
|
||||
|
||||
## Installation
|
||||
|
||||
go get gopkg.in/boj/redistore.v1
|
||||
|
||||
## Documentation
|
||||
|
||||
Available on [godoc.org](http://www.godoc.org/gopkg.in/boj/redistore.v1).
|
||||
|
||||
See http://www.gorillatoolkit.org/pkg/sessions for full documentation on underlying interface.
|
||||
|
||||
### Example
|
||||
|
||||
// Fetch new store.
|
||||
store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
// Get a session.
|
||||
session, err = store.Get(req, "session-key")
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
|
||||
// Add a value.
|
||||
session.Values["foo"] = "bar"
|
||||
|
||||
// Save.
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
|
||||
// Delete session.
|
||||
session.Options.MaxAge = -1
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
|
||||
// Change session storage configuration for MaxAge = 10 days.
|
||||
store.SetMaxAge(10*24*3600)
|
||||
|
||||
4
vendor/github.com/boj/redistore/doc.go
generated
vendored
Normal file
4
vendor/github.com/boj/redistore/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package redistore is a session store backend for gorilla/sessions
|
||||
*/
|
||||
package redistore
|
||||
358
vendor/github.com/boj/redistore/redistore.go
generated
vendored
Normal file
358
vendor/github.com/boj/redistore/redistore.go
generated
vendored
Normal file
@@ -0,0 +1,358 @@
|
||||
// Copyright 2012 Brian "bojo" Jones. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package redistore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base32"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
// Amount of time for cookies/redis keys to expire.
|
||||
var sessionExpire = 86400 * 30
|
||||
|
||||
// SessionSerializer provides an interface hook for alternative serializers
|
||||
type SessionSerializer interface {
|
||||
Deserialize(d []byte, ss *sessions.Session) error
|
||||
Serialize(ss *sessions.Session) ([]byte, error)
|
||||
}
|
||||
|
||||
// JSONSerializer encode the session map to JSON.
|
||||
type JSONSerializer struct{}
|
||||
|
||||
// Serialize to JSON. Will err if there are unmarshalable key values
|
||||
func (s JSONSerializer) Serialize(ss *sessions.Session) ([]byte, error) {
|
||||
m := make(map[string]interface{}, len(ss.Values))
|
||||
for k, v := range ss.Values {
|
||||
ks, ok := k.(string)
|
||||
if !ok {
|
||||
err := fmt.Errorf("Non-string key value, cannot serialize session to JSON: %v", k)
|
||||
fmt.Printf("redistore.JSONSerializer.serialize() Error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
m[ks] = v
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// Deserialize back to map[string]interface{}
|
||||
func (s JSONSerializer) Deserialize(d []byte, ss *sessions.Session) error {
|
||||
m := make(map[string]interface{})
|
||||
err := json.Unmarshal(d, &m)
|
||||
if err != nil {
|
||||
fmt.Printf("redistore.JSONSerializer.deserialize() Error: %v", err)
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
ss.Values[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GobSerializer uses gob package to encode the session map
|
||||
type GobSerializer struct{}
|
||||
|
||||
// Serialize using gob
|
||||
func (s GobSerializer) Serialize(ss *sessions.Session) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(ss.Values)
|
||||
if err == nil {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deserialize back to map[interface{}]interface{}
|
||||
func (s GobSerializer) Deserialize(d []byte, ss *sessions.Session) error {
|
||||
dec := gob.NewDecoder(bytes.NewBuffer(d))
|
||||
return dec.Decode(&ss.Values)
|
||||
}
|
||||
|
||||
// RediStore stores sessions in a redis backend.
|
||||
type RediStore struct {
|
||||
Pool *redis.Pool
|
||||
Codecs []securecookie.Codec
|
||||
Options *sessions.Options // default configuration
|
||||
DefaultMaxAge int // default Redis TTL for a MaxAge == 0 session
|
||||
maxLength int
|
||||
keyPrefix string
|
||||
serializer SessionSerializer
|
||||
}
|
||||
|
||||
// SetMaxLength sets RediStore.maxLength if the `l` argument is greater or equal 0
|
||||
// maxLength restricts the maximum length of new sessions to l.
|
||||
// If l is 0 there is no limit to the size of a session, use with caution.
|
||||
// The default for a new RediStore is 4096. Redis allows for max.
|
||||
// value sizes of up to 512MB (http://redis.io/topics/data-types)
|
||||
// Default: 4096,
|
||||
func (s *RediStore) SetMaxLength(l int) {
|
||||
if l >= 0 {
|
||||
s.maxLength = l
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeyPrefix set the prefix
|
||||
func (s *RediStore) SetKeyPrefix(p string) {
|
||||
s.keyPrefix = p
|
||||
}
|
||||
|
||||
// SetSerializer sets the serializer
|
||||
func (s *RediStore) SetSerializer(ss SessionSerializer) {
|
||||
s.serializer = ss
|
||||
}
|
||||
|
||||
// SetMaxAge restricts the maximum age, in seconds, of the session record
|
||||
// both in database and a browser. This is to change session storage configuration.
|
||||
// If you want just to remove session use your session `s` object and change it's
|
||||
// `Options.MaxAge` to -1, as specified in
|
||||
// http://godoc.org/github.com/gorilla/sessions#Options
|
||||
//
|
||||
// Default is the one provided by this package value - `sessionExpire`.
|
||||
// Set it to 0 for no restriction.
|
||||
// Because we use `MaxAge` also in SecureCookie crypting algorithm you should
|
||||
// use this function to change `MaxAge` value.
|
||||
func (s *RediStore) SetMaxAge(v int) {
|
||||
var c *securecookie.SecureCookie
|
||||
var ok bool
|
||||
s.Options.MaxAge = v
|
||||
for i := range s.Codecs {
|
||||
if c, ok = s.Codecs[i].(*securecookie.SecureCookie); ok {
|
||||
c.MaxAge(v)
|
||||
} else {
|
||||
fmt.Printf("Can't change MaxAge on codec %v\n", s.Codecs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dial(network, address, password string) (redis.Conn, error) {
|
||||
c, err := redis.Dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if password != "" {
|
||||
if _, err := c.Do("AUTH", password); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// NewRediStore returns a new RediStore.
|
||||
// size: maximum number of idle connections.
|
||||
func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) (*RediStore, error) {
|
||||
return NewRediStoreWithPool(&redis.Pool{
|
||||
MaxIdle: size,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
Dial: func() (redis.Conn, error) {
|
||||
return dial(network, address, password)
|
||||
},
|
||||
}, keyPairs...)
|
||||
}
|
||||
|
||||
func dialWithDB(network, address, password, DB string) (redis.Conn, error) {
|
||||
c, err := dial(network, address, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.Do("SELECT", DB); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
// NewRediStoreWithDB - like NewRedisStore but accepts `DB` parameter to select
|
||||
// redis DB instead of using the default one ("0")
|
||||
func NewRediStoreWithDB(size int, network, address, password, DB string, keyPairs ...[]byte) (*RediStore, error) {
|
||||
return NewRediStoreWithPool(&redis.Pool{
|
||||
MaxIdle: size,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
Dial: func() (redis.Conn, error) {
|
||||
return dialWithDB(network, address, password, DB)
|
||||
},
|
||||
}, keyPairs...)
|
||||
}
|
||||
|
||||
// NewRediStoreWithPool instantiates a RediStore with a *redis.Pool passed in.
|
||||
func NewRediStoreWithPool(pool *redis.Pool, keyPairs ...[]byte) (*RediStore, error) {
|
||||
rs := &RediStore{
|
||||
// http://godoc.org/github.com/garyburd/redigo/redis#Pool
|
||||
Pool: pool,
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
Options: &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: sessionExpire,
|
||||
},
|
||||
DefaultMaxAge: 60 * 20, // 20 minutes seems like a reasonable default
|
||||
maxLength: 4096,
|
||||
keyPrefix: "session_",
|
||||
serializer: GobSerializer{},
|
||||
}
|
||||
_, err := rs.ping()
|
||||
return rs, err
|
||||
}
|
||||
|
||||
// Close closes the underlying *redis.Pool
|
||||
func (s *RediStore) Close() error {
|
||||
return s.Pool.Close()
|
||||
}
|
||||
|
||||
// Get returns a session for the given name after adding it to the registry.
|
||||
//
|
||||
// See gorilla/sessions FilesystemStore.Get().
|
||||
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
|
||||
return sessions.GetRegistry(r).Get(s, name)
|
||||
}
|
||||
|
||||
// New returns a session for the given name without adding it to the registry.
|
||||
//
|
||||
// See gorilla/sessions FilesystemStore.New().
|
||||
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
|
||||
var err error
|
||||
session := sessions.NewSession(s, name)
|
||||
// make a copy
|
||||
options := *s.Options
|
||||
session.Options = &options
|
||||
session.IsNew = true
|
||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
||||
err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
|
||||
if err == nil {
|
||||
ok, err := s.load(session)
|
||||
session.IsNew = !(err == nil && ok) // not new if no error and data available
|
||||
}
|
||||
}
|
||||
return session, err
|
||||
}
|
||||
|
||||
// Save adds a single session to the response.
|
||||
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
|
||||
// Marked for deletion.
|
||||
if session.Options.MaxAge < 0 {
|
||||
if err := s.delete(session); err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
|
||||
} else {
|
||||
// Build an alphanumeric key for the redis store.
|
||||
if session.ID == "" {
|
||||
session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
|
||||
}
|
||||
if err := s.save(session); err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the session from redis, and sets the cookie to expire.
|
||||
//
|
||||
// WARNING: This method should be considered deprecated since it is not exposed via the gorilla/sessions interface.
|
||||
// Set session.Options.MaxAge = -1 and call Save instead. - July 18th, 2013
|
||||
func (s *RediStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
|
||||
conn := s.Pool.Get()
|
||||
defer conn.Close()
|
||||
if _, err := conn.Do("DEL", s.keyPrefix+session.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set cookie to expire.
|
||||
options := *session.Options
|
||||
options.MaxAge = -1
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options))
|
||||
// Clear session values.
|
||||
for k := range session.Values {
|
||||
delete(session.Values, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ping does an internal ping against a server to check if it is alive.
|
||||
func (s *RediStore) ping() (bool, error) {
|
||||
conn := s.Pool.Get()
|
||||
defer conn.Close()
|
||||
data, err := conn.Do("PING")
|
||||
if err != nil || data == nil {
|
||||
return false, err
|
||||
}
|
||||
return (data == "PONG"), nil
|
||||
}
|
||||
|
||||
// save stores the session in redis.
|
||||
func (s *RediStore) save(session *sessions.Session) error {
|
||||
b, err := s.serializer.Serialize(session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.maxLength != 0 && len(b) > s.maxLength {
|
||||
return errors.New("SessionStore: the value to store is too big")
|
||||
}
|
||||
conn := s.Pool.Get()
|
||||
defer conn.Close()
|
||||
if err = conn.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
age := session.Options.MaxAge
|
||||
if age == 0 {
|
||||
age = s.DefaultMaxAge
|
||||
}
|
||||
_, err = conn.Do("SETEX", s.keyPrefix+session.ID, age, b)
|
||||
return err
|
||||
}
|
||||
|
||||
// load reads the session from redis.
|
||||
// returns true if there is a sessoin data in DB
|
||||
func (s *RediStore) load(session *sessions.Session) (bool, error) {
|
||||
conn := s.Pool.Get()
|
||||
defer conn.Close()
|
||||
if err := conn.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
data, err := conn.Do("GET", s.keyPrefix+session.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if data == nil {
|
||||
return false, nil // no data was associated with this key
|
||||
}
|
||||
b, err := redis.Bytes(data, err)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, s.serializer.Deserialize(b, session)
|
||||
}
|
||||
|
||||
// delete removes keys from redis if MaxAge<0
|
||||
func (s *RediStore) delete(session *sessions.Session) error {
|
||||
conn := s.Pool.Get()
|
||||
defer conn.Close()
|
||||
if _, err := conn.Do("DEL", s.keyPrefix+session.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
404
vendor/github.com/boj/redistore/redistore_test.go
generated
vendored
Normal file
404
vendor/github.com/boj/redistore/redistore_test.go
generated
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
package redistore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ResponseRecorder
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct {
|
||||
Code int // the HTTP response code from WriteHeader
|
||||
HeaderMap http.Header // the HTTP response headers
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
Flushed bool
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
Body: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4"
|
||||
|
||||
// Header returns the response headers.
|
||||
func (rw *ResponseRecorder) Header() http.Header {
|
||||
return rw.HeaderMap
|
||||
}
|
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
if rw.Body != nil {
|
||||
rw.Body.Write(buf)
|
||||
}
|
||||
if rw.Code == 0 {
|
||||
rw.Code = http.StatusOK
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
rw.Code = code
|
||||
}
|
||||
|
||||
// Flush sets rw.Flushed to true.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
rw.Flushed = true
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type FlashMessage struct {
|
||||
Type int
|
||||
Message string
|
||||
}
|
||||
|
||||
func TestRediStore(t *testing.T) {
|
||||
var req *http.Request
|
||||
var rsp *ResponseRecorder
|
||||
var hdr http.Header
|
||||
var err error
|
||||
var ok bool
|
||||
var cookies []string
|
||||
var session *sessions.Session
|
||||
var flashes []interface{}
|
||||
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Round 1 ----------------------------------------------------------------
|
||||
|
||||
// RedisStore
|
||||
store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Get a flash.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected empty flashes; Got %v", flashes)
|
||||
}
|
||||
// Add some flashes.
|
||||
session.AddFlash("foo")
|
||||
session.AddFlash("bar")
|
||||
// Custom key.
|
||||
session.AddFlash("baz", "custom_key")
|
||||
// Save.
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
hdr = rsp.Header()
|
||||
cookies, ok = hdr["Set-Cookie"]
|
||||
if !ok || len(cookies) != 1 {
|
||||
t.Fatalf("No cookies. Header:", hdr)
|
||||
}
|
||||
|
||||
// Round 2 ----------------------------------------------------------------
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
req.Header.Add("Cookie", cookies[0])
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Check all saved values.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 2 {
|
||||
t.Fatalf("Expected flashes; Got %v", flashes)
|
||||
}
|
||||
if flashes[0] != "foo" || flashes[1] != "bar" {
|
||||
t.Errorf("Expected foo,bar; Got %v", flashes)
|
||||
}
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected dumped flashes; Got %v", flashes)
|
||||
}
|
||||
// Custom key.
|
||||
flashes = session.Flashes("custom_key")
|
||||
if len(flashes) != 1 {
|
||||
t.Errorf("Expected flashes; Got %v", flashes)
|
||||
} else if flashes[0] != "baz" {
|
||||
t.Errorf("Expected baz; Got %v", flashes)
|
||||
}
|
||||
flashes = session.Flashes("custom_key")
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected dumped flashes; Got %v", flashes)
|
||||
}
|
||||
|
||||
// RediStore specific
|
||||
// Set MaxAge to -1 to mark for deletion.
|
||||
session.Options.MaxAge = -1
|
||||
// Save.
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
|
||||
// Round 3 ----------------------------------------------------------------
|
||||
// Custom type
|
||||
|
||||
// RedisStore
|
||||
store, err = NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Get a flash.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected empty flashes; Got %v", flashes)
|
||||
}
|
||||
// Add some flashes.
|
||||
session.AddFlash(&FlashMessage{42, "foo"})
|
||||
// Save.
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
hdr = rsp.Header()
|
||||
cookies, ok = hdr["Set-Cookie"]
|
||||
if !ok || len(cookies) != 1 {
|
||||
t.Fatalf("No cookies. Header:", hdr)
|
||||
}
|
||||
|
||||
// Round 4 ----------------------------------------------------------------
|
||||
// Custom type
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
req.Header.Add("Cookie", cookies[0])
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Check all saved values.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 1 {
|
||||
t.Fatalf("Expected flashes; Got %v", flashes)
|
||||
}
|
||||
custom := flashes[0].(FlashMessage)
|
||||
if custom.Type != 42 || custom.Message != "foo" {
|
||||
t.Errorf("Expected %#v, got %#v", FlashMessage{42, "foo"}, custom)
|
||||
}
|
||||
|
||||
// RediStore specific
|
||||
// Set MaxAge to -1 to mark for deletion.
|
||||
session.Options.MaxAge = -1
|
||||
// Save.
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
|
||||
// Round 5 ----------------------------------------------------------------
|
||||
// RediStore Delete session (deprecated)
|
||||
|
||||
//req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
//req.Header.Add("Cookie", cookies[0])
|
||||
//rsp = NewRecorder()
|
||||
//// Get a session.
|
||||
//if session, err = store.Get(req, "session-key"); err != nil {
|
||||
// t.Fatalf("Error getting session: %v", err)
|
||||
//}
|
||||
//// Delete session.
|
||||
//if err = store.Delete(req, rsp, session); err != nil {
|
||||
// t.Fatalf("Error deleting session: %v", err)
|
||||
//}
|
||||
//// Get a flash.
|
||||
//flashes = session.Flashes()
|
||||
//if len(flashes) != 0 {
|
||||
// t.Errorf("Expected empty flashes; Got %v", flashes)
|
||||
//}
|
||||
//hdr = rsp.Header()
|
||||
//cookies, ok = hdr["Set-Cookie"]
|
||||
//if !ok || len(cookies) != 1 {
|
||||
// t.Fatalf("No cookies. Header:", hdr)
|
||||
//}
|
||||
|
||||
// Round 6 ----------------------------------------------------------------
|
||||
// RediStore change MaxLength of session
|
||||
|
||||
store, err = NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
req, err = http.NewRequest("GET", "http://www.example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
session, err = store.New(req, "my session")
|
||||
session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2))
|
||||
err = session.Save(req, w)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error, got nil")
|
||||
}
|
||||
|
||||
store.SetMaxLength(4096 * 3) // A bit more than the value size to account for encoding overhead.
|
||||
err = session.Save(req, w)
|
||||
if err != nil {
|
||||
t.Fatal("failed to Save:", err)
|
||||
}
|
||||
|
||||
// Round 7 ----------------------------------------------------------------
|
||||
|
||||
// RedisStoreWithDB
|
||||
store, err = NewRediStoreWithDB(10, "tcp", ":6379", "", "1", []byte("secret-key"))
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
rsp = NewRecorder()
|
||||
// Get a session. Using the same key as previously, but on different DB
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Get a flash.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected empty flashes; Got %v", flashes)
|
||||
}
|
||||
// Add some flashes.
|
||||
session.AddFlash("foo")
|
||||
// Save.
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
hdr = rsp.Header()
|
||||
cookies, ok = hdr["Set-Cookie"]
|
||||
if !ok || len(cookies) != 1 {
|
||||
t.Fatalf("No cookies. Header:", hdr)
|
||||
}
|
||||
|
||||
// Get a session.
|
||||
req.Header.Add("Cookie", cookies[0])
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Check all saved values.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 1 {
|
||||
t.Fatalf("Expected flashes; Got %v", flashes)
|
||||
}
|
||||
if flashes[0] != "foo" {
|
||||
t.Errorf("Expected foo,bar; Got %v", flashes)
|
||||
}
|
||||
|
||||
// Round 8 ----------------------------------------------------------------
|
||||
// JSONSerializer
|
||||
|
||||
// RedisStore
|
||||
store, err = NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
|
||||
store.SetSerializer(JSONSerializer{})
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Get a flash.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected empty flashes; Got %v", flashes)
|
||||
}
|
||||
// Add some flashes.
|
||||
session.AddFlash("foo")
|
||||
// Save.
|
||||
if err = sessions.Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
hdr = rsp.Header()
|
||||
cookies, ok = hdr["Set-Cookie"]
|
||||
if !ok || len(cookies) != 1 {
|
||||
t.Fatalf("No cookies. Header:", hdr)
|
||||
}
|
||||
|
||||
// Get a session.
|
||||
req.Header.Add("Cookie", cookies[0])
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Check all saved values.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 1 {
|
||||
t.Fatalf("Expected flashes; Got %v", flashes)
|
||||
}
|
||||
if flashes[0] != "foo" {
|
||||
t.Errorf("Expected foo,bar; Got %v", flashes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPingGoodPort(t *testing.T) {
|
||||
store, _ := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
|
||||
defer store.Close()
|
||||
ok, err := store.ping()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if !ok {
|
||||
t.Error("Expected server to PONG")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPingBadPort(t *testing.T) {
|
||||
store, _ := NewRediStore(10, "tcp", ":6378", "", []byte("secret-key"))
|
||||
defer store.Close()
|
||||
_, err := store.ping()
|
||||
if err == nil {
|
||||
t.Error("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleRediStore() {
|
||||
// RedisStore
|
||||
store, err := NewRediStore(10, "tcp", ":6379", "", []byte("secret-key"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer store.Close()
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(FlashMessage{})
|
||||
}
|
||||
5
vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md
generated
vendored
Normal file
5
vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Ask questions at
|
||||
[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).
|
||||
|
||||
[Open an issue](https://github.com/garyburd/redigo/issues/new) to discuss your
|
||||
plans before doing any work on Redigo.
|
||||
1
vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
1
vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis
|
||||
17
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
Normal file
17
vendor/github.com/garyburd/redigo/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
language: go
|
||||
sudo: false
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
||||
175
vendor/github.com/garyburd/redigo/LICENSE
generated
vendored
Normal file
175
vendor/github.com/garyburd/redigo/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
50
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
Normal file
50
vendor/github.com/garyburd/redigo/README.markdown
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
Redigo
|
||||
======
|
||||
|
||||
[](https://travis-ci.org/garyburd/redigo)
|
||||
[](https://godoc.org/github.com/garyburd/redigo/redis)
|
||||
|
||||
Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database.
|
||||
|
||||
Features
|
||||
-------
|
||||
|
||||
* A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.
|
||||
* [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions.
|
||||
* [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe).
|
||||
* [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool).
|
||||
* [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA.
|
||||
* [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
|
||||
- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install Redigo using the "go get" command:
|
||||
|
||||
go get github.com/garyburd/redigo/redis
|
||||
|
||||
The Go distribution is Redigo's only dependency.
|
||||
|
||||
Related Projects
|
||||
----------------
|
||||
|
||||
- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo.
|
||||
- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation.
|
||||
- [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo
|
||||
- [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/garyburd/redigo/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
|
||||
54
vendor/github.com/garyburd/redigo/internal/commandinfo.go
generated
vendored
Normal file
54
vendor/github.com/garyburd/redigo/internal/commandinfo.go
generated
vendored
Normal file
@@ -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 "github.com/garyburd/redigo/internal"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
WatchState = 1 << iota
|
||||
MultiState
|
||||
SubscribeState
|
||||
MonitorState
|
||||
)
|
||||
|
||||
type CommandInfo struct {
|
||||
Set, Clear int
|
||||
}
|
||||
|
||||
var commandInfos = map[string]CommandInfo{
|
||||
"WATCH": {Set: WatchState},
|
||||
"UNWATCH": {Clear: WatchState},
|
||||
"MULTI": {Set: MultiState},
|
||||
"EXEC": {Clear: WatchState | MultiState},
|
||||
"DISCARD": {Clear: WatchState | MultiState},
|
||||
"PSUBSCRIBE": {Set: SubscribeState},
|
||||
"SUBSCRIBE": {Set: SubscribeState},
|
||||
"MONITOR": {Set: MonitorState},
|
||||
}
|
||||
|
||||
func 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
vendor/github.com/garyburd/redigo/internal/commandinfo_test.go
generated
vendored
Normal file
27
vendor/github.com/garyburd/redigo/internal/commandinfo_test.go
generated
vendored
Normal file
@@ -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")
|
||||
}
|
||||
68
vendor/github.com/garyburd/redigo/internal/redistest/testdb.go
generated
vendored
Normal file
68
vendor/github.com/garyburd/redigo/internal/redistest/testdb.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// 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 {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n, err := redis.Int(c.Do("DBSIZE"))
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n != 0 {
|
||||
c.Close()
|
||||
return nil, errors.New("database #9 is not empty, test can not continue")
|
||||
}
|
||||
|
||||
return testConn{c}, nil
|
||||
}
|
||||
618
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
Normal file
618
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
Normal file
@@ -0,0 +1,618 @@
|
||||
// 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"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"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
|
||||
}
|
||||
|
||||
// DialTimeout acts like Dial but takes timeouts for establishing the
|
||||
// connection to the server, writing a command and reading a reply.
|
||||
//
|
||||
// Deprecated: Use Dial with options instead.
|
||||
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
|
||||
return Dial(network, address,
|
||||
DialConnectTimeout(connectTimeout),
|
||||
DialReadTimeout(readTimeout),
|
||||
DialWriteTimeout(writeTimeout))
|
||||
}
|
||||
|
||||
// DialOption specifies an option for dialing a Redis server.
|
||||
type DialOption struct {
|
||||
f func(*dialOptions)
|
||||
}
|
||||
|
||||
type dialOptions struct {
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
dial func(network, addr string) (net.Conn, error)
|
||||
db int
|
||||
password string
|
||||
dialTLS bool
|
||||
skipVerify bool
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// DialReadTimeout specifies the timeout for reading a single command reply.
|
||||
func DialReadTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.readTimeout = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialWriteTimeout specifies the timeout for writing a single command.
|
||||
func DialWriteTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.writeTimeout = d
|
||||
}}
|
||||
}
|
||||
|
||||
// DialConnectTimeout specifies the timeout for connecting to the Redis server.
|
||||
func DialConnectTimeout(d time.Duration) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
dialer := net.Dialer{Timeout: d}
|
||||
do.dial = dialer.Dial
|
||||
}}
|
||||
}
|
||||
|
||||
// DialNetDial specifies a custom dial function for creating TCP
|
||||
// connections. If this option is left out, then net.Dial is
|
||||
// used. DialNetDial overrides DialConnectTimeout.
|
||||
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.dial = dial
|
||||
}}
|
||||
}
|
||||
|
||||
// DialDatabase specifies the database to select when dialing a connection.
|
||||
func DialDatabase(db int) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.db = db
|
||||
}}
|
||||
}
|
||||
|
||||
// DialPassword specifies the password to use when connecting to
|
||||
// the Redis server.
|
||||
func DialPassword(password string) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.password = password
|
||||
}}
|
||||
}
|
||||
|
||||
// DialTLSConfig specifies the config to use when a TLS connection is dialed.
|
||||
// Has no effect when not dialing a TLS connection.
|
||||
func DialTLSConfig(c *tls.Config) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.tlsConfig = c
|
||||
}}
|
||||
}
|
||||
|
||||
// DialTLSSkipVerify to disable server name verification when connecting
|
||||
// over TLS. Has no effect when not dialing a TLS connection.
|
||||
func DialTLSSkipVerify(skip bool) DialOption {
|
||||
return DialOption{func(do *dialOptions) {
|
||||
do.skipVerify = skip
|
||||
}}
|
||||
}
|
||||
|
||||
// Dial connects to the Redis server at the given network and
|
||||
// address using the specified options.
|
||||
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
||||
do := dialOptions{
|
||||
dial: net.Dial,
|
||||
}
|
||||
for _, option := range options {
|
||||
option.f(&do)
|
||||
}
|
||||
|
||||
netConn, err := do.dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if do.dialTLS {
|
||||
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
|
||||
if tlsConfig.ServerName == "" {
|
||||
host, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.ServerName = host
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(netConn, tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
netConn = tlsConn
|
||||
}
|
||||
|
||||
c := &conn{
|
||||
conn: netConn,
|
||||
bw: bufio.NewWriter(netConn),
|
||||
br: bufio.NewReader(netConn),
|
||||
readTimeout: do.readTimeout,
|
||||
writeTimeout: do.writeTimeout,
|
||||
}
|
||||
|
||||
if do.password != "" {
|
||||
if _, err := c.Do("AUTH", do.password); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if do.db != 0 {
|
||||
if _, err := c.Do("SELECT", do.db); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func dialTLS(do *dialOptions) {
|
||||
do.dialTLS = true
|
||||
}
|
||||
|
||||
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
||||
|
||||
// DialURL connects to a Redis server at the given URL using the Redis
|
||||
// URI scheme. URLs should follow the draft IANA specification for the
|
||||
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
|
||||
func DialURL(rawurl string, options ...DialOption) (Conn, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Scheme != "redis" && u.Scheme != "rediss" {
|
||||
return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
|
||||
}
|
||||
|
||||
// As per the IANA draft spec, the host defaults to localhost and
|
||||
// the port defaults to 6379.
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
// assume port is missing
|
||||
host = u.Host
|
||||
port = "6379"
|
||||
}
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
address := net.JoinHostPort(host, port)
|
||||
|
||||
if u.User != nil {
|
||||
password, isSet := u.User.Password()
|
||||
if isSet {
|
||||
options = append(options, DialPassword(password))
|
||||
}
|
||||
}
|
||||
|
||||
match := pathDBRegexp.FindStringSubmatch(u.Path)
|
||||
if len(match) == 2 {
|
||||
db := 0
|
||||
if len(match[1]) > 0 {
|
||||
db, err = strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||
}
|
||||
}
|
||||
if db != 0 {
|
||||
options = append(options, DialDatabase(db))
|
||||
}
|
||||
} else if u.Path != "" {
|
||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
||||
}
|
||||
|
||||
if u.Scheme == "rediss" {
|
||||
options = append([]DialOption{{dialTLS}}, options...)
|
||||
}
|
||||
|
||||
return Dial("tcp", address, options...)
|
||||
}
|
||||
|
||||
// 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 != "" {
|
||||
if err := c.writeCommand(cmd, args); err != nil {
|
||||
return nil, c.fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
670
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
Normal file
670
vendor/github.com/garyburd/redigo/redis/conn_test.go
generated
vendored
Normal file
@@ -0,0 +1,670 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
type testConn struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (*testConn) Close() error { return nil }
|
||||
func (*testConn) LocalAddr() net.Addr { return nil }
|
||||
func (*testConn) RemoteAddr() net.Addr { return nil }
|
||||
func (*testConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (*testConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
func dialTestConn(r io.Reader, w io.Writer) redis.DialOption {
|
||||
return redis.DialNetDial(func(net, addr string) (net.Conn, error) {
|
||||
return &testConn{Reader: r, Writer: w}, nil
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
c, _ := redis.Dial("", "", dialTestConn(nil, &buf))
|
||||
err := c.Send(tt.args[0].(string), tt.args[1:]...)
|
||||
if err != nil {
|
||||
t.Errorf("Send(%v) returned error %v", tt.args, err)
|
||||
continue
|
||||
}
|
||||
c.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 {
|
||||
c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil))
|
||||
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 := redis.DialDefaultServer()
|
||||
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 := redis.DialDefaultServer()
|
||||
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 := redis.DialDefaultServer()
|
||||
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 := redis.DialDefaultServer()
|
||||
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 := redis.DialDefaultServer()
|
||||
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 TestReadTimeout(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()
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
// Do
|
||||
|
||||
c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
|
||||
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")
|
||||
}
|
||||
|
||||
// Send/Flush/Receive
|
||||
|
||||
c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
var dialErrors = []struct {
|
||||
rawurl string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
"localhost",
|
||||
"invalid redis URL scheme",
|
||||
},
|
||||
// The error message for invalid hosts is diffferent in different
|
||||
// versions of Go, so just check that there is an error message.
|
||||
{
|
||||
"redis://weird url",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"redis://foo:bar:baz",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"http://www.google.com",
|
||||
"invalid redis URL scheme: http",
|
||||
},
|
||||
{
|
||||
"redis://localhost:6379/abc123",
|
||||
"invalid database: abc123",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDialURLErrors(t *testing.T) {
|
||||
for _, d := range dialErrors {
|
||||
_, err := redis.DialURL(d.rawurl)
|
||||
if err == nil || !strings.Contains(err.Error(), d.expectedError) {
|
||||
t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLPort(t *testing.T) {
|
||||
checkPort := func(network, address string) (net.Conn, error) {
|
||||
if address != "localhost:6379" {
|
||||
t.Errorf("DialURL did not set port to 6379 by default (got %v)", address)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
_, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort))
|
||||
if err != nil {
|
||||
t.Error("dial error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLHost(t *testing.T) {
|
||||
checkHost := func(network, address string) (net.Conn, error) {
|
||||
if address != "localhost:6379" {
|
||||
t.Errorf("DialURL did not set host to localhost by default (got %v)", address)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
_, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost))
|
||||
if err != nil {
|
||||
t.Error("dial error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLPassword(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
_, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf))
|
||||
if err != nil {
|
||||
t.Error("dial error:", err)
|
||||
}
|
||||
expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"
|
||||
actual := buf.String()
|
||||
if actual != expected {
|
||||
t.Errorf("commands = %q, want %q", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialURLDatabase(t *testing.T) {
|
||||
var buf3 bytes.Buffer
|
||||
_, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3))
|
||||
if err3 != nil {
|
||||
t.Error("dial error:", err3)
|
||||
}
|
||||
expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"
|
||||
actual3 := buf3.String()
|
||||
if actual3 != expected3 {
|
||||
t.Errorf("commands = %q, want %q", actual3, expected3)
|
||||
}
|
||||
// empty DB means 0
|
||||
var buf0 bytes.Buffer
|
||||
_, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0))
|
||||
if err0 != nil {
|
||||
t.Error("dial error:", err0)
|
||||
}
|
||||
expected0 := ""
|
||||
actual0 := buf0.String()
|
||||
if actual0 != expected0 {
|
||||
t.Errorf("commands = %q, want %q", actual0, expected0)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to local instance of Redis running on the default port.
|
||||
func ExampleDial() {
|
||||
c, err := redis.Dial("tcp", ":6379")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer c.Close()
|
||||
}
|
||||
|
||||
// Connect to remote instance of Redis using a URL.
|
||||
func ExampleDialURL() {
|
||||
c, err := redis.DialURL(os.Getenv("REDIS_URL"))
|
||||
if err != nil {
|
||||
// handle connection 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 := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Execute commands that fail before EXEC is called.
|
||||
|
||||
c.Do("DEL", "k0")
|
||||
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("DEL", "k1")
|
||||
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[1])
|
||||
}
|
||||
|
||||
// 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 := redis.DialDefaultServer()
|
||||
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 := redis.DialDefaultServer()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
177
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
Normal file
177
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
// 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 support one concurrent caller to the Receive method and one
|
||||
// concurrent caller to the Send and Flush methods. No other concurrency is
|
||||
// supported including concurrent calls to the Do method.
|
||||
//
|
||||
// For full concurrent access to Redis, use the thread-safe Pool to get, use
|
||||
// and release a connection from within a goroutine. Connections returned from
|
||||
// a Pool have the concurrency restrictions described in the previous
|
||||
// paragraph.
|
||||
//
|
||||
// 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{Conn: 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
|
||||
// }
|
||||
//
|
||||
// Errors
|
||||
//
|
||||
// Connection methods return error replies from the server as type redis.Error.
|
||||
//
|
||||
// Call the connection Err() method to determine if the connection encountered
|
||||
// non-recoverable error such as a network error or protocol parsing error. If
|
||||
// Err() returns a non-nil value, then the connection is not usable and should
|
||||
// be closed.
|
||||
package redis // import "github.com/garyburd/redigo/redis"
|
||||
33
vendor/github.com/garyburd/redigo/redis/go17.go
generated
vendored
Normal file
33
vendor/github.com/garyburd/redigo/redis/go17.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// +build go1.7
|
||||
|
||||
package redis
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
|
||||
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{InsecureSkipVerify: skipVerify}
|
||||
}
|
||||
return &tls.Config{
|
||||
Rand: cfg.Rand,
|
||||
Time: cfg.Time,
|
||||
Certificates: cfg.Certificates,
|
||||
NameToCertificate: cfg.NameToCertificate,
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
RootCAs: cfg.RootCAs,
|
||||
NextProtos: cfg.NextProtos,
|
||||
ServerName: cfg.ServerName,
|
||||
ClientAuth: cfg.ClientAuth,
|
||||
ClientCAs: cfg.ClientCAs,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
CipherSuites: cfg.CipherSuites,
|
||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||
ClientSessionCache: cfg.ClientSessionCache,
|
||||
MinVersion: cfg.MinVersion,
|
||||
MaxVersion: cfg.MaxVersion,
|
||||
CurvePreferences: cfg.CurvePreferences,
|
||||
DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled,
|
||||
Renegotiation: cfg.Renegotiation,
|
||||
}
|
||||
}
|
||||
117
vendor/github.com/garyburd/redigo/redis/log.go
generated
vendored
Normal file
117
vendor/github.com/garyburd/redigo/redis/log.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
// NewLoggingConn returns a logging wrapper around a connection.
|
||||
func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
|
||||
if prefix != "" {
|
||||
prefix = prefix + "."
|
||||
}
|
||||
return &loggingConn{conn, logger, prefix}
|
||||
}
|
||||
|
||||
type loggingConn struct {
|
||||
Conn
|
||||
logger *log.Logger
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (c *loggingConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
|
||||
c.logger.Output(2, buf.String())
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
|
||||
const chop = 32
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case string:
|
||||
if len(v) > chop {
|
||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%q", v)
|
||||
}
|
||||
case []interface{}:
|
||||
if len(v) == 0 {
|
||||
buf.WriteString("[]")
|
||||
} else {
|
||||
sep := "["
|
||||
fin := "]"
|
||||
if len(v) > chop {
|
||||
v = v[:chop]
|
||||
fin = "...]"
|
||||
}
|
||||
for _, vv := range v {
|
||||
buf.WriteString(sep)
|
||||
c.printValue(buf, vv)
|
||||
sep = ", "
|
||||
}
|
||||
buf.WriteString(fin)
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(buf, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
|
||||
if method != "Receive" {
|
||||
buf.WriteString(commandName)
|
||||
for _, arg := range args {
|
||||
buf.WriteString(", ")
|
||||
c.printValue(&buf, arg)
|
||||
}
|
||||
}
|
||||
buf.WriteString(") -> (")
|
||||
if method != "Send" {
|
||||
c.printValue(&buf, reply)
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&buf, "%v)", err)
|
||||
c.logger.Output(3, buf.String())
|
||||
}
|
||||
|
||||
func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
|
||||
reply, err := c.Conn.Do(commandName, args...)
|
||||
c.print("Do", commandName, args, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Send(commandName string, args ...interface{}) error {
|
||||
err := c.Conn.Send(commandName, args...)
|
||||
c.print("Send", commandName, args, nil, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *loggingConn) Receive() (interface{}, error) {
|
||||
reply, err := c.Conn.Receive()
|
||||
c.print("Receive", "", nil, reply, err)
|
||||
return reply, err
|
||||
}
|
||||
416
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
Normal file
416
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
Normal file
@@ -0,0 +1,416 @@
|
||||
// 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 package level variable. The pool configuration used
|
||||
// here is an example, not a recommendation.
|
||||
//
|
||||
// func newPool(addr string) *redis.Pool {
|
||||
// return &redis.Pool{
|
||||
// MaxIdle: 3,
|
||||
// IdleTimeout: 240 * time.Second,
|
||||
// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// pool *redis.Pool
|
||||
// redisServer = flag.String("redisServer", ":6379", "")
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// flag.Parse()
|
||||
// pool = newPool(*redisServer)
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// 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()
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Use the Dial function to authenticate connections with the AUTH command or
|
||||
// select a database with the SELECT command:
|
||||
//
|
||||
// pool := &redis.Pool{
|
||||
// // Other pool configuration not shown in this example.
|
||||
// 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
|
||||
// }
|
||||
// if _, err := c.Do("SELECT", db); err != nil {
|
||||
// c.Close()
|
||||
// return nil, err
|
||||
// }
|
||||
// return c, nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Use the TestOnBorrow function to check the health of an idle connection
|
||||
// before the connection is returned to the application. This example PINGs
|
||||
// connections that have been idle more than a minute:
|
||||
//
|
||||
// pool := &redis.Pool{
|
||||
// // Other pool configuration not shown in this example.
|
||||
// TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
// if time.Since(t) < time.Minute {
|
||||
// return nil
|
||||
// }
|
||||
// _, err := c.Do("PING")
|
||||
// return err
|
||||
// },
|
||||
// }
|
||||
//
|
||||
type Pool struct {
|
||||
|
||||
// Dial is an application supplied function for creating and configuring a
|
||||
// connection.
|
||||
//
|
||||
// The connection returned from Dial must not be in a special state
|
||||
// (subscribed to pubsub channel, transaction started, ...).
|
||||
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 MaxActive 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.
|
||||
//
|
||||
// Deprecated: Initialize the Pool directory as shown in the 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 }
|
||||
684
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
Normal file
684
vendor/github.com/garyburd/redigo/redis/pool_test.go
generated
vendored
Normal file
@@ -0,0 +1,684 @@
|
||||
// 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/redis"
|
||||
)
|
||||
|
||||
type poolTestConn struct {
|
||||
d *poolDialer
|
||||
err error
|
||||
redis.Conn
|
||||
}
|
||||
|
||||
func (c *poolTestConn) Close() error {
|
||||
c.d.mu.Lock()
|
||||
c.d.open -= 1
|
||||
c.d.mu.Unlock()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *poolTestConn) Err() error { return c.err }
|
||||
|
||||
func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, 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 {
|
||||
mu sync.Mutex
|
||||
t *testing.T
|
||||
dialed int
|
||||
open int
|
||||
commands []string
|
||||
dialErr error
|
||||
}
|
||||
|
||||
func (d *poolDialer) dial() (redis.Conn, error) {
|
||||
d.mu.Lock()
|
||||
d.dialed += 1
|
||||
dialErr := d.dialErr
|
||||
d.mu.Unlock()
|
||||
if dialErr != nil {
|
||||
return nil, d.dialErr
|
||||
}
|
||||
c, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.mu.Lock()
|
||||
d.open += 1
|
||||
d.mu.Unlock()
|
||||
return &poolTestConn{d: d, Conn: c}, nil
|
||||
}
|
||||
|
||||
func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) {
|
||||
d.mu.Lock()
|
||||
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)
|
||||
}
|
||||
d.mu.Unlock()
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func TestPoolConcurrenSendReceive(t *testing.T) {
|
||||
p := &redis.Pool{
|
||||
Dial: redis.DialDefaultServer,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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") },
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
c := p.Get()
|
||||
c.Do("PING")
|
||||
c.Close()
|
||||
}
|
||||
d.check("1", p, 10, 1)
|
||||
}
|
||||
|
||||
func TestPoolMaxActive(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func TestPoolMonitorCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
c := p.Get()
|
||||
c.Send("MONITOR")
|
||||
c.Close()
|
||||
|
||||
d.check("", p, 1, 0)
|
||||
}
|
||||
|
||||
func TestPoolPubSubCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func TestPoolTransactionCleanup(t *testing.T) {
|
||||
d := poolDialer{t: t}
|
||||
p := &redis.Pool{
|
||||
MaxIdle: 2,
|
||||
MaxActive: 2,
|
||||
Dial: d.dial,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
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 errors, 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) {
|
||||
const 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.
|
||||
conns := make([]redis.Conn, count)
|
||||
for i := range conns {
|
||||
conns[i] = p.Get()
|
||||
}
|
||||
for i := range conns {
|
||||
conns[i].Close()
|
||||
}
|
||||
|
||||
// Spawn a bunch of goroutines to thrash the pool.
|
||||
var wg sync.WaitGroup
|
||||
wg.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()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.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: redis.DialDefaultServer, 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: redis.DialDefaultServer, 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: redis.DialDefaultServer, 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()
|
||||
}
|
||||
}
|
||||
31
vendor/github.com/garyburd/redigo/redis/pre_go17.go
generated
vendored
Normal file
31
vendor/github.com/garyburd/redigo/redis/pre_go17.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// +build !go1.7
|
||||
|
||||
package redis
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
|
||||
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
|
||||
if cfg == nil {
|
||||
return &tls.Config{InsecureSkipVerify: skipVerify}
|
||||
}
|
||||
return &tls.Config{
|
||||
Rand: cfg.Rand,
|
||||
Time: cfg.Time,
|
||||
Certificates: cfg.Certificates,
|
||||
NameToCertificate: cfg.NameToCertificate,
|
||||
GetCertificate: cfg.GetCertificate,
|
||||
RootCAs: cfg.RootCAs,
|
||||
NextProtos: cfg.NextProtos,
|
||||
ServerName: cfg.ServerName,
|
||||
ClientAuth: cfg.ClientAuth,
|
||||
ClientCAs: cfg.ClientCAs,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
CipherSuites: cfg.CipherSuites,
|
||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
||||
ClientSessionCache: cfg.ClientSessionCache,
|
||||
MinVersion: cfg.MinVersion,
|
||||
MaxVersion: cfg.MaxVersion,
|
||||
CurvePreferences: cfg.CurvePreferences,
|
||||
}
|
||||
}
|
||||
144
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
Normal file
144
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
Normal file
@@ -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")
|
||||
}
|
||||
148
vendor/github.com/garyburd/redigo/redis/pubsub_test.go
generated
vendored
Normal file
148
vendor/github.com/garyburd/redigo/redis/pubsub_test.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
// 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"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
func publish(channel, value interface{}) {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer pc.Close()
|
||||
|
||||
sc, err := redis.DialDefaultServer()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer sc.Close()
|
||||
|
||||
c := redis.PubSubConn{Conn: sc}
|
||||
|
||||
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{Data: "hello"})
|
||||
|
||||
c.Conn.Send("PING")
|
||||
c.Conn.Flush()
|
||||
expectPushed(t, c, `Send("PING")`, redis.Pong{})
|
||||
}
|
||||
41
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
Normal file
41
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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 when the connection is not usable.
|
||||
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)
|
||||
}
|
||||
393
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
Normal file
393
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
// 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 a helper that converts an array command reply to a []interface{}.
|
||||
//
|
||||
// Deprecated: Use Values instead.
|
||||
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)
|
||||
}
|
||||
|
||||
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
||||
// If err is not equal to nil, then ByteSlices returns nil, err. Nil array
|
||||
// items are stay nil. ByteSlices returns an error if an array item is not a
|
||||
// bulk string or nil.
|
||||
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch reply := reply.(type) {
|
||||
case []interface{}:
|
||||
result := make([][]byte, 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 ByteSlices, got type %T", reply[i])
|
||||
}
|
||||
result[i] = p
|
||||
}
|
||||
return result, nil
|
||||
case nil:
|
||||
return nil, ErrNil
|
||||
case Error:
|
||||
return nil, reply
|
||||
}
|
||||
return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, 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
|
||||
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
|
||||
}
|
||||
179
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
Normal file
179
vendor/github.com/garyburd/redigo/redis/reply_test.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
// 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/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),
|
||||
},
|
||||
{
|
||||
"byteslices([v1, v2])",
|
||||
ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
|
||||
ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
|
||||
},
|
||||
{
|
||||
"byteslices(nil)",
|
||||
ve(redis.ByteSlices(nil, nil)),
|
||||
ve([][]byte(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 DialDefaultServer() with a more suitable function name for examples.
|
||||
func dial() (redis.Conn, error) {
|
||||
return redis.DialDefaultServer()
|
||||
}
|
||||
|
||||
func ExampleBool() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.Do("SET", "hello", "world")
|
||||
s, err := redis.String(c.Do("GET", "hello"))
|
||||
fmt.Printf("%#v\n", s)
|
||||
// Output:
|
||||
// "world"
|
||||
}
|
||||
555
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
Normal file
555
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
Normal file
@@ -0,0 +1,555 @@
|
||||
// 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 {
|
||||
var sname string
|
||||
switch s.(type) {
|
||||
case string:
|
||||
sname = "Redis simple string"
|
||||
case Error:
|
||||
sname = "Redis error"
|
||||
case int64:
|
||||
sname = "Redis integer"
|
||||
case []byte:
|
||||
sname = "Redis bulk string"
|
||||
case []interface{}:
|
||||
sname = "Redis array"
|
||||
default:
|
||||
sname = reflect.TypeOf(s).String()
|
||||
}
|
||||
return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
|
||||
}
|
||||
|
||||
func convertAssignBulkString(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 = convertAssignBulkString(d, s)
|
||||
case int64:
|
||||
err = convertAssignInt(d, s)
|
||||
default:
|
||||
err = cannotConvert(d, s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func convertAssignArray(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 = convertAssignBulkString(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 string:
|
||||
switch d := d.(type) {
|
||||
case *string:
|
||||
*d = string(s)
|
||||
default:
|
||||
err = cannotConvert(reflect.ValueOf(d), 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 = convertAssignArray(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 {
|
||||
err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err)
|
||||
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 != "" && !f.Anonymous:
|
||||
// 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(fmt.Errorf("redigo: unknown field tag %s for type %s", s, 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: number of values not a multiple of 2")
|
||||
}
|
||||
|
||||
for i := 0; i < len(src); i += 2 {
|
||||
s := src[i+1]
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
name, ok := src[i].([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i)
|
||||
}
|
||||
fs := ss.fieldSpec(name)
|
||||
if fs == nil {
|
||||
continue
|
||||
}
|
||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
||||
return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, 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 fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, 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 fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", 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 fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, 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)
|
||||
if fs.omitEmpty {
|
||||
var empty = false
|
||||
switch fv.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
empty = fv.Len() == 0
|
||||
case reflect.Bool:
|
||||
empty = !fv.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
empty = fv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
empty = fv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
empty = fv.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
empty = fv.IsNil()
|
||||
}
|
||||
if empty {
|
||||
continue
|
||||
}
|
||||
}
|
||||
args = append(args, fs.name, fv.Interface())
|
||||
}
|
||||
return args
|
||||
}
|
||||
440
vendor/github.com/garyburd/redigo/redis/scan_test.go
generated
vendored
Normal file
440
vendor/github.com/garyburd/redigo/redis/scan_test.go
generated
vendored
Normal file
@@ -0,0 +1,440 @@
|
||||
// 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"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
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},
|
||||
{"hello", "hello"},
|
||||
{[]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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var albums []struct {
|
||||
Title string
|
||||
Rating int
|
||||
}
|
||||
if err := redis.ScanSlice(values, &albums); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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"`
|
||||
M map[string]string `redis:"m"`
|
||||
Bt bool
|
||||
Bf bool
|
||||
}{
|
||||
-1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false,
|
||||
}),
|
||||
redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "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},
|
||||
},
|
||||
{"struct omitempty",
|
||||
redis.Args{}.AddFlat(&struct {
|
||||
I int `redis:"i,omitempty"`
|
||||
U uint `redis:"u,omitempty"`
|
||||
S string `redis:"s,omitempty"`
|
||||
P []byte `redis:"p,omitempty"`
|
||||
M map[string]string `redis:"m,omitempty"`
|
||||
Bt bool `redis:"Bt,omitempty"`
|
||||
Bf bool `redis:"Bf,omitempty"`
|
||||
}{
|
||||
0, 0, "", []byte{}, map[string]string{}, true, false,
|
||||
}),
|
||||
redis.Args{"Bt", true},
|
||||
},
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
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 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
m := map[string]string{
|
||||
"title": "Example2",
|
||||
"author": "Steve",
|
||||
"body": "Map",
|
||||
}
|
||||
|
||||
if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, id := range []string{"id1", "id2"} {
|
||||
|
||||
v, err := redis.Values(c.Do("HGETALL", id))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := redis.ScanStruct(v, &p2); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", p2)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// {Title:Example Author:Gary Body:Hello}
|
||||
// {Title:Example2 Author:Steve Body:Map}
|
||||
}
|
||||
86
vendor/github.com/garyburd/redigo/redis/script.go
generated
vendored
Normal file
86
vendor/github.com/garyburd/redigo/redis/script.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2012 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Script encapsulates the source, hash and key count for a Lua script. See
|
||||
// http://redis.io/commands/eval for information on scripts in Redis.
|
||||
type Script struct {
|
||||
keyCount int
|
||||
src string
|
||||
hash string
|
||||
}
|
||||
|
||||
// NewScript returns a new script object. If keyCount is greater than or equal
|
||||
// to zero, then the count is automatically inserted in the EVAL command
|
||||
// argument list. If keyCount is less than zero, then the application supplies
|
||||
// the count as the first value in the keysAndArgs argument to the Do, Send and
|
||||
// SendHash methods.
|
||||
func NewScript(keyCount int, src string) *Script {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, src)
|
||||
return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
|
||||
}
|
||||
|
||||
func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
|
||||
var args []interface{}
|
||||
if s.keyCount < 0 {
|
||||
args = make([]interface{}, 1+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
copy(args[1:], keysAndArgs)
|
||||
} else {
|
||||
args = make([]interface{}, 2+len(keysAndArgs))
|
||||
args[0] = spec
|
||||
args[1] = s.keyCount
|
||||
copy(args[2:], keysAndArgs)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Do evaluates the script. Under the covers, Do optimistically evaluates the
|
||||
// script using the EVALSHA command. If the command fails because the script is
|
||||
// not loaded, then Do evaluates the script using the EVAL command (thus
|
||||
// causing the script to load).
|
||||
func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
|
||||
v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
|
||||
v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// SendHash evaluates the script without waiting for the reply. The script is
|
||||
// evaluated with the EVALSHA command. The application must ensure that the
|
||||
// script is loaded by a previous call to Send, Do or Load methods.
|
||||
func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Send evaluates the script without waiting for the reply.
|
||||
func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
|
||||
return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
|
||||
}
|
||||
|
||||
// Load loads the script without evaluating it.
|
||||
func (s *Script) Load(c Conn) error {
|
||||
_, err := c.Do("SCRIPT", "LOAD", s.src)
|
||||
return err
|
||||
}
|
||||
100
vendor/github.com/garyburd/redigo/redis/script_test.go
generated
vendored
Normal file
100
vendor/github.com/garyburd/redigo/redis/script_test.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
// These variables are declared at package level to remove distracting
|
||||
// details from the examples.
|
||||
c redis.Conn
|
||||
reply interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
func ExampleScript() {
|
||||
// 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 := redis.DialDefaultServer()
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
177
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
Normal file
177
vendor/github.com/garyburd/redigo/redis/test_test.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetNowFunc(f func() time.Time) {
|
||||
nowFunc = f
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNegativeInt = errNegativeInt
|
||||
|
||||
serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary")
|
||||
serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers")
|
||||
serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
|
||||
serverLog = ioutil.Discard
|
||||
|
||||
defaultServerMu sync.Mutex
|
||||
defaultServer *Server
|
||||
defaultServerErr error
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
name string
|
||||
cmd *exec.Cmd
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewServer(name string, args ...string) (*Server, error) {
|
||||
s := &Server{
|
||||
name: name,
|
||||
cmd: exec.Command(*serverPath, args...),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
r, err := s.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ready := make(chan error, 1)
|
||||
go s.watch(r, ready)
|
||||
|
||||
select {
|
||||
case err = <-ready:
|
||||
case <-time.After(time.Second * 10):
|
||||
err = errors.New("timeout waiting for server to start")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.Stop()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) watch(r io.Reader, ready chan error) {
|
||||
fmt.Fprintf(serverLog, "%d START %s \n", s.cmd.Process.Pid, s.name)
|
||||
var listening bool
|
||||
var text string
|
||||
scn := bufio.NewScanner(r)
|
||||
for scn.Scan() {
|
||||
text = scn.Text()
|
||||
fmt.Fprintf(serverLog, "%s\n", text)
|
||||
if !listening {
|
||||
if strings.Contains(text, "The server is now ready to accept connections on port") {
|
||||
listening = true
|
||||
ready <- nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if !listening {
|
||||
ready <- fmt.Errorf("server exited: %s", text)
|
||||
}
|
||||
s.cmd.Wait()
|
||||
fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name)
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
s.cmd.Process.Signal(os.Interrupt)
|
||||
<-s.done
|
||||
}
|
||||
|
||||
// stopDefaultServer stops the server created by DialDefaultServer.
|
||||
func stopDefaultServer() {
|
||||
defaultServerMu.Lock()
|
||||
defer defaultServerMu.Unlock()
|
||||
if defaultServer != nil {
|
||||
defaultServer.Stop()
|
||||
defaultServer = nil
|
||||
}
|
||||
}
|
||||
|
||||
// startDefaultServer starts the default server if not already running.
|
||||
func startDefaultServer() error {
|
||||
defaultServerMu.Lock()
|
||||
defer defaultServerMu.Unlock()
|
||||
if defaultServer != nil || defaultServerErr != nil {
|
||||
return defaultServerErr
|
||||
}
|
||||
defaultServer, defaultServerErr = NewServer(
|
||||
"default",
|
||||
"--port", strconv.Itoa(*serverBasePort),
|
||||
"--save", "",
|
||||
"--appendonly", "no")
|
||||
return defaultServerErr
|
||||
}
|
||||
|
||||
// DialDefaultServer starts the test server if not already started and dials a
|
||||
// connection to the server.
|
||||
func DialDefaultServer() (Conn, error) {
|
||||
if err := startDefaultServer(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Do("FLUSHDB")
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
flag.Parse()
|
||||
|
||||
var f *os.File
|
||||
if *serverLogName != "" {
|
||||
var err error
|
||||
f, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error opening redis-log: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
defer f.Close()
|
||||
serverLog = f
|
||||
}
|
||||
|
||||
defer stopDefaultServer()
|
||||
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
113
vendor/github.com/garyburd/redigo/redis/zpop_example_test.go
generated
vendored
Normal file
113
vendor/github.com/garyburd/redigo/redis/zpop_example_test.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2013 Gary Burd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.
|
||||
func zpop(c redis.Conn, key string) (result string, err error) {
|
||||
|
||||
defer func() {
|
||||
// Return connection to normal state on error.
|
||||
if err != nil {
|
||||
c.Do("DISCARD")
|
||||
}
|
||||
}()
|
||||
|
||||
// Loop until transaction is successful.
|
||||
for {
|
||||
if _, err := c.Do("WATCH", key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(members) != 1 {
|
||||
return "", redis.ErrNil
|
||||
}
|
||||
|
||||
c.Send("MULTI")
|
||||
c.Send("ZREM", key, members[0])
|
||||
queued, err := c.Do("EXEC")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if queued != nil {
|
||||
result = members[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// zpopScript pops a value from a ZSET.
|
||||
var zpopScript = redis.NewScript(1, `
|
||||
local r = redis.call('ZRANGE', KEYS[1], 0, 0)
|
||||
if r ~= nil then
|
||||
r = r[1]
|
||||
redis.call('ZREM', KEYS[1], r)
|
||||
end
|
||||
return r
|
||||
`)
|
||||
|
||||
// This example implements ZPOP as described at
|
||||
// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.
|
||||
func Example_zpop() {
|
||||
c, err := dial()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Add test data using a pipeline.
|
||||
|
||||
for i, member := range []string{"red", "blue", "green"} {
|
||||
c.Send("ZADD", "zset", i, member)
|
||||
}
|
||||
if _, err := c.Do(""); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Pop using WATCH/MULTI/EXEC
|
||||
|
||||
v, err := zpop(c, "zset")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(v)
|
||||
|
||||
// Pop using a script.
|
||||
|
||||
v, err = redis.String(zpopScript.Do(c, "zset"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(v)
|
||||
|
||||
// Output:
|
||||
// red
|
||||
// blue
|
||||
}
|
||||
152
vendor/github.com/garyburd/redigo/redisx/connmux.go
generated
vendored
Normal file
152
vendor/github.com/garyburd/redigo/redisx/connmux.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
// 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 redisx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/garyburd/redigo/internal"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
// ConnMux multiplexes one or more connections to a single underlying
|
||||
// connection. The ConnMux connections do not support concurrency, commands
|
||||
// that associate server side state with the connection or commands that put
|
||||
// the connection in a special mode.
|
||||
type ConnMux struct {
|
||||
c redis.Conn
|
||||
|
||||
sendMu sync.Mutex
|
||||
sendID uint
|
||||
|
||||
recvMu sync.Mutex
|
||||
recvID uint
|
||||
recvWait map[uint]chan struct{}
|
||||
}
|
||||
|
||||
func NewConnMux(c redis.Conn) *ConnMux {
|
||||
return &ConnMux{c: c, recvWait: make(map[uint]chan struct{})}
|
||||
}
|
||||
|
||||
// Get gets a connection. The application must close the returned connection.
|
||||
func (p *ConnMux) Get() redis.Conn {
|
||||
c := &muxConn{p: p}
|
||||
c.ids = c.buf[:0]
|
||||
return c
|
||||
}
|
||||
|
||||
// Close closes the underlying connection.
|
||||
func (p *ConnMux) Close() error {
|
||||
return p.c.Close()
|
||||
}
|
||||
|
||||
type muxConn struct {
|
||||
p *ConnMux
|
||||
ids []uint
|
||||
buf [8]uint
|
||||
}
|
||||
|
||||
func (c *muxConn) send(flush bool, cmd string, args ...interface{}) error {
|
||||
if internal.LookupCommandInfo(cmd).Set != 0 {
|
||||
return errors.New("command not supported by mux pool")
|
||||
}
|
||||
p := c.p
|
||||
p.sendMu.Lock()
|
||||
id := p.sendID
|
||||
c.ids = append(c.ids, id)
|
||||
p.sendID++
|
||||
err := p.c.Send(cmd, args...)
|
||||
if flush {
|
||||
err = p.c.Flush()
|
||||
}
|
||||
p.sendMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *muxConn) Send(cmd string, args ...interface{}) error {
|
||||
return c.send(false, cmd, args...)
|
||||
}
|
||||
|
||||
func (c *muxConn) Flush() error {
|
||||
p := c.p
|
||||
p.sendMu.Lock()
|
||||
err := p.c.Flush()
|
||||
p.sendMu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *muxConn) Receive() (interface{}, error) {
|
||||
if len(c.ids) == 0 {
|
||||
return nil, errors.New("mux pool underflow")
|
||||
}
|
||||
|
||||
id := c.ids[0]
|
||||
c.ids = c.ids[1:]
|
||||
if len(c.ids) == 0 {
|
||||
c.ids = c.buf[:0]
|
||||
}
|
||||
|
||||
p := c.p
|
||||
p.recvMu.Lock()
|
||||
if p.recvID != id {
|
||||
ch := make(chan struct{})
|
||||
p.recvWait[id] = ch
|
||||
p.recvMu.Unlock()
|
||||
<-ch
|
||||
p.recvMu.Lock()
|
||||
if p.recvID != id {
|
||||
panic("out of sync")
|
||||
}
|
||||
}
|
||||
|
||||
v, err := p.c.Receive()
|
||||
|
||||
id++
|
||||
p.recvID = id
|
||||
ch, ok := p.recvWait[id]
|
||||
if ok {
|
||||
delete(p.recvWait, id)
|
||||
}
|
||||
p.recvMu.Unlock()
|
||||
if ok {
|
||||
ch <- struct{}{}
|
||||
}
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (c *muxConn) Close() error {
|
||||
var err error
|
||||
if len(c.ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.Flush()
|
||||
for _ = range c.ids {
|
||||
_, err = c.Receive()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, error) {
|
||||
if err := c.send(true, cmd, args...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Receive()
|
||||
}
|
||||
|
||||
func (c *muxConn) Err() error {
|
||||
return c.p.c.Err()
|
||||
}
|
||||
259
vendor/github.com/garyburd/redigo/redisx/connmux_test.go
generated
vendored
Normal file
259
vendor/github.com/garyburd/redigo/redisx/connmux_test.go
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
// 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 redisx_test
|
||||
|
||||
import (
|
||||
"net/textproto"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/garyburd/redigo/internal/redistest"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/garyburd/redigo/redisx"
|
||||
)
|
||||
|
||||
func TestConnMux(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
m := redisx.NewConnMux(c)
|
||||
defer m.Close()
|
||||
|
||||
c1 := m.Get()
|
||||
c2 := m.Get()
|
||||
c1.Send("ECHO", "hello")
|
||||
c2.Send("ECHO", "world")
|
||||
c1.Flush()
|
||||
c2.Flush()
|
||||
s, err := redis.String(c1.Receive())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s != "hello" {
|
||||
t.Fatalf("echo returned %q, want %q", s, "hello")
|
||||
}
|
||||
s, err = redis.String(c2.Receive())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s != "world" {
|
||||
t.Fatalf("echo returned %q, want %q", s, "world")
|
||||
}
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
}
|
||||
|
||||
func TestConnMuxClose(t *testing.T) {
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
t.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
m := redisx.NewConnMux(c)
|
||||
defer m.Close()
|
||||
|
||||
c1 := m.Get()
|
||||
c2 := m.Get()
|
||||
|
||||
if err := c1.Send("ECHO", "hello"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c1.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c2.Send("ECHO", "world"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c2.Flush(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s, err := redis.String(c2.Receive())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s != "world" {
|
||||
t.Fatalf("echo returned %q, want %q", s, "world")
|
||||
}
|
||||
c2.Close()
|
||||
}
|
||||
|
||||
func BenchmarkConn(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
b.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConnMux(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
b.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
m := redisx.NewConnMux(c)
|
||||
defer m.Close()
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
c := m.Get()
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPool(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
p := redis.Pool{Dial: redistest.Dial, MaxIdle: 1}
|
||||
defer p.Close()
|
||||
|
||||
// Fill the pool.
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.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()
|
||||
}
|
||||
}
|
||||
|
||||
const numConcurrent = 10
|
||||
|
||||
func BenchmarkConnMuxConcurrent(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
b.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
m := redisx.NewConnMux(c)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numConcurrent)
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < numConcurrent; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c := m.Get()
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkPoolConcurrent(b *testing.B) {
|
||||
b.StopTimer()
|
||||
|
||||
p := redis.Pool{Dial: redistest.Dial, MaxIdle: numConcurrent}
|
||||
defer p.Close()
|
||||
|
||||
// Fill the pool.
|
||||
conns := make([]redis.Conn, numConcurrent)
|
||||
for i := range conns {
|
||||
c := p.Get()
|
||||
if err := c.Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
conns[i] = c
|
||||
}
|
||||
for _, c := range conns {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numConcurrent)
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < numConcurrent; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c := p.Get()
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkPipelineConcurrency(b *testing.B) {
|
||||
b.StopTimer()
|
||||
c, err := redistest.Dial()
|
||||
if err != nil {
|
||||
b.Fatalf("error connection to database, %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numConcurrent)
|
||||
|
||||
var pipeline textproto.Pipeline
|
||||
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < numConcurrent; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := pipeline.Next()
|
||||
pipeline.StartRequest(id)
|
||||
c.Send("PING")
|
||||
c.Flush()
|
||||
pipeline.EndRequest(id)
|
||||
pipeline.StartResponse(id)
|
||||
_, err := c.Receive()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
pipeline.EndResponse(id)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
17
vendor/github.com/garyburd/redigo/redisx/doc.go
generated
vendored
Normal file
17
vendor/github.com/garyburd/redigo/redisx/doc.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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 redisx contains experimental features for Redigo. Features in this
|
||||
// package may be modified or deleted at any time.
|
||||
package redisx // import "github.com/garyburd/redigo/redisx"
|
||||
2
vendor/github.com/gin-gonic/contrib/.gitignore
generated
vendored
Normal file
2
vendor/github.com/gin-gonic/contrib/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*/Godeps/*
|
||||
!*/Godeps/Godeps.json
|
||||
19
vendor/github.com/gin-gonic/contrib/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/gin-gonic/contrib/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.4
|
||||
- 1.7.4
|
||||
- tip
|
||||
|
||||
services:
|
||||
- memcache
|
||||
- redis-server
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/acc2c57482e94b44f557
|
||||
on_success: change
|
||||
on_failure: always
|
||||
on_start: false
|
||||
35
vendor/github.com/gin-gonic/contrib/README.md
generated
vendored
Normal file
35
vendor/github.com/gin-gonic/contrib/README.md
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# gin-gonic/contrib
|
||||
|
||||
[](https://travis-ci.org/gin-gonic/contrib)
|
||||
|
||||
Here you'll find middleware ready to use with [Gin Framework](https://github.com/gin-gonic/gin). Submit your pull request, either with the package in a folder, or by adding a link to this `README.md`.
|
||||
|
||||
If adding a package directly, don't forget to create a `README.md` inside with author name.
|
||||
If adding a link to your own repository, please follow this example:
|
||||
|
||||
```
|
||||
+ nameOfMiddleware (https://github.com/yourusername/yourrepo)
|
||||
```
|
||||
|
||||
Each author is responsible of maintaining his own code, although if you submit as a package, you allow the community to fix it. You can also submit a pull request to fix an existing package.
|
||||
|
||||
## List of external middleware
|
||||
|
||||
+ [RestGate](https://github.com/pjebs/restgate) - Secure authentication for REST API endpoints
|
||||
+ [staticbin](https://github.com/olebedev/staticbin) - middleware/handler for serving static files from binary data
|
||||
+ [gin-cors](https://github.com/gin-contrib/cors) - Official CORS gin's middleware
|
||||
+ [gin-csrf](https://github.com/utrack/gin-csrf) - CSRF protection
|
||||
+ [gin-health](https://github.com/utrack/gin-health) - middleware that simplifies stat reporting via [gocraft/health](https://github.com/gocraft/health)
|
||||
+ [gin-merry](https://github.com/utrack/gin-merry) - middleware for pretty-printing [merry](https://github.com/ansel1/merry) errors with context
|
||||
+ [gin-revision](https://github.com/appleboy/gin-revision-middleware) - Revision middleware for Gin framework
|
||||
+ [gin-jwt](https://github.com/appleboy/gin-jwt) - JWT Middleware for Gin Framework
|
||||
+ [gin-sessions](https://github.com/kimiazhu/ginweb-contrib/tree/master/sessions) - session middleware based on mongodb and mysql
|
||||
+ [gin-location](https://github.com/drone/gin-location) - middleware for exposing the server's hostname and scheme
|
||||
+ [gin-nice-recovery](https://github.com/ekyoung/gin-nice-recovery) - panic recovery middleware that let's you build a nicer user experience
|
||||
+ [gin-limit](https://github.com/aviddiviner/gin-limit) - limits simultaneous requests; can help with high traffic load
|
||||
+ [ez-gin-template](https://github.com/michelloworld/ez-gin-template) - easy template wrap for gin
|
||||
+ [gin-hydra](https://github.com/janekolszak/gin-hydra) - [Hydra](https://github.com/ory-am/hydra) middleware for Gin
|
||||
+ [gin-glog](https://github.com/zalando/gin-glog) - meant as drop-in replacement for Gin's default logger
|
||||
+ [gin-gomonitor](https://github.com/zalando/gin-gomonitor) - for exposing metrics with Go-Monitor
|
||||
+ [gin-oauth2](https://github.com/zalando/gin-oauth2) - for working with OAuth2
|
||||
+ [xss-mw](https://github.com/dvwright/xss-mw) - XssMw is a middleware designed to "auto remove XSS" from user submitted input
|
||||
22
vendor/github.com/gin-gonic/contrib/cache/Godeps/Godeps.json
generated
vendored
Normal file
22
vendor/github.com/gin-gonic/contrib/cache/Godeps/Godeps.json
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/contrib/cache",
|
||||
"GoVersion": "go1.3",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/bradfitz/gomemcache/memcache",
|
||||
"Rev": "72a68649ba712ee7c4b5b4a943a626bcd7d90eb8"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/garyburd/redigo/redis",
|
||||
"Rev": "535138d7bcd717d6531c701ef5933d98b1866257"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/gin",
|
||||
"Rev": "ac0ad2fed865d40a0adc1ac3ccaadc3acff5db4b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/robfig/go-cache",
|
||||
"Rev": "9fc39e0dbf62c034ec4e45e6120fc69433a3ec51"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
vendor/github.com/gin-gonic/contrib/cache/README.md
generated
vendored
Normal file
5
vendor/github.com/gin-gonic/contrib/cache/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# cache
|
||||
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2016-12-07. Please use [gin-contrib/cache](https://github.com/gin-contrib/cache) instead.**
|
||||
153
vendor/github.com/gin-gonic/contrib/cache/cache.go
generated
vendored
Normal file
153
vendor/github.com/gin-gonic/contrib/cache/cache.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT = time.Duration(0)
|
||||
FOREVER = time.Duration(-1)
|
||||
CACHE_MIDDLEWARE_KEY = "gincontrib.cache"
|
||||
)
|
||||
|
||||
var (
|
||||
PageCachePrefix = "gincontrib.page.cache"
|
||||
ErrCacheMiss = errors.New("cache: key not found.")
|
||||
ErrNotStored = errors.New("cache: not stored.")
|
||||
ErrNotSupport = errors.New("cache: not support.")
|
||||
)
|
||||
|
||||
type CacheStore interface {
|
||||
Get(key string, value interface{}) error
|
||||
Set(key string, value interface{}, expire time.Duration) error
|
||||
Add(key string, value interface{}, expire time.Duration) error
|
||||
Replace(key string, data interface{}, expire time.Duration) error
|
||||
Delete(key string) error
|
||||
Increment(key string, data uint64) (uint64, error)
|
||||
Decrement(key string, data uint64) (uint64, error)
|
||||
Flush() error
|
||||
}
|
||||
|
||||
type responseCache struct {
|
||||
status int
|
||||
header http.Header
|
||||
data []byte
|
||||
}
|
||||
|
||||
type cachedWriter struct {
|
||||
gin.ResponseWriter
|
||||
status int
|
||||
written bool
|
||||
store CacheStore
|
||||
expire time.Duration
|
||||
key string
|
||||
}
|
||||
|
||||
func urlEscape(prefix string, u string) string {
|
||||
key := url.QueryEscape(u)
|
||||
if len(key) > 200 {
|
||||
h := sha1.New()
|
||||
io.WriteString(h, u)
|
||||
key = string(h.Sum(nil))
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(prefix)
|
||||
buffer.WriteString(":")
|
||||
buffer.WriteString(key)
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func newCachedWriter(store CacheStore, expire time.Duration, writer gin.ResponseWriter, key string) *cachedWriter {
|
||||
return &cachedWriter{writer, 0, false, store, expire, key}
|
||||
}
|
||||
|
||||
func (w *cachedWriter) WriteHeader(code int) {
|
||||
w.status = code
|
||||
w.written = true
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *cachedWriter) Status() int {
|
||||
return w.status
|
||||
}
|
||||
|
||||
func (w *cachedWriter) Written() bool {
|
||||
return w.written
|
||||
}
|
||||
|
||||
func (w *cachedWriter) Write(data []byte) (int, error) {
|
||||
ret, err := w.ResponseWriter.Write(data)
|
||||
if err == nil {
|
||||
//cache response
|
||||
store := w.store
|
||||
val := responseCache{
|
||||
w.status,
|
||||
w.Header(),
|
||||
data,
|
||||
}
|
||||
err = store.Set(w.key, val, w.expire)
|
||||
if err != nil {
|
||||
// need logger
|
||||
}
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Cache Middleware
|
||||
func Cache(store *CacheStore) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Set(CACHE_MIDDLEWARE_KEY, store)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func SiteCache(store CacheStore, expire time.Duration) gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
var cache responseCache
|
||||
url := c.Request.URL
|
||||
key := urlEscape(PageCachePrefix, url.RequestURI())
|
||||
if err := store.Get(key, &cache); err != nil {
|
||||
c.Next()
|
||||
} else {
|
||||
c.Writer.WriteHeader(cache.status)
|
||||
for k, vals := range cache.header {
|
||||
for _, v := range vals {
|
||||
c.Writer.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
c.Writer.Write(cache.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache Decorator
|
||||
func CachePage(store CacheStore, expire time.Duration, handle gin.HandlerFunc) gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
var cache responseCache
|
||||
url := c.Request.URL
|
||||
key := urlEscape(PageCachePrefix, url.RequestURI())
|
||||
if err := store.Get(key, &cache); err != nil {
|
||||
// replace writer
|
||||
writer := newCachedWriter(store, expire, c.Writer, key)
|
||||
c.Writer = writer
|
||||
handle(c)
|
||||
} else {
|
||||
c.Writer.WriteHeader(cache.status)
|
||||
for k, vals := range cache.header {
|
||||
for _, v := range vals {
|
||||
c.Writer.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
c.Writer.Write(cache.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
202
vendor/github.com/gin-gonic/contrib/cache/cache_test.go
generated
vendored
Normal file
202
vendor/github.com/gin-gonic/contrib/cache/cache_test.go
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type cacheFactory func(*testing.T, time.Duration) CacheStore
|
||||
|
||||
// Test typical cache interactions
|
||||
func typicalGetSet(t *testing.T, newCache cacheFactory) {
|
||||
var err error
|
||||
cache := newCache(t, time.Hour)
|
||||
|
||||
value := "foo"
|
||||
if err = cache.Set("value", value, DEFAULT); err != nil {
|
||||
t.Errorf("Error setting a value: %s", err)
|
||||
}
|
||||
|
||||
value = ""
|
||||
err = cache.Get("value", &value)
|
||||
if err != nil {
|
||||
t.Errorf("Error getting a value: %s", err)
|
||||
}
|
||||
if value != "foo" {
|
||||
t.Errorf("Expected to get foo back, got %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the increment-decrement cases
|
||||
func incrDecr(t *testing.T, newCache cacheFactory) {
|
||||
var err error
|
||||
cache := newCache(t, time.Hour)
|
||||
|
||||
// Normal increment / decrement operation.
|
||||
if err = cache.Set("int", 10, DEFAULT); err != nil {
|
||||
t.Errorf("Error setting int: %s", err)
|
||||
}
|
||||
newValue, err := cache.Increment("int", 50)
|
||||
if err != nil {
|
||||
t.Errorf("Error incrementing int: %s", err)
|
||||
}
|
||||
if newValue != 60 {
|
||||
t.Errorf("Expected 60, was %d", newValue)
|
||||
}
|
||||
|
||||
if newValue, err = cache.Decrement("int", 50); err != nil {
|
||||
t.Errorf("Error decrementing: %s", err)
|
||||
}
|
||||
if newValue != 10 {
|
||||
t.Errorf("Expected 10, was %d", newValue)
|
||||
}
|
||||
|
||||
// Increment wraparound
|
||||
newValue, err = cache.Increment("int", math.MaxUint64-5)
|
||||
if err != nil {
|
||||
t.Errorf("Error wrapping around: %s", err)
|
||||
}
|
||||
if newValue != 4 {
|
||||
t.Errorf("Expected wraparound 4, got %d", newValue)
|
||||
}
|
||||
|
||||
// Decrement capped at 0
|
||||
newValue, err = cache.Decrement("int", 25)
|
||||
if err != nil {
|
||||
t.Errorf("Error decrementing below 0: %s", err)
|
||||
}
|
||||
if newValue != 0 {
|
||||
t.Errorf("Expected capped at 0, got %d", newValue)
|
||||
}
|
||||
}
|
||||
|
||||
func expiration(t *testing.T, newCache cacheFactory) {
|
||||
// memcached does not support expiration times less than 1 second.
|
||||
var err error
|
||||
cache := newCache(t, time.Second)
|
||||
// Test Set w/ DEFAULT
|
||||
value := 10
|
||||
cache.Set("int", value, DEFAULT)
|
||||
time.Sleep(2 * time.Second)
|
||||
err = cache.Get("int", &value)
|
||||
if err != ErrCacheMiss {
|
||||
t.Errorf("Expected CacheMiss, but got: %s", err)
|
||||
}
|
||||
|
||||
// Test Set w/ short time
|
||||
cache.Set("int", value, time.Second)
|
||||
time.Sleep(2 * time.Second)
|
||||
err = cache.Get("int", &value)
|
||||
if err != ErrCacheMiss {
|
||||
t.Errorf("Expected CacheMiss, but got: %s", err)
|
||||
}
|
||||
|
||||
// Test Set w/ longer time.
|
||||
cache.Set("int", value, time.Hour)
|
||||
time.Sleep(2 * time.Second)
|
||||
err = cache.Get("int", &value)
|
||||
if err != nil {
|
||||
t.Errorf("Expected to get the value, but got: %s", err)
|
||||
}
|
||||
|
||||
// Test Set w/ forever.
|
||||
cache.Set("int", value, FOREVER)
|
||||
time.Sleep(2 * time.Second)
|
||||
err = cache.Get("int", &value)
|
||||
if err != nil {
|
||||
t.Errorf("Expected to get the value, but got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func emptyCache(t *testing.T, newCache cacheFactory) {
|
||||
var err error
|
||||
cache := newCache(t, time.Hour)
|
||||
|
||||
err = cache.Get("notexist", 0)
|
||||
if err == nil {
|
||||
t.Errorf("Error expected for non-existent key")
|
||||
}
|
||||
if err != ErrCacheMiss {
|
||||
t.Errorf("Expected ErrCacheMiss for non-existent key: %s", err)
|
||||
}
|
||||
|
||||
err = cache.Delete("notexist")
|
||||
if err != ErrCacheMiss {
|
||||
t.Errorf("Expected ErrCacheMiss for non-existent key: %s", err)
|
||||
}
|
||||
|
||||
_, err = cache.Increment("notexist", 1)
|
||||
if err != ErrCacheMiss {
|
||||
t.Errorf("Expected cache miss incrementing non-existent key: %s", err)
|
||||
}
|
||||
|
||||
_, err = cache.Decrement("notexist", 1)
|
||||
if err != ErrCacheMiss {
|
||||
t.Errorf("Expected cache miss decrementing non-existent key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testReplace(t *testing.T, newCache cacheFactory) {
|
||||
var err error
|
||||
cache := newCache(t, time.Hour)
|
||||
|
||||
// Replace in an empty cache.
|
||||
if err = cache.Replace("notexist", 1, FOREVER); err != ErrNotStored {
|
||||
t.Errorf("Replace in empty cache: expected ErrNotStored, got: %s", err)
|
||||
}
|
||||
|
||||
// Set a value of 1, and replace it with 2
|
||||
if err = cache.Set("int", 1, time.Second); err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if err = cache.Replace("int", 2, time.Second); err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
var i int
|
||||
if err = cache.Get("int", &i); err != nil {
|
||||
t.Errorf("Unexpected error getting a replaced item: %s", err)
|
||||
}
|
||||
if i != 2 {
|
||||
t.Errorf("Expected 2, got %d", i)
|
||||
}
|
||||
|
||||
// Wait for it to expire and replace with 3 (unsuccessfully).
|
||||
time.Sleep(2 * time.Second)
|
||||
if err = cache.Replace("int", 3, time.Second); err != ErrNotStored {
|
||||
t.Errorf("Expected ErrNotStored, got: %s", err)
|
||||
}
|
||||
if err = cache.Get("int", &i); err != ErrCacheMiss {
|
||||
t.Errorf("Expected cache miss, got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testAdd(t *testing.T, newCache cacheFactory) {
|
||||
var err error
|
||||
cache := newCache(t, time.Hour)
|
||||
// Add to an empty cache.
|
||||
if err = cache.Add("int", 1, time.Second); err != nil {
|
||||
t.Errorf("Unexpected error adding to empty cache: %s", err)
|
||||
}
|
||||
|
||||
// Try to add again. (fail)
|
||||
if err = cache.Add("int", 2, time.Second); err != ErrNotStored {
|
||||
t.Errorf("Expected ErrNotStored adding dupe to cache: %s", err)
|
||||
}
|
||||
|
||||
// Wait for it to expire, and add again.
|
||||
time.Sleep(2 * time.Second)
|
||||
if err = cache.Add("int", 3, time.Second); err != nil {
|
||||
t.Errorf("Unexpected error adding to cache: %s", err)
|
||||
}
|
||||
|
||||
// Get and verify the value.
|
||||
var i int
|
||||
if err = cache.Get("int", &i); err != nil {
|
||||
t.Errorf("Unexpected error: %s", err)
|
||||
}
|
||||
if i != 3 {
|
||||
t.Errorf("Expected 3, got: %d", i)
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/gin-gonic/contrib/cache/example/example.go
generated
vendored
Normal file
25
vendor/github.com/gin-gonic/contrib/cache/example/example.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/contrib/cache"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
store := cache.NewInMemoryStore(time.Second)
|
||||
// Cached Page
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
|
||||
})
|
||||
|
||||
r.GET("/cache_ping", cache.CachePage(store, time.Minute, func(c *gin.Context) {
|
||||
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
|
||||
}))
|
||||
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
78
vendor/github.com/gin-gonic/contrib/cache/inmemory.go
generated
vendored
Normal file
78
vendor/github.com/gin-gonic/contrib/cache/inmemory.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/robfig/go-cache"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InMemoryStore struct {
|
||||
cache.Cache
|
||||
}
|
||||
|
||||
func NewInMemoryStore(defaultExpiration time.Duration) *InMemoryStore {
|
||||
return &InMemoryStore{*cache.New(defaultExpiration, time.Minute)}
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Get(key string, value interface{}) error {
|
||||
val, found := c.Cache.Get(key)
|
||||
if !found {
|
||||
return ErrCacheMiss
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(value)
|
||||
if v.Type().Kind() == reflect.Ptr && v.Elem().CanSet() {
|
||||
v.Elem().Set(reflect.ValueOf(val))
|
||||
return nil
|
||||
}
|
||||
return ErrNotStored
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Set(key string, value interface{}, expires time.Duration) error {
|
||||
// NOTE: go-cache understands the values of DEFAULT and FOREVER
|
||||
c.Cache.Set(key, value, expires)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Add(key string, value interface{}, expires time.Duration) error {
|
||||
err := c.Cache.Add(key, value, expires)
|
||||
if err == cache.ErrKeyExists {
|
||||
return ErrNotStored
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Replace(key string, value interface{}, expires time.Duration) error {
|
||||
if err := c.Cache.Replace(key, value, expires); err != nil {
|
||||
return ErrNotStored
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Delete(key string) error {
|
||||
if found := c.Cache.Delete(key); !found {
|
||||
return ErrCacheMiss
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Increment(key string, n uint64) (uint64, error) {
|
||||
newValue, err := c.Cache.Increment(key, n)
|
||||
if err == cache.ErrCacheMiss {
|
||||
return 0, ErrCacheMiss
|
||||
}
|
||||
return newValue, err
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Decrement(key string, n uint64) (uint64, error) {
|
||||
newValue, err := c.Cache.Decrement(key, n)
|
||||
if err == cache.ErrCacheMiss {
|
||||
return 0, ErrCacheMiss
|
||||
}
|
||||
return newValue, err
|
||||
}
|
||||
|
||||
func (c *InMemoryStore) Flush() error {
|
||||
c.Cache.Flush()
|
||||
return nil
|
||||
}
|
||||
35
vendor/github.com/gin-gonic/contrib/cache/inmemory_test.go
generated
vendored
Normal file
35
vendor/github.com/gin-gonic/contrib/cache/inmemory_test.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var newInMemoryStore = func(_ *testing.T, defaultExpiration time.Duration) CacheStore {
|
||||
return NewInMemoryStore(defaultExpiration)
|
||||
}
|
||||
|
||||
// Test typical cache interactions
|
||||
func TestInMemoryCache_TypicalGetSet(t *testing.T) {
|
||||
typicalGetSet(t, newInMemoryStore)
|
||||
}
|
||||
|
||||
func TestInMemoryCache_IncrDecr(t *testing.T) {
|
||||
incrDecr(t, newInMemoryStore)
|
||||
}
|
||||
|
||||
func TestInMemoryCache_Expiration(t *testing.T) {
|
||||
expiration(t, newInMemoryStore)
|
||||
}
|
||||
|
||||
func TestInMemoryCache_EmptyCache(t *testing.T) {
|
||||
emptyCache(t, newInMemoryStore)
|
||||
}
|
||||
|
||||
func TestInMemoryCache_Replace(t *testing.T) {
|
||||
testReplace(t, newInMemoryStore)
|
||||
}
|
||||
|
||||
func TestInMemoryCache_Add(t *testing.T) {
|
||||
testAdd(t, newInMemoryStore)
|
||||
}
|
||||
87
vendor/github.com/gin-gonic/contrib/cache/memcached.go
generated
vendored
Normal file
87
vendor/github.com/gin-gonic/contrib/cache/memcached.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/bradfitz/gomemcache/memcache"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemcachedStore struct {
|
||||
*memcache.Client
|
||||
defaultExpiration time.Duration
|
||||
}
|
||||
|
||||
func NewMemcachedStore(hostList []string, defaultExpiration time.Duration) *MemcachedStore {
|
||||
return &MemcachedStore{memcache.New(hostList...), defaultExpiration}
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Set(key string, value interface{}, expires time.Duration) error {
|
||||
return c.invoke((*memcache.Client).Set, key, value, expires)
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Add(key string, value interface{}, expires time.Duration) error {
|
||||
return c.invoke((*memcache.Client).Add, key, value, expires)
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Replace(key string, value interface{}, expires time.Duration) error {
|
||||
return c.invoke((*memcache.Client).Replace, key, value, expires)
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Get(key string, value interface{}) error {
|
||||
item, err := c.Client.Get(key)
|
||||
if err != nil {
|
||||
return convertMemcacheError(err)
|
||||
}
|
||||
return deserialize(item.Value, value)
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Delete(key string) error {
|
||||
return convertMemcacheError(c.Client.Delete(key))
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Increment(key string, delta uint64) (uint64, error) {
|
||||
newValue, err := c.Client.Increment(key, delta)
|
||||
return newValue, convertMemcacheError(err)
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Decrement(key string, delta uint64) (uint64, error) {
|
||||
newValue, err := c.Client.Decrement(key, delta)
|
||||
return newValue, convertMemcacheError(err)
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) Flush() error {
|
||||
return ErrNotSupport
|
||||
}
|
||||
|
||||
func (c *MemcachedStore) invoke(storeFn func(*memcache.Client, *memcache.Item) error,
|
||||
key string, value interface{}, expire time.Duration) error {
|
||||
|
||||
switch expire {
|
||||
case DEFAULT:
|
||||
expire = c.defaultExpiration
|
||||
case FOREVER:
|
||||
expire = time.Duration(0)
|
||||
}
|
||||
|
||||
b, err := serialize(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return convertMemcacheError(storeFn(c.Client, &memcache.Item{
|
||||
Key: key,
|
||||
Value: b,
|
||||
Expiration: int32(expire / time.Second),
|
||||
}))
|
||||
}
|
||||
|
||||
func convertMemcacheError(err error) error {
|
||||
switch err {
|
||||
case nil:
|
||||
return nil
|
||||
case memcache.ErrCacheMiss:
|
||||
return ErrCacheMiss
|
||||
case memcache.ErrNotStored:
|
||||
return ErrNotStored
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
46
vendor/github.com/gin-gonic/contrib/cache/memcached_test.go
generated
vendored
Normal file
46
vendor/github.com/gin-gonic/contrib/cache/memcached_test.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These tests require memcached running on localhost:11211 (the default)
|
||||
const testServer = "localhost:11211"
|
||||
|
||||
var newMemcachedStore = func(t *testing.T, defaultExpiration time.Duration) CacheStore {
|
||||
c, err := net.Dial("tcp", testServer)
|
||||
if err == nil {
|
||||
c.Write([]byte("flush_all\r\n"))
|
||||
c.Close()
|
||||
return NewMemcachedStore([]string{testServer}, defaultExpiration)
|
||||
}
|
||||
t.Errorf("couldn't connect to memcached on %s", testServer)
|
||||
t.FailNow()
|
||||
panic("")
|
||||
}
|
||||
|
||||
func TestMemcachedCache_TypicalGetSet(t *testing.T) {
|
||||
typicalGetSet(t, newMemcachedStore)
|
||||
}
|
||||
|
||||
func TestMemcachedCache_IncrDecr(t *testing.T) {
|
||||
incrDecr(t, newMemcachedStore)
|
||||
}
|
||||
|
||||
func TestMemcachedCache_Expiration(t *testing.T) {
|
||||
expiration(t, newMemcachedStore)
|
||||
}
|
||||
|
||||
func TestMemcachedCache_EmptyCache(t *testing.T) {
|
||||
emptyCache(t, newMemcachedStore)
|
||||
}
|
||||
|
||||
func TestMemcachedCache_Replace(t *testing.T) {
|
||||
testReplace(t, newMemcachedStore)
|
||||
}
|
||||
|
||||
func TestMemcachedCache_Add(t *testing.T) {
|
||||
testAdd(t, newMemcachedStore)
|
||||
}
|
||||
181
vendor/github.com/gin-gonic/contrib/cache/redis.go
generated
vendored
Normal file
181
vendor/github.com/gin-gonic/contrib/cache/redis.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Wraps the Redis client to meet the Cache interface.
|
||||
type RedisStore struct {
|
||||
pool *redis.Pool
|
||||
defaultExpiration time.Duration
|
||||
}
|
||||
|
||||
// until redigo supports sharding/clustering, only one host will be in hostList
|
||||
func NewRedisCache(host string, password string, defaultExpiration time.Duration) *RedisStore {
|
||||
var pool = &redis.Pool{
|
||||
MaxIdle: 5,
|
||||
IdleTimeout: 240 * time.Second,
|
||||
Dial: func() (redis.Conn, error) {
|
||||
// the redis protocol should probably be made sett-able
|
||||
c, err := redis.Dial("tcp", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(password) > 0 {
|
||||
if _, err := c.Do("AUTH", password); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// check with PING
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
// custom connection test method
|
||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
||||
if _, err := c.Do("PING"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return &RedisStore{pool, defaultExpiration}
|
||||
}
|
||||
|
||||
func (c *RedisStore) Set(key string, value interface{}, expires time.Duration) error {
|
||||
return c.invoke(c.pool.Get().Do, key, value, expires)
|
||||
}
|
||||
|
||||
func (c *RedisStore) Add(key string, value interface{}, expires time.Duration) error {
|
||||
conn := c.pool.Get()
|
||||
if exists(conn, key) {
|
||||
return ErrNotStored
|
||||
}
|
||||
return c.invoke(conn.Do, key, value, expires)
|
||||
}
|
||||
|
||||
func (c *RedisStore) Replace(key string, value interface{}, expires time.Duration) error {
|
||||
conn := c.pool.Get()
|
||||
if !exists(conn, key) {
|
||||
return ErrNotStored
|
||||
}
|
||||
err := c.invoke(conn.Do, key, value, expires)
|
||||
if value == nil {
|
||||
return ErrNotStored
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RedisStore) Get(key string, ptrValue interface{}) error {
|
||||
conn := c.pool.Get()
|
||||
defer conn.Close()
|
||||
raw, err := conn.Do("GET", key)
|
||||
if raw == nil {
|
||||
return ErrCacheMiss
|
||||
}
|
||||
item, err := redis.Bytes(raw, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return deserialize(item, ptrValue)
|
||||
}
|
||||
|
||||
func exists(conn redis.Conn, key string) bool {
|
||||
retval, _ := redis.Bool(conn.Do("EXISTS", key))
|
||||
return retval
|
||||
}
|
||||
|
||||
func (c *RedisStore) Delete(key string) error {
|
||||
conn := c.pool.Get()
|
||||
defer conn.Close()
|
||||
if !exists(conn, key) {
|
||||
return ErrCacheMiss
|
||||
}
|
||||
_, err := conn.Do("DEL", key)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RedisStore) Increment(key string, delta uint64) (uint64, error) {
|
||||
conn := c.pool.Get()
|
||||
defer conn.Close()
|
||||
// Check for existance *before* increment as per the cache contract.
|
||||
// redis will auto create the key, and we don't want that. Since we need to do increment
|
||||
// ourselves instead of natively via INCRBY (redis doesn't support wrapping), we get the value
|
||||
// and do the exists check this way to minimize calls to Redis
|
||||
val, err := conn.Do("GET", key)
|
||||
if val == nil {
|
||||
return 0, ErrCacheMiss
|
||||
}
|
||||
if err == nil {
|
||||
currentVal, err := redis.Int64(val, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var sum int64 = currentVal + int64(delta)
|
||||
_, err = conn.Do("SET", key, sum)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(sum), nil
|
||||
} else {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RedisStore) Decrement(key string, delta uint64) (newValue uint64, err error) {
|
||||
conn := c.pool.Get()
|
||||
defer conn.Close()
|
||||
// Check for existance *before* increment as per the cache contract.
|
||||
// redis will auto create the key, and we don't want that, hence the exists call
|
||||
if !exists(conn, key) {
|
||||
return 0, ErrCacheMiss
|
||||
}
|
||||
// Decrement contract says you can only go to 0
|
||||
// so we go fetch the value and if the delta is greater than the amount,
|
||||
// 0 out the value
|
||||
currentVal, err := redis.Int64(conn.Do("GET", key))
|
||||
if err == nil && delta > uint64(currentVal) {
|
||||
tempint, err := redis.Int64(conn.Do("DECRBY", key, currentVal))
|
||||
return uint64(tempint), err
|
||||
}
|
||||
tempint, err := redis.Int64(conn.Do("DECRBY", key, delta))
|
||||
return uint64(tempint), err
|
||||
}
|
||||
|
||||
func (c *RedisStore) Flush() error {
|
||||
conn := c.pool.Get()
|
||||
defer conn.Close()
|
||||
_, err := conn.Do("FLUSHALL")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RedisStore) invoke(f func(string, ...interface{}) (interface{}, error),
|
||||
key string, value interface{}, expires time.Duration) error {
|
||||
|
||||
switch expires {
|
||||
case DEFAULT:
|
||||
expires = c.defaultExpiration
|
||||
case FOREVER:
|
||||
expires = time.Duration(0)
|
||||
}
|
||||
|
||||
b, err := serialize(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := c.pool.Get()
|
||||
defer conn.Close()
|
||||
if expires > 0 {
|
||||
_, err := f("SETEX", key, int32(expires/time.Second), b)
|
||||
return err
|
||||
} else {
|
||||
_, err := f("SET", key, b)
|
||||
return err
|
||||
}
|
||||
}
|
||||
48
vendor/github.com/gin-gonic/contrib/cache/redis_test.go
generated
vendored
Normal file
48
vendor/github.com/gin-gonic/contrib/cache/redis_test.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These tests require redis server running on localhost:6379 (the default)
|
||||
const redisTestServer = "localhost:6379"
|
||||
|
||||
var newRedisStore = func(t *testing.T, defaultExpiration time.Duration) CacheStore {
|
||||
c, err := net.Dial("tcp", redisTestServer)
|
||||
if err == nil {
|
||||
c.Write([]byte("flush_all\r\n"))
|
||||
c.Close()
|
||||
redisCache := NewRedisCache(redisTestServer, "", defaultExpiration)
|
||||
redisCache.Flush()
|
||||
return redisCache
|
||||
}
|
||||
t.Errorf("couldn't connect to redis on %s", redisTestServer)
|
||||
t.FailNow()
|
||||
panic("")
|
||||
}
|
||||
|
||||
func TestRedisCache_TypicalGetSet(t *testing.T) {
|
||||
typicalGetSet(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedisCache_IncrDecr(t *testing.T) {
|
||||
incrDecr(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedisCache_Expiration(t *testing.T) {
|
||||
expiration(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedisCache_EmptyCache(t *testing.T) {
|
||||
emptyCache(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedisCache_Replace(t *testing.T) {
|
||||
testReplace(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedisCache_Add(t *testing.T) {
|
||||
testAdd(t, newRedisStore)
|
||||
}
|
||||
66
vendor/github.com/gin-gonic/contrib/cache/serializer.go
generated
vendored
Normal file
66
vendor/github.com/gin-gonic/contrib/cache/serializer.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func serialize(value interface{}) ([]byte, error) {
|
||||
if bytes, ok := value.([]byte); ok {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
switch v := reflect.ValueOf(value); v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return []byte(strconv.FormatInt(v.Int(), 10)), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return []byte(strconv.FormatUint(v.Uint(), 10)), nil
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
encoder := gob.NewEncoder(&b)
|
||||
if err := encoder.Encode(value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func deserialize(byt []byte, ptr interface{}) (err error) {
|
||||
if bytes, ok := ptr.(*[]byte); ok {
|
||||
*bytes = byt
|
||||
return nil
|
||||
}
|
||||
|
||||
if v := reflect.ValueOf(ptr); v.Kind() == reflect.Ptr {
|
||||
switch p := v.Elem(); p.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var i int64
|
||||
i, err = strconv.ParseInt(string(byt), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.SetInt(i)
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
var i uint64
|
||||
i, err = strconv.ParseUint(string(byt), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.SetUint(i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(byt)
|
||||
decoder := gob.NewDecoder(b)
|
||||
if err = decoder.Decode(ptr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
53
vendor/github.com/gin-gonic/contrib/commonlog/commonlog.go
generated
vendored
Normal file
53
vendor/github.com/gin-gonic/contrib/commonlog/commonlog.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package commonlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Instances a Logger middleware that will write the logs to gin.DefaultWriter
|
||||
// By default gin.DefaultWriter = os.Stdout
|
||||
func New() gin.HandlerFunc {
|
||||
return NewWithWriter(gin.DefaultWriter)
|
||||
}
|
||||
|
||||
// Instance a Logger middleware with the specified writter buffer.
|
||||
// Example: os.Stdout, a file opened in write mode, a socket...
|
||||
func NewWithWriter(out io.Writer) gin.HandlerFunc {
|
||||
pool := &sync.Pool{
|
||||
New: func() interface{} {
|
||||
buf := new(bytes.Buffer)
|
||||
return buf
|
||||
},
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
// Process request
|
||||
c.Next()
|
||||
|
||||
//127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
|
||||
w := pool.Get().(*bytes.Buffer)
|
||||
w.Reset()
|
||||
w.WriteString(c.ClientIP())
|
||||
w.WriteString(" - - ")
|
||||
w.WriteString(time.Now().Format("[02/Jan/2006:15:04:05 -0700] "))
|
||||
w.WriteString("\"")
|
||||
w.WriteString(c.Request.Method)
|
||||
w.WriteString(" ")
|
||||
w.WriteString(c.Request.URL.Path)
|
||||
w.WriteString(" ")
|
||||
w.WriteString(c.Request.Proto)
|
||||
w.WriteString("\" ")
|
||||
w.WriteString(strconv.Itoa(c.Writer.Status()))
|
||||
w.WriteString(" ")
|
||||
w.WriteString(strconv.Itoa(c.Writer.Size()))
|
||||
w.WriteString("\n")
|
||||
|
||||
w.WriteTo(out)
|
||||
pool.Put(w)
|
||||
}
|
||||
}
|
||||
5
vendor/github.com/gin-gonic/contrib/cors/README.md
generated
vendored
Normal file
5
vendor/github.com/gin-gonic/contrib/cors/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# cors
|
||||
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2016-12-07. Please use [gin-contrib/cors](https://github.com/gin-contrib/cors) instead.**
|
||||
109
vendor/github.com/gin-gonic/contrib/cors/config.go
generated
vendored
Normal file
109
vendor/github.com/gin-gonic/contrib/cors/config.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type settings struct {
|
||||
allowAllOrigins bool
|
||||
allowedOriginFunc func(string) bool
|
||||
allowedOrigins []string
|
||||
allowedMethods []string
|
||||
allowedHeaders []string
|
||||
exposedHeaders []string
|
||||
normalHeaders http.Header
|
||||
preflightHeaders http.Header
|
||||
}
|
||||
|
||||
func newSettings(c Config) *settings {
|
||||
if err := c.Validate(); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return &settings{
|
||||
allowedOriginFunc: c.AllowOriginFunc,
|
||||
allowAllOrigins: c.AllowAllOrigins,
|
||||
allowedOrigins: c.AllowedOrigins,
|
||||
allowedMethods: distinct(c.AllowedMethods),
|
||||
allowedHeaders: distinct(c.AllowedHeaders),
|
||||
normalHeaders: generateNormalHeaders(c),
|
||||
preflightHeaders: generatePreflightHeaders(c),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *settings) validateOrigin(origin string) (string, bool) {
|
||||
if c.allowAllOrigins {
|
||||
return "*", true
|
||||
}
|
||||
if c.allowedOriginFunc != nil {
|
||||
return origin, c.allowedOriginFunc(origin)
|
||||
}
|
||||
for _, value := range c.allowedOrigins {
|
||||
if value == origin {
|
||||
return origin, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (c *settings) validateMethod(method string) bool {
|
||||
// TODO!!!
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *settings) validateHeader(header string) bool {
|
||||
// TODO!!!
|
||||
return true
|
||||
}
|
||||
|
||||
func generateNormalHeaders(c Config) http.Header {
|
||||
headers := make(http.Header)
|
||||
if c.AllowCredentials {
|
||||
headers.Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
if len(c.ExposedHeaders) > 0 {
|
||||
headers.Set("Access-Control-Expose-Headers", strings.Join(c.ExposedHeaders, ", "))
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func generatePreflightHeaders(c Config) http.Header {
|
||||
headers := make(http.Header)
|
||||
if c.AllowCredentials {
|
||||
headers.Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
if len(c.AllowedMethods) > 0 {
|
||||
headers.Set("Access-Control-Allow-Methods", strings.Join(c.AllowedMethods, ", "))
|
||||
}
|
||||
if len(c.AllowedHeaders) > 0 {
|
||||
headers.Set("Access-Control-Allow-Headers", strings.Join(c.AllowedHeaders, ", "))
|
||||
}
|
||||
if c.MaxAge > time.Duration(0) {
|
||||
headers.Set("Access-Control-Max-Age", strconv.FormatInt(int64(c.MaxAge/time.Second), 10))
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func distinct(s []string) []string {
|
||||
m := map[string]bool{}
|
||||
for _, v := range s {
|
||||
if _, seen := m[v]; !seen {
|
||||
s[len(m)] = v
|
||||
m[v] = true
|
||||
}
|
||||
}
|
||||
return s[:len(m)]
|
||||
}
|
||||
|
||||
func parse(content string) []string {
|
||||
if len(content) == 0 {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(content, ",")
|
||||
for i := 0; i < len(parts); i++ {
|
||||
parts[i] = strings.TrimSpace(parts[i])
|
||||
}
|
||||
return parts
|
||||
}
|
||||
145
vendor/github.com/gin-gonic/contrib/cors/cors.go
generated
vendored
Normal file
145
vendor/github.com/gin-gonic/contrib/cors/cors.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AbortOnError bool
|
||||
AllowAllOrigins bool
|
||||
|
||||
// AllowedOrigins is a list of origins a cross-domain request can be executed from.
|
||||
// If the special "*" value is present in the list, all origins will be allowed.
|
||||
// Default value is ["*"]
|
||||
AllowedOrigins []string
|
||||
|
||||
// AllowOriginFunc is a custom function to validate the origin. It take the origin
|
||||
// as argument and returns true if allowed or false otherwise. If this option is
|
||||
// set, the content of AllowedOrigins is ignored.
|
||||
AllowOriginFunc func(origin string) bool
|
||||
|
||||
// AllowedMethods is a list of methods the client is allowed to use with
|
||||
// cross-domain requests. Default value is simple methods (GET and POST)
|
||||
AllowedMethods []string
|
||||
|
||||
// AllowedHeaders is list of non simple headers the client is allowed to use with
|
||||
// cross-domain requests.
|
||||
// If the special "*" value is present in the list, all headers will be allowed.
|
||||
// Default value is [] but "Origin" is always appended to the list.
|
||||
AllowedHeaders []string
|
||||
|
||||
// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
|
||||
// API specification
|
||||
ExposedHeaders []string
|
||||
|
||||
// AllowCredentials indicates whether the request can include user credentials like
|
||||
// cookies, HTTP authentication or client side SSL certificates.
|
||||
AllowCredentials bool
|
||||
|
||||
// MaxAge indicates how long (in seconds) the results of a preflight request
|
||||
// can be cached
|
||||
MaxAge time.Duration
|
||||
}
|
||||
|
||||
func (c *Config) AddAllowedMethods(methods ...string) {
|
||||
c.AllowedMethods = append(c.AllowedMethods, methods...)
|
||||
}
|
||||
|
||||
func (c *Config) AddAllowedHeaders(headers ...string) {
|
||||
c.AllowedHeaders = append(c.AllowedHeaders, headers...)
|
||||
}
|
||||
|
||||
func (c *Config) AddExposedHeaders(headers ...string) {
|
||||
c.ExposedHeaders = append(c.ExposedHeaders, headers...)
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
if c.AllowAllOrigins && (c.AllowOriginFunc != nil || len(c.AllowedOrigins) > 0) {
|
||||
return errors.New("conflict settings: all origins are allowed. AllowOriginFunc or AllowedOrigins is not needed")
|
||||
}
|
||||
if !c.AllowAllOrigins && c.AllowOriginFunc == nil && len(c.AllowedOrigins) == 0 {
|
||||
return errors.New("conflict settings: all origins disabled")
|
||||
}
|
||||
if c.AllowOriginFunc != nil && len(c.AllowedOrigins) > 0 {
|
||||
return errors.New("conflict settings: if a allow origin func is provided, AllowedOrigins is not needed")
|
||||
}
|
||||
for _, origin := range c.AllowedOrigins {
|
||||
if !strings.HasPrefix(origin, "http://") && !strings.HasPrefix(origin, "https://") {
|
||||
return errors.New("bad origin: origins must include http:// or https://")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var defaultConfig = Config{
|
||||
AbortOnError: false,
|
||||
AllowAllOrigins: true,
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "HEAD"},
|
||||
AllowedHeaders: []string{"Content-Type"},
|
||||
//ExposedHeaders: "",
|
||||
AllowCredentials: false,
|
||||
MaxAge: 12 * time.Hour,
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
cp := defaultConfig
|
||||
return cp
|
||||
}
|
||||
|
||||
func Default() gin.HandlerFunc {
|
||||
return New(defaultConfig)
|
||||
}
|
||||
|
||||
func New(config Config) gin.HandlerFunc {
|
||||
s := newSettings(config)
|
||||
|
||||
// Algorithm based in http://www.html5rocks.com/static/images/cors_server_flowchart.png
|
||||
return func(c *gin.Context) {
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
if len(origin) == 0 {
|
||||
return
|
||||
}
|
||||
origin, valid := s.validateOrigin(origin)
|
||||
if valid {
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
valid = handlePreflight(c, s)
|
||||
} else {
|
||||
valid = handleNormal(c, s)
|
||||
}
|
||||
}
|
||||
|
||||
if !valid {
|
||||
if config.AbortOnError {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.Header("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePreflight(c *gin.Context, s *settings) bool {
|
||||
c.AbortWithStatus(200)
|
||||
if !s.validateMethod(c.Request.Header.Get("Access-Control-Request-Method")) {
|
||||
return false
|
||||
}
|
||||
if !s.validateHeader(c.Request.Header.Get("Access-Control-Request-Header")) {
|
||||
return false
|
||||
}
|
||||
for key, value := range s.preflightHeaders {
|
||||
c.Writer.Header()[key] = value
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func handleNormal(c *gin.Context, s *settings) bool {
|
||||
for key, value := range s.normalHeaders {
|
||||
c.Writer.Header()[key] = value
|
||||
}
|
||||
return true
|
||||
}
|
||||
104
vendor/github.com/gin-gonic/contrib/cors/cors_test.go
generated
vendored
Normal file
104
vendor/github.com/gin-gonic/contrib/cors/cors_test.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
|
||||
req, _ := http.NewRequest(method, path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func TestBadConfig(t *testing.T) {
|
||||
assert.Panics(t, func() { New(Config{}) })
|
||||
assert.Panics(t, func() {
|
||||
New(Config{
|
||||
AllowAllOrigins: true,
|
||||
AllowedOrigins: []string{"http://google.com"},
|
||||
})
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
New(Config{
|
||||
AllowAllOrigins: true,
|
||||
AllowOriginFunc: func(origin string) bool { return false },
|
||||
})
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
New(Config{
|
||||
AllowedOrigins: []string{"http://google.com"},
|
||||
AllowOriginFunc: func(origin string) bool { return false },
|
||||
})
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
New(Config{
|
||||
AllowedOrigins: []string{"google.com"},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeny0(t *testing.T) {
|
||||
called := false
|
||||
|
||||
router := gin.Default()
|
||||
router.Use(New(Config{
|
||||
AllowedOrigins: []string{"http://example.com"},
|
||||
}))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
called = true
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.Header.Set("Origin", "https://example.com")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.True(t, called)
|
||||
assert.NotContains(t, w.Header(), "Access-Control")
|
||||
}
|
||||
|
||||
func TestDenyAbortOnError(t *testing.T) {
|
||||
called := false
|
||||
|
||||
router := gin.Default()
|
||||
router.Use(New(Config{
|
||||
AbortOnError: true,
|
||||
AllowedOrigins: []string{"http://example.com"},
|
||||
}))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
called = true
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.Header.Set("Origin", "https://example.com")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.False(t, called)
|
||||
assert.NotContains(t, w.Header(), "Access-Control")
|
||||
}
|
||||
|
||||
func TestDeny2(t *testing.T) {
|
||||
|
||||
}
|
||||
func TestDeny3(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestPasses0(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestPasses1(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestPasses2(t *testing.T) {
|
||||
|
||||
}
|
||||
27
vendor/github.com/gin-gonic/contrib/expvar/README.md
generated
vendored
Normal file
27
vendor/github.com/gin-gonic/contrib/expvar/README.md
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# expvar for gin
|
||||
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2016-12-15. Please use [gin-contrib/expvar](https://github.com/gin-contrib/expvar) instead.**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import "github.com/gin-gonic/contrib/expvar"
|
||||
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.GET("/debug/vars", expvar.Handler())
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
Request: `http://localhost:8080/debug/vars`
|
||||
|
||||
```json
|
||||
{
|
||||
"cmdline": ["/var/folders/zg/q__7tncn7kxc34pc3j0_v7rc0000gn/T/go-build115008999/command-line-arguments/_obj/exe/main"],
|
||||
"memstats": {"Alloc":406968,"TotalAlloc":3025088,"Sys":3999992,"Lookups":15,"Mallocs":10405,"Frees":9228,"HeapAlloc":406968,"HeapSys":1916928,"HeapIdle":1097728,"HeapInuse":819200,"HeapReleased":0,"HeapObjects":1177,"StackInuse":180224,"StackSys":180224,"MSpanInuse":7904,"MSpanSys":16384,"MCacheInuse":1200,"MCacheSys":16384,"BuckHashSys":1441424,"GCSys":137622,"OtherSys":291026,"NextGC":578160,"LastGC":1432428478423798618,"PauseTotalNs":4326675,"PauseNs":[105780,76617,91326,115727,195752,249831,554025,485129,344607,416552,400537,424639,463089,403064,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"PauseEnd":[1432428359211210066,1432428359211415512,1432428359211821428,1432428359212452447,1432428359212906768,1432428359214282772,1432428381213398837,1432428383977712300,1432428452748952359,1432428470574839824,1432428472452814302,1432428474379491025,1432428476329036668,1432428478423798444,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"NumGC":14,"EnableGC":true,"DebugGC":false,"BySize":[{"Size":0,"Mallocs":0,"Frees":0},{"Size":8,"Mallocs":258,"Frees":237},{"Size":16,"Mallocs":4125,"Frees":3670},{"Size":32,"Mallocs":765,"Frees":652},{"Size":48,"Mallocs":495,"Frees":367},{"Size":64,"Mallocs":305,"Frees":274},{"Size":80,"Mallocs":37,"Frees":24},{"Size":96,"Mallocs":32,"Frees":25},{"Size":112,"Mallocs":713,"Frees":569},{"Size":128,"Mallocs":283,"Frees":259},{"Size":144,"Mallocs":91,"Frees":85},{"Size":160,"Mallocs":139,"Frees":112},{"Size":176,"Mallocs":349,"Frees":315},{"Size":192,"Mallocs":0,"Frees":0},{"Size":208,"Mallocs":223,"Frees":196},{"Size":224,"Mallocs":2,"Frees":2},{"Size":240,"Mallocs":86,"Frees":80},{"Size":256,"Mallocs":21,"Frees":18},{"Size":288,"Mallocs":130,"Frees":104},{"Size":320,"Mallocs":19,"Frees":15},{"Size":352,"Mallocs":278,"Frees":259},{"Size":384,"Mallocs":5,"Frees":5},{"Size":416,"Mallocs":17,"Frees":10},{"Size":448,"Mallocs":0,"Frees":0},{"Size":480,"Mallocs":2,"Frees":1},{"Size":512,"Mallocs":16,"Frees":12},{"Size":576,"Mallocs":92,"Frees":83},{"Size":640,"Mallocs":12,"Frees":7},{"Size":704,"Mallocs":2,"Frees":2},{"Size":768,"Mallocs":0,"Frees":0},{"Size":896,"Mallocs":15,"Frees":12},{"Size":1024,"Mallocs":10,"Frees":8},{"Size":1152,"Mallocs":88,"Frees":82},{"Size":1280,"Mallocs":7,"Frees":6},{"Size":1408,"Mallocs":2,"Frees":1},{"Size":1536,"Mallocs":0,"Frees":0},{"Size":1664,"Mallocs":9,"Frees":4},{"Size":2048,"Mallocs":10,"Frees":9},{"Size":2304,"Mallocs":88,"Frees":82},{"Size":2560,"Mallocs":6,"Frees":5},{"Size":2816,"Mallocs":2,"Frees":1},{"Size":3072,"Mallocs":1,"Frees":1},{"Size":3328,"Mallocs":4,"Frees":1},{"Size":4096,"Mallocs":172,"Frees":167},{"Size":4608,"Mallocs":93,"Frees":81},{"Size":5376,"Mallocs":7,"Frees":0},{"Size":6144,"Mallocs":87,"Frees":81},{"Size":6400,"Mallocs":0,"Frees":0},{"Size":6656,"Mallocs":1,"Frees":0},{"Size":6912,"Mallocs":0,"Frees":0},{"Size":8192,"Mallocs":6,"Frees":6},{"Size":8448,"Mallocs":0,"Frees":0},{"Size":8704,"Mallocs":1,"Frees":1},{"Size":9472,"Mallocs":0,"Frees":0},{"Size":10496,"Mallocs":0,"Frees":0},{"Size":12288,"Mallocs":1,"Frees":1},{"Size":13568,"Mallocs":0,"Frees":0},{"Size":14080,"Mallocs":0,"Frees":0},{"Size":16384,"Mallocs":0,"Frees":0},{"Size":16640,"Mallocs":0,"Frees":0},{"Size":17664,"Mallocs":1,"Frees":0}]}
|
||||
}
|
||||
```
|
||||
26
vendor/github.com/gin-gonic/contrib/expvar/expvar.go
generated
vendored
Normal file
26
vendor/github.com/gin-gonic/contrib/expvar/expvar.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package expvar
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Handler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
w := c.Writer
|
||||
c.Header("Content-Type", "application/json; charset=utf-8")
|
||||
w.Write([]byte("{\n"))
|
||||
first := true
|
||||
expvar.Do(func(kv expvar.KeyValue) {
|
||||
if !first {
|
||||
w.Write([]byte(",\n"))
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||
})
|
||||
w.Write([]byte("\n}\n"))
|
||||
c.AbortWithStatus(200)
|
||||
}
|
||||
}
|
||||
10
vendor/github.com/gin-gonic/contrib/ginrus/Godeps/Godeps.json
generated
vendored
Normal file
10
vendor/github.com/gin-gonic/contrib/ginrus/Godeps/Godeps.json
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/contrib/ginrus",
|
||||
"GoVersion": "go1.3",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/gin",
|
||||
"Rev": "ac0ad2fed865d40a0adc1ac3ccaadc3acff5db4b"
|
||||
}
|
||||
]
|
||||
}
|
||||
38
vendor/github.com/gin-gonic/contrib/ginrus/example/example.go
generated
vendored
Normal file
38
vendor/github.com/gin-gonic/contrib/ginrus/example/example.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/contrib/ginrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.New()
|
||||
|
||||
// Add a ginrus middleware, which:
|
||||
// - Logs all requests, like a combined access and error log.
|
||||
// - Logs to stdout.
|
||||
// - RFC3339 with UTC time format.
|
||||
r.Use(ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true))
|
||||
|
||||
// Add similar middleware, but:
|
||||
// - Only logs requests with errors, like an error log.
|
||||
// - Logs to stderr instead of stdout.
|
||||
// - Local time zone instead of UTC.
|
||||
logger := logrus.New()
|
||||
logger.Level = logrus.ErrorLevel
|
||||
logger.Out = os.Stderr
|
||||
r.Use(ginrus.Ginrus(logger, time.RFC3339, false))
|
||||
|
||||
// Example ping request.
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
|
||||
})
|
||||
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
51
vendor/github.com/gin-gonic/contrib/ginrus/ginrus.go
generated
vendored
Normal file
51
vendor/github.com/gin-gonic/contrib/ginrus/ginrus.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// Package ginrus provides log handling using logrus package.
|
||||
//
|
||||
// Based on github.com/stephenmuss/ginerus but adds more options.
|
||||
package ginrus
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Ginrus returns a gin.HandlerFunc (middleware) that logs requests using logrus.
|
||||
//
|
||||
// Requests with errors are logged using logrus.Error().
|
||||
// Requests without errors are logged using logrus.Info().
|
||||
//
|
||||
// It receives:
|
||||
// 1. A time package format string (e.g. time.RFC3339).
|
||||
// 2. A boolean stating whether to use UTC time zone or local.
|
||||
func Ginrus(logger *logrus.Logger, timeFormat string, utc bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
// some evil middlewares modify this values
|
||||
path := c.Request.URL.Path
|
||||
c.Next()
|
||||
|
||||
end := time.Now()
|
||||
latency := end.Sub(start)
|
||||
if utc {
|
||||
end = end.UTC()
|
||||
}
|
||||
|
||||
entry := logger.WithFields(logrus.Fields{
|
||||
"status": c.Writer.Status(),
|
||||
"method": c.Request.Method,
|
||||
"path": path,
|
||||
"ip": c.ClientIP(),
|
||||
"latency": latency,
|
||||
"user-agent": c.Request.UserAgent(),
|
||||
"time": end.Format(timeFormat),
|
||||
})
|
||||
|
||||
if len(c.Errors) > 0 {
|
||||
// Append error field if this is an erroneous request.
|
||||
entry.Error(c.Errors.String())
|
||||
} else {
|
||||
entry.Info()
|
||||
}
|
||||
}
|
||||
}
|
||||
10
vendor/github.com/gin-gonic/contrib/gzip/Godeps/Godeps.json
generated
vendored
Normal file
10
vendor/github.com/gin-gonic/contrib/gzip/Godeps/Godeps.json
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/contrib/gzip",
|
||||
"GoVersion": "go1.3",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/gin",
|
||||
"Rev": "ac0ad2fed865d40a0adc1ac3ccaadc3acff5db4b"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
vendor/github.com/gin-gonic/contrib/gzip/README.md
generated
vendored
Normal file
5
vendor/github.com/gin-gonic/contrib/gzip/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# gzip
|
||||
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2016-12-07. Please use [gin-contrib/gzip](https://github.com/gin-contrib/gzip) instead.**
|
||||
19
vendor/github.com/gin-gonic/contrib/gzip/example/example.go
generated
vendored
Normal file
19
vendor/github.com/gin-gonic/contrib/gzip/example/example.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
r.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
|
||||
})
|
||||
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
68
vendor/github.com/gin-gonic/contrib/gzip/gzip.go
generated
vendored
Normal file
68
vendor/github.com/gin-gonic/contrib/gzip/gzip.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package gzip
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
BestCompression = gzip.BestCompression
|
||||
BestSpeed = gzip.BestSpeed
|
||||
DefaultCompression = gzip.DefaultCompression
|
||||
NoCompression = gzip.NoCompression
|
||||
)
|
||||
|
||||
func Gzip(level int) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if !shouldCompress(c.Request) {
|
||||
return
|
||||
}
|
||||
gz, err := gzip.NewWriterLevel(c.Writer, level)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Encoding", "gzip")
|
||||
c.Header("Vary", "Accept-Encoding")
|
||||
c.Writer = &gzipWriter{c.Writer, gz}
|
||||
defer func() {
|
||||
c.Header("Content-Length", "0")
|
||||
gz.Close()
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
type gzipWriter struct {
|
||||
gin.ResponseWriter
|
||||
writer *gzip.Writer
|
||||
}
|
||||
|
||||
func (g *gzipWriter) WriteString(s string) (int, error) {
|
||||
return g.writer.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (g *gzipWriter) Write(data []byte) (int, error) {
|
||||
return g.writer.Write(data)
|
||||
}
|
||||
|
||||
func shouldCompress(req *http.Request) bool {
|
||||
if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||
return false
|
||||
}
|
||||
extension := filepath.Ext(req.URL.Path)
|
||||
if len(extension) < 4 { // fast path
|
||||
return true
|
||||
}
|
||||
|
||||
switch extension {
|
||||
case ".png", ".gif", ".jpeg", ".jpg":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
81
vendor/github.com/gin-gonic/contrib/gzip/gzip_test.go
generated
vendored
Normal file
81
vendor/github.com/gin-gonic/contrib/gzip/gzip_test.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package gzip
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
testResponse = "Gzip Test Response "
|
||||
)
|
||||
|
||||
func newServer() *gin.Engine {
|
||||
router := gin.Default()
|
||||
router.Use(Gzip(DefaultCompression))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.Header("Content-Length", strconv.Itoa(len(testResponse)))
|
||||
c.String(200, testResponse)
|
||||
})
|
||||
return router
|
||||
}
|
||||
|
||||
func TestGzip(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.Header.Add("Accept-Encoding", "gzip")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := newServer()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Header().Get("Content-Encoding"), "gzip")
|
||||
assert.Equal(t, w.Header().Get("Vary"), "Accept-Encoding")
|
||||
assert.Equal(t, w.Header().Get("Content-Length"), "0")
|
||||
assert.NotEqual(t, w.Body.Len(), 19)
|
||||
|
||||
gr, err := gzip.NewReader(w.Body)
|
||||
assert.NoError(t, err)
|
||||
defer gr.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(gr)
|
||||
assert.Equal(t, string(body), testResponse)
|
||||
}
|
||||
|
||||
func TestGzipPNG(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/image.png", nil)
|
||||
req.Header.Add("Accept-Encoding", "gzip")
|
||||
|
||||
router := gin.New()
|
||||
router.Use(Gzip(DefaultCompression))
|
||||
router.GET("/image.png", func(c *gin.Context) {
|
||||
c.String(200, "this is a PNG!")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Header().Get("Content-Encoding"), "")
|
||||
assert.Equal(t, w.Header().Get("Vary"), "")
|
||||
assert.Equal(t, w.Body.String(), "this is a PNG!")
|
||||
}
|
||||
|
||||
func TestNoGzip(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := newServer()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Header().Get("Content-Encoding"), "")
|
||||
assert.Equal(t, w.Header().Get("Content-Length"), "19")
|
||||
assert.Equal(t, w.Body.String(), testResponse)
|
||||
}
|
||||
22
vendor/github.com/gin-gonic/contrib/jwt/Godeps/Godeps.json
generated
vendored
Normal file
22
vendor/github.com/gin-gonic/contrib/jwt/Godeps/Godeps.json
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/contrib/jwt",
|
||||
"GoVersion": "go1.3",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/dgrijalva/jwt-go",
|
||||
"Rev": "5ca80149b9d3f8b863af0e2bb6742e608603bd99"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/gin",
|
||||
"Rev": "ac0ad2fed865d40a0adc1ac3ccaadc3acff5db4b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jacobsa/oglematchers",
|
||||
"Rev": "3ecefc49db07722beca986d9bb71ddd026b133f0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/smartystreets/goconvey/convey",
|
||||
"Rev": "958443eebb772fc6c1dcc2a44cdc6a59e1b3ff0d"
|
||||
}
|
||||
]
|
||||
}
|
||||
83
vendor/github.com/gin-gonic/contrib/jwt/README.md
generated
vendored
Normal file
83
vendor/github.com/gin-gonic/contrib/jwt/README.md
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
JWT middleware for go gonic.
|
||||
|
||||
JSON Web Token (JWT) more information: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
|
||||
|
||||
EDIT: Below is the test for [christopherL91/Go-API](https://github.com/christopherL91/Go-API)
|
||||
|
||||
```go
|
||||
package jwt_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func createNewsUser(username, password string) *User {
|
||||
return &User{username, password}
|
||||
}
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
Convey("Should be able to login", t, func() {
|
||||
user := createNewsUser("jonas", "1234")
|
||||
jsondata, _ := json.Marshal(user)
|
||||
post_data := strings.NewReader(string(jsondata))
|
||||
req, _ := http.NewRequest("POST", "http://localhost:3000/api/login", post_data)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
client := &http.Client{}
|
||||
res, _ := client.Do(req)
|
||||
So(res.StatusCode, ShouldEqual, 200)
|
||||
|
||||
Convey("Should be able to parse body", func() {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
So(err, ShouldBeNil)
|
||||
Convey("Should be able to get json back", func() {
|
||||
responseData := new(Response)
|
||||
err := json.Unmarshal(body, responseData)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to be authorized", func() {
|
||||
token := responseData.Token
|
||||
req, _ := http.NewRequest("GET", "http://localhost:3000/api/auth/testAuth", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
client = &http.Client{}
|
||||
res, _ := client.Do(req)
|
||||
So(res.StatusCode, ShouldEqual, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Convey("Should not be able to login with false credentials", t, func() {
|
||||
user := createNewsUser("jnwfkjnkfneknvjwenv", "wenknfkwnfknfknkfjnwkfenw")
|
||||
jsondata, _ := json.Marshal(user)
|
||||
post_data := strings.NewReader(string(jsondata))
|
||||
req, _ := http.NewRequest("POST", "http://localhost:3000/api/login", post_data)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
client := &http.Client{}
|
||||
res, _ := client.Do(req)
|
||||
So(res.StatusCode, ShouldEqual, 401)
|
||||
})
|
||||
|
||||
Convey("Should not be able to authorize with false credentials", t, func() {
|
||||
token := ""
|
||||
req, _ := http.NewRequest("GET", "http://localhost:3000/api/auth/testAuth", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
client := &http.Client{}
|
||||
res, _ := client.Do(req)
|
||||
So(res.StatusCode, ShouldEqual, 401)
|
||||
})
|
||||
}
|
||||
```
|
||||
49
vendor/github.com/gin-gonic/contrib/jwt/example/example.go
generated
vendored
Normal file
49
vendor/github.com/gin-gonic/contrib/jwt/example/example.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
jwt_lib "github.com/dgrijalva/jwt-go"
|
||||
"github.com/gin-gonic/contrib/jwt"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
mysupersecretpassword = "unicornsAreAwesome"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
public := r.Group("/api")
|
||||
|
||||
public.GET("/", func(c *gin.Context) {
|
||||
// Create the token
|
||||
token := jwt_lib.New(jwt_lib.GetSigningMethod("HS256"))
|
||||
// Set some claims
|
||||
token.Claims = jwt_lib.MapClaims{
|
||||
"Id": "Christopher",
|
||||
"exp": time.Now().Add(time.Hour * 1).Unix(),
|
||||
}
|
||||
// Sign and get the complete encoded token as a string
|
||||
tokenString, err := token.SignedString([]byte(mysupersecretpassword))
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"message": "Could not generate token"})
|
||||
}
|
||||
c.JSON(200, gin.H{"token": tokenString})
|
||||
})
|
||||
|
||||
private := r.Group("/api/private")
|
||||
private.Use(jwt.Auth(mysupersecretpassword))
|
||||
|
||||
/*
|
||||
Set this header in your request to get here.
|
||||
Authorization: Bearer `token`
|
||||
*/
|
||||
|
||||
private.GET("/", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Hello from private"})
|
||||
})
|
||||
|
||||
r.Run("localhost:8080")
|
||||
}
|
||||
20
vendor/github.com/gin-gonic/contrib/jwt/jwt.go
generated
vendored
Normal file
20
vendor/github.com/gin-gonic/contrib/jwt/jwt.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
jwt_lib "github.com/dgrijalva/jwt-go"
|
||||
"github.com/dgrijalva/jwt-go/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Auth(secret string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
_, err := request.ParseFromRequest(c.Request, request.OAuth2Extractor, func(token *jwt_lib.Token) (interface{}, error) {
|
||||
b := ([]byte(secret))
|
||||
return b, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithError(401, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/gin-gonic/contrib/newrelic/newrelic.go
generated
vendored
Normal file
29
vendor/github.com/gin-gonic/contrib/newrelic/newrelic.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package newrelic
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
metrics "github.com/yvasiyarov/go-metrics"
|
||||
"github.com/yvasiyarov/gorelic"
|
||||
)
|
||||
|
||||
var agent *gorelic.Agent
|
||||
|
||||
func NewRelic(license string, appname string, verbose bool) gin.HandlerFunc {
|
||||
agent = gorelic.NewAgent()
|
||||
agent.NewrelicLicense = license
|
||||
|
||||
agent.HTTPTimer = metrics.NewTimer()
|
||||
agent.CollectHTTPStat = true
|
||||
agent.Verbose = verbose
|
||||
|
||||
agent.NewrelicName = appname
|
||||
agent.Run()
|
||||
|
||||
return func(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
c.Next()
|
||||
agent.HTTPTimer.UpdateSince(startTime)
|
||||
}
|
||||
}
|
||||
86
vendor/github.com/gin-gonic/contrib/renders/multitemplate/README.md
generated
vendored
Normal file
86
vendor/github.com/gin-gonic/contrib/renders/multitemplate/README.md
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2016-12-13. Please use [gin-contrib/multitemplate](https://github.com/gin-contrib/multitemplate) instead.**
|
||||
|
||||
This is a custom HTML render to support multi templates, ie. more than one `*template.Template`.
|
||||
|
||||
#Simple example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/contrib/renders/multitemplate"
|
||||
)
|
||||
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.HTMLRender = createMyRender()
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.HTML(200, "index", data)
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
|
||||
func createMyRender() multitemplate.Render {
|
||||
r := multitemplate.New()
|
||||
r.AddFromFiles("index", "base.html", "base.html")
|
||||
r.AddFromFiles("article", "base.html", "article.html")
|
||||
r.AddFromFiles("login", "base.html", "login.html")
|
||||
r.AddFromFiles("dashboard", "base.html", "dashboard.html")
|
||||
|
||||
return r
|
||||
}
|
||||
```
|
||||
|
||||
##Advanced example
|
||||
|
||||
[https://elithrar.github.io/article/approximating-html-template-inheritance/](https://elithrar.github.io/article/approximating-html-template-inheritance/)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gin-gonic/contrib/renders/multitemplate"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.HTMLRender = loadTemplates("./templates")
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.HTML(200, "index.tmpl", gin.H{
|
||||
"title": "Welcome!",
|
||||
})
|
||||
})
|
||||
router.Run(":8080")
|
||||
}
|
||||
|
||||
func loadTemplates(templatesDir string) multitemplate.Render {
|
||||
r := multitemplate.New()
|
||||
|
||||
layouts, err := filepath.Glob(templatesDir + "layouts/*.tmpl")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
includes, err := filepath.Glob(templatesDir + "includes/*.tmpl")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
// Generate our templates map from our layouts/ and includes/ directories
|
||||
for _, layout := range layouts {
|
||||
files := append(includes, layout)
|
||||
r.Add(filepath.Base(layout), template.Must(template.ParseFiles(files...)))
|
||||
}
|
||||
return r
|
||||
}
|
||||
```
|
||||
|
||||
50
vendor/github.com/gin-gonic/contrib/renders/multitemplate/multitemplate.go
generated
vendored
Normal file
50
vendor/github.com/gin-gonic/contrib/renders/multitemplate/multitemplate.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package multitemplate
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"github.com/gin-gonic/gin/render"
|
||||
)
|
||||
|
||||
type Render map[string]*template.Template
|
||||
|
||||
var _ render.HTMLRender = Render{}
|
||||
|
||||
func New() Render {
|
||||
return make(Render)
|
||||
}
|
||||
|
||||
func (r Render) Add(name string, tmpl *template.Template) {
|
||||
if tmpl == nil {
|
||||
panic("template can not be nil")
|
||||
}
|
||||
if len(name) == 0 {
|
||||
panic("template name cannot be empty")
|
||||
}
|
||||
r[name] = tmpl
|
||||
}
|
||||
|
||||
func (r Render) AddFromFiles(name string, files ...string) *template.Template {
|
||||
tmpl := template.Must(template.ParseFiles(files...))
|
||||
r.Add(name, tmpl)
|
||||
return tmpl
|
||||
}
|
||||
|
||||
func (r Render) AddFromGlob(name, glob string) *template.Template {
|
||||
tmpl := template.Must(template.ParseGlob(glob))
|
||||
r.Add(name, tmpl)
|
||||
return tmpl
|
||||
}
|
||||
|
||||
func (r *Render) AddFromString(name, templateString string) *template.Template {
|
||||
tmpl := template.Must(template.New("").Parse(templateString))
|
||||
r.Add(name, tmpl)
|
||||
return tmpl
|
||||
}
|
||||
|
||||
func (r Render) Instance(name string, data interface{}) render.Render {
|
||||
return render.HTML{
|
||||
Template: r[name],
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
46
vendor/github.com/gin-gonic/contrib/rest/rest.go
generated
vendored
Normal file
46
vendor/github.com/gin-gonic/contrib/rest/rest.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// All of the methods are the same type as HandlerFunc
|
||||
// if you don't want to support any methods of CRUD, then don't implement it
|
||||
type CreateSupported interface {
|
||||
CreateHandler(*gin.Context)
|
||||
}
|
||||
type ListSupported interface {
|
||||
ListHandler(*gin.Context)
|
||||
}
|
||||
type TakeSupported interface {
|
||||
TakeHandler(*gin.Context)
|
||||
}
|
||||
type UpdateSupported interface {
|
||||
UpdateHandler(*gin.Context)
|
||||
}
|
||||
type DeleteSupported interface {
|
||||
DeleteHandler(*gin.Context)
|
||||
}
|
||||
|
||||
// It defines
|
||||
// POST: /path
|
||||
// GET: /path
|
||||
// PUT: /path/:id
|
||||
// POST: /path/:id
|
||||
func CRUD(group *gin.RouterGroup, path string, resource interface{}) {
|
||||
if resource, ok := resource.(CreateSupported); ok {
|
||||
group.POST(path, resource.CreateHandler)
|
||||
}
|
||||
if resource, ok := resource.(ListSupported); ok {
|
||||
group.GET(path, resource.ListHandler)
|
||||
}
|
||||
if resource, ok := resource.(TakeSupported); ok {
|
||||
group.GET(path+"/:id", resource.TakeHandler)
|
||||
}
|
||||
if resource, ok := resource.(UpdateSupported); ok {
|
||||
group.PUT(path+"/:id", resource.UpdateHandler)
|
||||
}
|
||||
if resource, ok := resource.(DeleteSupported); ok {
|
||||
group.DELETE(path+"/:id", resource.DeleteHandler)
|
||||
}
|
||||
}
|
||||
10
vendor/github.com/gin-gonic/contrib/secure/Godeps/Godeps.json
generated
vendored
Normal file
10
vendor/github.com/gin-gonic/contrib/secure/Godeps/Godeps.json
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/contrib/secure",
|
||||
"GoVersion": "go1.3",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/gin",
|
||||
"Rev": "ac0ad2fed865d40a0adc1ac3ccaadc3acff5db4b"
|
||||
}
|
||||
]
|
||||
}
|
||||
32
vendor/github.com/gin-gonic/contrib/secure/example/example.go
generated
vendored
Normal file
32
vendor/github.com/gin-gonic/contrib/secure/example/example.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/contrib/secure"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(secure.Secure(secure.Options{
|
||||
AllowedHosts: []string{"example.com", "ssl.example.com"},
|
||||
SSLRedirect: true,
|
||||
SSLHost: "ssl.example.com",
|
||||
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
|
||||
STSSeconds: 315360000,
|
||||
STSIncludeSubdomains: true,
|
||||
FrameDeny: true,
|
||||
ContentTypeNosniff: true,
|
||||
BrowserXssFilter: true,
|
||||
ContentSecurityPolicy: "default-src 'self'",
|
||||
}))
|
||||
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
|
||||
})
|
||||
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
179
vendor/github.com/gin-gonic/contrib/secure/secure.go
generated
vendored
Normal file
179
vendor/github.com/gin-gonic/contrib/secure/secure.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
package secure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
stsHeader = "Strict-Transport-Security"
|
||||
stsSubdomainString = "; includeSubdomains"
|
||||
frameOptionsHeader = "X-Frame-Options"
|
||||
frameOptionsValue = "DENY"
|
||||
contentTypeHeader = "X-Content-Type-Options"
|
||||
contentTypeValue = "nosniff"
|
||||
xssProtectionHeader = "X-XSS-Protection"
|
||||
xssProtectionValue = "1; mode=block"
|
||||
cspHeader = "Content-Security-Policy"
|
||||
)
|
||||
|
||||
func defaultBadHostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Bad Host", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Options is a struct for specifying configuration options for the secure.Secure middleware.
|
||||
type Options struct {
|
||||
// AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names.
|
||||
AllowedHosts []string
|
||||
// If SSLRedirect is set to true, then only allow https requests. Default is false.
|
||||
SSLRedirect bool
|
||||
// If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
|
||||
SSLTemporaryRedirect bool
|
||||
// SSLHost is the host name that is used to redirect http requests to https. Default is "", which indicates to use the same host.
|
||||
SSLHost string
|
||||
// SSLProxyHeaders is set of header keys with associated values that would indicate a valid https request. Useful when using Nginx: `map[string]string{"X-Forwarded-Proto": "https"}`. Default is blank map.
|
||||
SSLProxyHeaders map[string]string
|
||||
// STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
|
||||
STSSeconds int64
|
||||
// If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
|
||||
STSIncludeSubdomains bool
|
||||
// If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is false.
|
||||
FrameDeny bool
|
||||
// CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option.
|
||||
CustomFrameOptionsValue string
|
||||
// If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is false.
|
||||
ContentTypeNosniff bool
|
||||
// If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false.
|
||||
BrowserXssFilter bool
|
||||
// ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "".
|
||||
ContentSecurityPolicy string
|
||||
// When developing, the AllowedHosts, SSL, and STS options can cause some unwanted effects. Usually testing happens on http, not https, and on localhost, not your production domain... so set this to true for dev environment.
|
||||
// If you would like your development environment to mimic production with complete Host blocking, SSL redirects, and STS headers, leave this as false. Default if false.
|
||||
IsDevelopment bool
|
||||
|
||||
// Handlers for when an error occurs (ie bad host).
|
||||
BadHostHandler http.Handler
|
||||
}
|
||||
|
||||
// Secure is a middleware that helps setup a few basic security features. A single secure.Options struct can be
|
||||
// provided to configure which features should be enabled, and the ability to override a few of the default values.
|
||||
type secure struct {
|
||||
// Customize Secure with an Options struct.
|
||||
opt Options
|
||||
}
|
||||
|
||||
// Constructs a new Secure instance with supplied options.
|
||||
func New(options Options) *secure {
|
||||
if options.BadHostHandler == nil {
|
||||
options.BadHostHandler = http.HandlerFunc(defaultBadHostHandler)
|
||||
}
|
||||
|
||||
return &secure{
|
||||
opt: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *secure) process(w http.ResponseWriter, r *http.Request) error {
|
||||
// Allowed hosts check.
|
||||
if len(s.opt.AllowedHosts) > 0 && !s.opt.IsDevelopment {
|
||||
isGoodHost := false
|
||||
for _, allowedHost := range s.opt.AllowedHosts {
|
||||
if strings.EqualFold(allowedHost, r.Host) {
|
||||
isGoodHost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isGoodHost {
|
||||
s.opt.BadHostHandler.ServeHTTP(w, r)
|
||||
return fmt.Errorf("Bad host name: %s", r.Host)
|
||||
}
|
||||
}
|
||||
|
||||
// SSL check.
|
||||
if s.opt.SSLRedirect && s.opt.IsDevelopment == false {
|
||||
isSSL := false
|
||||
if strings.EqualFold(r.URL.Scheme, "https") || r.TLS != nil {
|
||||
isSSL = true
|
||||
} else {
|
||||
for k, v := range s.opt.SSLProxyHeaders {
|
||||
if r.Header.Get(k) == v {
|
||||
isSSL = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isSSL == false {
|
||||
url := r.URL
|
||||
url.Scheme = "https"
|
||||
url.Host = r.Host
|
||||
|
||||
if len(s.opt.SSLHost) > 0 {
|
||||
url.Host = s.opt.SSLHost
|
||||
}
|
||||
|
||||
status := http.StatusMovedPermanently
|
||||
if s.opt.SSLTemporaryRedirect {
|
||||
status = http.StatusTemporaryRedirect
|
||||
}
|
||||
|
||||
http.Redirect(w, r, url.String(), status)
|
||||
return fmt.Errorf("Redirecting to HTTPS")
|
||||
}
|
||||
}
|
||||
|
||||
// Strict Transport Security header.
|
||||
if s.opt.STSSeconds != 0 && !s.opt.IsDevelopment {
|
||||
stsSub := ""
|
||||
if s.opt.STSIncludeSubdomains {
|
||||
stsSub = stsSubdomainString
|
||||
}
|
||||
|
||||
w.Header().Add(stsHeader, fmt.Sprintf("max-age=%d%s", s.opt.STSSeconds, stsSub))
|
||||
}
|
||||
|
||||
// Frame Options header.
|
||||
if len(s.opt.CustomFrameOptionsValue) > 0 {
|
||||
w.Header().Add(frameOptionsHeader, s.opt.CustomFrameOptionsValue)
|
||||
} else if s.opt.FrameDeny {
|
||||
w.Header().Add(frameOptionsHeader, frameOptionsValue)
|
||||
}
|
||||
|
||||
// Content Type Options header.
|
||||
if s.opt.ContentTypeNosniff {
|
||||
w.Header().Add(contentTypeHeader, contentTypeValue)
|
||||
}
|
||||
|
||||
// XSS Protection header.
|
||||
if s.opt.BrowserXssFilter {
|
||||
w.Header().Add(xssProtectionHeader, xssProtectionValue)
|
||||
}
|
||||
|
||||
// Content Security Policy header.
|
||||
if len(s.opt.ContentSecurityPolicy) > 0 {
|
||||
w.Header().Add(cspHeader, s.opt.ContentSecurityPolicy)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func Secure(options Options) gin.HandlerFunc {
|
||||
s := New(options)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
err := s.process(c.Writer, c.Request)
|
||||
if err != nil {
|
||||
if c.Writer.Written() {
|
||||
c.AbortWithStatus(c.Writer.Status())
|
||||
} else {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
470
vendor/github.com/gin-gonic/contrib/secure/secure_test.go
generated
vendored
Normal file
470
vendor/github.com/gin-gonic/contrib/secure/secure_test.go
generated
vendored
Normal file
@@ -0,0 +1,470 @@
|
||||
package secure
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
testResponse = "bar"
|
||||
)
|
||||
|
||||
func newServer(options Options) *gin.Engine {
|
||||
r := gin.Default()
|
||||
r.Use(Secure(options))
|
||||
r.GET("/foo", func(c *gin.Context) {
|
||||
c.String(200, testResponse)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func TestNoConfig(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
// Intentionally left blank.
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "http://example.com/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Body.String(), "bar")
|
||||
}
|
||||
|
||||
func TestNoAllowHosts(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
AllowedHosts: []string{},
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Body.String(), `bar`)
|
||||
}
|
||||
|
||||
func TestGoodSingleAllowHosts(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
AllowedHosts: []string{"www.example.com"},
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Body.String(), `bar`)
|
||||
}
|
||||
|
||||
func TestBadSingleAllowHosts(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
AllowedHosts: []string{"sub.example.com"},
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func TestGoodMultipleAllowHosts(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
AllowedHosts: []string{"www.example.com", "sub.example.com"},
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "sub.example.com"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Body.String(), `bar`)
|
||||
}
|
||||
|
||||
func TestBadMultipleAllowHosts(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
AllowedHosts: []string{"www.example.com", "sub.example.com"},
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www3.example.com"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func TestAllowHostsInDevMode(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
AllowedHosts: []string{"www.example.com", "sub.example.com"},
|
||||
IsDevelopment: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www3.example.com"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestBadHostHandler(t *testing.T) {
|
||||
|
||||
badHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "BadHost", http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
s := newServer(Options{
|
||||
AllowedHosts: []string{"www.example.com", "sub.example.com"},
|
||||
BadHostHandler: badHandler,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www3.example.com"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusInternalServerError)
|
||||
|
||||
// http.Error outputs a new line character with the response.
|
||||
expect(t, res.Body.String(), "BadHost\n")
|
||||
}
|
||||
|
||||
func TestSSL(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "https"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestSSLInDevMode(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
IsDevelopment: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestBasicSSL(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusMovedPermanently)
|
||||
expect(t, res.Header().Get("Location"), "https://www.example.com/foo")
|
||||
}
|
||||
|
||||
func TestBasicSSLWithHost(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
SSLHost: "secure.example.com",
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusMovedPermanently)
|
||||
expect(t, res.Header().Get("Location"), "https://secure.example.com/foo")
|
||||
}
|
||||
|
||||
func TestBadProxySSL(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
req.Header.Add("X-Forwarded-Proto", "https")
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusMovedPermanently)
|
||||
expect(t, res.Header().Get("Location"), "https://www.example.com/foo")
|
||||
}
|
||||
|
||||
func TestCustomProxySSL(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
req.Header.Add("X-Forwarded-Proto", "https")
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestCustomProxySSLInDevMode(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
|
||||
IsDevelopment: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
req.Header.Add("X-Forwarded-Proto", "http")
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestCustomProxyAndHostSSL(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
|
||||
SSLHost: "secure.example.com",
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
req.Header.Add("X-Forwarded-Proto", "https")
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestCustomBadProxyAndHostSSL(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "superman"},
|
||||
SSLHost: "secure.example.com",
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
req.Header.Add("X-Forwarded-Proto", "https")
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusMovedPermanently)
|
||||
expect(t, res.Header().Get("Location"), "https://secure.example.com/foo")
|
||||
}
|
||||
|
||||
func TestCustomBadProxyAndHostSSLWithTempRedirect(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
SSLRedirect: true,
|
||||
SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "superman"},
|
||||
SSLHost: "secure.example.com",
|
||||
SSLTemporaryRedirect: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
req.Host = "www.example.com"
|
||||
req.URL.Scheme = "http"
|
||||
req.Header.Add("X-Forwarded-Proto", "https")
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusTemporaryRedirect)
|
||||
expect(t, res.Header().Get("Location"), "https://secure.example.com/foo")
|
||||
}
|
||||
|
||||
func TestStsHeader(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
STSSeconds: 315360000,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("Strict-Transport-Security"), "max-age=315360000")
|
||||
}
|
||||
|
||||
func TestStsHeaderInDevMode(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
STSSeconds: 315360000,
|
||||
IsDevelopment: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("Strict-Transport-Security"), "")
|
||||
}
|
||||
|
||||
func TestStsHeaderWithSubdomain(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
STSSeconds: 315360000,
|
||||
STSIncludeSubdomains: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("Strict-Transport-Security"), "max-age=315360000; includeSubdomains")
|
||||
}
|
||||
|
||||
func TestFrameDeny(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
FrameDeny: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("X-Frame-Options"), "DENY")
|
||||
}
|
||||
|
||||
func TestCustomFrameValue(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
CustomFrameOptionsValue: "SAMEORIGIN",
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("X-Frame-Options"), "SAMEORIGIN")
|
||||
}
|
||||
|
||||
func TestCustomFrameValueWithDeny(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
FrameDeny: true,
|
||||
CustomFrameOptionsValue: "SAMEORIGIN",
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("X-Frame-Options"), "SAMEORIGIN")
|
||||
}
|
||||
|
||||
func TestContentNosniff(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
ContentTypeNosniff: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("X-Content-Type-Options"), "nosniff")
|
||||
}
|
||||
|
||||
func TestXSSProtection(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
BrowserXssFilter: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("X-XSS-Protection"), "1; mode=block")
|
||||
}
|
||||
|
||||
func TestCsp(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
ContentSecurityPolicy: "default-src 'self'",
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("Content-Security-Policy"), "default-src 'self'")
|
||||
}
|
||||
|
||||
func TestInlineSecure(t *testing.T) {
|
||||
s := newServer(Options{
|
||||
FrameDeny: true,
|
||||
})
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foo", nil)
|
||||
|
||||
s.ServeHTTP(res, req)
|
||||
|
||||
expect(t, res.Code, http.StatusOK)
|
||||
expect(t, res.Header().Get("X-Frame-Options"), "DENY")
|
||||
}
|
||||
|
||||
/* Test Helpers */
|
||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Errorf("Expected [%v] (type %v) - Got [%v] (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/gin-gonic/contrib/sentry/README.md
generated
vendored
Normal file
29
vendor/github.com/gin-gonic/contrib/sentry/README.md
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# sentry
|
||||
|
||||
Middleware to integrate with [sentry](https://getsentry.com/) crash reporting. Middleware version of `raven.RecoveryHandler()`.
|
||||
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2017-01-13. Please use [gin-contrib/sentry](https://github.com/gin-contrib/sentry) instead.**
|
||||
|
||||
## Example
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/gin-gonic/contrib/sentry"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
raven.SetDSN("https://<key>:<secret>@app.getsentry.com/<project>")
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
r.Use(sentry.Recovery(raven.DefaultClient, false))
|
||||
// ...
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
44
vendor/github.com/gin-gonic/contrib/sentry/recovery.go
generated
vendored
Normal file
44
vendor/github.com/gin-gonic/contrib/sentry/recovery.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/getsentry/raven-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Recovery(client *raven.Client, onlyCrashes bool) gin.HandlerFunc {
|
||||
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
flags := map[string]string{
|
||||
"endpoint": c.Request.RequestURI,
|
||||
}
|
||||
if rval := recover(); rval != nil {
|
||||
debug.PrintStack()
|
||||
rvalStr := fmt.Sprint(rval)
|
||||
packet := raven.NewPacket(rvalStr,
|
||||
raven.NewException(errors.New(rvalStr), raven.NewStacktrace(2, 3, nil)),
|
||||
raven.NewHttp(c.Request))
|
||||
client.Capture(packet, flags)
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
if !onlyCrashes {
|
||||
for _, item := range c.Errors {
|
||||
packet := raven.NewPacket(item.Error(),
|
||||
&raven.Message{
|
||||
Message: item.Error(),
|
||||
Params: []interface{}{item.Meta},
|
||||
},
|
||||
raven.NewHttp(c.Request))
|
||||
client.Capture(packet, flags)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
18
vendor/github.com/gin-gonic/contrib/sessions/Godeps/Godeps.json
generated
vendored
Normal file
18
vendor/github.com/gin-gonic/contrib/sessions/Godeps/Godeps.json
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/contrib/sessions",
|
||||
"GoVersion": "go1.3",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/boj/redistore",
|
||||
"Rev": "0cd55584b172cefd31c4cb84e2904585d898c7b2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gin-gonic/gin",
|
||||
"Rev": "ac0ad2fed865d40a0adc1ac3ccaadc3acff5db4b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/sessions",
|
||||
"Rev": "f61c3ec2cf65d69e7efedfd4d060fe128882c951"
|
||||
}
|
||||
]
|
||||
}
|
||||
75
vendor/github.com/gin-gonic/contrib/sessions/README.md
generated
vendored
Normal file
75
vendor/github.com/gin-gonic/contrib/sessions/README.md
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
# sessions
|
||||
|
||||
Gin middleware for session management with multi-backend support (currently cookie, Redis).
|
||||
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2016-12-07. Please use [gin-contrib/sessions](https://github.com/gin-contrib/sessions) instead.**
|
||||
|
||||
## Examples
|
||||
|
||||
#### cookie-based
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
store := sessions.NewCookieStore([]byte("secret"))
|
||||
r.Use(sessions.Sessions("mysession", store))
|
||||
|
||||
r.GET("/incr", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
var count int
|
||||
v := session.Get("count")
|
||||
if v == nil {
|
||||
count = 0
|
||||
} else {
|
||||
count = v.(int)
|
||||
count += 1
|
||||
}
|
||||
session.Set("count", count)
|
||||
session.Save()
|
||||
c.JSON(200, gin.H{"count": count})
|
||||
})
|
||||
r.Run(":8000")
|
||||
}
|
||||
```
|
||||
|
||||
#### Redis
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
store, _ := sessions.NewRedisStore(10, "tcp", "localhost:6379", "", []byte("secret"))
|
||||
r.Use(sessions.Sessions("session", store))
|
||||
|
||||
r.GET("/incr", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
var count int
|
||||
v := session.Get("count")
|
||||
if v == nil {
|
||||
count = 0
|
||||
} else {
|
||||
count = v.(int)
|
||||
count += 1
|
||||
}
|
||||
session.Set("count", count)
|
||||
session.Save()
|
||||
c.JSON(200, gin.H{"count": count})
|
||||
})
|
||||
r.Run(":8000")
|
||||
}
|
||||
```
|
||||
36
vendor/github.com/gin-gonic/contrib/sessions/cookie.go
generated
vendored
Normal file
36
vendor/github.com/gin-gonic/contrib/sessions/cookie.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type CookieStore interface {
|
||||
Store
|
||||
}
|
||||
|
||||
// Keys are defined in pairs to allow key rotation, but the common case is to set a single
|
||||
// authentication key and optionally an encryption key.
|
||||
//
|
||||
// The first key in a pair is used for authentication and the second for encryption. The
|
||||
// encryption key can be set to nil or omitted in the last pair, but the authentication key
|
||||
// is required in all pairs.
|
||||
//
|
||||
// It is recommended to use an authentication key with 32 or 64 bytes. The encryption key,
|
||||
// if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes.
|
||||
func NewCookieStore(keyPairs ...[]byte) CookieStore {
|
||||
return &cookieStore{sessions.NewCookieStore(keyPairs...)}
|
||||
}
|
||||
|
||||
type cookieStore struct {
|
||||
*sessions.CookieStore
|
||||
}
|
||||
|
||||
func (c *cookieStore) Options(options Options) {
|
||||
c.CookieStore.Options = &sessions.Options{
|
||||
Path: options.Path,
|
||||
Domain: options.Domain,
|
||||
MaxAge: options.MaxAge,
|
||||
Secure: options.Secure,
|
||||
HttpOnly: options.HttpOnly,
|
||||
}
|
||||
}
|
||||
30
vendor/github.com/gin-gonic/contrib/sessions/cookie_test.go
generated
vendored
Normal file
30
vendor/github.com/gin-gonic/contrib/sessions/cookie_test.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var newCookieStore = func(_ *testing.T) Store {
|
||||
store := NewCookieStore([]byte("secret"))
|
||||
return store
|
||||
}
|
||||
|
||||
func TestCookie_SessionGetSet(t *testing.T) {
|
||||
sessionGetSet(t, newCookieStore)
|
||||
}
|
||||
|
||||
func TestCookie_SessionDeleteKey(t *testing.T) {
|
||||
sessionDeleteKey(t, newCookieStore)
|
||||
}
|
||||
|
||||
func TestCookie_SessionFlashes(t *testing.T) {
|
||||
sessionFlashes(t, newCookieStore)
|
||||
}
|
||||
|
||||
func TestCookie_SessionClear(t *testing.T) {
|
||||
sessionClear(t, newCookieStore)
|
||||
}
|
||||
|
||||
func TestCookie_SessionOptions(t *testing.T) {
|
||||
sessionOptions(t, newCookieStore)
|
||||
}
|
||||
28
vendor/github.com/gin-gonic/contrib/sessions/example_cookie/main.go
generated
vendored
Normal file
28
vendor/github.com/gin-gonic/contrib/sessions/example_cookie/main.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
store := sessions.NewCookieStore([]byte("secret"))
|
||||
r.Use(sessions.Sessions("mysession", store))
|
||||
|
||||
r.GET("/incr", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
var count int
|
||||
v := session.Get("count")
|
||||
if v == nil {
|
||||
count = 0
|
||||
} else {
|
||||
count = v.(int)
|
||||
count += 1
|
||||
}
|
||||
session.Set("count", count)
|
||||
session.Save()
|
||||
c.JSON(200, gin.H{"count": count})
|
||||
})
|
||||
r.Run(":8000")
|
||||
}
|
||||
28
vendor/github.com/gin-gonic/contrib/sessions/example_redis/main.go
generated
vendored
Normal file
28
vendor/github.com/gin-gonic/contrib/sessions/example_redis/main.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
store, _ := sessions.NewRedisStore(10, "tcp", "localhost:6379", "", []byte("secret"))
|
||||
r.Use(sessions.Sessions("mysession", store))
|
||||
|
||||
r.GET("/incr", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
var count int
|
||||
v := session.Get("count")
|
||||
if v == nil {
|
||||
count = 0
|
||||
} else {
|
||||
count = v.(int)
|
||||
count += 1
|
||||
}
|
||||
session.Set("count", count)
|
||||
session.Save()
|
||||
c.JSON(200, gin.H{"count": count})
|
||||
})
|
||||
r.Run(":8000")
|
||||
}
|
||||
45
vendor/github.com/gin-gonic/contrib/sessions/redis.go
generated
vendored
Normal file
45
vendor/github.com/gin-gonic/contrib/sessions/redis.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"github.com/boj/redistore"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type RedisStore interface {
|
||||
Store
|
||||
}
|
||||
|
||||
// size: maximum number of idle connections.
|
||||
// network: tcp or udp
|
||||
// address: host:port
|
||||
// password: redis-password
|
||||
// Keys are defined in pairs to allow key rotation, but the common case is to set a single
|
||||
// authentication key and optionally an encryption key.
|
||||
//
|
||||
// The first key in a pair is used for authentication and the second for encryption. The
|
||||
// encryption key can be set to nil or omitted in the last pair, but the authentication key
|
||||
// is required in all pairs.
|
||||
//
|
||||
// It is recommended to use an authentication key with 32 or 64 bytes. The encryption key,
|
||||
// if set, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes.
|
||||
func NewRedisStore(size int, network, address, password string, keyPairs ...[]byte) (RedisStore, error) {
|
||||
store, err := redistore.NewRediStore(size, network, address, password, keyPairs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &redisStore{store}, nil
|
||||
}
|
||||
|
||||
type redisStore struct {
|
||||
*redistore.RediStore
|
||||
}
|
||||
|
||||
func (c *redisStore) Options(options Options) {
|
||||
c.RediStore.Options = &sessions.Options{
|
||||
Path: options.Path,
|
||||
Domain: options.Domain,
|
||||
MaxAge: options.MaxAge,
|
||||
Secure: options.Secure,
|
||||
HttpOnly: options.HttpOnly,
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/gin-gonic/contrib/sessions/redis_test.go
generated
vendored
Normal file
35
vendor/github.com/gin-gonic/contrib/sessions/redis_test.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const redisTestServer = "localhost:6379"
|
||||
|
||||
var newRedisStore = func(_ *testing.T) Store {
|
||||
store, err := NewRedisStore(10, "tcp", redisTestServer, "", []byte("secret"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func TestRedis_SessionGetSet(t *testing.T) {
|
||||
sessionGetSet(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedis_SessionDeleteKey(t *testing.T) {
|
||||
sessionDeleteKey(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedis_SessionFlashes(t *testing.T) {
|
||||
sessionFlashes(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedis_SessionClear(t *testing.T) {
|
||||
sessionClear(t, newRedisStore)
|
||||
}
|
||||
|
||||
func TestRedis_SessionOptions(t *testing.T) {
|
||||
sessionOptions(t, newRedisStore)
|
||||
}
|
||||
147
vendor/github.com/gin-gonic/contrib/sessions/sessions.go
generated
vendored
Normal file
147
vendor/github.com/gin-gonic/contrib/sessions/sessions.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/context"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultKey = "github.com/gin-gonic/contrib/sessions"
|
||||
errorFormat = "[sessions] ERROR! %s\n"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
sessions.Store
|
||||
Options(Options)
|
||||
}
|
||||
|
||||
// Options stores configuration for a session or session store.
|
||||
// Fields are a subset of http.Cookie fields.
|
||||
type Options struct {
|
||||
Path string
|
||||
Domain string
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
|
||||
// MaxAge>0 means Max-Age attribute present and given in seconds.
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
}
|
||||
|
||||
// Wraps thinly gorilla-session methods.
|
||||
// Session stores the values and optional configuration for a session.
|
||||
type Session interface {
|
||||
// Get returns the session value associated to the given key.
|
||||
Get(key interface{}) interface{}
|
||||
// Set sets the session value associated to the given key.
|
||||
Set(key interface{}, val interface{})
|
||||
// Delete removes the session value associated to the given key.
|
||||
Delete(key interface{})
|
||||
// Clear deletes all values in the session.
|
||||
Clear()
|
||||
// AddFlash adds a flash message to the session.
|
||||
// A single variadic argument is accepted, and it is optional: it defines the flash key.
|
||||
// If not defined "_flash" is used by default.
|
||||
AddFlash(value interface{}, vars ...string)
|
||||
// Flashes returns a slice of flash messages from the session.
|
||||
// A single variadic argument is accepted, and it is optional: it defines the flash key.
|
||||
// If not defined "_flash" is used by default.
|
||||
Flashes(vars ...string) []interface{}
|
||||
// Options sets confuguration for a session.
|
||||
Options(Options)
|
||||
// Save saves all sessions used during the current request.
|
||||
Save() error
|
||||
}
|
||||
|
||||
func Sessions(name string, store Store) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
s := &session{name, c.Request, store, nil, false, c.Writer}
|
||||
c.Set(DefaultKey, s)
|
||||
defer context.Clear(c.Request)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
type session struct {
|
||||
name string
|
||||
request *http.Request
|
||||
store Store
|
||||
session *sessions.Session
|
||||
written bool
|
||||
writer http.ResponseWriter
|
||||
}
|
||||
|
||||
func (s *session) Get(key interface{}) interface{} {
|
||||
return s.Session().Values[key]
|
||||
}
|
||||
|
||||
func (s *session) Set(key interface{}, val interface{}) {
|
||||
s.Session().Values[key] = val
|
||||
s.written = true
|
||||
}
|
||||
|
||||
func (s *session) Delete(key interface{}) {
|
||||
delete(s.Session().Values, key)
|
||||
s.written = true
|
||||
}
|
||||
|
||||
func (s *session) Clear() {
|
||||
for key := range s.Session().Values {
|
||||
s.Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) AddFlash(value interface{}, vars ...string) {
|
||||
s.Session().AddFlash(value, vars...)
|
||||
s.written = true
|
||||
}
|
||||
|
||||
func (s *session) Flashes(vars ...string) []interface{} {
|
||||
s.written = true
|
||||
return s.Session().Flashes(vars...)
|
||||
}
|
||||
|
||||
func (s *session) Options(options Options) {
|
||||
s.Session().Options = &sessions.Options{
|
||||
Path: options.Path,
|
||||
Domain: options.Domain,
|
||||
MaxAge: options.MaxAge,
|
||||
Secure: options.Secure,
|
||||
HttpOnly: options.HttpOnly,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) Save() error {
|
||||
if s.Written() {
|
||||
e := s.Session().Save(s.request, s.writer)
|
||||
if e == nil {
|
||||
s.written = false
|
||||
}
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) Session() *sessions.Session {
|
||||
if s.session == nil {
|
||||
var err error
|
||||
s.session, err = s.store.Get(s.request, s.name)
|
||||
if err != nil {
|
||||
log.Printf(errorFormat, err)
|
||||
}
|
||||
}
|
||||
return s.session
|
||||
}
|
||||
|
||||
func (s *session) Written() bool {
|
||||
return s.written
|
||||
}
|
||||
|
||||
// shortcut to get session
|
||||
func Default(c *gin.Context) Session {
|
||||
return c.MustGet(DefaultKey).(Session)
|
||||
}
|
||||
219
vendor/github.com/gin-gonic/contrib/sessions/sessions_test.go
generated
vendored
Normal file
219
vendor/github.com/gin-gonic/contrib/sessions/sessions_test.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type storeFactory func(*testing.T) Store
|
||||
|
||||
const sessionName = "mysession"
|
||||
|
||||
const ok = "ok"
|
||||
|
||||
func sessionGetSet(t *testing.T, newStore storeFactory) {
|
||||
r := gin.Default()
|
||||
r.Use(Sessions(sessionName, newStore(t)))
|
||||
|
||||
r.GET("/set", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
session.Set("key", ok)
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
r.GET("/get", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
if session.Get("key") != ok {
|
||||
t.Error("Session writing failed")
|
||||
}
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
res1 := httptest.NewRecorder()
|
||||
req1, _ := http.NewRequest("GET", "/set", nil)
|
||||
r.ServeHTTP(res1, req1)
|
||||
|
||||
res2 := httptest.NewRecorder()
|
||||
req2, _ := http.NewRequest("GET", "/get", nil)
|
||||
req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie"))
|
||||
r.ServeHTTP(res2, req2)
|
||||
}
|
||||
|
||||
func sessionDeleteKey(t *testing.T, newStore storeFactory) {
|
||||
r := gin.Default()
|
||||
r.Use(Sessions(sessionName, newStore(t)))
|
||||
|
||||
r.GET("/set", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
session.Set("key", ok)
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
r.GET("/delete", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
session.Delete("key")
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
r.GET("/get", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
if session.Get("key") != nil {
|
||||
t.Error("Session deleting failed")
|
||||
}
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
res1 := httptest.NewRecorder()
|
||||
req1, _ := http.NewRequest("GET", "/set", nil)
|
||||
r.ServeHTTP(res1, req1)
|
||||
|
||||
res2 := httptest.NewRecorder()
|
||||
req2, _ := http.NewRequest("GET", "/delete", nil)
|
||||
req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie"))
|
||||
r.ServeHTTP(res2, req2)
|
||||
|
||||
res3 := httptest.NewRecorder()
|
||||
req3, _ := http.NewRequest("GET", "/get", nil)
|
||||
req3.Header.Set("Cookie", res2.Header().Get("Set-Cookie"))
|
||||
r.ServeHTTP(res3, req3)
|
||||
}
|
||||
|
||||
func sessionFlashes(t *testing.T, newStore storeFactory) {
|
||||
r := gin.Default()
|
||||
store := newStore(t)
|
||||
store.Options(Options{
|
||||
Domain: "localhost",
|
||||
})
|
||||
r.Use(Sessions(sessionName, store))
|
||||
|
||||
r.GET("/set", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
session.AddFlash(ok)
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
r.GET("/flash", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
l := len(session.Flashes())
|
||||
if l != 1 {
|
||||
t.Error("Flashes count does not equal 1. Equals ", l)
|
||||
}
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
r.GET("/check", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
l := len(session.Flashes())
|
||||
if l != 0 {
|
||||
t.Error("flashes count is not 0 after reading. Equals ", l)
|
||||
}
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
res1 := httptest.NewRecorder()
|
||||
req1, _ := http.NewRequest("GET", "/set", nil)
|
||||
r.ServeHTTP(res1, req1)
|
||||
|
||||
res2 := httptest.NewRecorder()
|
||||
req2, _ := http.NewRequest("GET", "/flash", nil)
|
||||
req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie"))
|
||||
r.ServeHTTP(res2, req2)
|
||||
|
||||
res3 := httptest.NewRecorder()
|
||||
req3, _ := http.NewRequest("GET", "/check", nil)
|
||||
req3.Header.Set("Cookie", res2.Header().Get("Set-Cookie"))
|
||||
r.ServeHTTP(res3, req3)
|
||||
}
|
||||
|
||||
func sessionClear(t *testing.T, newStore storeFactory) {
|
||||
data := map[string]string{
|
||||
"key": "val",
|
||||
"foo": "bar",
|
||||
}
|
||||
r := gin.Default()
|
||||
store := newStore(t)
|
||||
r.Use(Sessions(sessionName, store))
|
||||
|
||||
r.GET("/set", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
for k, v := range data {
|
||||
session.Set(k, v)
|
||||
}
|
||||
session.Clear()
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
r.GET("/check", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
for k, v := range data {
|
||||
if session.Get(k) == v {
|
||||
t.Fatal("Session clear failed")
|
||||
}
|
||||
}
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
|
||||
res1 := httptest.NewRecorder()
|
||||
req1, _ := http.NewRequest("GET", "/set", nil)
|
||||
r.ServeHTTP(res1, req1)
|
||||
|
||||
res2 := httptest.NewRecorder()
|
||||
req2, _ := http.NewRequest("GET", "/check", nil)
|
||||
req2.Header.Set("Cookie", res1.Header().Get("Set-Cookie"))
|
||||
r.ServeHTTP(res2, req2)
|
||||
}
|
||||
|
||||
func sessionOptions(t *testing.T, newStore storeFactory) {
|
||||
r := gin.Default()
|
||||
store := newStore(t)
|
||||
store.Options(Options{
|
||||
Domain: "localhost",
|
||||
})
|
||||
r.Use(Sessions(sessionName, store))
|
||||
|
||||
r.GET("/domain", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
session.Set("key", ok)
|
||||
session.Options(Options{
|
||||
Path: "/foo/bar/bat",
|
||||
})
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
r.GET("/path", func(c *gin.Context) {
|
||||
session := Default(c)
|
||||
session.Set("key", ok)
|
||||
session.Save()
|
||||
c.String(200, ok)
|
||||
})
|
||||
res1 := httptest.NewRecorder()
|
||||
req1, _ := http.NewRequest("GET", "/domain", nil)
|
||||
r.ServeHTTP(res1, req1)
|
||||
|
||||
res2 := httptest.NewRecorder()
|
||||
req2, _ := http.NewRequest("GET", "/path", nil)
|
||||
r.ServeHTTP(res2, req2)
|
||||
|
||||
s := strings.Split(res1.Header().Get("Set-Cookie"), ";")
|
||||
if s[1] != " Path=/foo/bar/bat" {
|
||||
t.Error("Error writing path with options:", s[1])
|
||||
}
|
||||
|
||||
s = strings.Split(res2.Header().Get("Set-Cookie"), ";")
|
||||
if s[1] != " Domain=localhost" {
|
||||
t.Error("Error writing domain with options:", s[1])
|
||||
}
|
||||
}
|
||||
5
vendor/github.com/gin-gonic/contrib/static/README.md
generated
vendored
Normal file
5
vendor/github.com/gin-gonic/contrib/static/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# static
|
||||
|
||||
## EOL-warning
|
||||
|
||||
**This package has been abandoned on 2016-12-13. Please use [gin-contrib/static](https://github.com/gin-contrib/static) instead.**
|
||||
121
vendor/github.com/gin-gonic/contrib/static/example/bindata/bindata.go
generated
vendored
Normal file
121
vendor/github.com/gin-gonic/contrib/static/example/bindata/bindata.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func bindata_read(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func data_index_html() ([]byte, error) {
|
||||
return bindata_read([]byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0x44, 0xce,
|
||||
0xbd, 0xae, 0xc2, 0x30, 0x0c, 0x05, 0xe0, 0xbd, 0x4f, 0xe1, 0x9b, 0xbd,
|
||||
0xaa, 0xba, 0xdd, 0x21, 0xcd, 0xc2, 0xef, 0x06, 0x43, 0x19, 0x18, 0x5d,
|
||||
0x62, 0x35, 0x41, 0x4e, 0x22, 0x15, 0x4b, 0x88, 0xb7, 0x27, 0x21, 0x45,
|
||||
0x4c, 0x39, 0xb1, 0xf5, 0x1d, 0x59, 0xff, 0x6d, 0x4f, 0x9b, 0xf1, 0x7a,
|
||||
0xde, 0x81, 0x93, 0xc0, 0xa6, 0xd1, 0xe5, 0x01, 0xc6, 0x38, 0x0f, 0xea,
|
||||
0x8e, 0xca, 0x34, 0x00, 0xda, 0x11, 0xda, 0x12, 0x72, 0x0c, 0x24, 0x08,
|
||||
0x37, 0x87, 0xcb, 0x83, 0x64, 0x50, 0x97, 0x71, 0xdf, 0xfe, 0x2b, 0xe8,
|
||||
0xd6, 0xa5, 0x78, 0x61, 0x32, 0x73, 0x6a, 0x27, 0x1f, 0x2d, 0x0a, 0xea,
|
||||
0xae, 0x4e, 0x4a, 0x47, 0xf7, 0x2d, 0xd1, 0x53, 0xb2, 0xaf, 0x15, 0xb8,
|
||||
0xde, 0x1c, 0x89, 0x39, 0xc1, 0xc1, 0x47, 0xf8, 0x39, 0x08, 0xde, 0x5a,
|
||||
0xa6, 0x27, 0x2e, 0x94, 0x5d, 0x5f, 0x7d, 0x65, 0xf9, 0xff, 0x39, 0xf3,
|
||||
0x1d, 0x00, 0x00, 0xff, 0xff, 0x51, 0x69, 0x85, 0x27, 0xb7, 0x00, 0x00,
|
||||
0x00,
|
||||
},
|
||||
"data/index.html",
|
||||
)
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
return f()
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() ([]byte, error){
|
||||
"data/index.html": data_index_html,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
node := _bintree
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for name := range node.Children {
|
||||
rv = append(rv, name)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type _bintree_t struct {
|
||||
Func func() ([]byte, error)
|
||||
Children map[string]*_bintree_t
|
||||
}
|
||||
|
||||
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
|
||||
"data": &_bintree_t{nil, map[string]*_bintree_t{
|
||||
"index.html": &_bintree_t{data_index_html, map[string]*_bintree_t{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// AssetInfo returns file info of given path
|
||||
func AssetInfo(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
10
vendor/github.com/gin-gonic/contrib/static/example/bindata/data/index.html
generated
vendored
Normal file
10
vendor/github.com/gin-gonic/contrib/static/example/bindata/data/index.html
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>go-bindata</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello Gin go-bindata middleware</h1>
|
||||
</body>
|
||||
</html>
|
||||
51
vendor/github.com/gin-gonic/contrib/static/example/bindata/example.go
generated
vendored
Normal file
51
vendor/github.com/gin-gonic/contrib/static/example/bindata/example.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
"github.com/gin-gonic/contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type binaryFileSystem struct {
|
||||
fs http.FileSystem
|
||||
}
|
||||
|
||||
func (b *binaryFileSystem) Open(name string) (http.File, error) {
|
||||
return b.fs.Open(name)
|
||||
}
|
||||
|
||||
func (b *binaryFileSystem) Exists(prefix string, filepath string) bool {
|
||||
|
||||
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
|
||||
if _, err := b.fs.Open(p); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func BinaryFileSystem(root string) *binaryFileSystem {
|
||||
fs := &assetfs.AssetFS{Asset, AssetDir, AssetInfo, root}
|
||||
return &binaryFileSystem{
|
||||
fs,
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
// $ go-bindata data/
|
||||
// $ go build && ./bindata
|
||||
//
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(static.Serve("/static", BinaryFileSystem("data")))
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "test")
|
||||
})
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
22
vendor/github.com/gin-gonic/contrib/static/example/simple/example.go
generated
vendored
Normal file
22
vendor/github.com/gin-gonic/contrib/static/example/simple/example.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
// if Allow DirectoryIndex
|
||||
//r.Use(static.Serve("/", static.LocalFile("/tmp", true)))
|
||||
// set prefix
|
||||
//r.Use(static.Serve("/static", static.LocalFile("/tmp", true)))
|
||||
|
||||
r.Use(static.Serve("/", static.LocalFile("/tmp", false)))
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "test")
|
||||
})
|
||||
// Listen and Server in 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
62
vendor/github.com/gin-gonic/contrib/static/static.go
generated
vendored
Normal file
62
vendor/github.com/gin-gonic/contrib/static/static.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ServeFileSystem interface {
|
||||
http.FileSystem
|
||||
Exists(prefix string, path string) bool
|
||||
}
|
||||
|
||||
type localFileSystem struct {
|
||||
http.FileSystem
|
||||
root string
|
||||
indexes bool
|
||||
}
|
||||
|
||||
func LocalFile(root string, indexes bool) *localFileSystem {
|
||||
return &localFileSystem{
|
||||
FileSystem: gin.Dir(root, indexes),
|
||||
root: root,
|
||||
indexes: indexes,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *localFileSystem) Exists(prefix string, filepath string) bool {
|
||||
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
|
||||
name := path.Join(l.root, p)
|
||||
stats, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if !l.indexes && stats.IsDir() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
|
||||
return Serve(urlPrefix, LocalFile(root, false))
|
||||
}
|
||||
|
||||
// Static returns a middleware handler that serves static files in the given directory.
|
||||
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
|
||||
fileserver := http.FileServer(fs)
|
||||
if urlPrefix != "" {
|
||||
fileserver = http.StripPrefix(urlPrefix, fileserver)
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
if fs.Exists(urlPrefix, c.Request.URL.Path) {
|
||||
fileserver.ServeHTTP(c.Writer, c.Request)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
86
vendor/github.com/gin-gonic/contrib/static/static_test.go
generated
vendored
Normal file
86
vendor/github.com/gin-gonic/contrib/static/static_test.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder {
|
||||
req, _ := http.NewRequest(method, path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
func TestEmptyDirectory(t *testing.T) {
|
||||
// SETUP file
|
||||
testRoot, _ := os.Getwd()
|
||||
f, err := ioutil.TempFile(testRoot, "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
f.WriteString("Gin Web Framework")
|
||||
f.Close()
|
||||
|
||||
dir, filename := filepath.Split(f.Name())
|
||||
|
||||
router := gin.New()
|
||||
router.Use(ServeRoot("/", dir))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.String(200, "index")
|
||||
})
|
||||
router.GET("/a", func(c *gin.Context) {
|
||||
c.String(200, "a")
|
||||
})
|
||||
router.GET("/"+filename, func(c *gin.Context) {
|
||||
c.String(200, "this is not printed")
|
||||
})
|
||||
w := performRequest(router, "GET", "/")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Body.String(), "index")
|
||||
|
||||
w = performRequest(router, "GET", "/"+filename)
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Body.String(), "Gin Web Framework")
|
||||
|
||||
w = performRequest(router, "GET", "/"+filename+"a")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
|
||||
w = performRequest(router, "GET", "/a")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Body.String(), "a")
|
||||
|
||||
router2 := gin.New()
|
||||
router2.Use(ServeRoot("/static", dir))
|
||||
router2.GET("/"+filename, func(c *gin.Context) {
|
||||
c.String(200, "this is printed")
|
||||
})
|
||||
|
||||
w = performRequest(router2, "GET", "/")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
|
||||
w = performRequest(router2, "GET", "/static")
|
||||
assert.Equal(t, w.Code, 404)
|
||||
router2.GET("/static", func(c *gin.Context) {
|
||||
c.String(200, "index")
|
||||
})
|
||||
|
||||
w = performRequest(router2, "GET", "/static")
|
||||
assert.Equal(t, w.Code, 200)
|
||||
|
||||
w = performRequest(router2, "GET", "/"+filename)
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Body.String(), "this is printed")
|
||||
|
||||
w = performRequest(router2, "GET", "/static/"+filename)
|
||||
assert.Equal(t, w.Code, 200)
|
||||
assert.Equal(t, w.Body.String(), "Gin Web Framework")
|
||||
}
|
||||
4
vendor/github.com/gin-gonic/gin/.gitignore
generated
vendored
Normal file
4
vendor/github.com/gin-gonic/gin/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
Godeps/*
|
||||
!Godeps/Godeps.json
|
||||
coverage.out
|
||||
count.out
|
||||
22
vendor/github.com/gin-gonic/gin/.travis.yml
generated
vendored
Normal file
22
vendor/github.com/gin-gonic/gin/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5.4
|
||||
- 1.6.4
|
||||
- 1.7.4
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v -covermode=count -coverprofile=coverage.out
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user