mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
Now vendored, with Bolt v1.2.0
Former-commit-id: 900e1a398fd82aa1cea4f319e89b8088dd81cf6c [formerly f172c22b12c49c0291e0d986dc4af94fcc91d192] [formerly 43c5da81442a5f0ca79a6eabb1dfdfbfb3f22680 [formerly 2694d0b183
]]
Former-commit-id: fa39d6a984adc4ca8f8c82c5df145c336885a53f [formerly 94543f8081bc18b1a39daf8500cfa7e0b1ba7393]
Former-commit-id: 809aae62e28a3f99a01854f71fcd5a85f89d2972
This commit is contained in:
parent
ad4ed37898
commit
e8a4d30139
160
Godeps/Godeps.json
generated
Normal file
160
Godeps/Godeps.json
generated
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/schollz/cowyo",
|
||||||
|
"GoVersion": "go1.7",
|
||||||
|
"GodepVersion": "v78",
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/boj/redistore",
|
||||||
|
"Comment": "v1.2",
|
||||||
|
"Rev": "fc113767cd6b051980f260d6dbe84b2740c46ab0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/boltdb/bolt",
|
||||||
|
"Comment": "v1.2.0",
|
||||||
|
"Rev": "c6ba97b89e0454fec9aa92e1d33a4e2c5fc1f631"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/garyburd/redigo/internal",
|
||||||
|
"Comment": "v1.0.0-17-g908534c",
|
||||||
|
"Rev": "908534c8b97586a4597e3fa195875d2d26502b97"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/garyburd/redigo/redis",
|
||||||
|
"Comment": "v1.0.0-17-g908534c",
|
||||||
|
"Rev": "908534c8b97586a4597e3fa195875d2d26502b97"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gin-gonic/contrib/sessions",
|
||||||
|
"Rev": "4d2dccc9a4541014fec054e483cc76609b97fb16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gin-gonic/gin",
|
||||||
|
"Comment": "v1.1.4",
|
||||||
|
"Rev": "e2212d40c62a98b388a5eb48ecbdcf88534688ba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gin-gonic/gin/binding",
|
||||||
|
"Comment": "v1.1.4",
|
||||||
|
"Rev": "e2212d40c62a98b388a5eb48ecbdcf88534688ba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gin-gonic/gin/render",
|
||||||
|
"Comment": "v1.1.4",
|
||||||
|
"Rev": "e2212d40c62a98b388a5eb48ecbdcf88534688ba"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/protobuf/proto",
|
||||||
|
"Rev": "8ee79997227bf9b34611aee7946ae64735e6fd93"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/context",
|
||||||
|
"Comment": "v1.1-7-g08b5f42",
|
||||||
|
"Rev": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/securecookie",
|
||||||
|
"Comment": "v1.1-5-gfa5329f",
|
||||||
|
"Rev": "fa5329f913702981df43dcb2a380bac429c810b5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/sessions",
|
||||||
|
"Comment": "v1.1-2-g83c8db3",
|
||||||
|
"Rev": "83c8db3bdc9be789e57e3756ffbcffd2d7d40176"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/websocket",
|
||||||
|
"Comment": "v1.1.0-19-gc36f2fe",
|
||||||
|
"Rev": "c36f2fe5c330f0ac404b616b96c438b8616b1aaf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/jcelliott/lumber",
|
||||||
|
"Rev": "dd349441af25132d146d7095c6693a15431fc9b1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/manucorporat/sse",
|
||||||
|
"Rev": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/mattn/go-isatty",
|
||||||
|
"Rev": "30a891c33c7cde7b02a981314b4228ec99380cca"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/microcosm-cc/bluemonday",
|
||||||
|
"Rev": "e79763773ab6222ca1d5a7cbd9d62d83c1f77081"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/russross/blackfriday",
|
||||||
|
"Comment": "v1.4-40-g5f33e7b",
|
||||||
|
"Rev": "5f33e7b7878355cd2b7e6b8eefc48a5472c69f70"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/sergi/go-diff/diffmatchpatch",
|
||||||
|
"Rev": "24e2351369ec4949b2ed0dc5c477afdd4c4034e8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/shurcooL/sanitized_anchor_name",
|
||||||
|
"Rev": "1dba4b3954bc059efc3991ec364f9f9a35f597d2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/bcrypt",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/cast5",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/openpgp",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/openpgp/armor",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/openpgp/elgamal",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/openpgp/errors",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/openpgp/packet",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/openpgp/s2k",
|
||||||
|
"Rev": "2f8be38b9a7533b8763d48273737ff6e90428a96"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/context",
|
||||||
|
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/html",
|
||||||
|
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/net/html/atom",
|
||||||
|
"Rev": "f2499483f923065a842d38eb4c7f1927e6fc6e6d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/sys/unix",
|
||||||
|
"Rev": "d75a52659825e75fff6158388dddc6a5b04f9ba5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/go-playground/validator.v8",
|
||||||
|
"Comment": "v8.18.1",
|
||||||
|
"Rev": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/yaml.v2",
|
||||||
|
"Rev": "4c78c975fe7c825c6d1466c42be594d1d6f3aba6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
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
|
||||||
|
|
||||||
|
[![Build Status](https://drone.io/github.com/boj/redistore/status.png)](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
|
||||||
|
}
|
4
vendor/github.com/boltdb/bolt/.gitignore
generated
vendored
Normal file
4
vendor/github.com/boltdb/bolt/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.prof
|
||||||
|
*.test
|
||||||
|
*.swp
|
||||||
|
/bin/
|
20
vendor/github.com/boltdb/bolt/LICENSE
generated
vendored
Normal file
20
vendor/github.com/boltdb/bolt/LICENSE
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 Ben Johnson
|
||||||
|
|
||||||
|
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.
|
18
vendor/github.com/boltdb/bolt/Makefile
generated
vendored
Normal file
18
vendor/github.com/boltdb/bolt/Makefile
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||||
|
COMMIT=`git rev-parse --short HEAD`
|
||||||
|
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
||||||
|
|
||||||
|
default: build
|
||||||
|
|
||||||
|
race:
|
||||||
|
@go test -v -race -test.run="TestSimulate_(100op|1000op)"
|
||||||
|
|
||||||
|
# go get github.com/kisielk/errcheck
|
||||||
|
errcheck:
|
||||||
|
@errcheck -ignorepkg=bytes -ignore=os:Remove github.com/boltdb/bolt
|
||||||
|
|
||||||
|
test:
|
||||||
|
@go test -v -cover .
|
||||||
|
@go test -v ./cmd/bolt
|
||||||
|
|
||||||
|
.PHONY: fmt test
|
844
vendor/github.com/boltdb/bolt/README.md
generated
vendored
Normal file
844
vendor/github.com/boltdb/bolt/README.md
generated
vendored
Normal file
@ -0,0 +1,844 @@
|
|||||||
|
Bolt [![Build Status](https://drone.io/github.com/boltdb/bolt/status.png)](https://drone.io/github.com/boltdb/bolt/latest) [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg)
|
||||||
|
====
|
||||||
|
|
||||||
|
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
|
||||||
|
[LMDB project][lmdb]. The goal of the project is to provide a simple,
|
||||||
|
fast, and reliable database for projects that don't require a full database
|
||||||
|
server such as Postgres or MySQL.
|
||||||
|
|
||||||
|
Since Bolt is meant to be used as such a low-level piece of functionality,
|
||||||
|
simplicity is key. The API will be small and only focus on getting values
|
||||||
|
and setting values. That's it.
|
||||||
|
|
||||||
|
[hyc_symas]: https://twitter.com/hyc_symas
|
||||||
|
[lmdb]: http://symas.com/mdb/
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
Bolt is stable and the API is fixed. Full unit test coverage and randomized
|
||||||
|
black box testing are used to ensure database consistency and thread safety.
|
||||||
|
Bolt is currently in high-load production environments serving databases as
|
||||||
|
large as 1TB. Many companies such as Shopify and Heroku use Bolt-backed
|
||||||
|
services every day.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Installing](#installing)
|
||||||
|
- [Opening a database](#opening-a-database)
|
||||||
|
- [Transactions](#transactions)
|
||||||
|
- [Read-write transactions](#read-write-transactions)
|
||||||
|
- [Read-only transactions](#read-only-transactions)
|
||||||
|
- [Batch read-write transactions](#batch-read-write-transactions)
|
||||||
|
- [Managing transactions manually](#managing-transactions-manually)
|
||||||
|
- [Using buckets](#using-buckets)
|
||||||
|
- [Using key/value pairs](#using-keyvalue-pairs)
|
||||||
|
- [Autoincrementing integer for the bucket](#autoincrementing-integer-for-the-bucket)
|
||||||
|
- [Iterating over keys](#iterating-over-keys)
|
||||||
|
- [Prefix scans](#prefix-scans)
|
||||||
|
- [Range scans](#range-scans)
|
||||||
|
- [ForEach()](#foreach)
|
||||||
|
- [Nested buckets](#nested-buckets)
|
||||||
|
- [Database backups](#database-backups)
|
||||||
|
- [Statistics](#statistics)
|
||||||
|
- [Read-Only Mode](#read-only-mode)
|
||||||
|
- [Mobile Use (iOS/Android)](#mobile-use-iosandroid)
|
||||||
|
- [Resources](#resources)
|
||||||
|
- [Comparison with other databases](#comparison-with-other-databases)
|
||||||
|
- [Postgres, MySQL, & other relational databases](#postgres-mysql--other-relational-databases)
|
||||||
|
- [LevelDB, RocksDB](#leveldb-rocksdb)
|
||||||
|
- [LMDB](#lmdb)
|
||||||
|
- [Caveats & Limitations](#caveats--limitations)
|
||||||
|
- [Reading the Source](#reading-the-source)
|
||||||
|
- [Other Projects Using Bolt](#other-projects-using-bolt)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Installing
|
||||||
|
|
||||||
|
To start using Bolt, install Go and run `go get`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ go get github.com/boltdb/bolt/...
|
||||||
|
```
|
||||||
|
|
||||||
|
This will retrieve the library and install the `bolt` command line utility into
|
||||||
|
your `$GOBIN` path.
|
||||||
|
|
||||||
|
|
||||||
|
### Opening a database
|
||||||
|
|
||||||
|
The top-level object in Bolt is a `DB`. It is represented as a single file on
|
||||||
|
your disk and represents a consistent snapshot of your data.
|
||||||
|
|
||||||
|
To open your database, simply use the `bolt.Open()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Open the my.db data file in your current directory.
|
||||||
|
// It will be created if it doesn't exist.
|
||||||
|
db, err := bolt.Open("my.db", 0600, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that Bolt obtains a file lock on the data file so multiple processes
|
||||||
|
cannot open the same database at the same time. Opening an already open Bolt
|
||||||
|
database will cause it to hang until the other process closes it. To prevent
|
||||||
|
an indefinite wait you can pass a timeout option to the `Open()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Transactions
|
||||||
|
|
||||||
|
Bolt allows only one read-write transaction at a time but allows as many
|
||||||
|
read-only transactions as you want at a time. Each transaction has a consistent
|
||||||
|
view of the data as it existed when the transaction started.
|
||||||
|
|
||||||
|
Individual transactions and all objects created from them (e.g. buckets, keys)
|
||||||
|
are not thread safe. To work with data in multiple goroutines you must start
|
||||||
|
a transaction for each one or use locking to ensure only one goroutine accesses
|
||||||
|
a transaction at a time. Creating transaction from the `DB` is thread safe.
|
||||||
|
|
||||||
|
Read-only transactions and read-write transactions should not depend on one
|
||||||
|
another and generally shouldn't be opened simultaneously in the same goroutine.
|
||||||
|
This can cause a deadlock as the read-write transaction needs to periodically
|
||||||
|
re-map the data file but it cannot do so while a read-only transaction is open.
|
||||||
|
|
||||||
|
|
||||||
|
#### Read-write transactions
|
||||||
|
|
||||||
|
To start a read-write transaction, you can use the `DB.Update()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
...
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the closure, you have a consistent view of the database. You commit the
|
||||||
|
transaction by returning `nil` at the end. You can also rollback the transaction
|
||||||
|
at any point by returning an error. All database operations are allowed inside
|
||||||
|
a read-write transaction.
|
||||||
|
|
||||||
|
Always check the return error as it will report any disk failures that can cause
|
||||||
|
your transaction to not complete. If you return an error within your closure
|
||||||
|
it will be passed through.
|
||||||
|
|
||||||
|
|
||||||
|
#### Read-only transactions
|
||||||
|
|
||||||
|
To start a read-only transaction, you can use the `DB.View()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := db.View(func(tx *bolt.Tx) error {
|
||||||
|
...
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
You also get a consistent view of the database within this closure, however,
|
||||||
|
no mutating operations are allowed within a read-only transaction. You can only
|
||||||
|
retrieve buckets, retrieve values, and copy the database within a read-only
|
||||||
|
transaction.
|
||||||
|
|
||||||
|
|
||||||
|
#### Batch read-write transactions
|
||||||
|
|
||||||
|
Each `DB.Update()` waits for disk to commit the writes. This overhead
|
||||||
|
can be minimized by combining multiple updates with the `DB.Batch()`
|
||||||
|
function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := db.Batch(func(tx *bolt.Tx) error {
|
||||||
|
...
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Concurrent Batch calls are opportunistically combined into larger
|
||||||
|
transactions. Batch is only useful when there are multiple goroutines
|
||||||
|
calling it.
|
||||||
|
|
||||||
|
The trade-off is that `Batch` can call the given
|
||||||
|
function multiple times, if parts of the transaction fail. The
|
||||||
|
function must be idempotent and side effects must take effect only
|
||||||
|
after a successful return from `DB.Batch()`.
|
||||||
|
|
||||||
|
For example: don't display messages from inside the function, instead
|
||||||
|
set variables in the enclosing scope:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var id uint64
|
||||||
|
err := db.Batch(func(tx *bolt.Tx) error {
|
||||||
|
// Find last key in bucket, decode as bigendian uint64, increment
|
||||||
|
// by one, encode back to []byte, and add new key.
|
||||||
|
...
|
||||||
|
id = newValue
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return ...
|
||||||
|
}
|
||||||
|
fmt.Println("Allocated ID %d", id)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Managing transactions manually
|
||||||
|
|
||||||
|
The `DB.View()` and `DB.Update()` functions are wrappers around the `DB.Begin()`
|
||||||
|
function. These helper functions will start the transaction, execute a function,
|
||||||
|
and then safely close your transaction if an error is returned. This is the
|
||||||
|
recommended way to use Bolt transactions.
|
||||||
|
|
||||||
|
However, sometimes you may want to manually start and end your transactions.
|
||||||
|
You can use the `Tx.Begin()` function directly but **please** be sure to close
|
||||||
|
the transaction.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Start a writable transaction.
|
||||||
|
tx, err := db.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Use the transaction...
|
||||||
|
_, err := tx.CreateBucket([]byte("MyBucket"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the transaction and check for error.
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The first argument to `DB.Begin()` is a boolean stating if the transaction
|
||||||
|
should be writable.
|
||||||
|
|
||||||
|
|
||||||
|
### Using buckets
|
||||||
|
|
||||||
|
Buckets are collections of key/value pairs within the database. All keys in a
|
||||||
|
bucket must be unique. You can create a bucket using the `DB.CreateBucket()`
|
||||||
|
function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b, err := tx.CreateBucket([]byte("MyBucket"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create bucket: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also create a bucket only if it doesn't exist by using the
|
||||||
|
`Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this
|
||||||
|
function for all your top-level buckets after you open your database so you can
|
||||||
|
guarantee that they exist for future transactions.
|
||||||
|
|
||||||
|
To delete a bucket, simply call the `Tx.DeleteBucket()` function.
|
||||||
|
|
||||||
|
|
||||||
|
### Using key/value pairs
|
||||||
|
|
||||||
|
To save a key/value pair to a bucket, use the `Bucket.Put()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("MyBucket"))
|
||||||
|
err := b.Put([]byte("answer"), []byte("42"))
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This will set the value of the `"answer"` key to `"42"` in the `MyBucket`
|
||||||
|
bucket. To retrieve this value, we can use the `Bucket.Get()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("MyBucket"))
|
||||||
|
v := b.Get([]byte("answer"))
|
||||||
|
fmt.Printf("The answer is: %s\n", v)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Get()` function does not return an error because its operation is
|
||||||
|
guaranteed to work (unless there is some kind of system failure). If the key
|
||||||
|
exists then it will return its byte slice value. If it doesn't exist then it
|
||||||
|
will return `nil`. It's important to note that you can have a zero-length value
|
||||||
|
set to a key which is different than the key not existing.
|
||||||
|
|
||||||
|
Use the `Bucket.Delete()` function to delete a key from the bucket.
|
||||||
|
|
||||||
|
Please note that values returned from `Get()` are only valid while the
|
||||||
|
transaction is open. If you need to use a value outside of the transaction
|
||||||
|
then you must use `copy()` to copy it to another byte slice.
|
||||||
|
|
||||||
|
|
||||||
|
### Autoincrementing integer for the bucket
|
||||||
|
By using the `NextSequence()` function, you can let Bolt determine a sequence
|
||||||
|
which can be used as the unique identifier for your key/value pairs. See the
|
||||||
|
example below.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// CreateUser saves u to the store. The new user ID is set on u once the data is persisted.
|
||||||
|
func (s *Store) CreateUser(u *User) error {
|
||||||
|
return s.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
// Retrieve the users bucket.
|
||||||
|
// This should be created when the DB is first opened.
|
||||||
|
b := tx.Bucket([]byte("users"))
|
||||||
|
|
||||||
|
// Generate ID for the user.
|
||||||
|
// This returns an error only if the Tx is closed or not writeable.
|
||||||
|
// That can't happen in an Update() call so I ignore the error check.
|
||||||
|
id, _ = b.NextSequence()
|
||||||
|
u.ID = int(id)
|
||||||
|
|
||||||
|
// Marshal user data into bytes.
|
||||||
|
buf, err := json.Marshal(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist bytes to users bucket.
|
||||||
|
return b.Put(itob(u.ID), buf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// itob returns an 8-byte big endian representation of v.
|
||||||
|
func itob(v int) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, uint64(v))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iterating over keys
|
||||||
|
|
||||||
|
Bolt stores its keys in byte-sorted order within a bucket. This makes sequential
|
||||||
|
iteration over these keys extremely fast. To iterate over keys we'll use a
|
||||||
|
`Cursor`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
// Assume bucket exists and has keys
|
||||||
|
b := tx.Bucket([]byte("MyBucket"))
|
||||||
|
|
||||||
|
c := b.Cursor()
|
||||||
|
|
||||||
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
|
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The cursor allows you to move to a specific point in the list of keys and move
|
||||||
|
forward or backward through the keys one at a time.
|
||||||
|
|
||||||
|
The following functions are available on the cursor:
|
||||||
|
|
||||||
|
```
|
||||||
|
First() Move to the first key.
|
||||||
|
Last() Move to the last key.
|
||||||
|
Seek() Move to a specific key.
|
||||||
|
Next() Move to the next key.
|
||||||
|
Prev() Move to the previous key.
|
||||||
|
```
|
||||||
|
|
||||||
|
Each of those functions has a return signature of `(key []byte, value []byte)`.
|
||||||
|
When you have iterated to the end of the cursor then `Next()` will return a
|
||||||
|
`nil` key. You must seek to a position using `First()`, `Last()`, or `Seek()`
|
||||||
|
before calling `Next()` or `Prev()`. If you do not seek to a position then
|
||||||
|
these functions will return a `nil` key.
|
||||||
|
|
||||||
|
During iteration, if the key is non-`nil` but the value is `nil`, that means
|
||||||
|
the key refers to a bucket rather than a value. Use `Bucket.Bucket()` to
|
||||||
|
access the sub-bucket.
|
||||||
|
|
||||||
|
|
||||||
|
#### Prefix scans
|
||||||
|
|
||||||
|
To iterate over a key prefix, you can combine `Seek()` and `bytes.HasPrefix()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
// Assume bucket exists and has keys
|
||||||
|
c := tx.Bucket([]byte("MyBucket")).Cursor()
|
||||||
|
|
||||||
|
prefix := []byte("1234")
|
||||||
|
for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() {
|
||||||
|
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Range scans
|
||||||
|
|
||||||
|
Another common use case is scanning over a range such as a time range. If you
|
||||||
|
use a sortable time encoding such as RFC3339 then you can query a specific
|
||||||
|
date range like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
// Assume our events bucket exists and has RFC3339 encoded time keys.
|
||||||
|
c := tx.Bucket([]byte("Events")).Cursor()
|
||||||
|
|
||||||
|
// Our time range spans the 90's decade.
|
||||||
|
min := []byte("1990-01-01T00:00:00Z")
|
||||||
|
max := []byte("2000-01-01T00:00:00Z")
|
||||||
|
|
||||||
|
// Iterate over the 90's.
|
||||||
|
for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() {
|
||||||
|
fmt.Printf("%s: %s\n", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### ForEach()
|
||||||
|
|
||||||
|
You can also use the function `ForEach()` if you know you'll be iterating over
|
||||||
|
all the keys in a bucket:
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
// Assume bucket exists and has keys
|
||||||
|
b := tx.Bucket([]byte("MyBucket"))
|
||||||
|
|
||||||
|
b.ForEach(func(k, v []byte) error {
|
||||||
|
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Nested buckets
|
||||||
|
|
||||||
|
You can also store a bucket in a key to create nested buckets. The API is the
|
||||||
|
same as the bucket management API on the `DB` object:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (*Bucket) CreateBucket(key []byte) (*Bucket, error)
|
||||||
|
func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)
|
||||||
|
func (*Bucket) DeleteBucket(key []byte) error
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Database backups
|
||||||
|
|
||||||
|
Bolt is a single file so it's easy to backup. You can use the `Tx.WriteTo()`
|
||||||
|
function to write a consistent view of the database to a writer. If you call
|
||||||
|
this from a read-only transaction, it will perform a hot backup and not block
|
||||||
|
your other database reads and writes.
|
||||||
|
|
||||||
|
By default, it will use a regular file handle which will utilize the operating
|
||||||
|
system's page cache. See the [`Tx`](https://godoc.org/github.com/boltdb/bolt#Tx)
|
||||||
|
documentation for information about optimizing for larger-than-RAM datasets.
|
||||||
|
|
||||||
|
One common use case is to backup over HTTP so you can use tools like `cURL` to
|
||||||
|
do database backups:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func BackupHandleFunc(w http.ResponseWriter, req *http.Request) {
|
||||||
|
err := db.View(func(tx *bolt.Tx) error {
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
w.Header().Set("Content-Disposition", `attachment; filename="my.db"`)
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
|
||||||
|
_, err := tx.WriteTo(w)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can backup using this command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ curl http://localhost/backup > my.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can open your browser to `http://localhost/backup` and it will download
|
||||||
|
automatically.
|
||||||
|
|
||||||
|
If you want to backup to another file you can use the `Tx.CopyFile()` helper
|
||||||
|
function.
|
||||||
|
|
||||||
|
|
||||||
|
### Statistics
|
||||||
|
|
||||||
|
The database keeps a running count of many of the internal operations it
|
||||||
|
performs so you can better understand what's going on. By grabbing a snapshot
|
||||||
|
of these stats at two points in time we can see what operations were performed
|
||||||
|
in that time range.
|
||||||
|
|
||||||
|
For example, we could start a goroutine to log stats every 10 seconds:
|
||||||
|
|
||||||
|
```go
|
||||||
|
go func() {
|
||||||
|
// Grab the initial stats.
|
||||||
|
prev := db.Stats()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Wait for 10s.
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
|
// Grab the current stats and diff them.
|
||||||
|
stats := db.Stats()
|
||||||
|
diff := stats.Sub(&prev)
|
||||||
|
|
||||||
|
// Encode stats to JSON and print to STDERR.
|
||||||
|
json.NewEncoder(os.Stderr).Encode(diff)
|
||||||
|
|
||||||
|
// Save stats for the next loop.
|
||||||
|
prev = stats
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
```
|
||||||
|
|
||||||
|
It's also useful to pipe these stats to a service such as statsd for monitoring
|
||||||
|
or to provide an HTTP endpoint that will perform a fixed-length sample.
|
||||||
|
|
||||||
|
|
||||||
|
### Read-Only Mode
|
||||||
|
|
||||||
|
Sometimes it is useful to create a shared, read-only Bolt database. To this,
|
||||||
|
set the `Options.ReadOnly` flag when opening your database. Read-only mode
|
||||||
|
uses a shared lock to allow multiple processes to read from the database but
|
||||||
|
it will block any processes from opening the database in read-write mode.
|
||||||
|
|
||||||
|
```go
|
||||||
|
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mobile Use (iOS/Android)
|
||||||
|
|
||||||
|
Bolt is able to run on mobile devices by leveraging the binding feature of the
|
||||||
|
[gomobile](https://github.com/golang/mobile) tool. Create a struct that will
|
||||||
|
contain your database logic and a reference to a `*bolt.DB` with a initializing
|
||||||
|
contstructor that takes in a filepath where the database file will be stored.
|
||||||
|
Neither Android nor iOS require extra permissions or cleanup from using this method.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBoltDB(filepath string) *BoltDB {
|
||||||
|
db, err := bolt.Open(filepath+"/demo.db", 0600, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BoltDB{db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoltDB struct {
|
||||||
|
db *bolt.DB
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltDB) Path() string {
|
||||||
|
return b.db.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltDB) Close() {
|
||||||
|
b.db.Close()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Database logic should be defined as methods on this wrapper struct.
|
||||||
|
|
||||||
|
To initialize this struct from the native language (both platforms now sync
|
||||||
|
their local storage to the cloud. These snippets disable that functionality for the
|
||||||
|
database file):
|
||||||
|
|
||||||
|
#### Android
|
||||||
|
|
||||||
|
```java
|
||||||
|
String path;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){
|
||||||
|
path = getNoBackupFilesDir().getAbsolutePath();
|
||||||
|
} else{
|
||||||
|
path = getFilesDir().getAbsolutePath();
|
||||||
|
}
|
||||||
|
Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### iOS
|
||||||
|
|
||||||
|
```objc
|
||||||
|
- (void)demo {
|
||||||
|
NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
||||||
|
NSUserDomainMask,
|
||||||
|
YES) objectAtIndex:0];
|
||||||
|
GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path);
|
||||||
|
[self addSkipBackupAttributeToItemAtPath:demo.path];
|
||||||
|
//Some DB Logic would go here
|
||||||
|
[demo close];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
|
||||||
|
{
|
||||||
|
NSURL* URL= [NSURL fileURLWithPath: filePathString];
|
||||||
|
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
|
||||||
|
forKey: NSURLIsExcludedFromBackupKey error: &error];
|
||||||
|
if(!success){
|
||||||
|
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
For more information on getting started with Bolt, check out the following articles:
|
||||||
|
|
||||||
|
* [Intro to BoltDB: Painless Performant Persistence](http://npf.io/2014/07/intro-to-boltdb-painless-performant-persistence/) by [Nate Finch](https://github.com/natefinch).
|
||||||
|
* [Bolt -- an embedded key/value database for Go](https://www.progville.com/go/bolt-embedded-db-golang/) by Progville
|
||||||
|
|
||||||
|
|
||||||
|
## Comparison with other databases
|
||||||
|
|
||||||
|
### Postgres, MySQL, & other relational databases
|
||||||
|
|
||||||
|
Relational databases structure data into rows and are only accessible through
|
||||||
|
the use of SQL. This approach provides flexibility in how you store and query
|
||||||
|
your data but also incurs overhead in parsing and planning SQL statements. Bolt
|
||||||
|
accesses all data by a byte slice key. This makes Bolt fast to read and write
|
||||||
|
data by key but provides no built-in support for joining values together.
|
||||||
|
|
||||||
|
Most relational databases (with the exception of SQLite) are standalone servers
|
||||||
|
that run separately from your application. This gives your systems
|
||||||
|
flexibility to connect multiple application servers to a single database
|
||||||
|
server but also adds overhead in serializing and transporting data over the
|
||||||
|
network. Bolt runs as a library included in your application so all data access
|
||||||
|
has to go through your application's process. This brings data closer to your
|
||||||
|
application but limits multi-process access to the data.
|
||||||
|
|
||||||
|
|
||||||
|
### LevelDB, RocksDB
|
||||||
|
|
||||||
|
LevelDB and its derivatives (RocksDB, HyperLevelDB) are similar to Bolt in that
|
||||||
|
they are libraries bundled into the application, however, their underlying
|
||||||
|
structure is a log-structured merge-tree (LSM tree). An LSM tree optimizes
|
||||||
|
random writes by using a write ahead log and multi-tiered, sorted files called
|
||||||
|
SSTables. Bolt uses a B+tree internally and only a single file. Both approaches
|
||||||
|
have trade-offs.
|
||||||
|
|
||||||
|
If you require a high random write throughput (>10,000 w/sec) or you need to use
|
||||||
|
spinning disks then LevelDB could be a good choice. If your application is
|
||||||
|
read-heavy or does a lot of range scans then Bolt could be a good choice.
|
||||||
|
|
||||||
|
One other important consideration is that LevelDB does not have transactions.
|
||||||
|
It supports batch writing of key/values pairs and it supports read snapshots
|
||||||
|
but it will not give you the ability to do a compare-and-swap operation safely.
|
||||||
|
Bolt supports fully serializable ACID transactions.
|
||||||
|
|
||||||
|
|
||||||
|
### LMDB
|
||||||
|
|
||||||
|
Bolt was originally a port of LMDB so it is architecturally similar. Both use
|
||||||
|
a B+tree, have ACID semantics with fully serializable transactions, and support
|
||||||
|
lock-free MVCC using a single writer and multiple readers.
|
||||||
|
|
||||||
|
The two projects have somewhat diverged. LMDB heavily focuses on raw performance
|
||||||
|
while Bolt has focused on simplicity and ease of use. For example, LMDB allows
|
||||||
|
several unsafe actions such as direct writes for the sake of performance. Bolt
|
||||||
|
opts to disallow actions which can leave the database in a corrupted state. The
|
||||||
|
only exception to this in Bolt is `DB.NoSync`.
|
||||||
|
|
||||||
|
There are also a few differences in API. LMDB requires a maximum mmap size when
|
||||||
|
opening an `mdb_env` whereas Bolt will handle incremental mmap resizing
|
||||||
|
automatically. LMDB overloads the getter and setter functions with multiple
|
||||||
|
flags whereas Bolt splits these specialized cases into their own functions.
|
||||||
|
|
||||||
|
|
||||||
|
## Caveats & Limitations
|
||||||
|
|
||||||
|
It's important to pick the right tool for the job and Bolt is no exception.
|
||||||
|
Here are a few things to note when evaluating and using Bolt:
|
||||||
|
|
||||||
|
* Bolt is good for read intensive workloads. Sequential write performance is
|
||||||
|
also fast but random writes can be slow. You can use `DB.Batch()` or add a
|
||||||
|
write-ahead log to help mitigate this issue.
|
||||||
|
|
||||||
|
* Bolt uses a B+tree internally so there can be a lot of random page access.
|
||||||
|
SSDs provide a significant performance boost over spinning disks.
|
||||||
|
|
||||||
|
* Try to avoid long running read transactions. Bolt uses copy-on-write so
|
||||||
|
old pages cannot be reclaimed while an old transaction is using them.
|
||||||
|
|
||||||
|
* Byte slices returned from Bolt are only valid during a transaction. Once the
|
||||||
|
transaction has been committed or rolled back then the memory they point to
|
||||||
|
can be reused by a new page or can be unmapped from virtual memory and you'll
|
||||||
|
see an `unexpected fault address` panic when accessing it.
|
||||||
|
|
||||||
|
* Be careful when using `Bucket.FillPercent`. Setting a high fill percent for
|
||||||
|
buckets that have random inserts will cause your database to have very poor
|
||||||
|
page utilization.
|
||||||
|
|
||||||
|
* Use larger buckets in general. Smaller buckets causes poor page utilization
|
||||||
|
once they become larger than the page size (typically 4KB).
|
||||||
|
|
||||||
|
* Bulk loading a lot of random writes into a new bucket can be slow as the
|
||||||
|
page will not split until the transaction is committed. Randomly inserting
|
||||||
|
more than 100,000 key/value pairs into a single new bucket in a single
|
||||||
|
transaction is not advised.
|
||||||
|
|
||||||
|
* Bolt uses a memory-mapped file so the underlying operating system handles the
|
||||||
|
caching of the data. Typically, the OS will cache as much of the file as it
|
||||||
|
can in memory and will release memory as needed to other processes. This means
|
||||||
|
that Bolt can show very high memory usage when working with large databases.
|
||||||
|
However, this is expected and the OS will release memory as needed. Bolt can
|
||||||
|
handle databases much larger than the available physical RAM, provided its
|
||||||
|
memory-map fits in the process virtual address space. It may be problematic
|
||||||
|
on 32-bits systems.
|
||||||
|
|
||||||
|
* The data structures in the Bolt database are memory mapped so the data file
|
||||||
|
will be endian specific. This means that you cannot copy a Bolt file from a
|
||||||
|
little endian machine to a big endian machine and have it work. For most
|
||||||
|
users this is not a concern since most modern CPUs are little endian.
|
||||||
|
|
||||||
|
* Because of the way pages are laid out on disk, Bolt cannot truncate data files
|
||||||
|
and return free pages back to the disk. Instead, Bolt maintains a free list
|
||||||
|
of unused pages within its data file. These free pages can be reused by later
|
||||||
|
transactions. This works well for many use cases as databases generally tend
|
||||||
|
to grow. However, it's important to note that deleting large chunks of data
|
||||||
|
will not allow you to reclaim that space on disk.
|
||||||
|
|
||||||
|
For more information on page allocation, [see this comment][page-allocation].
|
||||||
|
|
||||||
|
[page-allocation]: https://github.com/boltdb/bolt/issues/308#issuecomment-74811638
|
||||||
|
|
||||||
|
|
||||||
|
## Reading the Source
|
||||||
|
|
||||||
|
Bolt is a relatively small code base (<3KLOC) for an embedded, serializable,
|
||||||
|
transactional key/value database so it can be a good starting point for people
|
||||||
|
interested in how databases work.
|
||||||
|
|
||||||
|
The best places to start are the main entry points into Bolt:
|
||||||
|
|
||||||
|
- `Open()` - Initializes the reference to the database. It's responsible for
|
||||||
|
creating the database if it doesn't exist, obtaining an exclusive lock on the
|
||||||
|
file, reading the meta pages, & memory-mapping the file.
|
||||||
|
|
||||||
|
- `DB.Begin()` - Starts a read-only or read-write transaction depending on the
|
||||||
|
value of the `writable` argument. This requires briefly obtaining the "meta"
|
||||||
|
lock to keep track of open transactions. Only one read-write transaction can
|
||||||
|
exist at a time so the "rwlock" is acquired during the life of a read-write
|
||||||
|
transaction.
|
||||||
|
|
||||||
|
- `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the
|
||||||
|
arguments, a cursor is used to traverse the B+tree to the page and position
|
||||||
|
where they key & value will be written. Once the position is found, the bucket
|
||||||
|
materializes the underlying page and the page's parent pages into memory as
|
||||||
|
"nodes". These nodes are where mutations occur during read-write transactions.
|
||||||
|
These changes get flushed to disk during commit.
|
||||||
|
|
||||||
|
- `Bucket.Get()` - Retrieves a key/value pair from a bucket. This uses a cursor
|
||||||
|
to move to the page & position of a key/value pair. During a read-only
|
||||||
|
transaction, the key and value data is returned as a direct reference to the
|
||||||
|
underlying mmap file so there's no allocation overhead. For read-write
|
||||||
|
transactions, this data may reference the mmap file or one of the in-memory
|
||||||
|
node values.
|
||||||
|
|
||||||
|
- `Cursor` - This object is simply for traversing the B+tree of on-disk pages
|
||||||
|
or in-memory nodes. It can seek to a specific key, move to the first or last
|
||||||
|
value, or it can move forward or backward. The cursor handles the movement up
|
||||||
|
and down the B+tree transparently to the end user.
|
||||||
|
|
||||||
|
- `Tx.Commit()` - Converts the in-memory dirty nodes and the list of free pages
|
||||||
|
into pages to be written to disk. Writing to disk then occurs in two phases.
|
||||||
|
First, the dirty pages are written to disk and an `fsync()` occurs. Second, a
|
||||||
|
new meta page with an incremented transaction ID is written and another
|
||||||
|
`fsync()` occurs. This two phase write ensures that partially written data
|
||||||
|
pages are ignored in the event of a crash since the meta page pointing to them
|
||||||
|
is never written. Partially written meta pages are invalidated because they
|
||||||
|
are written with a checksum.
|
||||||
|
|
||||||
|
If you have additional notes that could be helpful for others, please submit
|
||||||
|
them via pull request.
|
||||||
|
|
||||||
|
|
||||||
|
## Other Projects Using Bolt
|
||||||
|
|
||||||
|
Below is a list of public, open source projects that use Bolt:
|
||||||
|
|
||||||
|
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
|
||||||
|
* [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside.
|
||||||
|
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
|
||||||
|
* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics.
|
||||||
|
* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects.
|
||||||
|
* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday.
|
||||||
|
* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
|
||||||
|
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
|
||||||
|
* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
|
||||||
|
* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka.
|
||||||
|
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
|
||||||
|
* [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt.
|
||||||
|
* [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site.
|
||||||
|
* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage.
|
||||||
|
* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters.
|
||||||
|
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
|
||||||
|
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
|
||||||
|
* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server.
|
||||||
|
* [SkyDB](https://github.com/skydb/sky) - Behavioral analytics database.
|
||||||
|
* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read.
|
||||||
|
* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics.
|
||||||
|
* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data.
|
||||||
|
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
|
||||||
|
* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
|
||||||
|
* [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
|
||||||
|
* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems.
|
||||||
|
* [stow](https://github.com/djherbis/stow) - a persistence manager for objects
|
||||||
|
backed by boltdb.
|
||||||
|
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
|
||||||
|
simple tx and key scans.
|
||||||
|
* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
|
||||||
|
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
|
||||||
|
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
|
||||||
|
* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
|
||||||
|
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
|
||||||
|
|
||||||
|
If you are using Bolt in a project please send a pull request to add it to the list.
|
18
vendor/github.com/boltdb/bolt/appveyor.yml
generated
vendored
Normal file
18
vendor/github.com/boltdb/bolt/appveyor.yml
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
version: "{build}"
|
||||||
|
|
||||||
|
os: Windows Server 2012 R2
|
||||||
|
|
||||||
|
clone_folder: c:\gopath\src\github.com\boltdb\bolt
|
||||||
|
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
|
||||||
|
install:
|
||||||
|
- echo %PATH%
|
||||||
|
- echo %GOPATH%
|
||||||
|
- go version
|
||||||
|
- go env
|
||||||
|
- go get -v -t ./...
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- go test -v ./...
|
7
vendor/github.com/boltdb/bolt/bolt_386.go
generated
vendored
Normal file
7
vendor/github.com/boltdb/bolt/bolt_386.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0xFFFFFFF
|
7
vendor/github.com/boltdb/bolt/bolt_amd64.go
generated
vendored
Normal file
7
vendor/github.com/boltdb/bolt/bolt_amd64.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
7
vendor/github.com/boltdb/bolt/bolt_arm.go
generated
vendored
Normal file
7
vendor/github.com/boltdb/bolt/bolt_arm.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0xFFFFFFF
|
9
vendor/github.com/boltdb/bolt/bolt_arm64.go
generated
vendored
Normal file
9
vendor/github.com/boltdb/bolt/bolt_arm64.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build arm64
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
10
vendor/github.com/boltdb/bolt/bolt_linux.go
generated
vendored
Normal file
10
vendor/github.com/boltdb/bolt/bolt_linux.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fdatasync flushes written data to a file descriptor.
|
||||||
|
func fdatasync(db *DB) error {
|
||||||
|
return syscall.Fdatasync(int(db.file.Fd()))
|
||||||
|
}
|
27
vendor/github.com/boltdb/bolt/bolt_openbsd.go
generated
vendored
Normal file
27
vendor/github.com/boltdb/bolt/bolt_openbsd.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
msAsync = 1 << iota // perform asynchronous writes
|
||||||
|
msSync // perform synchronous writes
|
||||||
|
msInvalidate // invalidate cached data
|
||||||
|
)
|
||||||
|
|
||||||
|
func msync(db *DB) error {
|
||||||
|
_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fdatasync(db *DB) error {
|
||||||
|
if db.data != nil {
|
||||||
|
return msync(db)
|
||||||
|
}
|
||||||
|
return db.file.Sync()
|
||||||
|
}
|
9
vendor/github.com/boltdb/bolt/bolt_ppc.go
generated
vendored
Normal file
9
vendor/github.com/boltdb/bolt/bolt_ppc.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build ppc
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0x7FFFFFFF // 2GB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0xFFFFFFF
|
9
vendor/github.com/boltdb/bolt/bolt_ppc64.go
generated
vendored
Normal file
9
vendor/github.com/boltdb/bolt/bolt_ppc64.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build ppc64
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
9
vendor/github.com/boltdb/bolt/bolt_ppc64le.go
generated
vendored
Normal file
9
vendor/github.com/boltdb/bolt/bolt_ppc64le.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build ppc64le
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
9
vendor/github.com/boltdb/bolt/bolt_s390x.go
generated
vendored
Normal file
9
vendor/github.com/boltdb/bolt/bolt_s390x.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build s390x
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
89
vendor/github.com/boltdb/bolt/bolt_unix.go
generated
vendored
Normal file
89
vendor/github.com/boltdb/bolt/bolt_unix.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// +build !windows,!plan9,!solaris
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
|
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
|
||||||
|
var t time.Time
|
||||||
|
for {
|
||||||
|
// If we're beyond our timeout then return an error.
|
||||||
|
// This can only occur after we've attempted a flock once.
|
||||||
|
if t.IsZero() {
|
||||||
|
t = time.Now()
|
||||||
|
} else if timeout > 0 && time.Since(t) > timeout {
|
||||||
|
return ErrTimeout
|
||||||
|
}
|
||||||
|
flag := syscall.LOCK_SH
|
||||||
|
if exclusive {
|
||||||
|
flag = syscall.LOCK_EX
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise attempt to obtain an exclusive lock.
|
||||||
|
err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
} else if err != syscall.EWOULDBLOCK {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a bit and try again.
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
|
func funlock(db *DB) error {
|
||||||
|
return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmap memory maps a DB's data file.
|
||||||
|
func mmap(db *DB, sz int) error {
|
||||||
|
// Map the data file to memory.
|
||||||
|
b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advise the kernel that the mmap is accessed randomly.
|
||||||
|
if err := madvise(b, syscall.MADV_RANDOM); err != nil {
|
||||||
|
return fmt.Errorf("madvise: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the original byte slice and convert to a byte array pointer.
|
||||||
|
db.dataref = b
|
||||||
|
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||||
|
db.datasz = sz
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// munmap unmaps a DB's data file from memory.
|
||||||
|
func munmap(db *DB) error {
|
||||||
|
// Ignore the unmap if we have no mapped data.
|
||||||
|
if db.dataref == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap using the original byte slice.
|
||||||
|
err := syscall.Munmap(db.dataref)
|
||||||
|
db.dataref = nil
|
||||||
|
db.data = nil
|
||||||
|
db.datasz = 0
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This function is copied from stdlib because it is not available on darwin.
|
||||||
|
func madvise(b []byte, advice int) (err error) {
|
||||||
|
_, _, e1 := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), uintptr(advice))
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
90
vendor/github.com/boltdb/bolt/bolt_unix_solaris.go
generated
vendored
Normal file
90
vendor/github.com/boltdb/bolt/bolt_unix_solaris.go
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
|
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
|
||||||
|
var t time.Time
|
||||||
|
for {
|
||||||
|
// If we're beyond our timeout then return an error.
|
||||||
|
// This can only occur after we've attempted a flock once.
|
||||||
|
if t.IsZero() {
|
||||||
|
t = time.Now()
|
||||||
|
} else if timeout > 0 && time.Since(t) > timeout {
|
||||||
|
return ErrTimeout
|
||||||
|
}
|
||||||
|
var lock syscall.Flock_t
|
||||||
|
lock.Start = 0
|
||||||
|
lock.Len = 0
|
||||||
|
lock.Pid = 0
|
||||||
|
lock.Whence = 0
|
||||||
|
lock.Pid = 0
|
||||||
|
if exclusive {
|
||||||
|
lock.Type = syscall.F_WRLCK
|
||||||
|
} else {
|
||||||
|
lock.Type = syscall.F_RDLCK
|
||||||
|
}
|
||||||
|
err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
} else if err != syscall.EAGAIN {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a bit and try again.
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
|
func funlock(db *DB) error {
|
||||||
|
var lock syscall.Flock_t
|
||||||
|
lock.Start = 0
|
||||||
|
lock.Len = 0
|
||||||
|
lock.Type = syscall.F_UNLCK
|
||||||
|
lock.Whence = 0
|
||||||
|
return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmap memory maps a DB's data file.
|
||||||
|
func mmap(db *DB, sz int) error {
|
||||||
|
// Map the data file to memory.
|
||||||
|
b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advise the kernel that the mmap is accessed randomly.
|
||||||
|
if err := unix.Madvise(b, syscall.MADV_RANDOM); err != nil {
|
||||||
|
return fmt.Errorf("madvise: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the original byte slice and convert to a byte array pointer.
|
||||||
|
db.dataref = b
|
||||||
|
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||||
|
db.datasz = sz
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// munmap unmaps a DB's data file from memory.
|
||||||
|
func munmap(db *DB) error {
|
||||||
|
// Ignore the unmap if we have no mapped data.
|
||||||
|
if db.dataref == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap using the original byte slice.
|
||||||
|
err := unix.Munmap(db.dataref)
|
||||||
|
db.dataref = nil
|
||||||
|
db.data = nil
|
||||||
|
db.datasz = 0
|
||||||
|
return err
|
||||||
|
}
|
144
vendor/github.com/boltdb/bolt/bolt_windows.go
generated
vendored
Normal file
144
vendor/github.com/boltdb/bolt/bolt_windows.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1
|
||||||
|
var (
|
||||||
|
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
procLockFileEx = modkernel32.NewProc("LockFileEx")
|
||||||
|
procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lockExt = ".lock"
|
||||||
|
|
||||||
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
|
||||||
|
flagLockExclusive = 2
|
||||||
|
flagLockFailImmediately = 1
|
||||||
|
|
||||||
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
|
||||||
|
errLockViolation syscall.Errno = 0x21
|
||||||
|
)
|
||||||
|
|
||||||
|
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
|
||||||
|
r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
|
||||||
|
r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdatasync flushes written data to a file descriptor.
|
||||||
|
func fdatasync(db *DB) error {
|
||||||
|
return db.file.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
|
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
|
||||||
|
// Create a separate lock file on windows because a process
|
||||||
|
// cannot share an exclusive lock on the same file. This is
|
||||||
|
// needed during Tx.WriteTo().
|
||||||
|
f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db.lockfile = f
|
||||||
|
|
||||||
|
var t time.Time
|
||||||
|
for {
|
||||||
|
// If we're beyond our timeout then return an error.
|
||||||
|
// This can only occur after we've attempted a flock once.
|
||||||
|
if t.IsZero() {
|
||||||
|
t = time.Now()
|
||||||
|
} else if timeout > 0 && time.Since(t) > timeout {
|
||||||
|
return ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var flag uint32 = flagLockFailImmediately
|
||||||
|
if exclusive {
|
||||||
|
flag |= flagLockExclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
} else if err != errLockViolation {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a bit and try again.
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
|
func funlock(db *DB) error {
|
||||||
|
err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{})
|
||||||
|
db.lockfile.Close()
|
||||||
|
os.Remove(db.path+lockExt)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmap memory maps a DB's data file.
|
||||||
|
// Based on: https://github.com/edsrzf/mmap-go
|
||||||
|
func mmap(db *DB, sz int) error {
|
||||||
|
if !db.readOnly {
|
||||||
|
// Truncate the database to the size of the mmap.
|
||||||
|
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||||
|
return fmt.Errorf("truncate: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open a file mapping handle.
|
||||||
|
sizelo := uint32(sz >> 32)
|
||||||
|
sizehi := uint32(sz) & 0xffffffff
|
||||||
|
h, errno := syscall.CreateFileMapping(syscall.Handle(db.file.Fd()), nil, syscall.PAGE_READONLY, sizelo, sizehi, nil)
|
||||||
|
if h == 0 {
|
||||||
|
return os.NewSyscallError("CreateFileMapping", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the memory map.
|
||||||
|
addr, errno := syscall.MapViewOfFile(h, syscall.FILE_MAP_READ, 0, 0, uintptr(sz))
|
||||||
|
if addr == 0 {
|
||||||
|
return os.NewSyscallError("MapViewOfFile", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close mapping handle.
|
||||||
|
if err := syscall.CloseHandle(syscall.Handle(h)); err != nil {
|
||||||
|
return os.NewSyscallError("CloseHandle", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to a byte array.
|
||||||
|
db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr)))
|
||||||
|
db.datasz = sz
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// munmap unmaps a pointer from a file.
|
||||||
|
// Based on: https://github.com/edsrzf/mmap-go
|
||||||
|
func munmap(db *DB) error {
|
||||||
|
if db.data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := (uintptr)(unsafe.Pointer(&db.data[0]))
|
||||||
|
if err := syscall.UnmapViewOfFile(addr); err != nil {
|
||||||
|
return os.NewSyscallError("UnmapViewOfFile", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
8
vendor/github.com/boltdb/bolt/boltsync_unix.go
generated
vendored
Normal file
8
vendor/github.com/boltdb/bolt/boltsync_unix.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// +build !windows,!plan9,!linux,!openbsd
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// fdatasync flushes written data to a file descriptor.
|
||||||
|
func fdatasync(db *DB) error {
|
||||||
|
return db.file.Sync()
|
||||||
|
}
|
748
vendor/github.com/boltdb/bolt/bucket.go
generated
vendored
Normal file
748
vendor/github.com/boltdb/bolt/bucket.go
generated
vendored
Normal file
@ -0,0 +1,748 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxKeySize is the maximum length of a key, in bytes.
|
||||||
|
MaxKeySize = 32768
|
||||||
|
|
||||||
|
// MaxValueSize is the maximum length of a value, in bytes.
|
||||||
|
MaxValueSize = (1 << 31) - 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxUint = ^uint(0)
|
||||||
|
minUint = 0
|
||||||
|
maxInt = int(^uint(0) >> 1)
|
||||||
|
minInt = -maxInt - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
const bucketHeaderSize = int(unsafe.Sizeof(bucket{}))
|
||||||
|
|
||||||
|
const (
|
||||||
|
minFillPercent = 0.1
|
||||||
|
maxFillPercent = 1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultFillPercent is the percentage that split pages are filled.
|
||||||
|
// This value can be changed by setting Bucket.FillPercent.
|
||||||
|
const DefaultFillPercent = 0.5
|
||||||
|
|
||||||
|
// Bucket represents a collection of key/value pairs inside the database.
|
||||||
|
type Bucket struct {
|
||||||
|
*bucket
|
||||||
|
tx *Tx // the associated transaction
|
||||||
|
buckets map[string]*Bucket // subbucket cache
|
||||||
|
page *page // inline page reference
|
||||||
|
rootNode *node // materialized node for the root page.
|
||||||
|
nodes map[pgid]*node // node cache
|
||||||
|
|
||||||
|
// Sets the threshold for filling nodes when they split. By default,
|
||||||
|
// the bucket will fill to 50% but it can be useful to increase this
|
||||||
|
// amount if you know that your write workloads are mostly append-only.
|
||||||
|
//
|
||||||
|
// This is non-persisted across transactions so it must be set in every Tx.
|
||||||
|
FillPercent float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// bucket represents the on-file representation of a bucket.
|
||||||
|
// This is stored as the "value" of a bucket key. If the bucket is small enough,
|
||||||
|
// then its root page can be stored inline in the "value", after the bucket
|
||||||
|
// header. In the case of inline buckets, the "root" will be 0.
|
||||||
|
type bucket struct {
|
||||||
|
root pgid // page id of the bucket's root-level page
|
||||||
|
sequence uint64 // monotonically incrementing, used by NextSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBucket returns a new bucket associated with a transaction.
|
||||||
|
func newBucket(tx *Tx) Bucket {
|
||||||
|
var b = Bucket{tx: tx, FillPercent: DefaultFillPercent}
|
||||||
|
if tx.writable {
|
||||||
|
b.buckets = make(map[string]*Bucket)
|
||||||
|
b.nodes = make(map[pgid]*node)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tx returns the tx of the bucket.
|
||||||
|
func (b *Bucket) Tx() *Tx {
|
||||||
|
return b.tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the root of the bucket.
|
||||||
|
func (b *Bucket) Root() pgid {
|
||||||
|
return b.root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writable returns whether the bucket is writable.
|
||||||
|
func (b *Bucket) Writable() bool {
|
||||||
|
return b.tx.writable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor creates a cursor associated with the bucket.
|
||||||
|
// The cursor is only valid as long as the transaction is open.
|
||||||
|
// Do not use a cursor after the transaction is closed.
|
||||||
|
func (b *Bucket) Cursor() *Cursor {
|
||||||
|
// Update transaction statistics.
|
||||||
|
b.tx.stats.CursorCount++
|
||||||
|
|
||||||
|
// Allocate and return a cursor.
|
||||||
|
return &Cursor{
|
||||||
|
bucket: b,
|
||||||
|
stack: make([]elemRef, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket retrieves a nested bucket by name.
|
||||||
|
// Returns nil if the bucket does not exist.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
|
func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||||
|
if b.buckets != nil {
|
||||||
|
if child := b.buckets[string(name)]; child != nil {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move cursor to key.
|
||||||
|
c := b.Cursor()
|
||||||
|
k, v, flags := c.seek(name)
|
||||||
|
|
||||||
|
// Return nil if the key doesn't exist or it is not a bucket.
|
||||||
|
if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create a bucket and cache it.
|
||||||
|
var child = b.openBucket(v)
|
||||||
|
if b.buckets != nil {
|
||||||
|
b.buckets[string(name)] = child
|
||||||
|
}
|
||||||
|
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method that re-interprets a sub-bucket value
|
||||||
|
// from a parent into a Bucket
|
||||||
|
func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||||
|
var child = newBucket(b.tx)
|
||||||
|
|
||||||
|
// If this is a writable transaction then we need to copy the bucket entry.
|
||||||
|
// Read-only transactions can point directly at the mmap entry.
|
||||||
|
if b.tx.writable {
|
||||||
|
child.bucket = &bucket{}
|
||||||
|
*child.bucket = *(*bucket)(unsafe.Pointer(&value[0]))
|
||||||
|
} else {
|
||||||
|
child.bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save a reference to the inline page if the bucket is inline.
|
||||||
|
if child.root == 0 {
|
||||||
|
child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &child
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucket creates a new bucket at the given key and returns the new bucket.
|
||||||
|
// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
|
func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||||
|
if b.tx.db == nil {
|
||||||
|
return nil, ErrTxClosed
|
||||||
|
} else if !b.tx.writable {
|
||||||
|
return nil, ErrTxNotWritable
|
||||||
|
} else if len(key) == 0 {
|
||||||
|
return nil, ErrBucketNameRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move cursor to correct position.
|
||||||
|
c := b.Cursor()
|
||||||
|
k, _, flags := c.seek(key)
|
||||||
|
|
||||||
|
// Return an error if there is an existing key.
|
||||||
|
if bytes.Equal(key, k) {
|
||||||
|
if (flags & bucketLeafFlag) != 0 {
|
||||||
|
return nil, ErrBucketExists
|
||||||
|
} else {
|
||||||
|
return nil, ErrIncompatibleValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create empty, inline bucket.
|
||||||
|
var bucket = Bucket{
|
||||||
|
bucket: &bucket{},
|
||||||
|
rootNode: &node{isLeaf: true},
|
||||||
|
FillPercent: DefaultFillPercent,
|
||||||
|
}
|
||||||
|
var value = bucket.write()
|
||||||
|
|
||||||
|
// Insert into node.
|
||||||
|
key = cloneBytes(key)
|
||||||
|
c.node().put(key, key, value, 0, bucketLeafFlag)
|
||||||
|
|
||||||
|
// Since subbuckets are not allowed on inline buckets, we need to
|
||||||
|
// dereference the inline page, if it exists. This will cause the bucket
|
||||||
|
// to be treated as a regular, non-inline bucket for the rest of the tx.
|
||||||
|
b.page = nil
|
||||||
|
|
||||||
|
return b.Bucket(key), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it.
|
||||||
|
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
|
func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) {
|
||||||
|
child, err := b.CreateBucket(key)
|
||||||
|
if err == ErrBucketExists {
|
||||||
|
return b.Bucket(key), nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return child, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucket deletes a bucket at the given key.
|
||||||
|
// Returns an error if the bucket does not exists, or if the key represents a non-bucket value.
|
||||||
|
func (b *Bucket) DeleteBucket(key []byte) error {
|
||||||
|
if b.tx.db == nil {
|
||||||
|
return ErrTxClosed
|
||||||
|
} else if !b.Writable() {
|
||||||
|
return ErrTxNotWritable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move cursor to correct position.
|
||||||
|
c := b.Cursor()
|
||||||
|
k, _, flags := c.seek(key)
|
||||||
|
|
||||||
|
// Return an error if bucket doesn't exist or is not a bucket.
|
||||||
|
if !bytes.Equal(key, k) {
|
||||||
|
return ErrBucketNotFound
|
||||||
|
} else if (flags & bucketLeafFlag) == 0 {
|
||||||
|
return ErrIncompatibleValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively delete all child buckets.
|
||||||
|
child := b.Bucket(key)
|
||||||
|
err := child.ForEach(func(k, v []byte) error {
|
||||||
|
if v == nil {
|
||||||
|
if err := child.DeleteBucket(k); err != nil {
|
||||||
|
return fmt.Errorf("delete bucket: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove cached copy.
|
||||||
|
delete(b.buckets, string(key))
|
||||||
|
|
||||||
|
// Release all bucket pages to freelist.
|
||||||
|
child.nodes = nil
|
||||||
|
child.rootNode = nil
|
||||||
|
child.free()
|
||||||
|
|
||||||
|
// Delete the node if we have a matching key.
|
||||||
|
c.node().del(key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves the value for a key in the bucket.
|
||||||
|
// Returns a nil value if the key does not exist or if the key is a nested bucket.
|
||||||
|
// The returned value is only valid for the life of the transaction.
|
||||||
|
func (b *Bucket) Get(key []byte) []byte {
|
||||||
|
k, v, flags := b.Cursor().seek(key)
|
||||||
|
|
||||||
|
// Return nil if this is a bucket.
|
||||||
|
if (flags & bucketLeafFlag) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If our target node isn't the same key as what's passed in then return nil.
|
||||||
|
if !bytes.Equal(key, k) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put sets the value for a key in the bucket.
|
||||||
|
// If the key exist then its previous value will be overwritten.
|
||||||
|
// Supplied value must remain valid for the life of the transaction.
|
||||||
|
// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large.
|
||||||
|
func (b *Bucket) Put(key []byte, value []byte) error {
|
||||||
|
if b.tx.db == nil {
|
||||||
|
return ErrTxClosed
|
||||||
|
} else if !b.Writable() {
|
||||||
|
return ErrTxNotWritable
|
||||||
|
} else if len(key) == 0 {
|
||||||
|
return ErrKeyRequired
|
||||||
|
} else if len(key) > MaxKeySize {
|
||||||
|
return ErrKeyTooLarge
|
||||||
|
} else if int64(len(value)) > MaxValueSize {
|
||||||
|
return ErrValueTooLarge
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move cursor to correct position.
|
||||||
|
c := b.Cursor()
|
||||||
|
k, _, flags := c.seek(key)
|
||||||
|
|
||||||
|
// Return an error if there is an existing key with a bucket value.
|
||||||
|
if bytes.Equal(key, k) && (flags&bucketLeafFlag) != 0 {
|
||||||
|
return ErrIncompatibleValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert into node.
|
||||||
|
key = cloneBytes(key)
|
||||||
|
c.node().put(key, key, value, 0, 0)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a key from the bucket.
|
||||||
|
// If the key does not exist then nothing is done and a nil error is returned.
|
||||||
|
// Returns an error if the bucket was created from a read-only transaction.
|
||||||
|
func (b *Bucket) Delete(key []byte) error {
|
||||||
|
if b.tx.db == nil {
|
||||||
|
return ErrTxClosed
|
||||||
|
} else if !b.Writable() {
|
||||||
|
return ErrTxNotWritable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move cursor to correct position.
|
||||||
|
c := b.Cursor()
|
||||||
|
_, _, flags := c.seek(key)
|
||||||
|
|
||||||
|
// Return an error if there is already existing bucket value.
|
||||||
|
if (flags & bucketLeafFlag) != 0 {
|
||||||
|
return ErrIncompatibleValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the node if we have a matching key.
|
||||||
|
c.node().del(key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextSequence returns an autoincrementing integer for the bucket.
|
||||||
|
func (b *Bucket) NextSequence() (uint64, error) {
|
||||||
|
if b.tx.db == nil {
|
||||||
|
return 0, ErrTxClosed
|
||||||
|
} else if !b.Writable() {
|
||||||
|
return 0, ErrTxNotWritable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Materialize the root node if it hasn't been already so that the
|
||||||
|
// bucket will be saved during commit.
|
||||||
|
if b.rootNode == nil {
|
||||||
|
_ = b.node(b.root, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment and return the sequence.
|
||||||
|
b.bucket.sequence++
|
||||||
|
return b.bucket.sequence, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEach executes a function for each key/value pair in a bucket.
|
||||||
|
// If the provided function returns an error then the iteration is stopped and
|
||||||
|
// the error is returned to the caller. The provided function must not modify
|
||||||
|
// the bucket; this will result in undefined behavior.
|
||||||
|
func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
|
||||||
|
if b.tx.db == nil {
|
||||||
|
return ErrTxClosed
|
||||||
|
}
|
||||||
|
c := b.Cursor()
|
||||||
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
|
if err := fn(k, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns stats on a bucket.
|
||||||
|
func (b *Bucket) Stats() BucketStats {
|
||||||
|
var s, subStats BucketStats
|
||||||
|
pageSize := b.tx.db.pageSize
|
||||||
|
s.BucketN += 1
|
||||||
|
if b.root == 0 {
|
||||||
|
s.InlineBucketN += 1
|
||||||
|
}
|
||||||
|
b.forEachPage(func(p *page, depth int) {
|
||||||
|
if (p.flags & leafPageFlag) != 0 {
|
||||||
|
s.KeyN += int(p.count)
|
||||||
|
|
||||||
|
// used totals the used bytes for the page
|
||||||
|
used := pageHeaderSize
|
||||||
|
|
||||||
|
if p.count != 0 {
|
||||||
|
// If page has any elements, add all element headers.
|
||||||
|
used += leafPageElementSize * int(p.count-1)
|
||||||
|
|
||||||
|
// Add all element key, value sizes.
|
||||||
|
// The computation takes advantage of the fact that the position
|
||||||
|
// of the last element's key/value equals to the total of the sizes
|
||||||
|
// of all previous elements' keys and values.
|
||||||
|
// It also includes the last element's header.
|
||||||
|
lastElement := p.leafPageElement(p.count - 1)
|
||||||
|
used += int(lastElement.pos + lastElement.ksize + lastElement.vsize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.root == 0 {
|
||||||
|
// For inlined bucket just update the inline stats
|
||||||
|
s.InlineBucketInuse += used
|
||||||
|
} else {
|
||||||
|
// For non-inlined bucket update all the leaf stats
|
||||||
|
s.LeafPageN++
|
||||||
|
s.LeafInuse += used
|
||||||
|
s.LeafOverflowN += int(p.overflow)
|
||||||
|
|
||||||
|
// Collect stats from sub-buckets.
|
||||||
|
// Do that by iterating over all element headers
|
||||||
|
// looking for the ones with the bucketLeafFlag.
|
||||||
|
for i := uint16(0); i < p.count; i++ {
|
||||||
|
e := p.leafPageElement(i)
|
||||||
|
if (e.flags & bucketLeafFlag) != 0 {
|
||||||
|
// For any bucket element, open the element value
|
||||||
|
// and recursively call Stats on the contained bucket.
|
||||||
|
subStats.Add(b.openBucket(e.value()).Stats())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (p.flags & branchPageFlag) != 0 {
|
||||||
|
s.BranchPageN++
|
||||||
|
lastElement := p.branchPageElement(p.count - 1)
|
||||||
|
|
||||||
|
// used totals the used bytes for the page
|
||||||
|
// Add header and all element headers.
|
||||||
|
used := pageHeaderSize + (branchPageElementSize * int(p.count-1))
|
||||||
|
|
||||||
|
// Add size of all keys and values.
|
||||||
|
// Again, use the fact that last element's position equals to
|
||||||
|
// the total of key, value sizes of all previous elements.
|
||||||
|
used += int(lastElement.pos + lastElement.ksize)
|
||||||
|
s.BranchInuse += used
|
||||||
|
s.BranchOverflowN += int(p.overflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of maximum page depth.
|
||||||
|
if depth+1 > s.Depth {
|
||||||
|
s.Depth = (depth + 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Alloc stats can be computed from page counts and pageSize.
|
||||||
|
s.BranchAlloc = (s.BranchPageN + s.BranchOverflowN) * pageSize
|
||||||
|
s.LeafAlloc = (s.LeafPageN + s.LeafOverflowN) * pageSize
|
||||||
|
|
||||||
|
// Add the max depth of sub-buckets to get total nested depth.
|
||||||
|
s.Depth += subStats.Depth
|
||||||
|
// Add the stats for all sub-buckets
|
||||||
|
s.Add(subStats)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// forEachPage iterates over every page in a bucket, including inline pages.
|
||||||
|
func (b *Bucket) forEachPage(fn func(*page, int)) {
|
||||||
|
// If we have an inline page then just use that.
|
||||||
|
if b.page != nil {
|
||||||
|
fn(b.page, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise traverse the page hierarchy.
|
||||||
|
b.tx.forEachPage(b.root, 0, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// forEachPageNode iterates over every page (or node) in a bucket.
|
||||||
|
// This also includes inline pages.
|
||||||
|
func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) {
|
||||||
|
// If we have an inline page or root node then just use that.
|
||||||
|
if b.page != nil {
|
||||||
|
fn(b.page, nil, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b._forEachPageNode(b.root, 0, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bucket) _forEachPageNode(pgid pgid, depth int, fn func(*page, *node, int)) {
|
||||||
|
var p, n = b.pageNode(pgid)
|
||||||
|
|
||||||
|
// Execute function.
|
||||||
|
fn(p, n, depth)
|
||||||
|
|
||||||
|
// Recursively loop over children.
|
||||||
|
if p != nil {
|
||||||
|
if (p.flags & branchPageFlag) != 0 {
|
||||||
|
for i := 0; i < int(p.count); i++ {
|
||||||
|
elem := p.branchPageElement(uint16(i))
|
||||||
|
b._forEachPageNode(elem.pgid, depth+1, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !n.isLeaf {
|
||||||
|
for _, inode := range n.inodes {
|
||||||
|
b._forEachPageNode(inode.pgid, depth+1, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spill writes all the nodes for this bucket to dirty pages.
|
||||||
|
func (b *Bucket) spill() error {
|
||||||
|
// Spill all child buckets first.
|
||||||
|
for name, child := range b.buckets {
|
||||||
|
// If the child bucket is small enough and it has no child buckets then
|
||||||
|
// write it inline into the parent bucket's page. Otherwise spill it
|
||||||
|
// like a normal bucket and make the parent value a pointer to the page.
|
||||||
|
var value []byte
|
||||||
|
if child.inlineable() {
|
||||||
|
child.free()
|
||||||
|
value = child.write()
|
||||||
|
} else {
|
||||||
|
if err := child.spill(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the child bucket header in this bucket.
|
||||||
|
value = make([]byte, unsafe.Sizeof(bucket{}))
|
||||||
|
var bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||||
|
*bucket = *child.bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip writing the bucket if there are no materialized nodes.
|
||||||
|
if child.rootNode == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update parent node.
|
||||||
|
var c = b.Cursor()
|
||||||
|
k, _, flags := c.seek([]byte(name))
|
||||||
|
if !bytes.Equal([]byte(name), k) {
|
||||||
|
panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k))
|
||||||
|
}
|
||||||
|
if flags&bucketLeafFlag == 0 {
|
||||||
|
panic(fmt.Sprintf("unexpected bucket header flag: %x", flags))
|
||||||
|
}
|
||||||
|
c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore if there's not a materialized root node.
|
||||||
|
if b.rootNode == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spill nodes.
|
||||||
|
if err := b.rootNode.spill(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.rootNode = b.rootNode.root()
|
||||||
|
|
||||||
|
// Update the root node for this bucket.
|
||||||
|
if b.rootNode.pgid >= b.tx.meta.pgid {
|
||||||
|
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.pgid))
|
||||||
|
}
|
||||||
|
b.root = b.rootNode.pgid
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// inlineable returns true if a bucket is small enough to be written inline
|
||||||
|
// and if it contains no subbuckets. Otherwise returns false.
|
||||||
|
func (b *Bucket) inlineable() bool {
|
||||||
|
var n = b.rootNode
|
||||||
|
|
||||||
|
// Bucket must only contain a single leaf node.
|
||||||
|
if n == nil || !n.isLeaf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket is not inlineable if it contains subbuckets or if it goes beyond
|
||||||
|
// our threshold for inline bucket size.
|
||||||
|
var size = pageHeaderSize
|
||||||
|
for _, inode := range n.inodes {
|
||||||
|
size += leafPageElementSize + len(inode.key) + len(inode.value)
|
||||||
|
|
||||||
|
if inode.flags&bucketLeafFlag != 0 {
|
||||||
|
return false
|
||||||
|
} else if size > b.maxInlineBucketSize() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the maximum total size of a bucket to make it a candidate for inlining.
|
||||||
|
func (b *Bucket) maxInlineBucketSize() int {
|
||||||
|
return b.tx.db.pageSize / 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// write allocates and writes a bucket to a byte slice.
|
||||||
|
func (b *Bucket) write() []byte {
|
||||||
|
// Allocate the appropriate size.
|
||||||
|
var n = b.rootNode
|
||||||
|
var value = make([]byte, bucketHeaderSize+n.size())
|
||||||
|
|
||||||
|
// Write a bucket header.
|
||||||
|
var bucket = (*bucket)(unsafe.Pointer(&value[0]))
|
||||||
|
*bucket = *b.bucket
|
||||||
|
|
||||||
|
// Convert byte slice to a fake page and write the root node.
|
||||||
|
var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
|
||||||
|
n.write(p)
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// rebalance attempts to balance all nodes.
|
||||||
|
func (b *Bucket) rebalance() {
|
||||||
|
for _, n := range b.nodes {
|
||||||
|
n.rebalance()
|
||||||
|
}
|
||||||
|
for _, child := range b.buckets {
|
||||||
|
child.rebalance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// node creates a node from a page and associates it with a given parent.
|
||||||
|
func (b *Bucket) node(pgid pgid, parent *node) *node {
|
||||||
|
_assert(b.nodes != nil, "nodes map expected")
|
||||||
|
|
||||||
|
// Retrieve node if it's already been created.
|
||||||
|
if n := b.nodes[pgid]; n != nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create a node and cache it.
|
||||||
|
n := &node{bucket: b, parent: parent}
|
||||||
|
if parent == nil {
|
||||||
|
b.rootNode = n
|
||||||
|
} else {
|
||||||
|
parent.children = append(parent.children, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the inline page if this is an inline bucket.
|
||||||
|
var p = b.page
|
||||||
|
if p == nil {
|
||||||
|
p = b.tx.page(pgid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the page into the node and cache it.
|
||||||
|
n.read(p)
|
||||||
|
b.nodes[pgid] = n
|
||||||
|
|
||||||
|
// Update statistics.
|
||||||
|
b.tx.stats.NodeCount++
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// free recursively frees all pages in the bucket.
|
||||||
|
func (b *Bucket) free() {
|
||||||
|
if b.root == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tx = b.tx
|
||||||
|
b.forEachPageNode(func(p *page, n *node, _ int) {
|
||||||
|
if p != nil {
|
||||||
|
tx.db.freelist.free(tx.meta.txid, p)
|
||||||
|
} else {
|
||||||
|
n.free()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.root = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// dereference removes all references to the old mmap.
|
||||||
|
func (b *Bucket) dereference() {
|
||||||
|
if b.rootNode != nil {
|
||||||
|
b.rootNode.root().dereference()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range b.buckets {
|
||||||
|
child.dereference()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pageNode returns the in-memory node, if it exists.
|
||||||
|
// Otherwise returns the underlying page.
|
||||||
|
func (b *Bucket) pageNode(id pgid) (*page, *node) {
|
||||||
|
// Inline buckets have a fake page embedded in their value so treat them
|
||||||
|
// differently. We'll return the rootNode (if available) or the fake page.
|
||||||
|
if b.root == 0 {
|
||||||
|
if id != 0 {
|
||||||
|
panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id))
|
||||||
|
}
|
||||||
|
if b.rootNode != nil {
|
||||||
|
return nil, b.rootNode
|
||||||
|
}
|
||||||
|
return b.page, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the node cache for non-inline buckets.
|
||||||
|
if b.nodes != nil {
|
||||||
|
if n := b.nodes[id]; n != nil {
|
||||||
|
return nil, n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally lookup the page from the transaction if no node is materialized.
|
||||||
|
return b.tx.page(id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BucketStats records statistics about resources used by a bucket.
|
||||||
|
type BucketStats struct {
|
||||||
|
// Page count statistics.
|
||||||
|
BranchPageN int // number of logical branch pages
|
||||||
|
BranchOverflowN int // number of physical branch overflow pages
|
||||||
|
LeafPageN int // number of logical leaf pages
|
||||||
|
LeafOverflowN int // number of physical leaf overflow pages
|
||||||
|
|
||||||
|
// Tree statistics.
|
||||||
|
KeyN int // number of keys/value pairs
|
||||||
|
Depth int // number of levels in B+tree
|
||||||
|
|
||||||
|
// Page size utilization.
|
||||||
|
BranchAlloc int // bytes allocated for physical branch pages
|
||||||
|
BranchInuse int // bytes actually used for branch data
|
||||||
|
LeafAlloc int // bytes allocated for physical leaf pages
|
||||||
|
LeafInuse int // bytes actually used for leaf data
|
||||||
|
|
||||||
|
// Bucket statistics
|
||||||
|
BucketN int // total number of buckets including the top bucket
|
||||||
|
InlineBucketN int // total number on inlined buckets
|
||||||
|
InlineBucketInuse int // bytes used for inlined buckets (also accounted for in LeafInuse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BucketStats) Add(other BucketStats) {
|
||||||
|
s.BranchPageN += other.BranchPageN
|
||||||
|
s.BranchOverflowN += other.BranchOverflowN
|
||||||
|
s.LeafPageN += other.LeafPageN
|
||||||
|
s.LeafOverflowN += other.LeafOverflowN
|
||||||
|
s.KeyN += other.KeyN
|
||||||
|
if s.Depth < other.Depth {
|
||||||
|
s.Depth = other.Depth
|
||||||
|
}
|
||||||
|
s.BranchAlloc += other.BranchAlloc
|
||||||
|
s.BranchInuse += other.BranchInuse
|
||||||
|
s.LeafAlloc += other.LeafAlloc
|
||||||
|
s.LeafInuse += other.LeafInuse
|
||||||
|
|
||||||
|
s.BucketN += other.BucketN
|
||||||
|
s.InlineBucketN += other.InlineBucketN
|
||||||
|
s.InlineBucketInuse += other.InlineBucketInuse
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneBytes returns a copy of a given slice.
|
||||||
|
func cloneBytes(v []byte) []byte {
|
||||||
|
var clone = make([]byte, len(v))
|
||||||
|
copy(clone, v)
|
||||||
|
return clone
|
||||||
|
}
|
400
vendor/github.com/boltdb/bolt/cursor.go
generated
vendored
Normal file
400
vendor/github.com/boltdb/bolt/cursor.go
generated
vendored
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order.
|
||||||
|
// Cursors see nested buckets with value == nil.
|
||||||
|
// Cursors can be obtained from a transaction and are valid as long as the transaction is open.
|
||||||
|
//
|
||||||
|
// Keys and values returned from the cursor are only valid for the life of the transaction.
|
||||||
|
//
|
||||||
|
// Changing data while traversing with a cursor may cause it to be invalidated
|
||||||
|
// and return unexpected keys and/or values. You must reposition your cursor
|
||||||
|
// after mutating data.
|
||||||
|
type Cursor struct {
|
||||||
|
bucket *Bucket
|
||||||
|
stack []elemRef
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket returns the bucket that this cursor was created from.
|
||||||
|
func (c *Cursor) Bucket() *Bucket {
|
||||||
|
return c.bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// First moves the cursor to the first item in the bucket and returns its key and value.
|
||||||
|
// If the bucket is empty then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
|
func (c *Cursor) First() (key []byte, value []byte) {
|
||||||
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
|
c.stack = c.stack[:0]
|
||||||
|
p, n := c.bucket.pageNode(c.bucket.root)
|
||||||
|
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
||||||
|
c.first()
|
||||||
|
|
||||||
|
// If we land on an empty page then move to the next value.
|
||||||
|
// https://github.com/boltdb/bolt/issues/450
|
||||||
|
if c.stack[len(c.stack)-1].count() == 0 {
|
||||||
|
c.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
k, v, flags := c.keyValue()
|
||||||
|
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
return k, v
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last moves the cursor to the last item in the bucket and returns its key and value.
|
||||||
|
// If the bucket is empty then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
|
func (c *Cursor) Last() (key []byte, value []byte) {
|
||||||
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
|
c.stack = c.stack[:0]
|
||||||
|
p, n := c.bucket.pageNode(c.bucket.root)
|
||||||
|
ref := elemRef{page: p, node: n}
|
||||||
|
ref.index = ref.count() - 1
|
||||||
|
c.stack = append(c.stack, ref)
|
||||||
|
c.last()
|
||||||
|
k, v, flags := c.keyValue()
|
||||||
|
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
return k, v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next moves the cursor to the next item in the bucket and returns its key and value.
|
||||||
|
// If the cursor is at the end of the bucket then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
|
func (c *Cursor) Next() (key []byte, value []byte) {
|
||||||
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
|
k, v, flags := c.next()
|
||||||
|
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
return k, v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prev moves the cursor to the previous item in the bucket and returns its key and value.
|
||||||
|
// If the cursor is at the beginning of the bucket then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
|
func (c *Cursor) Prev() (key []byte, value []byte) {
|
||||||
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
|
|
||||||
|
// Attempt to move back one element until we're successful.
|
||||||
|
// Move up the stack as we hit the beginning of each page in our stack.
|
||||||
|
for i := len(c.stack) - 1; i >= 0; i-- {
|
||||||
|
elem := &c.stack[i]
|
||||||
|
if elem.index > 0 {
|
||||||
|
elem.index--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.stack = c.stack[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've hit the end then return nil.
|
||||||
|
if len(c.stack) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move down the stack to find the last element of the last leaf under this branch.
|
||||||
|
c.last()
|
||||||
|
k, v, flags := c.keyValue()
|
||||||
|
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
return k, v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek moves the cursor to a given key and returns it.
|
||||||
|
// If the key does not exist then the next key is used. If no keys
|
||||||
|
// follow, a nil key is returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
|
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||||
|
k, v, flags := c.seek(seek)
|
||||||
|
|
||||||
|
// If we ended up after the last element of a page then move to the next one.
|
||||||
|
if ref := &c.stack[len(c.stack)-1]; ref.index >= ref.count() {
|
||||||
|
k, v, flags = c.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if k == nil {
|
||||||
|
return nil, nil
|
||||||
|
} else if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
return k, v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the current key/value under the cursor from the bucket.
|
||||||
|
// Delete fails if current key/value is a bucket or if the transaction is not writable.
|
||||||
|
func (c *Cursor) Delete() error {
|
||||||
|
if c.bucket.tx.db == nil {
|
||||||
|
return ErrTxClosed
|
||||||
|
} else if !c.bucket.Writable() {
|
||||||
|
return ErrTxNotWritable
|
||||||
|
}
|
||||||
|
|
||||||
|
key, _, flags := c.keyValue()
|
||||||
|
// Return an error if current value is a bucket.
|
||||||
|
if (flags & bucketLeafFlag) != 0 {
|
||||||
|
return ErrIncompatibleValue
|
||||||
|
}
|
||||||
|
c.node().del(key)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// seek moves the cursor to a given key and returns it.
|
||||||
|
// If the key does not exist then the next key is used.
|
||||||
|
func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) {
|
||||||
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
|
|
||||||
|
// Start from root page/node and traverse to correct page.
|
||||||
|
c.stack = c.stack[:0]
|
||||||
|
c.search(seek, c.bucket.root)
|
||||||
|
ref := &c.stack[len(c.stack)-1]
|
||||||
|
|
||||||
|
// If the cursor is pointing to the end of page/node then return nil.
|
||||||
|
if ref.index >= ref.count() {
|
||||||
|
return nil, nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a bucket then return a nil value.
|
||||||
|
return c.keyValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// first moves the cursor to the first leaf element under the last page in the stack.
|
||||||
|
func (c *Cursor) first() {
|
||||||
|
for {
|
||||||
|
// Exit when we hit a leaf page.
|
||||||
|
var ref = &c.stack[len(c.stack)-1]
|
||||||
|
if ref.isLeaf() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep adding pages pointing to the first element to the stack.
|
||||||
|
var pgid pgid
|
||||||
|
if ref.node != nil {
|
||||||
|
pgid = ref.node.inodes[ref.index].pgid
|
||||||
|
} else {
|
||||||
|
pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
|
||||||
|
}
|
||||||
|
p, n := c.bucket.pageNode(pgid)
|
||||||
|
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last moves the cursor to the last leaf element under the last page in the stack.
|
||||||
|
func (c *Cursor) last() {
|
||||||
|
for {
|
||||||
|
// Exit when we hit a leaf page.
|
||||||
|
ref := &c.stack[len(c.stack)-1]
|
||||||
|
if ref.isLeaf() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep adding pages pointing to the last element in the stack.
|
||||||
|
var pgid pgid
|
||||||
|
if ref.node != nil {
|
||||||
|
pgid = ref.node.inodes[ref.index].pgid
|
||||||
|
} else {
|
||||||
|
pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
|
||||||
|
}
|
||||||
|
p, n := c.bucket.pageNode(pgid)
|
||||||
|
|
||||||
|
var nextRef = elemRef{page: p, node: n}
|
||||||
|
nextRef.index = nextRef.count() - 1
|
||||||
|
c.stack = append(c.stack, nextRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// next moves to the next leaf element and returns the key and value.
|
||||||
|
// If the cursor is at the last leaf element then it stays there and returns nil.
|
||||||
|
func (c *Cursor) next() (key []byte, value []byte, flags uint32) {
|
||||||
|
for {
|
||||||
|
// Attempt to move over one element until we're successful.
|
||||||
|
// Move up the stack as we hit the end of each page in our stack.
|
||||||
|
var i int
|
||||||
|
for i = len(c.stack) - 1; i >= 0; i-- {
|
||||||
|
elem := &c.stack[i]
|
||||||
|
if elem.index < elem.count()-1 {
|
||||||
|
elem.index++
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've hit the root page then stop and return. This will leave the
|
||||||
|
// cursor on the last element of the last page.
|
||||||
|
if i == -1 {
|
||||||
|
return nil, nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise start from where we left off in the stack and find the
|
||||||
|
// first element of the first leaf page.
|
||||||
|
c.stack = c.stack[:i+1]
|
||||||
|
c.first()
|
||||||
|
|
||||||
|
// If this is an empty page then restart and move back up the stack.
|
||||||
|
// https://github.com/boltdb/bolt/issues/450
|
||||||
|
if c.stack[len(c.stack)-1].count() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.keyValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// search recursively performs a binary search against a given page/node until it finds a given key.
|
||||||
|
func (c *Cursor) search(key []byte, pgid pgid) {
|
||||||
|
p, n := c.bucket.pageNode(pgid)
|
||||||
|
if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 {
|
||||||
|
panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags))
|
||||||
|
}
|
||||||
|
e := elemRef{page: p, node: n}
|
||||||
|
c.stack = append(c.stack, e)
|
||||||
|
|
||||||
|
// If we're on a leaf page/node then find the specific node.
|
||||||
|
if e.isLeaf() {
|
||||||
|
c.nsearch(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != nil {
|
||||||
|
c.searchNode(key, n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.searchPage(key, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) searchNode(key []byte, n *node) {
|
||||||
|
var exact bool
|
||||||
|
index := sort.Search(len(n.inodes), func(i int) bool {
|
||||||
|
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
||||||
|
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
||||||
|
ret := bytes.Compare(n.inodes[i].key, key)
|
||||||
|
if ret == 0 {
|
||||||
|
exact = true
|
||||||
|
}
|
||||||
|
return ret != -1
|
||||||
|
})
|
||||||
|
if !exact && index > 0 {
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
c.stack[len(c.stack)-1].index = index
|
||||||
|
|
||||||
|
// Recursively search to the next page.
|
||||||
|
c.search(key, n.inodes[index].pgid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) searchPage(key []byte, p *page) {
|
||||||
|
// Binary search for the correct range.
|
||||||
|
inodes := p.branchPageElements()
|
||||||
|
|
||||||
|
var exact bool
|
||||||
|
index := sort.Search(int(p.count), func(i int) bool {
|
||||||
|
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
||||||
|
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
||||||
|
ret := bytes.Compare(inodes[i].key(), key)
|
||||||
|
if ret == 0 {
|
||||||
|
exact = true
|
||||||
|
}
|
||||||
|
return ret != -1
|
||||||
|
})
|
||||||
|
if !exact && index > 0 {
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
c.stack[len(c.stack)-1].index = index
|
||||||
|
|
||||||
|
// Recursively search to the next page.
|
||||||
|
c.search(key, inodes[index].pgid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nsearch searches the leaf node on the top of the stack for a key.
|
||||||
|
func (c *Cursor) nsearch(key []byte) {
|
||||||
|
e := &c.stack[len(c.stack)-1]
|
||||||
|
p, n := e.page, e.node
|
||||||
|
|
||||||
|
// If we have a node then search its inodes.
|
||||||
|
if n != nil {
|
||||||
|
index := sort.Search(len(n.inodes), func(i int) bool {
|
||||||
|
return bytes.Compare(n.inodes[i].key, key) != -1
|
||||||
|
})
|
||||||
|
e.index = index
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a page then search its leaf elements.
|
||||||
|
inodes := p.leafPageElements()
|
||||||
|
index := sort.Search(int(p.count), func(i int) bool {
|
||||||
|
return bytes.Compare(inodes[i].key(), key) != -1
|
||||||
|
})
|
||||||
|
e.index = index
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyValue returns the key and value of the current leaf element.
|
||||||
|
func (c *Cursor) keyValue() ([]byte, []byte, uint32) {
|
||||||
|
ref := &c.stack[len(c.stack)-1]
|
||||||
|
if ref.count() == 0 || ref.index >= ref.count() {
|
||||||
|
return nil, nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve value from node.
|
||||||
|
if ref.node != nil {
|
||||||
|
inode := &ref.node.inodes[ref.index]
|
||||||
|
return inode.key, inode.value, inode.flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or retrieve value from page.
|
||||||
|
elem := ref.page.leafPageElement(uint16(ref.index))
|
||||||
|
return elem.key(), elem.value(), elem.flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// node returns the node that the cursor is currently positioned on.
|
||||||
|
func (c *Cursor) node() *node {
|
||||||
|
_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
|
||||||
|
|
||||||
|
// If the top of the stack is a leaf node then just return it.
|
||||||
|
if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() {
|
||||||
|
return ref.node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start from root and traverse down the hierarchy.
|
||||||
|
var n = c.stack[0].node
|
||||||
|
if n == nil {
|
||||||
|
n = c.bucket.node(c.stack[0].page.id, nil)
|
||||||
|
}
|
||||||
|
for _, ref := range c.stack[:len(c.stack)-1] {
|
||||||
|
_assert(!n.isLeaf, "expected branch node")
|
||||||
|
n = n.childAt(int(ref.index))
|
||||||
|
}
|
||||||
|
_assert(n.isLeaf, "expected leaf node")
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// elemRef represents a reference to an element on a given page/node.
|
||||||
|
type elemRef struct {
|
||||||
|
page *page
|
||||||
|
node *node
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLeaf returns whether the ref is pointing at a leaf page/node.
|
||||||
|
func (r *elemRef) isLeaf() bool {
|
||||||
|
if r.node != nil {
|
||||||
|
return r.node.isLeaf
|
||||||
|
}
|
||||||
|
return (r.page.flags & leafPageFlag) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// count returns the number of inodes or page elements.
|
||||||
|
func (r *elemRef) count() int {
|
||||||
|
if r.node != nil {
|
||||||
|
return len(r.node.inodes)
|
||||||
|
}
|
||||||
|
return int(r.page.count)
|
||||||
|
}
|
993
vendor/github.com/boltdb/bolt/db.go
generated
vendored
Normal file
993
vendor/github.com/boltdb/bolt/db.go
generated
vendored
Normal file
@ -0,0 +1,993 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The largest step that can be taken when remapping the mmap.
|
||||||
|
const maxMmapStep = 1 << 30 // 1GB
|
||||||
|
|
||||||
|
// The data file format version.
|
||||||
|
const version = 2
|
||||||
|
|
||||||
|
// Represents a marker value to indicate that a file is a Bolt DB.
|
||||||
|
const magic uint32 = 0xED0CDAED
|
||||||
|
|
||||||
|
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
||||||
|
// syncing changes to a file. This is required as some operating systems,
|
||||||
|
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
||||||
|
// must be synchronized using the msync(2) syscall.
|
||||||
|
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
||||||
|
|
||||||
|
// Default values if not set in a DB instance.
|
||||||
|
const (
|
||||||
|
DefaultMaxBatchSize int = 1000
|
||||||
|
DefaultMaxBatchDelay = 10 * time.Millisecond
|
||||||
|
DefaultAllocSize = 16 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// DB represents a collection of buckets persisted to a file on disk.
|
||||||
|
// All data access is performed through transactions which can be obtained through the DB.
|
||||||
|
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
|
||||||
|
type DB struct {
|
||||||
|
// When enabled, the database will perform a Check() after every commit.
|
||||||
|
// A panic is issued if the database is in an inconsistent state. This
|
||||||
|
// flag has a large performance impact so it should only be used for
|
||||||
|
// debugging purposes.
|
||||||
|
StrictMode bool
|
||||||
|
|
||||||
|
// Setting the NoSync flag will cause the database to skip fsync()
|
||||||
|
// calls after each commit. This can be useful when bulk loading data
|
||||||
|
// into a database and you can restart the bulk load in the event of
|
||||||
|
// a system failure or database corruption. Do not set this flag for
|
||||||
|
// normal use.
|
||||||
|
//
|
||||||
|
// If the package global IgnoreNoSync constant is true, this value is
|
||||||
|
// ignored. See the comment on that constant for more details.
|
||||||
|
//
|
||||||
|
// THIS IS UNSAFE. PLEASE USE WITH CAUTION.
|
||||||
|
NoSync bool
|
||||||
|
|
||||||
|
// When true, skips the truncate call when growing the database.
|
||||||
|
// Setting this to true is only safe on non-ext3/ext4 systems.
|
||||||
|
// Skipping truncation avoids preallocation of hard drive space and
|
||||||
|
// bypasses a truncate() and fsync() syscall on remapping.
|
||||||
|
//
|
||||||
|
// https://github.com/boltdb/bolt/issues/284
|
||||||
|
NoGrowSync bool
|
||||||
|
|
||||||
|
// If you want to read the entire database fast, you can set MmapFlag to
|
||||||
|
// syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead.
|
||||||
|
MmapFlags int
|
||||||
|
|
||||||
|
// MaxBatchSize is the maximum size of a batch. Default value is
|
||||||
|
// copied from DefaultMaxBatchSize in Open.
|
||||||
|
//
|
||||||
|
// If <=0, disables batching.
|
||||||
|
//
|
||||||
|
// Do not change concurrently with calls to Batch.
|
||||||
|
MaxBatchSize int
|
||||||
|
|
||||||
|
// MaxBatchDelay is the maximum delay before a batch starts.
|
||||||
|
// Default value is copied from DefaultMaxBatchDelay in Open.
|
||||||
|
//
|
||||||
|
// If <=0, effectively disables batching.
|
||||||
|
//
|
||||||
|
// Do not change concurrently with calls to Batch.
|
||||||
|
MaxBatchDelay time.Duration
|
||||||
|
|
||||||
|
// AllocSize is the amount of space allocated when the database
|
||||||
|
// needs to create new pages. This is done to amortize the cost
|
||||||
|
// of truncate() and fsync() when growing the data file.
|
||||||
|
AllocSize int
|
||||||
|
|
||||||
|
path string
|
||||||
|
file *os.File
|
||||||
|
lockfile *os.File // windows only
|
||||||
|
dataref []byte // mmap'ed readonly, write throws SEGV
|
||||||
|
data *[maxMapSize]byte
|
||||||
|
datasz int
|
||||||
|
filesz int // current on disk file size
|
||||||
|
meta0 *meta
|
||||||
|
meta1 *meta
|
||||||
|
pageSize int
|
||||||
|
opened bool
|
||||||
|
rwtx *Tx
|
||||||
|
txs []*Tx
|
||||||
|
freelist *freelist
|
||||||
|
stats Stats
|
||||||
|
|
||||||
|
batchMu sync.Mutex
|
||||||
|
batch *batch
|
||||||
|
|
||||||
|
rwlock sync.Mutex // Allows only one writer at a time.
|
||||||
|
metalock sync.Mutex // Protects meta page access.
|
||||||
|
mmaplock sync.RWMutex // Protects mmap access during remapping.
|
||||||
|
statlock sync.RWMutex // Protects stats access.
|
||||||
|
|
||||||
|
ops struct {
|
||||||
|
writeAt func(b []byte, off int64) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read only mode.
|
||||||
|
// When true, Update() and Begin(true) return ErrDatabaseReadOnly immediately.
|
||||||
|
readOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the path to currently open database file.
|
||||||
|
func (db *DB) Path() string {
|
||||||
|
return db.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString returns the Go string representation of the database.
|
||||||
|
func (db *DB) GoString() string {
|
||||||
|
return fmt.Sprintf("bolt.DB{path:%q}", db.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the database.
|
||||||
|
func (db *DB) String() string {
|
||||||
|
return fmt.Sprintf("DB<%q>", db.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open creates and opens a database at the given path.
|
||||||
|
// If the file does not exist then it will be created automatically.
|
||||||
|
// Passing in nil options will cause Bolt to open the database with the default options.
|
||||||
|
func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||||
|
var db = &DB{opened: true}
|
||||||
|
|
||||||
|
// Set default options if no options are provided.
|
||||||
|
if options == nil {
|
||||||
|
options = DefaultOptions
|
||||||
|
}
|
||||||
|
db.NoGrowSync = options.NoGrowSync
|
||||||
|
db.MmapFlags = options.MmapFlags
|
||||||
|
|
||||||
|
// Set default values for later DB operations.
|
||||||
|
db.MaxBatchSize = DefaultMaxBatchSize
|
||||||
|
db.MaxBatchDelay = DefaultMaxBatchDelay
|
||||||
|
db.AllocSize = DefaultAllocSize
|
||||||
|
|
||||||
|
flag := os.O_RDWR
|
||||||
|
if options.ReadOnly {
|
||||||
|
flag = os.O_RDONLY
|
||||||
|
db.readOnly = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open data file and separate sync handler for metadata writes.
|
||||||
|
db.path = path
|
||||||
|
var err error
|
||||||
|
if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil {
|
||||||
|
_ = db.close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock file so that other processes using Bolt in read-write mode cannot
|
||||||
|
// use the database at the same time. This would cause corruption since
|
||||||
|
// the two processes would write meta pages and free pages separately.
|
||||||
|
// The database file is locked exclusively (only one process can grab the lock)
|
||||||
|
// if !options.ReadOnly.
|
||||||
|
// The database file is locked using the shared lock (more than one process may
|
||||||
|
// hold a lock at the same time) otherwise (options.ReadOnly is set).
|
||||||
|
if err := flock(db, mode, !db.readOnly, options.Timeout); err != nil {
|
||||||
|
_ = db.close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default values for test hooks
|
||||||
|
db.ops.writeAt = db.file.WriteAt
|
||||||
|
|
||||||
|
// Initialize the database if it doesn't exist.
|
||||||
|
if info, err := db.file.Stat(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if info.Size() == 0 {
|
||||||
|
// Initialize new files with meta pages.
|
||||||
|
if err := db.init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Read the first meta page to determine the page size.
|
||||||
|
var buf [0x1000]byte
|
||||||
|
if _, err := db.file.ReadAt(buf[:], 0); err == nil {
|
||||||
|
m := db.pageInBuffer(buf[:], 0).meta()
|
||||||
|
if err := m.validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db.pageSize = int(m.pageSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory map the data file.
|
||||||
|
if err := db.mmap(options.InitialMmapSize); err != nil {
|
||||||
|
_ = db.close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in the freelist.
|
||||||
|
db.freelist = newFreelist()
|
||||||
|
db.freelist.read(db.page(db.meta().freelist))
|
||||||
|
|
||||||
|
// Mark the database as opened and return.
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmap opens the underlying memory-mapped file and initializes the meta references.
|
||||||
|
// minsz is the minimum size that the new mmap can be.
|
||||||
|
func (db *DB) mmap(minsz int) error {
|
||||||
|
db.mmaplock.Lock()
|
||||||
|
defer db.mmaplock.Unlock()
|
||||||
|
|
||||||
|
info, err := db.file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("mmap stat error: %s", err)
|
||||||
|
} else if int(info.Size()) < db.pageSize*2 {
|
||||||
|
return fmt.Errorf("file size too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the size is at least the minimum size.
|
||||||
|
var size = int(info.Size())
|
||||||
|
if size < minsz {
|
||||||
|
size = minsz
|
||||||
|
}
|
||||||
|
size, err = db.mmapSize(size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dereference all mmap references before unmapping.
|
||||||
|
if db.rwtx != nil {
|
||||||
|
db.rwtx.root.dereference()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap existing data before continuing.
|
||||||
|
if err := db.munmap(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory-map the data file as a byte slice.
|
||||||
|
if err := mmap(db, size); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save references to the meta pages.
|
||||||
|
db.meta0 = db.page(0).meta()
|
||||||
|
db.meta1 = db.page(1).meta()
|
||||||
|
|
||||||
|
// Validate the meta pages.
|
||||||
|
if err := db.meta0.validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := db.meta1.validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// munmap unmaps the data file from memory.
|
||||||
|
func (db *DB) munmap() error {
|
||||||
|
if err := munmap(db); err != nil {
|
||||||
|
return fmt.Errorf("unmap error: " + err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmapSize determines the appropriate size for the mmap given the current size
|
||||||
|
// of the database. The minimum size is 32KB and doubles until it reaches 1GB.
|
||||||
|
// Returns an error if the new mmap size is greater than the max allowed.
|
||||||
|
func (db *DB) mmapSize(size int) (int, error) {
|
||||||
|
// Double the size from 32KB until 1GB.
|
||||||
|
for i := uint(15); i <= 30; i++ {
|
||||||
|
if size <= 1<<i {
|
||||||
|
return 1 << i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the requested size is not above the maximum allowed.
|
||||||
|
if size > maxMapSize {
|
||||||
|
return 0, fmt.Errorf("mmap too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If larger than 1GB then grow by 1GB at a time.
|
||||||
|
sz := int64(size)
|
||||||
|
if remainder := sz % int64(maxMmapStep); remainder > 0 {
|
||||||
|
sz += int64(maxMmapStep) - remainder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the mmap size is a multiple of the page size.
|
||||||
|
// This should always be true since we're incrementing in MBs.
|
||||||
|
pageSize := int64(db.pageSize)
|
||||||
|
if (sz % pageSize) != 0 {
|
||||||
|
sz = ((sz / pageSize) + 1) * pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've exceeded the max size then only grow up to the max size.
|
||||||
|
if sz > maxMapSize {
|
||||||
|
sz = maxMapSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(sz), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// init creates a new database file and initializes its meta pages.
|
||||||
|
func (db *DB) init() error {
|
||||||
|
// Set the page size to the OS page size.
|
||||||
|
db.pageSize = os.Getpagesize()
|
||||||
|
|
||||||
|
// Create two meta pages on a buffer.
|
||||||
|
buf := make([]byte, db.pageSize*4)
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
p := db.pageInBuffer(buf[:], pgid(i))
|
||||||
|
p.id = pgid(i)
|
||||||
|
p.flags = metaPageFlag
|
||||||
|
|
||||||
|
// Initialize the meta page.
|
||||||
|
m := p.meta()
|
||||||
|
m.magic = magic
|
||||||
|
m.version = version
|
||||||
|
m.pageSize = uint32(db.pageSize)
|
||||||
|
m.freelist = 2
|
||||||
|
m.root = bucket{root: 3}
|
||||||
|
m.pgid = 4
|
||||||
|
m.txid = txid(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write an empty freelist at page 3.
|
||||||
|
p := db.pageInBuffer(buf[:], pgid(2))
|
||||||
|
p.id = pgid(2)
|
||||||
|
p.flags = freelistPageFlag
|
||||||
|
p.count = 0
|
||||||
|
|
||||||
|
// Write an empty leaf page at page 4.
|
||||||
|
p = db.pageInBuffer(buf[:], pgid(3))
|
||||||
|
p.id = pgid(3)
|
||||||
|
p.flags = leafPageFlag
|
||||||
|
p.count = 0
|
||||||
|
|
||||||
|
// Write the buffer to our data file.
|
||||||
|
if _, err := db.ops.writeAt(buf, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fdatasync(db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases all database resources.
|
||||||
|
// All transactions must be closed before closing the database.
|
||||||
|
func (db *DB) Close() error {
|
||||||
|
db.rwlock.Lock()
|
||||||
|
defer db.rwlock.Unlock()
|
||||||
|
|
||||||
|
db.metalock.Lock()
|
||||||
|
defer db.metalock.Unlock()
|
||||||
|
|
||||||
|
db.mmaplock.RLock()
|
||||||
|
defer db.mmaplock.RUnlock()
|
||||||
|
|
||||||
|
return db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) close() error {
|
||||||
|
if !db.opened {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
db.opened = false
|
||||||
|
|
||||||
|
db.freelist = nil
|
||||||
|
db.path = ""
|
||||||
|
|
||||||
|
// Clear ops.
|
||||||
|
db.ops.writeAt = nil
|
||||||
|
|
||||||
|
// Close the mmap.
|
||||||
|
if err := db.munmap(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close file handles.
|
||||||
|
if db.file != nil {
|
||||||
|
// No need to unlock read-only file.
|
||||||
|
if !db.readOnly {
|
||||||
|
// Unlock the file.
|
||||||
|
if err := funlock(db); err != nil {
|
||||||
|
log.Printf("bolt.Close(): funlock error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the file descriptor.
|
||||||
|
if err := db.file.Close(); err != nil {
|
||||||
|
return fmt.Errorf("db file close: %s", err)
|
||||||
|
}
|
||||||
|
db.file = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin starts a new transaction.
|
||||||
|
// Multiple read-only transactions can be used concurrently but only one
|
||||||
|
// write transaction can be used at a time. Starting multiple write transactions
|
||||||
|
// will cause the calls to block and be serialized until the current write
|
||||||
|
// transaction finishes.
|
||||||
|
//
|
||||||
|
// Transactions should not be dependent on one another. Opening a read
|
||||||
|
// transaction and a write transaction in the same goroutine can cause the
|
||||||
|
// writer to deadlock because the database periodically needs to re-mmap itself
|
||||||
|
// as it grows and it cannot do that while a read transaction is open.
|
||||||
|
//
|
||||||
|
// If a long running read transaction (for example, a snapshot transaction) is
|
||||||
|
// needed, you might want to set DB.InitialMmapSize to a large enough value
|
||||||
|
// to avoid potential blocking of write transaction.
|
||||||
|
//
|
||||||
|
// IMPORTANT: You must close read-only transactions after you are finished or
|
||||||
|
// else the database will not reclaim old pages.
|
||||||
|
func (db *DB) Begin(writable bool) (*Tx, error) {
|
||||||
|
if writable {
|
||||||
|
return db.beginRWTx()
|
||||||
|
}
|
||||||
|
return db.beginTx()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) beginTx() (*Tx, error) {
|
||||||
|
// Lock the meta pages while we initialize the transaction. We obtain
|
||||||
|
// the meta lock before the mmap lock because that's the order that the
|
||||||
|
// write transaction will obtain them.
|
||||||
|
db.metalock.Lock()
|
||||||
|
|
||||||
|
// Obtain a read-only lock on the mmap. When the mmap is remapped it will
|
||||||
|
// obtain a write lock so all transactions must finish before it can be
|
||||||
|
// remapped.
|
||||||
|
db.mmaplock.RLock()
|
||||||
|
|
||||||
|
// Exit if the database is not open yet.
|
||||||
|
if !db.opened {
|
||||||
|
db.mmaplock.RUnlock()
|
||||||
|
db.metalock.Unlock()
|
||||||
|
return nil, ErrDatabaseNotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a transaction associated with the database.
|
||||||
|
t := &Tx{}
|
||||||
|
t.init(db)
|
||||||
|
|
||||||
|
// Keep track of transaction until it closes.
|
||||||
|
db.txs = append(db.txs, t)
|
||||||
|
n := len(db.txs)
|
||||||
|
|
||||||
|
// Unlock the meta pages.
|
||||||
|
db.metalock.Unlock()
|
||||||
|
|
||||||
|
// Update the transaction stats.
|
||||||
|
db.statlock.Lock()
|
||||||
|
db.stats.TxN++
|
||||||
|
db.stats.OpenTxN = n
|
||||||
|
db.statlock.Unlock()
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) beginRWTx() (*Tx, error) {
|
||||||
|
// If the database was opened with Options.ReadOnly, return an error.
|
||||||
|
if db.readOnly {
|
||||||
|
return nil, ErrDatabaseReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtain writer lock. This is released by the transaction when it closes.
|
||||||
|
// This enforces only one writer transaction at a time.
|
||||||
|
db.rwlock.Lock()
|
||||||
|
|
||||||
|
// Once we have the writer lock then we can lock the meta pages so that
|
||||||
|
// we can set up the transaction.
|
||||||
|
db.metalock.Lock()
|
||||||
|
defer db.metalock.Unlock()
|
||||||
|
|
||||||
|
// Exit if the database is not open yet.
|
||||||
|
if !db.opened {
|
||||||
|
db.rwlock.Unlock()
|
||||||
|
return nil, ErrDatabaseNotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a transaction associated with the database.
|
||||||
|
t := &Tx{writable: true}
|
||||||
|
t.init(db)
|
||||||
|
db.rwtx = t
|
||||||
|
|
||||||
|
// Free any pages associated with closed read-only transactions.
|
||||||
|
var minid txid = 0xFFFFFFFFFFFFFFFF
|
||||||
|
for _, t := range db.txs {
|
||||||
|
if t.meta.txid < minid {
|
||||||
|
minid = t.meta.txid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if minid > 0 {
|
||||||
|
db.freelist.release(minid - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeTx removes a transaction from the database.
|
||||||
|
func (db *DB) removeTx(tx *Tx) {
|
||||||
|
// Release the read lock on the mmap.
|
||||||
|
db.mmaplock.RUnlock()
|
||||||
|
|
||||||
|
// Use the meta lock to restrict access to the DB object.
|
||||||
|
db.metalock.Lock()
|
||||||
|
|
||||||
|
// Remove the transaction.
|
||||||
|
for i, t := range db.txs {
|
||||||
|
if t == tx {
|
||||||
|
db.txs = append(db.txs[:i], db.txs[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n := len(db.txs)
|
||||||
|
|
||||||
|
// Unlock the meta pages.
|
||||||
|
db.metalock.Unlock()
|
||||||
|
|
||||||
|
// Merge statistics.
|
||||||
|
db.statlock.Lock()
|
||||||
|
db.stats.OpenTxN = n
|
||||||
|
db.stats.TxStats.add(&tx.stats)
|
||||||
|
db.statlock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update executes a function within the context of a read-write managed transaction.
|
||||||
|
// If no error is returned from the function then the transaction is committed.
|
||||||
|
// If an error is returned then the entire transaction is rolled back.
|
||||||
|
// Any error that is returned from the function or returned from the commit is
|
||||||
|
// returned from the Update() method.
|
||||||
|
//
|
||||||
|
// Attempting to manually commit or rollback within the function will cause a panic.
|
||||||
|
func (db *DB) Update(fn func(*Tx) error) error {
|
||||||
|
t, err := db.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the transaction rolls back in the event of a panic.
|
||||||
|
defer func() {
|
||||||
|
if t.db != nil {
|
||||||
|
t.rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Mark as a managed tx so that the inner function cannot manually commit.
|
||||||
|
t.managed = true
|
||||||
|
|
||||||
|
// If an error is returned from the function then rollback and return error.
|
||||||
|
err = fn(t)
|
||||||
|
t.managed = false
|
||||||
|
if err != nil {
|
||||||
|
_ = t.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// View executes a function within the context of a managed read-only transaction.
|
||||||
|
// Any error that is returned from the function is returned from the View() method.
|
||||||
|
//
|
||||||
|
// Attempting to manually rollback within the function will cause a panic.
|
||||||
|
func (db *DB) View(fn func(*Tx) error) error {
|
||||||
|
t, err := db.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the transaction rolls back in the event of a panic.
|
||||||
|
defer func() {
|
||||||
|
if t.db != nil {
|
||||||
|
t.rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Mark as a managed tx so that the inner function cannot manually rollback.
|
||||||
|
t.managed = true
|
||||||
|
|
||||||
|
// If an error is returned from the function then pass it through.
|
||||||
|
err = fn(t)
|
||||||
|
t.managed = false
|
||||||
|
if err != nil {
|
||||||
|
_ = t.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.Rollback(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch calls fn as part of a batch. It behaves similar to Update,
|
||||||
|
// except:
|
||||||
|
//
|
||||||
|
// 1. concurrent Batch calls can be combined into a single Bolt
|
||||||
|
// transaction.
|
||||||
|
//
|
||||||
|
// 2. the function passed to Batch may be called multiple times,
|
||||||
|
// regardless of whether it returns error or not.
|
||||||
|
//
|
||||||
|
// This means that Batch function side effects must be idempotent and
|
||||||
|
// take permanent effect only after a successful return is seen in
|
||||||
|
// caller.
|
||||||
|
//
|
||||||
|
// The maximum batch size and delay can be adjusted with DB.MaxBatchSize
|
||||||
|
// and DB.MaxBatchDelay, respectively.
|
||||||
|
//
|
||||||
|
// Batch is only useful when there are multiple goroutines calling it.
|
||||||
|
func (db *DB) Batch(fn func(*Tx) error) error {
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
|
||||||
|
db.batchMu.Lock()
|
||||||
|
if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) {
|
||||||
|
// There is no existing batch, or the existing batch is full; start a new one.
|
||||||
|
db.batch = &batch{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger)
|
||||||
|
}
|
||||||
|
db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh})
|
||||||
|
if len(db.batch.calls) >= db.MaxBatchSize {
|
||||||
|
// wake up batch, it's ready to run
|
||||||
|
go db.batch.trigger()
|
||||||
|
}
|
||||||
|
db.batchMu.Unlock()
|
||||||
|
|
||||||
|
err := <-errCh
|
||||||
|
if err == trySolo {
|
||||||
|
err = db.Update(fn)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type call struct {
|
||||||
|
fn func(*Tx) error
|
||||||
|
err chan<- error
|
||||||
|
}
|
||||||
|
|
||||||
|
type batch struct {
|
||||||
|
db *DB
|
||||||
|
timer *time.Timer
|
||||||
|
start sync.Once
|
||||||
|
calls []call
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger runs the batch if it hasn't already been run.
|
||||||
|
func (b *batch) trigger() {
|
||||||
|
b.start.Do(b.run)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run performs the transactions in the batch and communicates results
|
||||||
|
// back to DB.Batch.
|
||||||
|
func (b *batch) run() {
|
||||||
|
b.db.batchMu.Lock()
|
||||||
|
b.timer.Stop()
|
||||||
|
// Make sure no new work is added to this batch, but don't break
|
||||||
|
// other batches.
|
||||||
|
if b.db.batch == b {
|
||||||
|
b.db.batch = nil
|
||||||
|
}
|
||||||
|
b.db.batchMu.Unlock()
|
||||||
|
|
||||||
|
retry:
|
||||||
|
for len(b.calls) > 0 {
|
||||||
|
var failIdx = -1
|
||||||
|
err := b.db.Update(func(tx *Tx) error {
|
||||||
|
for i, c := range b.calls {
|
||||||
|
if err := safelyCall(c.fn, tx); err != nil {
|
||||||
|
failIdx = i
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if failIdx >= 0 {
|
||||||
|
// take the failing transaction out of the batch. it's
|
||||||
|
// safe to shorten b.calls here because db.batch no longer
|
||||||
|
// points to us, and we hold the mutex anyway.
|
||||||
|
c := b.calls[failIdx]
|
||||||
|
b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1]
|
||||||
|
// tell the submitter re-run it solo, continue with the rest of the batch
|
||||||
|
c.err <- trySolo
|
||||||
|
continue retry
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass success, or bolt internal errors, to all callers
|
||||||
|
for _, c := range b.calls {
|
||||||
|
if c.err != nil {
|
||||||
|
c.err <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trySolo is a special sentinel error value used for signaling that a
|
||||||
|
// transaction function should be re-run. It should never be seen by
|
||||||
|
// callers.
|
||||||
|
var trySolo = errors.New("batch function returned an error and should be re-run solo")
|
||||||
|
|
||||||
|
type panicked struct {
|
||||||
|
reason interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p panicked) Error() string {
|
||||||
|
if err, ok := p.reason.(error); ok {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("panic: %v", p.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
err = panicked{p}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return fn(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync executes fdatasync() against the database file handle.
|
||||||
|
//
|
||||||
|
// This is not necessary under normal operation, however, if you use NoSync
|
||||||
|
// then it allows you to force the database file to sync against the disk.
|
||||||
|
func (db *DB) Sync() error { return fdatasync(db) }
|
||||||
|
|
||||||
|
// Stats retrieves ongoing performance stats for the database.
|
||||||
|
// This is only updated when a transaction closes.
|
||||||
|
func (db *DB) Stats() Stats {
|
||||||
|
db.statlock.RLock()
|
||||||
|
defer db.statlock.RUnlock()
|
||||||
|
return db.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for internal access to the raw data bytes from the C cursor, use
|
||||||
|
// carefully, or not at all.
|
||||||
|
func (db *DB) Info() *Info {
|
||||||
|
return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize}
|
||||||
|
}
|
||||||
|
|
||||||
|
// page retrieves a page reference from the mmap based on the current page size.
|
||||||
|
func (db *DB) page(id pgid) *page {
|
||||||
|
pos := id * pgid(db.pageSize)
|
||||||
|
return (*page)(unsafe.Pointer(&db.data[pos]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pageInBuffer retrieves a page reference from a given byte array based on the current page size.
|
||||||
|
func (db *DB) pageInBuffer(b []byte, id pgid) *page {
|
||||||
|
return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// meta retrieves the current meta page reference.
|
||||||
|
func (db *DB) meta() *meta {
|
||||||
|
if db.meta0.txid > db.meta1.txid {
|
||||||
|
return db.meta0
|
||||||
|
}
|
||||||
|
return db.meta1
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate returns a contiguous block of memory starting at a given page.
|
||||||
|
func (db *DB) allocate(count int) (*page, error) {
|
||||||
|
// Allocate a temporary buffer for the page.
|
||||||
|
buf := make([]byte, count*db.pageSize)
|
||||||
|
p := (*page)(unsafe.Pointer(&buf[0]))
|
||||||
|
p.overflow = uint32(count - 1)
|
||||||
|
|
||||||
|
// Use pages from the freelist if they are available.
|
||||||
|
if p.id = db.freelist.allocate(count); p.id != 0 {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize mmap() if we're at the end.
|
||||||
|
p.id = db.rwtx.meta.pgid
|
||||||
|
var minsz = int((p.id+pgid(count))+1) * db.pageSize
|
||||||
|
if minsz >= db.datasz {
|
||||||
|
if err := db.mmap(minsz); err != nil {
|
||||||
|
return nil, fmt.Errorf("mmap allocate error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the page id high water mark.
|
||||||
|
db.rwtx.meta.pgid += pgid(count)
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// grow grows the size of the database to the given sz.
|
||||||
|
func (db *DB) grow(sz int) error {
|
||||||
|
// Ignore if the new size is less than available file size.
|
||||||
|
if sz <= db.filesz {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the data is smaller than the alloc size then only allocate what's needed.
|
||||||
|
// Once it goes over the allocation size then allocate in chunks.
|
||||||
|
if db.datasz < db.AllocSize {
|
||||||
|
sz = db.datasz
|
||||||
|
} else {
|
||||||
|
sz += db.AllocSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate and fsync to ensure file size metadata is flushed.
|
||||||
|
// https://github.com/boltdb/bolt/issues/284
|
||||||
|
if !db.NoGrowSync && !db.readOnly {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||||
|
return fmt.Errorf("file resize error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := db.file.Sync(); err != nil {
|
||||||
|
return fmt.Errorf("file sync error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.filesz = sz
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) IsReadOnly() bool {
|
||||||
|
return db.readOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options represents the options that can be set when opening a database.
|
||||||
|
type Options struct {
|
||||||
|
// Timeout is the amount of time to wait to obtain a file lock.
|
||||||
|
// When set to zero it will wait indefinitely. This option is only
|
||||||
|
// available on Darwin and Linux.
|
||||||
|
Timeout time.Duration
|
||||||
|
|
||||||
|
// Sets the DB.NoGrowSync flag before memory mapping the file.
|
||||||
|
NoGrowSync bool
|
||||||
|
|
||||||
|
// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
|
||||||
|
// grab a shared lock (UNIX).
|
||||||
|
ReadOnly bool
|
||||||
|
|
||||||
|
// Sets the DB.MmapFlags flag before memory mapping the file.
|
||||||
|
MmapFlags int
|
||||||
|
|
||||||
|
// InitialMmapSize is the initial mmap size of the database
|
||||||
|
// in bytes. Read transactions won't block write transaction
|
||||||
|
// if the InitialMmapSize is large enough to hold database mmap
|
||||||
|
// size. (See DB.Begin for more information)
|
||||||
|
//
|
||||||
|
// If <=0, the initial map size is 0.
|
||||||
|
// If initialMmapSize is smaller than the previous database size,
|
||||||
|
// it takes no effect.
|
||||||
|
InitialMmapSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOptions represent the options used if nil options are passed into Open().
|
||||||
|
// No timeout is used which will cause Bolt to wait indefinitely for a lock.
|
||||||
|
var DefaultOptions = &Options{
|
||||||
|
Timeout: 0,
|
||||||
|
NoGrowSync: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats represents statistics about the database.
|
||||||
|
type Stats struct {
|
||||||
|
// Freelist stats
|
||||||
|
FreePageN int // total number of free pages on the freelist
|
||||||
|
PendingPageN int // total number of pending pages on the freelist
|
||||||
|
FreeAlloc int // total bytes allocated in free pages
|
||||||
|
FreelistInuse int // total bytes used by the freelist
|
||||||
|
|
||||||
|
// Transaction stats
|
||||||
|
TxN int // total number of started read transactions
|
||||||
|
OpenTxN int // number of currently open read transactions
|
||||||
|
|
||||||
|
TxStats TxStats // global, ongoing stats.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub calculates and returns the difference between two sets of database stats.
|
||||||
|
// This is useful when obtaining stats at two different points and time and
|
||||||
|
// you need the performance counters that occurred within that time span.
|
||||||
|
func (s *Stats) Sub(other *Stats) Stats {
|
||||||
|
if other == nil {
|
||||||
|
return *s
|
||||||
|
}
|
||||||
|
var diff Stats
|
||||||
|
diff.FreePageN = s.FreePageN
|
||||||
|
diff.PendingPageN = s.PendingPageN
|
||||||
|
diff.FreeAlloc = s.FreeAlloc
|
||||||
|
diff.FreelistInuse = s.FreelistInuse
|
||||||
|
diff.TxN = other.TxN - s.TxN
|
||||||
|
diff.TxStats = s.TxStats.Sub(&other.TxStats)
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) add(other *Stats) {
|
||||||
|
s.TxStats.add(&other.TxStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Data uintptr
|
||||||
|
PageSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
type meta struct {
|
||||||
|
magic uint32
|
||||||
|
version uint32
|
||||||
|
pageSize uint32
|
||||||
|
flags uint32
|
||||||
|
root bucket
|
||||||
|
freelist pgid
|
||||||
|
pgid pgid
|
||||||
|
txid txid
|
||||||
|
checksum uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate checks the marker bytes and version of the meta page to ensure it matches this binary.
|
||||||
|
func (m *meta) validate() error {
|
||||||
|
if m.checksum != 0 && m.checksum != m.sum64() {
|
||||||
|
return ErrChecksum
|
||||||
|
} else if m.magic != magic {
|
||||||
|
return ErrInvalid
|
||||||
|
} else if m.version != version {
|
||||||
|
return ErrVersionMismatch
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy copies one meta object to another.
|
||||||
|
func (m *meta) copy(dest *meta) {
|
||||||
|
*dest = *m
|
||||||
|
}
|
||||||
|
|
||||||
|
// write writes the meta onto a page.
|
||||||
|
func (m *meta) write(p *page) {
|
||||||
|
if m.root.root >= m.pgid {
|
||||||
|
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
|
||||||
|
} else if m.freelist >= m.pgid {
|
||||||
|
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
|
||||||
|
p.id = pgid(m.txid % 2)
|
||||||
|
p.flags |= metaPageFlag
|
||||||
|
|
||||||
|
// Calculate the checksum.
|
||||||
|
m.checksum = m.sum64()
|
||||||
|
|
||||||
|
m.copy(p.meta())
|
||||||
|
}
|
||||||
|
|
||||||
|
// generates the checksum for the meta.
|
||||||
|
func (m *meta) sum64() uint64 {
|
||||||
|
var h = fnv.New64a()
|
||||||
|
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// _assert will panic with a given formatted message if the given condition is false.
|
||||||
|
func _assert(condition bool, msg string, v ...interface{}) {
|
||||||
|
if !condition {
|
||||||
|
panic(fmt.Sprintf("assertion failed: "+msg, v...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) }
|
||||||
|
func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) }
|
||||||
|
|
||||||
|
func printstack() {
|
||||||
|
stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n")
|
||||||
|
fmt.Fprintln(os.Stderr, stack)
|
||||||
|
}
|
44
vendor/github.com/boltdb/bolt/doc.go
generated
vendored
Normal file
44
vendor/github.com/boltdb/bolt/doc.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Package bolt implements a low-level key/value store in pure Go. It supports
|
||||||
|
fully serializable transactions, ACID semantics, and lock-free MVCC with
|
||||||
|
multiple readers and a single writer. Bolt can be used for projects that
|
||||||
|
want a simple data store without the need to add large dependencies such as
|
||||||
|
Postgres or MySQL.
|
||||||
|
|
||||||
|
Bolt is a single-level, zero-copy, B+tree data store. This means that Bolt is
|
||||||
|
optimized for fast read access and does not require recovery in the event of a
|
||||||
|
system crash. Transactions which have not finished committing will simply be
|
||||||
|
rolled back in the event of a crash.
|
||||||
|
|
||||||
|
The design of Bolt is based on Howard Chu's LMDB database project.
|
||||||
|
|
||||||
|
Bolt currently works on Windows, Mac OS X, and Linux.
|
||||||
|
|
||||||
|
|
||||||
|
Basics
|
||||||
|
|
||||||
|
There are only a few types in Bolt: DB, Bucket, Tx, and Cursor. The DB is
|
||||||
|
a collection of buckets and is represented by a single file on disk. A bucket is
|
||||||
|
a collection of unique keys that are associated with values.
|
||||||
|
|
||||||
|
Transactions provide either read-only or read-write access to the database.
|
||||||
|
Read-only transactions can retrieve key/value pairs and can use Cursors to
|
||||||
|
iterate over the dataset sequentially. Read-write transactions can create and
|
||||||
|
delete buckets and can insert and remove keys. Only one read-write transaction
|
||||||
|
is allowed at a time.
|
||||||
|
|
||||||
|
|
||||||
|
Caveats
|
||||||
|
|
||||||
|
The database uses a read-only, memory-mapped data file to ensure that
|
||||||
|
applications cannot corrupt the database, however, this means that keys and
|
||||||
|
values returned from Bolt cannot be changed. Writing to a read-only byte slice
|
||||||
|
will cause Go to panic.
|
||||||
|
|
||||||
|
Keys and values retrieved from the database are only valid for the life of
|
||||||
|
the transaction. When used outside the transaction, these byte slices can
|
||||||
|
point to different data or can point to invalid memory which will cause a panic.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
package bolt
|
70
vendor/github.com/boltdb/bolt/errors.go
generated
vendored
Normal file
70
vendor/github.com/boltdb/bolt/errors.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// These errors can be returned when opening or calling methods on a DB.
|
||||||
|
var (
|
||||||
|
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
|
||||||
|
// is opened or after it is closed.
|
||||||
|
ErrDatabaseNotOpen = errors.New("database not open")
|
||||||
|
|
||||||
|
// ErrDatabaseOpen is returned when opening a database that is
|
||||||
|
// already open.
|
||||||
|
ErrDatabaseOpen = errors.New("database already open")
|
||||||
|
|
||||||
|
// ErrInvalid is returned when a data file is not a Bolt-formatted database.
|
||||||
|
ErrInvalid = errors.New("invalid database")
|
||||||
|
|
||||||
|
// ErrVersionMismatch is returned when the data file was created with a
|
||||||
|
// different version of Bolt.
|
||||||
|
ErrVersionMismatch = errors.New("version mismatch")
|
||||||
|
|
||||||
|
// ErrChecksum is returned when either meta page checksum does not match.
|
||||||
|
ErrChecksum = errors.New("checksum error")
|
||||||
|
|
||||||
|
// ErrTimeout is returned when a database cannot obtain an exclusive lock
|
||||||
|
// on the data file after the timeout passed to Open().
|
||||||
|
ErrTimeout = errors.New("timeout")
|
||||||
|
)
|
||||||
|
|
||||||
|
// These errors can occur when beginning or committing a Tx.
|
||||||
|
var (
|
||||||
|
// ErrTxNotWritable is returned when performing a write operation on a
|
||||||
|
// read-only transaction.
|
||||||
|
ErrTxNotWritable = errors.New("tx not writable")
|
||||||
|
|
||||||
|
// ErrTxClosed is returned when committing or rolling back a transaction
|
||||||
|
// that has already been committed or rolled back.
|
||||||
|
ErrTxClosed = errors.New("tx closed")
|
||||||
|
|
||||||
|
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
|
||||||
|
// read-only database.
|
||||||
|
ErrDatabaseReadOnly = errors.New("database is in read-only mode")
|
||||||
|
)
|
||||||
|
|
||||||
|
// These errors can occur when putting or deleting a value or a bucket.
|
||||||
|
var (
|
||||||
|
// ErrBucketNotFound is returned when trying to access a bucket that has
|
||||||
|
// not been created yet.
|
||||||
|
ErrBucketNotFound = errors.New("bucket not found")
|
||||||
|
|
||||||
|
// ErrBucketExists is returned when creating a bucket that already exists.
|
||||||
|
ErrBucketExists = errors.New("bucket already exists")
|
||||||
|
|
||||||
|
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
|
||||||
|
ErrBucketNameRequired = errors.New("bucket name required")
|
||||||
|
|
||||||
|
// ErrKeyRequired is returned when inserting a zero-length key.
|
||||||
|
ErrKeyRequired = errors.New("key required")
|
||||||
|
|
||||||
|
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
|
||||||
|
ErrKeyTooLarge = errors.New("key too large")
|
||||||
|
|
||||||
|
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
|
||||||
|
ErrValueTooLarge = errors.New("value too large")
|
||||||
|
|
||||||
|
// ErrIncompatibleValue is returned when trying create or delete a bucket
|
||||||
|
// on an existing non-bucket key or when trying to create or delete a
|
||||||
|
// non-bucket key on an existing bucket key.
|
||||||
|
ErrIncompatibleValue = errors.New("incompatible value")
|
||||||
|
)
|
242
vendor/github.com/boltdb/bolt/freelist.go
generated
vendored
Normal file
242
vendor/github.com/boltdb/bolt/freelist.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// freelist represents a list of all pages that are available for allocation.
|
||||||
|
// It also tracks pages that have been freed but are still in use by open transactions.
|
||||||
|
type freelist struct {
|
||||||
|
ids []pgid // all free and available free page ids.
|
||||||
|
pending map[txid][]pgid // mapping of soon-to-be free page ids by tx.
|
||||||
|
cache map[pgid]bool // fast lookup of all free and pending page ids.
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFreelist returns an empty, initialized freelist.
|
||||||
|
func newFreelist() *freelist {
|
||||||
|
return &freelist{
|
||||||
|
pending: make(map[txid][]pgid),
|
||||||
|
cache: make(map[pgid]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// size returns the size of the page after serialization.
|
||||||
|
func (f *freelist) size() int {
|
||||||
|
return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * f.count())
|
||||||
|
}
|
||||||
|
|
||||||
|
// count returns count of pages on the freelist
|
||||||
|
func (f *freelist) count() int {
|
||||||
|
return f.free_count() + f.pending_count()
|
||||||
|
}
|
||||||
|
|
||||||
|
// free_count returns count of free pages
|
||||||
|
func (f *freelist) free_count() int {
|
||||||
|
return len(f.ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pending_count returns count of pending pages
|
||||||
|
func (f *freelist) pending_count() int {
|
||||||
|
var count int
|
||||||
|
for _, list := range f.pending {
|
||||||
|
count += len(list)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// all returns a list of all free ids and all pending ids in one sorted list.
|
||||||
|
func (f *freelist) all() []pgid {
|
||||||
|
m := make(pgids, 0)
|
||||||
|
|
||||||
|
for _, list := range f.pending {
|
||||||
|
m = append(m, list...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(m)
|
||||||
|
return pgids(f.ids).merge(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate returns the starting page id of a contiguous list of pages of a given size.
|
||||||
|
// If a contiguous block cannot be found then 0 is returned.
|
||||||
|
func (f *freelist) allocate(n int) pgid {
|
||||||
|
if len(f.ids) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var initial, previd pgid
|
||||||
|
for i, id := range f.ids {
|
||||||
|
if id <= 1 {
|
||||||
|
panic(fmt.Sprintf("invalid page allocation: %d", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset initial page if this is not contiguous.
|
||||||
|
if previd == 0 || id-previd != 1 {
|
||||||
|
initial = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found a contiguous block then remove it and return it.
|
||||||
|
if (id-initial)+1 == pgid(n) {
|
||||||
|
// If we're allocating off the beginning then take the fast path
|
||||||
|
// and just adjust the existing slice. This will use extra memory
|
||||||
|
// temporarily but the append() in free() will realloc the slice
|
||||||
|
// as is necessary.
|
||||||
|
if (i + 1) == n {
|
||||||
|
f.ids = f.ids[i+1:]
|
||||||
|
} else {
|
||||||
|
copy(f.ids[i-n+1:], f.ids[i+1:])
|
||||||
|
f.ids = f.ids[:len(f.ids)-n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from the free cache.
|
||||||
|
for i := pgid(0); i < pgid(n); i++ {
|
||||||
|
delete(f.cache, initial+i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return initial
|
||||||
|
}
|
||||||
|
|
||||||
|
previd = id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// free releases a page and its overflow for a given transaction id.
|
||||||
|
// If the page is already free then a panic will occur.
|
||||||
|
func (f *freelist) free(txid txid, p *page) {
|
||||||
|
if p.id <= 1 {
|
||||||
|
panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free page and all its overflow pages.
|
||||||
|
var ids = f.pending[txid]
|
||||||
|
for id := p.id; id <= p.id+pgid(p.overflow); id++ {
|
||||||
|
// Verify that page is not already free.
|
||||||
|
if f.cache[id] {
|
||||||
|
panic(fmt.Sprintf("page %d already freed", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the freelist and cache.
|
||||||
|
ids = append(ids, id)
|
||||||
|
f.cache[id] = true
|
||||||
|
}
|
||||||
|
f.pending[txid] = ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// release moves all page ids for a transaction id (or older) to the freelist.
|
||||||
|
func (f *freelist) release(txid txid) {
|
||||||
|
m := make(pgids, 0)
|
||||||
|
for tid, ids := range f.pending {
|
||||||
|
if tid <= txid {
|
||||||
|
// Move transaction's pending pages to the available freelist.
|
||||||
|
// Don't remove from the cache since the page is still free.
|
||||||
|
m = append(m, ids...)
|
||||||
|
delete(f.pending, tid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(m)
|
||||||
|
f.ids = pgids(f.ids).merge(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollback removes the pages from a given pending tx.
|
||||||
|
func (f *freelist) rollback(txid txid) {
|
||||||
|
// Remove page ids from cache.
|
||||||
|
for _, id := range f.pending[txid] {
|
||||||
|
delete(f.cache, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pages from pending list.
|
||||||
|
delete(f.pending, txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// freed returns whether a given page is in the free list.
|
||||||
|
func (f *freelist) freed(pgid pgid) bool {
|
||||||
|
return f.cache[pgid]
|
||||||
|
}
|
||||||
|
|
||||||
|
// read initializes the freelist from a freelist page.
|
||||||
|
func (f *freelist) read(p *page) {
|
||||||
|
// If the page.count is at the max uint16 value (64k) then it's considered
|
||||||
|
// an overflow and the size of the freelist is stored as the first element.
|
||||||
|
idx, count := 0, int(p.count)
|
||||||
|
if count == 0xFFFF {
|
||||||
|
idx = 1
|
||||||
|
count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the list of page ids from the freelist.
|
||||||
|
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count]
|
||||||
|
f.ids = make([]pgid, len(ids))
|
||||||
|
copy(f.ids, ids)
|
||||||
|
|
||||||
|
// Make sure they're sorted.
|
||||||
|
sort.Sort(pgids(f.ids))
|
||||||
|
|
||||||
|
// Rebuild the page cache.
|
||||||
|
f.reindex()
|
||||||
|
}
|
||||||
|
|
||||||
|
// write writes the page ids onto a freelist page. All free and pending ids are
|
||||||
|
// saved to disk since in the event of a program crash, all pending ids will
|
||||||
|
// become free.
|
||||||
|
func (f *freelist) write(p *page) error {
|
||||||
|
// Combine the old free pgids and pgids waiting on an open transaction.
|
||||||
|
ids := f.all()
|
||||||
|
|
||||||
|
// Update the header flag.
|
||||||
|
p.flags |= freelistPageFlag
|
||||||
|
|
||||||
|
// The page.count can only hold up to 64k elements so if we overflow that
|
||||||
|
// number then we handle it by putting the size in the first element.
|
||||||
|
if len(ids) < 0xFFFF {
|
||||||
|
p.count = uint16(len(ids))
|
||||||
|
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids)
|
||||||
|
} else {
|
||||||
|
p.count = 0xFFFF
|
||||||
|
((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(len(ids))
|
||||||
|
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:], ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload reads the freelist from a page and filters out pending items.
|
||||||
|
func (f *freelist) reload(p *page) {
|
||||||
|
f.read(p)
|
||||||
|
|
||||||
|
// Build a cache of only pending pages.
|
||||||
|
pcache := make(map[pgid]bool)
|
||||||
|
for _, pendingIDs := range f.pending {
|
||||||
|
for _, pendingID := range pendingIDs {
|
||||||
|
pcache[pendingID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each page in the freelist and build a new available freelist
|
||||||
|
// with any pages not in the pending lists.
|
||||||
|
var a []pgid
|
||||||
|
for _, id := range f.ids {
|
||||||
|
if !pcache[id] {
|
||||||
|
a = append(a, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.ids = a
|
||||||
|
|
||||||
|
// Once the available list is rebuilt then rebuild the free cache so that
|
||||||
|
// it includes the available and pending free pages.
|
||||||
|
f.reindex()
|
||||||
|
}
|
||||||
|
|
||||||
|
// reindex rebuilds the free cache based on available and pending free lists.
|
||||||
|
func (f *freelist) reindex() {
|
||||||
|
f.cache = make(map[pgid]bool)
|
||||||
|
for _, id := range f.ids {
|
||||||
|
f.cache[id] = true
|
||||||
|
}
|
||||||
|
for _, pendingIDs := range f.pending {
|
||||||
|
for _, pendingID := range pendingIDs {
|
||||||
|
f.cache[pendingID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
599
vendor/github.com/boltdb/bolt/node.go
generated
vendored
Normal file
599
vendor/github.com/boltdb/bolt/node.go
generated
vendored
Normal file
@ -0,0 +1,599 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// node represents an in-memory, deserialized page.
|
||||||
|
type node struct {
|
||||||
|
bucket *Bucket
|
||||||
|
isLeaf bool
|
||||||
|
unbalanced bool
|
||||||
|
spilled bool
|
||||||
|
key []byte
|
||||||
|
pgid pgid
|
||||||
|
parent *node
|
||||||
|
children nodes
|
||||||
|
inodes inodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// root returns the top-level node this node is attached to.
|
||||||
|
func (n *node) root() *node {
|
||||||
|
if n.parent == nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return n.parent.root()
|
||||||
|
}
|
||||||
|
|
||||||
|
// minKeys returns the minimum number of inodes this node should have.
|
||||||
|
func (n *node) minKeys() int {
|
||||||
|
if n.isLeaf {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// size returns the size of the node after serialization.
|
||||||
|
func (n *node) size() int {
|
||||||
|
sz, elsz := pageHeaderSize, n.pageElementSize()
|
||||||
|
for i := 0; i < len(n.inodes); i++ {
|
||||||
|
item := &n.inodes[i]
|
||||||
|
sz += elsz + len(item.key) + len(item.value)
|
||||||
|
}
|
||||||
|
return sz
|
||||||
|
}
|
||||||
|
|
||||||
|
// sizeLessThan returns true if the node is less than a given size.
|
||||||
|
// This is an optimization to avoid calculating a large node when we only need
|
||||||
|
// to know if it fits inside a certain page size.
|
||||||
|
func (n *node) sizeLessThan(v int) bool {
|
||||||
|
sz, elsz := pageHeaderSize, n.pageElementSize()
|
||||||
|
for i := 0; i < len(n.inodes); i++ {
|
||||||
|
item := &n.inodes[i]
|
||||||
|
sz += elsz + len(item.key) + len(item.value)
|
||||||
|
if sz >= v {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// pageElementSize returns the size of each page element based on the type of node.
|
||||||
|
func (n *node) pageElementSize() int {
|
||||||
|
if n.isLeaf {
|
||||||
|
return leafPageElementSize
|
||||||
|
}
|
||||||
|
return branchPageElementSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// childAt returns the child node at a given index.
|
||||||
|
func (n *node) childAt(index int) *node {
|
||||||
|
if n.isLeaf {
|
||||||
|
panic(fmt.Sprintf("invalid childAt(%d) on a leaf node", index))
|
||||||
|
}
|
||||||
|
return n.bucket.node(n.inodes[index].pgid, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// childIndex returns the index of a given child node.
|
||||||
|
func (n *node) childIndex(child *node) int {
|
||||||
|
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, child.key) != -1 })
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
// numChildren returns the number of children.
|
||||||
|
func (n *node) numChildren() int {
|
||||||
|
return len(n.inodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextSibling returns the next node with the same parent.
|
||||||
|
func (n *node) nextSibling() *node {
|
||||||
|
if n.parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
index := n.parent.childIndex(n)
|
||||||
|
if index >= n.parent.numChildren()-1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return n.parent.childAt(index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevSibling returns the previous node with the same parent.
|
||||||
|
func (n *node) prevSibling() *node {
|
||||||
|
if n.parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
index := n.parent.childIndex(n)
|
||||||
|
if index == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return n.parent.childAt(index - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// put inserts a key/value.
|
||||||
|
func (n *node) put(oldKey, newKey, value []byte, pgid pgid, flags uint32) {
|
||||||
|
if pgid >= n.bucket.tx.meta.pgid {
|
||||||
|
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", pgid, n.bucket.tx.meta.pgid))
|
||||||
|
} else if len(oldKey) <= 0 {
|
||||||
|
panic("put: zero-length old key")
|
||||||
|
} else if len(newKey) <= 0 {
|
||||||
|
panic("put: zero-length new key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find insertion index.
|
||||||
|
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 })
|
||||||
|
|
||||||
|
// Add capacity and shift nodes if we don't have an exact match and need to insert.
|
||||||
|
exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey))
|
||||||
|
if !exact {
|
||||||
|
n.inodes = append(n.inodes, inode{})
|
||||||
|
copy(n.inodes[index+1:], n.inodes[index:])
|
||||||
|
}
|
||||||
|
|
||||||
|
inode := &n.inodes[index]
|
||||||
|
inode.flags = flags
|
||||||
|
inode.key = newKey
|
||||||
|
inode.value = value
|
||||||
|
inode.pgid = pgid
|
||||||
|
_assert(len(inode.key) > 0, "put: zero-length inode key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// del removes a key from the node.
|
||||||
|
func (n *node) del(key []byte) {
|
||||||
|
// Find index of key.
|
||||||
|
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 })
|
||||||
|
|
||||||
|
// Exit if the key isn't found.
|
||||||
|
if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].key, key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete inode from the node.
|
||||||
|
n.inodes = append(n.inodes[:index], n.inodes[index+1:]...)
|
||||||
|
|
||||||
|
// Mark the node as needing rebalancing.
|
||||||
|
n.unbalanced = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// read initializes the node from a page.
|
||||||
|
func (n *node) read(p *page) {
|
||||||
|
n.pgid = p.id
|
||||||
|
n.isLeaf = ((p.flags & leafPageFlag) != 0)
|
||||||
|
n.inodes = make(inodes, int(p.count))
|
||||||
|
|
||||||
|
for i := 0; i < int(p.count); i++ {
|
||||||
|
inode := &n.inodes[i]
|
||||||
|
if n.isLeaf {
|
||||||
|
elem := p.leafPageElement(uint16(i))
|
||||||
|
inode.flags = elem.flags
|
||||||
|
inode.key = elem.key()
|
||||||
|
inode.value = elem.value()
|
||||||
|
} else {
|
||||||
|
elem := p.branchPageElement(uint16(i))
|
||||||
|
inode.pgid = elem.pgid
|
||||||
|
inode.key = elem.key()
|
||||||
|
}
|
||||||
|
_assert(len(inode.key) > 0, "read: zero-length inode key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save first key so we can find the node in the parent when we spill.
|
||||||
|
if len(n.inodes) > 0 {
|
||||||
|
n.key = n.inodes[0].key
|
||||||
|
_assert(len(n.key) > 0, "read: zero-length node key")
|
||||||
|
} else {
|
||||||
|
n.key = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write writes the items onto one or more pages.
|
||||||
|
func (n *node) write(p *page) {
|
||||||
|
// Initialize page.
|
||||||
|
if n.isLeaf {
|
||||||
|
p.flags |= leafPageFlag
|
||||||
|
} else {
|
||||||
|
p.flags |= branchPageFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n.inodes) >= 0xFFFF {
|
||||||
|
panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id))
|
||||||
|
}
|
||||||
|
p.count = uint16(len(n.inodes))
|
||||||
|
|
||||||
|
// Loop over each item and write it to the page.
|
||||||
|
b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):]
|
||||||
|
for i, item := range n.inodes {
|
||||||
|
_assert(len(item.key) > 0, "write: zero-length inode key")
|
||||||
|
|
||||||
|
// Write the page element.
|
||||||
|
if n.isLeaf {
|
||||||
|
elem := p.leafPageElement(uint16(i))
|
||||||
|
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
|
||||||
|
elem.flags = item.flags
|
||||||
|
elem.ksize = uint32(len(item.key))
|
||||||
|
elem.vsize = uint32(len(item.value))
|
||||||
|
} else {
|
||||||
|
elem := p.branchPageElement(uint16(i))
|
||||||
|
elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
|
||||||
|
elem.ksize = uint32(len(item.key))
|
||||||
|
elem.pgid = item.pgid
|
||||||
|
_assert(elem.pgid != p.id, "write: circular dependency occurred")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the length of key+value is larger than the max allocation size
|
||||||
|
// then we need to reallocate the byte array pointer.
|
||||||
|
//
|
||||||
|
// See: https://github.com/boltdb/bolt/pull/335
|
||||||
|
klen, vlen := len(item.key), len(item.value)
|
||||||
|
if len(b) < klen+vlen {
|
||||||
|
b = (*[maxAllocSize]byte)(unsafe.Pointer(&b[0]))[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data for the element to the end of the page.
|
||||||
|
copy(b[0:], item.key)
|
||||||
|
b = b[klen:]
|
||||||
|
copy(b[0:], item.value)
|
||||||
|
b = b[vlen:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEBUG ONLY: n.dump()
|
||||||
|
}
|
||||||
|
|
||||||
|
// split breaks up a node into multiple smaller nodes, if appropriate.
|
||||||
|
// This should only be called from the spill() function.
|
||||||
|
func (n *node) split(pageSize int) []*node {
|
||||||
|
var nodes []*node
|
||||||
|
|
||||||
|
node := n
|
||||||
|
for {
|
||||||
|
// Split node into two.
|
||||||
|
a, b := node.splitTwo(pageSize)
|
||||||
|
nodes = append(nodes, a)
|
||||||
|
|
||||||
|
// If we can't split then exit the loop.
|
||||||
|
if b == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set node to b so it gets split on the next iteration.
|
||||||
|
node = b
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitTwo breaks up a node into two smaller nodes, if appropriate.
|
||||||
|
// This should only be called from the split() function.
|
||||||
|
func (n *node) splitTwo(pageSize int) (*node, *node) {
|
||||||
|
// Ignore the split if the page doesn't have at least enough nodes for
|
||||||
|
// two pages or if the nodes can fit in a single page.
|
||||||
|
if len(n.inodes) <= (minKeysPerPage*2) || n.sizeLessThan(pageSize) {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the threshold before starting a new node.
|
||||||
|
var fillPercent = n.bucket.FillPercent
|
||||||
|
if fillPercent < minFillPercent {
|
||||||
|
fillPercent = minFillPercent
|
||||||
|
} else if fillPercent > maxFillPercent {
|
||||||
|
fillPercent = maxFillPercent
|
||||||
|
}
|
||||||
|
threshold := int(float64(pageSize) * fillPercent)
|
||||||
|
|
||||||
|
// Determine split position and sizes of the two pages.
|
||||||
|
splitIndex, _ := n.splitIndex(threshold)
|
||||||
|
|
||||||
|
// Split node into two separate nodes.
|
||||||
|
// If there's no parent then we'll need to create one.
|
||||||
|
if n.parent == nil {
|
||||||
|
n.parent = &node{bucket: n.bucket, children: []*node{n}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new node and add it to the parent.
|
||||||
|
next := &node{bucket: n.bucket, isLeaf: n.isLeaf, parent: n.parent}
|
||||||
|
n.parent.children = append(n.parent.children, next)
|
||||||
|
|
||||||
|
// Split inodes across two nodes.
|
||||||
|
next.inodes = n.inodes[splitIndex:]
|
||||||
|
n.inodes = n.inodes[:splitIndex]
|
||||||
|
|
||||||
|
// Update the statistics.
|
||||||
|
n.bucket.tx.stats.Split++
|
||||||
|
|
||||||
|
return n, next
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitIndex finds the position where a page will fill a given threshold.
|
||||||
|
// It returns the index as well as the size of the first page.
|
||||||
|
// This is only be called from split().
|
||||||
|
func (n *node) splitIndex(threshold int) (index, sz int) {
|
||||||
|
sz = pageHeaderSize
|
||||||
|
|
||||||
|
// Loop until we only have the minimum number of keys required for the second page.
|
||||||
|
for i := 0; i < len(n.inodes)-minKeysPerPage; i++ {
|
||||||
|
index = i
|
||||||
|
inode := n.inodes[i]
|
||||||
|
elsize := n.pageElementSize() + len(inode.key) + len(inode.value)
|
||||||
|
|
||||||
|
// If we have at least the minimum number of keys and adding another
|
||||||
|
// node would put us over the threshold then exit and return.
|
||||||
|
if i >= minKeysPerPage && sz+elsize > threshold {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the element size to the total size.
|
||||||
|
sz += elsize
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// spill writes the nodes to dirty pages and splits nodes as it goes.
|
||||||
|
// Returns an error if dirty pages cannot be allocated.
|
||||||
|
func (n *node) spill() error {
|
||||||
|
var tx = n.bucket.tx
|
||||||
|
if n.spilled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spill child nodes first. Child nodes can materialize sibling nodes in
|
||||||
|
// the case of split-merge so we cannot use a range loop. We have to check
|
||||||
|
// the children size on every loop iteration.
|
||||||
|
sort.Sort(n.children)
|
||||||
|
for i := 0; i < len(n.children); i++ {
|
||||||
|
if err := n.children[i].spill(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We no longer need the child list because it's only used for spill tracking.
|
||||||
|
n.children = nil
|
||||||
|
|
||||||
|
// Split nodes into appropriate sizes. The first node will always be n.
|
||||||
|
var nodes = n.split(tx.db.pageSize)
|
||||||
|
for _, node := range nodes {
|
||||||
|
// Add node's page to the freelist if it's not new.
|
||||||
|
if node.pgid > 0 {
|
||||||
|
tx.db.freelist.free(tx.meta.txid, tx.page(node.pgid))
|
||||||
|
node.pgid = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate contiguous space for the node.
|
||||||
|
p, err := tx.allocate((node.size() / tx.db.pageSize) + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the node.
|
||||||
|
if p.id >= tx.meta.pgid {
|
||||||
|
panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.id, tx.meta.pgid))
|
||||||
|
}
|
||||||
|
node.pgid = p.id
|
||||||
|
node.write(p)
|
||||||
|
node.spilled = true
|
||||||
|
|
||||||
|
// Insert into parent inodes.
|
||||||
|
if node.parent != nil {
|
||||||
|
var key = node.key
|
||||||
|
if key == nil {
|
||||||
|
key = node.inodes[0].key
|
||||||
|
}
|
||||||
|
|
||||||
|
node.parent.put(key, node.inodes[0].key, nil, node.pgid, 0)
|
||||||
|
node.key = node.inodes[0].key
|
||||||
|
_assert(len(node.key) > 0, "spill: zero-length node key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the statistics.
|
||||||
|
tx.stats.Spill++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the root node split and created a new root then we need to spill that
|
||||||
|
// as well. We'll clear out the children to make sure it doesn't try to respill.
|
||||||
|
if n.parent != nil && n.parent.pgid == 0 {
|
||||||
|
n.children = nil
|
||||||
|
return n.parent.spill()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rebalance attempts to combine the node with sibling nodes if the node fill
|
||||||
|
// size is below a threshold or if there are not enough keys.
|
||||||
|
func (n *node) rebalance() {
|
||||||
|
if !n.unbalanced {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.unbalanced = false
|
||||||
|
|
||||||
|
// Update statistics.
|
||||||
|
n.bucket.tx.stats.Rebalance++
|
||||||
|
|
||||||
|
// Ignore if node is above threshold (25%) and has enough keys.
|
||||||
|
var threshold = n.bucket.tx.db.pageSize / 4
|
||||||
|
if n.size() > threshold && len(n.inodes) > n.minKeys() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root node has special handling.
|
||||||
|
if n.parent == nil {
|
||||||
|
// If root node is a branch and only has one node then collapse it.
|
||||||
|
if !n.isLeaf && len(n.inodes) == 1 {
|
||||||
|
// Move root's child up.
|
||||||
|
child := n.bucket.node(n.inodes[0].pgid, n)
|
||||||
|
n.isLeaf = child.isLeaf
|
||||||
|
n.inodes = child.inodes[:]
|
||||||
|
n.children = child.children
|
||||||
|
|
||||||
|
// Reparent all child nodes being moved.
|
||||||
|
for _, inode := range n.inodes {
|
||||||
|
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||||
|
child.parent = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old child.
|
||||||
|
child.parent = nil
|
||||||
|
delete(n.bucket.nodes, child.pgid)
|
||||||
|
child.free()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If node has no keys then just remove it.
|
||||||
|
if n.numChildren() == 0 {
|
||||||
|
n.parent.del(n.key)
|
||||||
|
n.parent.removeChild(n)
|
||||||
|
delete(n.bucket.nodes, n.pgid)
|
||||||
|
n.free()
|
||||||
|
n.parent.rebalance()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_assert(n.parent.numChildren() > 1, "parent must have at least 2 children")
|
||||||
|
|
||||||
|
// Destination node is right sibling if idx == 0, otherwise left sibling.
|
||||||
|
var target *node
|
||||||
|
var useNextSibling = (n.parent.childIndex(n) == 0)
|
||||||
|
if useNextSibling {
|
||||||
|
target = n.nextSibling()
|
||||||
|
} else {
|
||||||
|
target = n.prevSibling()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both this node and the target node are too small then merge them.
|
||||||
|
if useNextSibling {
|
||||||
|
// Reparent all child nodes being moved.
|
||||||
|
for _, inode := range target.inodes {
|
||||||
|
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||||
|
child.parent.removeChild(child)
|
||||||
|
child.parent = n
|
||||||
|
child.parent.children = append(child.parent.children, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over inodes from target and remove target.
|
||||||
|
n.inodes = append(n.inodes, target.inodes...)
|
||||||
|
n.parent.del(target.key)
|
||||||
|
n.parent.removeChild(target)
|
||||||
|
delete(n.bucket.nodes, target.pgid)
|
||||||
|
target.free()
|
||||||
|
} else {
|
||||||
|
// Reparent all child nodes being moved.
|
||||||
|
for _, inode := range n.inodes {
|
||||||
|
if child, ok := n.bucket.nodes[inode.pgid]; ok {
|
||||||
|
child.parent.removeChild(child)
|
||||||
|
child.parent = target
|
||||||
|
child.parent.children = append(child.parent.children, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over inodes to target and remove node.
|
||||||
|
target.inodes = append(target.inodes, n.inodes...)
|
||||||
|
n.parent.del(n.key)
|
||||||
|
n.parent.removeChild(n)
|
||||||
|
delete(n.bucket.nodes, n.pgid)
|
||||||
|
n.free()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either this node or the target node was deleted from the parent so rebalance it.
|
||||||
|
n.parent.rebalance()
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes a node from the list of in-memory children.
|
||||||
|
// This does not affect the inodes.
|
||||||
|
func (n *node) removeChild(target *node) {
|
||||||
|
for i, child := range n.children {
|
||||||
|
if child == target {
|
||||||
|
n.children = append(n.children[:i], n.children[i+1:]...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dereference causes the node to copy all its inode key/value references to heap memory.
|
||||||
|
// This is required when the mmap is reallocated so inodes are not pointing to stale data.
|
||||||
|
func (n *node) dereference() {
|
||||||
|
if n.key != nil {
|
||||||
|
key := make([]byte, len(n.key))
|
||||||
|
copy(key, n.key)
|
||||||
|
n.key = key
|
||||||
|
_assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range n.inodes {
|
||||||
|
inode := &n.inodes[i]
|
||||||
|
|
||||||
|
key := make([]byte, len(inode.key))
|
||||||
|
copy(key, inode.key)
|
||||||
|
inode.key = key
|
||||||
|
_assert(len(inode.key) > 0, "dereference: zero-length inode key")
|
||||||
|
|
||||||
|
value := make([]byte, len(inode.value))
|
||||||
|
copy(value, inode.value)
|
||||||
|
inode.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively dereference children.
|
||||||
|
for _, child := range n.children {
|
||||||
|
child.dereference()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update statistics.
|
||||||
|
n.bucket.tx.stats.NodeDeref++
|
||||||
|
}
|
||||||
|
|
||||||
|
// free adds the node's underlying page to the freelist.
|
||||||
|
func (n *node) free() {
|
||||||
|
if n.pgid != 0 {
|
||||||
|
n.bucket.tx.db.freelist.free(n.bucket.tx.meta.txid, n.bucket.tx.page(n.pgid))
|
||||||
|
n.pgid = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump writes the contents of the node to STDERR for debugging purposes.
|
||||||
|
/*
|
||||||
|
func (n *node) dump() {
|
||||||
|
// Write node header.
|
||||||
|
var typ = "branch"
|
||||||
|
if n.isLeaf {
|
||||||
|
typ = "leaf"
|
||||||
|
}
|
||||||
|
warnf("[NODE %d {type=%s count=%d}]", n.pgid, typ, len(n.inodes))
|
||||||
|
|
||||||
|
// Write out abbreviated version of each item.
|
||||||
|
for _, item := range n.inodes {
|
||||||
|
if n.isLeaf {
|
||||||
|
if item.flags&bucketLeafFlag != 0 {
|
||||||
|
bucket := (*bucket)(unsafe.Pointer(&item.value[0]))
|
||||||
|
warnf("+L %08x -> (bucket root=%d)", trunc(item.key, 4), bucket.root)
|
||||||
|
} else {
|
||||||
|
warnf("+L %08x -> %08x", trunc(item.key, 4), trunc(item.value, 4))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warnf("+B %08x -> pgid=%d", trunc(item.key, 4), item.pgid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warn("")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type nodes []*node
|
||||||
|
|
||||||
|
func (s nodes) Len() int { return len(s) }
|
||||||
|
func (s nodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s nodes) Less(i, j int) bool { return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1 }
|
||||||
|
|
||||||
|
// inode represents an internal node inside of a node.
|
||||||
|
// It can be used to point to elements in a page or point
|
||||||
|
// to an element which hasn't been added to a page yet.
|
||||||
|
type inode struct {
|
||||||
|
flags uint32
|
||||||
|
pgid pgid
|
||||||
|
key []byte
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type inodes []inode
|
172
vendor/github.com/boltdb/bolt/page.go
generated
vendored
Normal file
172
vendor/github.com/boltdb/bolt/page.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const pageHeaderSize = int(unsafe.Offsetof(((*page)(nil)).ptr))
|
||||||
|
|
||||||
|
const minKeysPerPage = 2
|
||||||
|
|
||||||
|
const branchPageElementSize = int(unsafe.Sizeof(branchPageElement{}))
|
||||||
|
const leafPageElementSize = int(unsafe.Sizeof(leafPageElement{}))
|
||||||
|
|
||||||
|
const (
|
||||||
|
branchPageFlag = 0x01
|
||||||
|
leafPageFlag = 0x02
|
||||||
|
metaPageFlag = 0x04
|
||||||
|
freelistPageFlag = 0x10
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bucketLeafFlag = 0x01
|
||||||
|
)
|
||||||
|
|
||||||
|
type pgid uint64
|
||||||
|
|
||||||
|
type page struct {
|
||||||
|
id pgid
|
||||||
|
flags uint16
|
||||||
|
count uint16
|
||||||
|
overflow uint32
|
||||||
|
ptr uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// typ returns a human readable page type string used for debugging.
|
||||||
|
func (p *page) typ() string {
|
||||||
|
if (p.flags & branchPageFlag) != 0 {
|
||||||
|
return "branch"
|
||||||
|
} else if (p.flags & leafPageFlag) != 0 {
|
||||||
|
return "leaf"
|
||||||
|
} else if (p.flags & metaPageFlag) != 0 {
|
||||||
|
return "meta"
|
||||||
|
} else if (p.flags & freelistPageFlag) != 0 {
|
||||||
|
return "freelist"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unknown<%02x>", p.flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// meta returns a pointer to the metadata section of the page.
|
||||||
|
func (p *page) meta() *meta {
|
||||||
|
return (*meta)(unsafe.Pointer(&p.ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// leafPageElement retrieves the leaf node by index
|
||||||
|
func (p *page) leafPageElement(index uint16) *leafPageElement {
|
||||||
|
n := &((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[index]
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// leafPageElements retrieves a list of leaf nodes.
|
||||||
|
func (p *page) leafPageElements() []leafPageElement {
|
||||||
|
return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// branchPageElement retrieves the branch node by index
|
||||||
|
func (p *page) branchPageElement(index uint16) *branchPageElement {
|
||||||
|
return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// branchPageElements retrieves a list of branch nodes.
|
||||||
|
func (p *page) branchPageElements() []branchPageElement {
|
||||||
|
return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump writes n bytes of the page to STDERR as hex output.
|
||||||
|
func (p *page) hexdump(n int) {
|
||||||
|
buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:n]
|
||||||
|
fmt.Fprintf(os.Stderr, "%x\n", buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pages []*page
|
||||||
|
|
||||||
|
func (s pages) Len() int { return len(s) }
|
||||||
|
func (s pages) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s pages) Less(i, j int) bool { return s[i].id < s[j].id }
|
||||||
|
|
||||||
|
// branchPageElement represents a node on a branch page.
|
||||||
|
type branchPageElement struct {
|
||||||
|
pos uint32
|
||||||
|
ksize uint32
|
||||||
|
pgid pgid
|
||||||
|
}
|
||||||
|
|
||||||
|
// key returns a byte slice of the node key.
|
||||||
|
func (n *branchPageElement) key() []byte {
|
||||||
|
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
||||||
|
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize]
|
||||||
|
}
|
||||||
|
|
||||||
|
// leafPageElement represents a node on a leaf page.
|
||||||
|
type leafPageElement struct {
|
||||||
|
flags uint32
|
||||||
|
pos uint32
|
||||||
|
ksize uint32
|
||||||
|
vsize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// key returns a byte slice of the node key.
|
||||||
|
func (n *leafPageElement) key() []byte {
|
||||||
|
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
||||||
|
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize]
|
||||||
|
}
|
||||||
|
|
||||||
|
// value returns a byte slice of the node value.
|
||||||
|
func (n *leafPageElement) value() []byte {
|
||||||
|
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
||||||
|
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize]
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageInfo represents human readable information about a page.
|
||||||
|
type PageInfo struct {
|
||||||
|
ID int
|
||||||
|
Type string
|
||||||
|
Count int
|
||||||
|
OverflowCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
type pgids []pgid
|
||||||
|
|
||||||
|
func (s pgids) Len() int { return len(s) }
|
||||||
|
func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s pgids) Less(i, j int) bool { return s[i] < s[j] }
|
||||||
|
|
||||||
|
// merge returns the sorted union of a and b.
|
||||||
|
func (a pgids) merge(b pgids) pgids {
|
||||||
|
// Return the opposite slice if one is nil.
|
||||||
|
if len(a) == 0 {
|
||||||
|
return b
|
||||||
|
} else if len(b) == 0 {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a list to hold all elements from both lists.
|
||||||
|
merged := make(pgids, 0, len(a)+len(b))
|
||||||
|
|
||||||
|
// Assign lead to the slice with a lower starting value, follow to the higher value.
|
||||||
|
lead, follow := a, b
|
||||||
|
if b[0] < a[0] {
|
||||||
|
lead, follow = b, a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue while there are elements in the lead.
|
||||||
|
for len(lead) > 0 {
|
||||||
|
// Merge largest prefix of lead that is ahead of follow[0].
|
||||||
|
n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
|
||||||
|
merged = append(merged, lead[:n]...)
|
||||||
|
if n >= len(lead) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap lead and follow.
|
||||||
|
lead, follow = follow, lead[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append what's left in follow.
|
||||||
|
merged = append(merged, follow...)
|
||||||
|
|
||||||
|
return merged
|
||||||
|
}
|
666
vendor/github.com/boltdb/bolt/tx.go
generated
vendored
Normal file
666
vendor/github.com/boltdb/bolt/tx.go
generated
vendored
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// txid represents the internal transaction identifier.
|
||||||
|
type txid uint64
|
||||||
|
|
||||||
|
// Tx represents a read-only or read/write transaction on the database.
|
||||||
|
// Read-only transactions can be used for retrieving values for keys and creating cursors.
|
||||||
|
// Read/write transactions can create and remove buckets and create and remove keys.
|
||||||
|
//
|
||||||
|
// IMPORTANT: You must commit or rollback transactions when you are done with
|
||||||
|
// them. Pages can not be reclaimed by the writer until no more transactions
|
||||||
|
// are using them. A long running read transaction can cause the database to
|
||||||
|
// quickly grow.
|
||||||
|
type Tx struct {
|
||||||
|
writable bool
|
||||||
|
managed bool
|
||||||
|
db *DB
|
||||||
|
meta *meta
|
||||||
|
root Bucket
|
||||||
|
pages map[pgid]*page
|
||||||
|
stats TxStats
|
||||||
|
commitHandlers []func()
|
||||||
|
|
||||||
|
// WriteFlag specifies the flag for write-related methods like WriteTo().
|
||||||
|
// Tx opens the database file with the specified flag to copy the data.
|
||||||
|
//
|
||||||
|
// By default, the flag is unset, which works well for mostly in-memory
|
||||||
|
// workloads. For databases that are much larger than available RAM,
|
||||||
|
// set the flag to syscall.O_DIRECT to avoid trashing the page cache.
|
||||||
|
WriteFlag int
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes the transaction.
|
||||||
|
func (tx *Tx) init(db *DB) {
|
||||||
|
tx.db = db
|
||||||
|
tx.pages = nil
|
||||||
|
|
||||||
|
// Copy the meta page since it can be changed by the writer.
|
||||||
|
tx.meta = &meta{}
|
||||||
|
db.meta().copy(tx.meta)
|
||||||
|
|
||||||
|
// Copy over the root bucket.
|
||||||
|
tx.root = newBucket(tx)
|
||||||
|
tx.root.bucket = &bucket{}
|
||||||
|
*tx.root.bucket = tx.meta.root
|
||||||
|
|
||||||
|
// Increment the transaction id and add a page cache for writable transactions.
|
||||||
|
if tx.writable {
|
||||||
|
tx.pages = make(map[pgid]*page)
|
||||||
|
tx.meta.txid += txid(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the transaction id.
|
||||||
|
func (tx *Tx) ID() int {
|
||||||
|
return int(tx.meta.txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB returns a reference to the database that created the transaction.
|
||||||
|
func (tx *Tx) DB() *DB {
|
||||||
|
return tx.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns current database size in bytes as seen by this transaction.
|
||||||
|
func (tx *Tx) Size() int64 {
|
||||||
|
return int64(tx.meta.pgid) * int64(tx.db.pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writable returns whether the transaction can perform write operations.
|
||||||
|
func (tx *Tx) Writable() bool {
|
||||||
|
return tx.writable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor creates a cursor associated with the root bucket.
|
||||||
|
// All items in the cursor will return a nil value because all root bucket keys point to buckets.
|
||||||
|
// The cursor is only valid as long as the transaction is open.
|
||||||
|
// Do not use a cursor after the transaction is closed.
|
||||||
|
func (tx *Tx) Cursor() *Cursor {
|
||||||
|
return tx.root.Cursor()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats retrieves a copy of the current transaction statistics.
|
||||||
|
func (tx *Tx) Stats() TxStats {
|
||||||
|
return tx.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket retrieves a bucket by name.
|
||||||
|
// Returns nil if the bucket does not exist.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
|
func (tx *Tx) Bucket(name []byte) *Bucket {
|
||||||
|
return tx.root.Bucket(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucket creates a new bucket.
|
||||||
|
// Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
|
func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {
|
||||||
|
return tx.root.CreateBucket(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist.
|
||||||
|
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
|
func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) {
|
||||||
|
return tx.root.CreateBucketIfNotExists(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBucket deletes a bucket.
|
||||||
|
// Returns an error if the bucket cannot be found or if the key represents a non-bucket value.
|
||||||
|
func (tx *Tx) DeleteBucket(name []byte) error {
|
||||||
|
return tx.root.DeleteBucket(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEach executes a function for each bucket in the root.
|
||||||
|
// If the provided function returns an error then the iteration is stopped and
|
||||||
|
// the error is returned to the caller.
|
||||||
|
func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error {
|
||||||
|
return tx.root.ForEach(func(k, v []byte) error {
|
||||||
|
if err := fn(k, tx.root.Bucket(k)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnCommit adds a handler function to be executed after the transaction successfully commits.
|
||||||
|
func (tx *Tx) OnCommit(fn func()) {
|
||||||
|
tx.commitHandlers = append(tx.commitHandlers, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit writes all changes to disk and updates the meta page.
|
||||||
|
// Returns an error if a disk write error occurs, or if Commit is
|
||||||
|
// called on a read-only transaction.
|
||||||
|
func (tx *Tx) Commit() error {
|
||||||
|
_assert(!tx.managed, "managed tx commit not allowed")
|
||||||
|
if tx.db == nil {
|
||||||
|
return ErrTxClosed
|
||||||
|
} else if !tx.writable {
|
||||||
|
return ErrTxNotWritable
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
|
||||||
|
|
||||||
|
// Rebalance nodes which have had deletions.
|
||||||
|
var startTime = time.Now()
|
||||||
|
tx.root.rebalance()
|
||||||
|
if tx.stats.Rebalance > 0 {
|
||||||
|
tx.stats.RebalanceTime += time.Since(startTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// spill data onto dirty pages.
|
||||||
|
startTime = time.Now()
|
||||||
|
if err := tx.root.spill(); err != nil {
|
||||||
|
tx.rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.stats.SpillTime += time.Since(startTime)
|
||||||
|
|
||||||
|
// Free the old root bucket.
|
||||||
|
tx.meta.root.root = tx.root.root
|
||||||
|
|
||||||
|
opgid := tx.meta.pgid
|
||||||
|
|
||||||
|
// Free the freelist and allocate new pages for it. This will overestimate
|
||||||
|
// the size of the freelist but not underestimate the size (which would be bad).
|
||||||
|
tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
|
||||||
|
p, err := tx.allocate((tx.db.freelist.size() / tx.db.pageSize) + 1)
|
||||||
|
if err != nil {
|
||||||
|
tx.rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.db.freelist.write(p); err != nil {
|
||||||
|
tx.rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.meta.freelist = p.id
|
||||||
|
|
||||||
|
// If the high water mark has moved up then attempt to grow the database.
|
||||||
|
if tx.meta.pgid > opgid {
|
||||||
|
if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil {
|
||||||
|
tx.rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write dirty pages to disk.
|
||||||
|
startTime = time.Now()
|
||||||
|
if err := tx.write(); err != nil {
|
||||||
|
tx.rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If strict mode is enabled then perform a consistency check.
|
||||||
|
// Only the first consistency error is reported in the panic.
|
||||||
|
if tx.db.StrictMode {
|
||||||
|
ch := tx.Check()
|
||||||
|
var errs []string
|
||||||
|
for {
|
||||||
|
err, ok := <-ch
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
panic("check fail: " + strings.Join(errs, "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write meta to disk.
|
||||||
|
if err := tx.writeMeta(); err != nil {
|
||||||
|
tx.rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.stats.WriteTime += time.Since(startTime)
|
||||||
|
|
||||||
|
// Finalize the transaction.
|
||||||
|
tx.close()
|
||||||
|
|
||||||
|
// Execute commit handlers now that the locks have been removed.
|
||||||
|
for _, fn := range tx.commitHandlers {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback closes the transaction and ignores all previous updates. Read-only
|
||||||
|
// transactions must be rolled back and not committed.
|
||||||
|
func (tx *Tx) Rollback() error {
|
||||||
|
_assert(!tx.managed, "managed tx rollback not allowed")
|
||||||
|
if tx.db == nil {
|
||||||
|
return ErrTxClosed
|
||||||
|
}
|
||||||
|
tx.rollback()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) rollback() {
|
||||||
|
if tx.db == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tx.writable {
|
||||||
|
tx.db.freelist.rollback(tx.meta.txid)
|
||||||
|
tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist))
|
||||||
|
}
|
||||||
|
tx.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) close() {
|
||||||
|
if tx.db == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tx.writable {
|
||||||
|
// Grab freelist stats.
|
||||||
|
var freelistFreeN = tx.db.freelist.free_count()
|
||||||
|
var freelistPendingN = tx.db.freelist.pending_count()
|
||||||
|
var freelistAlloc = tx.db.freelist.size()
|
||||||
|
|
||||||
|
// Remove transaction ref & writer lock.
|
||||||
|
tx.db.rwtx = nil
|
||||||
|
tx.db.rwlock.Unlock()
|
||||||
|
|
||||||
|
// Merge statistics.
|
||||||
|
tx.db.statlock.Lock()
|
||||||
|
tx.db.stats.FreePageN = freelistFreeN
|
||||||
|
tx.db.stats.PendingPageN = freelistPendingN
|
||||||
|
tx.db.stats.FreeAlloc = (freelistFreeN + freelistPendingN) * tx.db.pageSize
|
||||||
|
tx.db.stats.FreelistInuse = freelistAlloc
|
||||||
|
tx.db.stats.TxStats.add(&tx.stats)
|
||||||
|
tx.db.statlock.Unlock()
|
||||||
|
} else {
|
||||||
|
tx.db.removeTx(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all references.
|
||||||
|
tx.db = nil
|
||||||
|
tx.meta = nil
|
||||||
|
tx.root = Bucket{tx: tx}
|
||||||
|
tx.pages = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy writes the entire database to a writer.
|
||||||
|
// This function exists for backwards compatibility. Use WriteTo() instead.
|
||||||
|
func (tx *Tx) Copy(w io.Writer) error {
|
||||||
|
_, err := tx.WriteTo(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes the entire database to a writer.
|
||||||
|
// If err == nil then exactly tx.Size() bytes will be written into the writer.
|
||||||
|
func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
// Attempt to open reader with WriteFlag
|
||||||
|
f, err := os.OpenFile(tx.db.path, os.O_RDONLY|tx.WriteFlag, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
|
// Generate a meta page. We use the same page data for both meta pages.
|
||||||
|
buf := make([]byte, tx.db.pageSize)
|
||||||
|
page := (*page)(unsafe.Pointer(&buf[0]))
|
||||||
|
page.flags = metaPageFlag
|
||||||
|
*page.meta() = *tx.meta
|
||||||
|
|
||||||
|
// Write meta 0.
|
||||||
|
page.id = 0
|
||||||
|
page.meta().checksum = page.meta().sum64()
|
||||||
|
nn, err := w.Write(buf)
|
||||||
|
n += int64(nn)
|
||||||
|
if err != nil {
|
||||||
|
return n, fmt.Errorf("meta 0 copy: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write meta 1 with a lower transaction id.
|
||||||
|
page.id = 1
|
||||||
|
page.meta().txid -= 1
|
||||||
|
page.meta().checksum = page.meta().sum64()
|
||||||
|
nn, err = w.Write(buf)
|
||||||
|
n += int64(nn)
|
||||||
|
if err != nil {
|
||||||
|
return n, fmt.Errorf("meta 1 copy: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move past the meta pages in the file.
|
||||||
|
if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil {
|
||||||
|
return n, fmt.Errorf("seek: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data pages.
|
||||||
|
wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2))
|
||||||
|
n += wn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFile copies the entire database to file at the given path.
|
||||||
|
// A reader transaction is maintained during the copy so it is safe to continue
|
||||||
|
// using the database while a copy is in progress.
|
||||||
|
func (tx *Tx) CopyFile(path string, mode os.FileMode) error {
|
||||||
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Copy(f)
|
||||||
|
if err != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check performs several consistency checks on the database for this transaction.
|
||||||
|
// An error is returned if any inconsistency is found.
|
||||||
|
//
|
||||||
|
// It can be safely run concurrently on a writable transaction. However, this
|
||||||
|
// incurs a high cost for large databases and databases with a lot of subbuckets
|
||||||
|
// because of caching. This overhead can be removed if running on a read-only
|
||||||
|
// transaction, however, it is not safe to execute other writer transactions at
|
||||||
|
// the same time.
|
||||||
|
func (tx *Tx) Check() <-chan error {
|
||||||
|
ch := make(chan error)
|
||||||
|
go tx.check(ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) check(ch chan error) {
|
||||||
|
// Check if any pages are double freed.
|
||||||
|
freed := make(map[pgid]bool)
|
||||||
|
for _, id := range tx.db.freelist.all() {
|
||||||
|
if freed[id] {
|
||||||
|
ch <- fmt.Errorf("page %d: already freed", id)
|
||||||
|
}
|
||||||
|
freed[id] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track every reachable page.
|
||||||
|
reachable := make(map[pgid]*page)
|
||||||
|
reachable[0] = tx.page(0) // meta0
|
||||||
|
reachable[1] = tx.page(1) // meta1
|
||||||
|
for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ {
|
||||||
|
reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively check buckets.
|
||||||
|
tx.checkBucket(&tx.root, reachable, freed, ch)
|
||||||
|
|
||||||
|
// Ensure all pages below high water mark are either reachable or freed.
|
||||||
|
for i := pgid(0); i < tx.meta.pgid; i++ {
|
||||||
|
_, isReachable := reachable[i]
|
||||||
|
if !isReachable && !freed[i] {
|
||||||
|
ch <- fmt.Errorf("page %d: unreachable unfreed", int(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the channel to signal completion.
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool, ch chan error) {
|
||||||
|
// Ignore inline buckets.
|
||||||
|
if b.root == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check every page used by this bucket.
|
||||||
|
b.tx.forEachPage(b.root, 0, func(p *page, _ int) {
|
||||||
|
if p.id > tx.meta.pgid {
|
||||||
|
ch <- fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure each page is only referenced once.
|
||||||
|
for i := pgid(0); i <= pgid(p.overflow); i++ {
|
||||||
|
var id = p.id + i
|
||||||
|
if _, ok := reachable[id]; ok {
|
||||||
|
ch <- fmt.Errorf("page %d: multiple references", int(id))
|
||||||
|
}
|
||||||
|
reachable[id] = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should only encounter un-freed leaf and branch pages.
|
||||||
|
if freed[p.id] {
|
||||||
|
ch <- fmt.Errorf("page %d: reachable freed", int(p.id))
|
||||||
|
} else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 {
|
||||||
|
ch <- fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check each bucket within this bucket.
|
||||||
|
_ = b.ForEach(func(k, v []byte) error {
|
||||||
|
if child := b.Bucket(k); child != nil {
|
||||||
|
tx.checkBucket(child, reachable, freed, ch)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate returns a contiguous block of memory starting at a given page.
|
||||||
|
func (tx *Tx) allocate(count int) (*page, error) {
|
||||||
|
p, err := tx.db.allocate(count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to our page cache.
|
||||||
|
tx.pages[p.id] = p
|
||||||
|
|
||||||
|
// Update statistics.
|
||||||
|
tx.stats.PageCount++
|
||||||
|
tx.stats.PageAlloc += count * tx.db.pageSize
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// write writes any dirty pages to disk.
|
||||||
|
func (tx *Tx) write() error {
|
||||||
|
// Sort pages by id.
|
||||||
|
pages := make(pages, 0, len(tx.pages))
|
||||||
|
for _, p := range tx.pages {
|
||||||
|
pages = append(pages, p)
|
||||||
|
}
|
||||||
|
sort.Sort(pages)
|
||||||
|
|
||||||
|
// Write pages to disk in order.
|
||||||
|
for _, p := range pages {
|
||||||
|
size := (int(p.overflow) + 1) * tx.db.pageSize
|
||||||
|
offset := int64(p.id) * int64(tx.db.pageSize)
|
||||||
|
|
||||||
|
// Write out page in "max allocation" sized chunks.
|
||||||
|
ptr := (*[maxAllocSize]byte)(unsafe.Pointer(p))
|
||||||
|
for {
|
||||||
|
// Limit our write to our max allocation size.
|
||||||
|
sz := size
|
||||||
|
if sz > maxAllocSize-1 {
|
||||||
|
sz = maxAllocSize - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write chunk to disk.
|
||||||
|
buf := ptr[:sz]
|
||||||
|
if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update statistics.
|
||||||
|
tx.stats.Write++
|
||||||
|
|
||||||
|
// Exit inner for loop if we've written all the chunks.
|
||||||
|
size -= sz
|
||||||
|
if size == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise move offset forward and move pointer to next chunk.
|
||||||
|
offset += int64(sz)
|
||||||
|
ptr = (*[maxAllocSize]byte)(unsafe.Pointer(&ptr[sz]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore file sync if flag is set on DB.
|
||||||
|
if !tx.db.NoSync || IgnoreNoSync {
|
||||||
|
if err := fdatasync(tx.db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out page cache.
|
||||||
|
tx.pages = make(map[pgid]*page)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeMeta writes the meta to the disk.
|
||||||
|
func (tx *Tx) writeMeta() error {
|
||||||
|
// Create a temporary buffer for the meta page.
|
||||||
|
buf := make([]byte, tx.db.pageSize)
|
||||||
|
p := tx.db.pageInBuffer(buf, 0)
|
||||||
|
tx.meta.write(p)
|
||||||
|
|
||||||
|
// Write the meta page to file.
|
||||||
|
if _, err := tx.db.ops.writeAt(buf, int64(p.id)*int64(tx.db.pageSize)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !tx.db.NoSync || IgnoreNoSync {
|
||||||
|
if err := fdatasync(tx.db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update statistics.
|
||||||
|
tx.stats.Write++
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// page returns a reference to the page with a given id.
|
||||||
|
// If page has been written to then a temporary buffered page is returned.
|
||||||
|
func (tx *Tx) page(id pgid) *page {
|
||||||
|
// Check the dirty pages first.
|
||||||
|
if tx.pages != nil {
|
||||||
|
if p, ok := tx.pages[id]; ok {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise return directly from the mmap.
|
||||||
|
return tx.db.page(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// forEachPage iterates over every page within a given page and executes a function.
|
||||||
|
func (tx *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) {
|
||||||
|
p := tx.page(pgid)
|
||||||
|
|
||||||
|
// Execute function.
|
||||||
|
fn(p, depth)
|
||||||
|
|
||||||
|
// Recursively loop over children.
|
||||||
|
if (p.flags & branchPageFlag) != 0 {
|
||||||
|
for i := 0; i < int(p.count); i++ {
|
||||||
|
elem := p.branchPageElement(uint16(i))
|
||||||
|
tx.forEachPage(elem.pgid, depth+1, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page returns page information for a given page number.
|
||||||
|
// This is only safe for concurrent use when used by a writable transaction.
|
||||||
|
func (tx *Tx) Page(id int) (*PageInfo, error) {
|
||||||
|
if tx.db == nil {
|
||||||
|
return nil, ErrTxClosed
|
||||||
|
} else if pgid(id) >= tx.meta.pgid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the page info.
|
||||||
|
p := tx.db.page(pgid(id))
|
||||||
|
info := &PageInfo{
|
||||||
|
ID: id,
|
||||||
|
Count: int(p.count),
|
||||||
|
OverflowCount: int(p.overflow),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the type (or if it's free).
|
||||||
|
if tx.db.freelist.freed(pgid(id)) {
|
||||||
|
info.Type = "free"
|
||||||
|
} else {
|
||||||
|
info.Type = p.typ()
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxStats represents statistics about the actions performed by the transaction.
|
||||||
|
type TxStats struct {
|
||||||
|
// Page statistics.
|
||||||
|
PageCount int // number of page allocations
|
||||||
|
PageAlloc int // total bytes allocated
|
||||||
|
|
||||||
|
// Cursor statistics.
|
||||||
|
CursorCount int // number of cursors created
|
||||||
|
|
||||||
|
// Node statistics
|
||||||
|
NodeCount int // number of node allocations
|
||||||
|
NodeDeref int // number of node dereferences
|
||||||
|
|
||||||
|
// Rebalance statistics.
|
||||||
|
Rebalance int // number of node rebalances
|
||||||
|
RebalanceTime time.Duration // total time spent rebalancing
|
||||||
|
|
||||||
|
// Split/Spill statistics.
|
||||||
|
Split int // number of nodes split
|
||||||
|
Spill int // number of nodes spilled
|
||||||
|
SpillTime time.Duration // total time spent spilling
|
||||||
|
|
||||||
|
// Write statistics.
|
||||||
|
Write int // number of writes performed
|
||||||
|
WriteTime time.Duration // total time spent writing to disk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TxStats) add(other *TxStats) {
|
||||||
|
s.PageCount += other.PageCount
|
||||||
|
s.PageAlloc += other.PageAlloc
|
||||||
|
s.CursorCount += other.CursorCount
|
||||||
|
s.NodeCount += other.NodeCount
|
||||||
|
s.NodeDeref += other.NodeDeref
|
||||||
|
s.Rebalance += other.Rebalance
|
||||||
|
s.RebalanceTime += other.RebalanceTime
|
||||||
|
s.Split += other.Split
|
||||||
|
s.Spill += other.Spill
|
||||||
|
s.SpillTime += other.SpillTime
|
||||||
|
s.Write += other.Write
|
||||||
|
s.WriteTime += other.WriteTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub calculates and returns the difference between two sets of transaction stats.
|
||||||
|
// This is useful when obtaining stats at two different points and time and
|
||||||
|
// you need the performance counters that occurred within that time span.
|
||||||
|
func (s *TxStats) Sub(other *TxStats) TxStats {
|
||||||
|
var diff TxStats
|
||||||
|
diff.PageCount = s.PageCount - other.PageCount
|
||||||
|
diff.PageAlloc = s.PageAlloc - other.PageAlloc
|
||||||
|
diff.CursorCount = s.CursorCount - other.CursorCount
|
||||||
|
diff.NodeCount = s.NodeCount - other.NodeCount
|
||||||
|
diff.NodeDeref = s.NodeDeref - other.NodeDeref
|
||||||
|
diff.Rebalance = s.Rebalance - other.Rebalance
|
||||||
|
diff.RebalanceTime = s.RebalanceTime - other.RebalanceTime
|
||||||
|
diff.Split = s.Split - other.Split
|
||||||
|
diff.Spill = s.Spill - other.Spill
|
||||||
|
diff.SpillTime = s.SpillTime - other.SpillTime
|
||||||
|
diff.Write = s.Write - other.Write
|
||||||
|
diff.WriteTime = s.WriteTime - other.WriteTime
|
||||||
|
return diff
|
||||||
|
}
|
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.
|
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 (
|
||||||
|
"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)]
|
||||||
|
}
|
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
|
||||||
|
}
|
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
|
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 }
|
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")
|
||||||
|
}
|
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
|
||||||
|
}
|
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
|
||||||
|
}
|
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
|
||||||
|
}
|
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,
|
||||||
|
}
|
||||||
|
}
|
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,
|
||||||
|
}
|
||||||
|
}
|
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)
|
||||||
|
}
|
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
|
229
vendor/github.com/gin-gonic/gin/AUTHORS.md
generated
vendored
Normal file
229
vendor/github.com/gin-gonic/gin/AUTHORS.md
generated
vendored
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
List of all the awesome people working to make Gin the best Web Framework in Go.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##gin 0.x series authors
|
||||||
|
|
||||||
|
**Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
||||||
|
|
||||||
|
People and companies, who have contributed, in alphabetical order.
|
||||||
|
|
||||||
|
**@858806258 (杰哥)**
|
||||||
|
- Fix typo in example
|
||||||
|
|
||||||
|
|
||||||
|
**@achedeuzot (Klemen Sever)**
|
||||||
|
- Fix newline debug printing
|
||||||
|
|
||||||
|
|
||||||
|
**@adammck (Adam Mckaig)**
|
||||||
|
- Add MIT license
|
||||||
|
|
||||||
|
|
||||||
|
**@AlexanderChen1989 (Alexander)**
|
||||||
|
- Typos in README
|
||||||
|
|
||||||
|
|
||||||
|
**@alexanderdidenko (Aleksandr Didenko)**
|
||||||
|
- Add support multipart/form-data
|
||||||
|
|
||||||
|
|
||||||
|
**@alexandernyquist (Alexander Nyquist)**
|
||||||
|
- Using template.Must to fix multiple return issue
|
||||||
|
- ★ Added support for OPTIONS verb
|
||||||
|
- ★ Setting response headers before calling WriteHeader
|
||||||
|
- Improved documentation for model binding
|
||||||
|
- ★ Added Content.Redirect()
|
||||||
|
- ★ Added tons of Unit tests
|
||||||
|
|
||||||
|
|
||||||
|
**@austinheap (Austin Heap)**
|
||||||
|
- Added travis CI integration
|
||||||
|
|
||||||
|
|
||||||
|
**@andredublin (Andre Dublin)**
|
||||||
|
- Fix typo in comment
|
||||||
|
|
||||||
|
|
||||||
|
**@bredov (Ludwig Valda Vasquez)**
|
||||||
|
- Fix html templating in debug mode
|
||||||
|
|
||||||
|
|
||||||
|
**@bluele (Jun Kimura)**
|
||||||
|
- Fixes code examples in README
|
||||||
|
|
||||||
|
|
||||||
|
**@chad-russell**
|
||||||
|
- ★ Support for serializing gin.H into XML
|
||||||
|
|
||||||
|
|
||||||
|
**@dickeyxxx (Jeff Dickey)**
|
||||||
|
- Typos in README
|
||||||
|
- Add example about serving static files
|
||||||
|
|
||||||
|
|
||||||
|
**@donileo (Adonis)**
|
||||||
|
- Add NoMethod handler
|
||||||
|
|
||||||
|
|
||||||
|
**@dutchcoders (DutchCoders)**
|
||||||
|
- ★ Fix security bug that allows client to spoof ip
|
||||||
|
- Fix typo. r.HTMLTemplates -> SetHTMLTemplate
|
||||||
|
|
||||||
|
|
||||||
|
**@el3ctro- (Joshua Loper)**
|
||||||
|
- Fix typo in example
|
||||||
|
|
||||||
|
|
||||||
|
**@ethankan (Ethan Kan)**
|
||||||
|
- Unsigned integers in binding
|
||||||
|
|
||||||
|
|
||||||
|
**(Evgeny Persienko)**
|
||||||
|
- Validate sub structures
|
||||||
|
|
||||||
|
|
||||||
|
**@frankbille (Frank Bille)**
|
||||||
|
- Add support for HTTP Realm Auth
|
||||||
|
|
||||||
|
|
||||||
|
**@fmd (Fareed Dudhia)**
|
||||||
|
- Fix typo. SetHTTPTemplate -> SetHTMLTemplate
|
||||||
|
|
||||||
|
|
||||||
|
**@ironiridis (Christopher Harrington)**
|
||||||
|
- Remove old reference
|
||||||
|
|
||||||
|
|
||||||
|
**@jammie-stackhouse (Jamie Stackhouse)**
|
||||||
|
- Add more shortcuts for router methods
|
||||||
|
|
||||||
|
|
||||||
|
**@jasonrhansen**
|
||||||
|
- Fix spelling and grammar errors in documentation
|
||||||
|
|
||||||
|
|
||||||
|
**@JasonSoft (Jason Lee)**
|
||||||
|
- Fix typo in comment
|
||||||
|
|
||||||
|
|
||||||
|
**@joiggama (Ignacio Galindo)**
|
||||||
|
- Add utf-8 charset header on renders
|
||||||
|
|
||||||
|
|
||||||
|
**@julienschmidt (Julien Schmidt)**
|
||||||
|
- gofmt the code examples
|
||||||
|
|
||||||
|
|
||||||
|
**@kelcecil (Kel Cecil)**
|
||||||
|
- Fix readme typo
|
||||||
|
|
||||||
|
|
||||||
|
**@kyledinh (Kyle Dinh)**
|
||||||
|
- Adds RunTLS()
|
||||||
|
|
||||||
|
|
||||||
|
**@LinusU (Linus Unnebäck)**
|
||||||
|
- Small fixes in README
|
||||||
|
|
||||||
|
|
||||||
|
**@loongmxbt (Saint Asky)**
|
||||||
|
- Fix typo in example
|
||||||
|
|
||||||
|
|
||||||
|
**@lucas-clemente (Lucas Clemente)**
|
||||||
|
- ★ work around path.Join removing trailing slashes from routes
|
||||||
|
|
||||||
|
|
||||||
|
**@mattn (Yasuhiro Matsumoto)**
|
||||||
|
- Improve color logger
|
||||||
|
|
||||||
|
|
||||||
|
**@mdigger (Dmitry Sedykh)**
|
||||||
|
- Fixes Form binding when content-type is x-www-form-urlencoded
|
||||||
|
- No repeat call c.Writer.Status() in gin.Logger
|
||||||
|
- Fixes Content-Type for json render
|
||||||
|
|
||||||
|
|
||||||
|
**@mirzac (Mirza Ceric)**
|
||||||
|
- Fix debug printing
|
||||||
|
|
||||||
|
|
||||||
|
**@mopemope (Yutaka Matsubara)**
|
||||||
|
- ★ Adds Godep support (Dependencies Manager)
|
||||||
|
- Fix variadic parameter in the flexible render API
|
||||||
|
- Fix Corrupted plain render
|
||||||
|
- Add Pluggable View Renderer Example
|
||||||
|
|
||||||
|
|
||||||
|
**@msemenistyi (Mykyta Semenistyi)**
|
||||||
|
- update Readme.md. Add code to String method
|
||||||
|
|
||||||
|
|
||||||
|
**@msoedov (Sasha Myasoedov)**
|
||||||
|
- ★ Adds tons of unit tests.
|
||||||
|
|
||||||
|
|
||||||
|
**@ngerakines (Nick Gerakines)**
|
||||||
|
- ★ Improves API, c.GET() doesn't panic
|
||||||
|
- Adds MustGet() method
|
||||||
|
|
||||||
|
|
||||||
|
**@r8k (Rajiv Kilaparti)**
|
||||||
|
- Fix Port usage in README.
|
||||||
|
|
||||||
|
|
||||||
|
**@rayrod2030 (Ray Rodriguez)**
|
||||||
|
- Fix typo in example
|
||||||
|
|
||||||
|
|
||||||
|
**@rns**
|
||||||
|
- Fix typo in example
|
||||||
|
|
||||||
|
|
||||||
|
**@RobAWilkinson (Robert Wilkinson)**
|
||||||
|
- Add example of forms and params
|
||||||
|
|
||||||
|
|
||||||
|
**@rogierlommers (Rogier Lommers)**
|
||||||
|
- Add updated static serve example
|
||||||
|
|
||||||
|
|
||||||
|
**@se77en (Damon Zhao)**
|
||||||
|
- Improve color logging
|
||||||
|
|
||||||
|
|
||||||
|
**@silasb (Silas Baronda)**
|
||||||
|
- Fixing quotes in README
|
||||||
|
|
||||||
|
|
||||||
|
**@SkuliOskarsson (Skuli Oskarsson)**
|
||||||
|
- Fixes some texts in README II
|
||||||
|
|
||||||
|
|
||||||
|
**@slimmy (Jimmy Pettersson)**
|
||||||
|
- Added messages for required bindings
|
||||||
|
|
||||||
|
|
||||||
|
**@smira (Andrey Smirnov)**
|
||||||
|
- Add support for ignored/unexported fields in binding
|
||||||
|
|
||||||
|
|
||||||
|
**@superalsrk (SRK.Lyu)**
|
||||||
|
- Update httprouter godeps
|
||||||
|
|
||||||
|
|
||||||
|
**@tebeka (Miki Tebeka)**
|
||||||
|
- Use net/http constants instead of numeric values
|
||||||
|
|
||||||
|
|
||||||
|
**@techjanitor**
|
||||||
|
- Update context.go reserved IPs
|
||||||
|
|
||||||
|
|
||||||
|
**@yosssi (Keiji Yoshida)**
|
||||||
|
- Fix link in README
|
||||||
|
|
||||||
|
|
||||||
|
**@yuyabee**
|
||||||
|
- Fixed README
|
298
vendor/github.com/gin-gonic/gin/BENCHMARKS.md
generated
vendored
Normal file
298
vendor/github.com/gin-gonic/gin/BENCHMARKS.md
generated
vendored
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
**Machine:** intel i7 ivy bridge quad-core. 8GB RAM.
|
||||||
|
**Date:** June 4th, 2015
|
||||||
|
[https://github.com/gin-gonic/go-http-routing-benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkAce_Param 5000000 372 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkBear_Param 1000000 1165 ns/op 424 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_Param 1000000 2440 ns/op 720 B/op 10 allocs/op
|
||||||
|
BenchmarkBone_Param 1000000 1067 ns/op 384 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_Param 5000000 240 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_Param 10000000 130 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_Param 10000000 133 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_Param 1000000 1826 ns/op 656 B/op 9 allocs/op
|
||||||
|
BenchmarkGoji_Param 2000000 957 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGoJsonRest_Param 1000000 2021 ns/op 657 B/op 14 allocs/op
|
||||||
|
BenchmarkGoRestful_Param 200000 8825 ns/op 2496 B/op 31 allocs/op
|
||||||
|
BenchmarkGorillaMux_Param 500000 3340 ns/op 784 B/op 9 allocs/op
|
||||||
|
BenchmarkHttpRouter_Param 10000000 152 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_Param 2000000 717 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkKocha_Param 3000000 423 ns/op 56 B/op 3 allocs/op
|
||||||
|
BenchmarkMacaron_Param 1000000 3410 ns/op 1104 B/op 11 allocs/op
|
||||||
|
BenchmarkMartini_Param 200000 7101 ns/op 1152 B/op 12 allocs/op
|
||||||
|
BenchmarkPat_Param 1000000 2040 ns/op 656 B/op 14 allocs/op
|
||||||
|
BenchmarkPossum_Param 1000000 2048 ns/op 624 B/op 7 allocs/op
|
||||||
|
BenchmarkR2router_Param 1000000 1144 ns/op 432 B/op 6 allocs/op
|
||||||
|
BenchmarkRevel_Param 200000 6725 ns/op 1672 B/op 28 allocs/op
|
||||||
|
BenchmarkRivet_Param 1000000 1121 ns/op 464 B/op 5 allocs/op
|
||||||
|
BenchmarkTango_Param 1000000 1479 ns/op 256 B/op 10 allocs/op
|
||||||
|
BenchmarkTigerTonic_Param 1000000 3393 ns/op 992 B/op 19 allocs/op
|
||||||
|
BenchmarkTraffic_Param 300000 5525 ns/op 1984 B/op 23 allocs/op
|
||||||
|
BenchmarkVulcan_Param 2000000 924 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_Param 1000000 1084 ns/op 368 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_Param5 3000000 614 ns/op 160 B/op 1 allocs/op
|
||||||
|
BenchmarkBear_Param5 1000000 1617 ns/op 469 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_Param5 1000000 3373 ns/op 992 B/op 13 allocs/op
|
||||||
|
BenchmarkBone_Param5 1000000 1478 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_Param5 3000000 570 ns/op 160 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_Param5 5000000 256 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_Param5 10000000 222 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_Param5 1000000 2789 ns/op 928 B/op 12 allocs/op
|
||||||
|
BenchmarkGoji_Param5 1000000 1287 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGoJsonRest_Param5 1000000 3670 ns/op 1105 B/op 17 allocs/op
|
||||||
|
BenchmarkGoRestful_Param5 200000 10756 ns/op 2672 B/op 31 allocs/op
|
||||||
|
BenchmarkGorillaMux_Param5 300000 5543 ns/op 912 B/op 9 allocs/op
|
||||||
|
BenchmarkHttpRouter_Param5 5000000 403 ns/op 160 B/op 1 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_Param5 1000000 1089 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkKocha_Param5 1000000 1682 ns/op 440 B/op 10 allocs/op
|
||||||
|
BenchmarkMacaron_Param5 300000 4596 ns/op 1376 B/op 14 allocs/op
|
||||||
|
BenchmarkMartini_Param5 100000 15703 ns/op 1280 B/op 12 allocs/op
|
||||||
|
BenchmarkPat_Param5 300000 5320 ns/op 1008 B/op 42 allocs/op
|
||||||
|
BenchmarkPossum_Param5 1000000 2155 ns/op 624 B/op 7 allocs/op
|
||||||
|
BenchmarkR2router_Param5 1000000 1559 ns/op 432 B/op 6 allocs/op
|
||||||
|
BenchmarkRevel_Param5 200000 8184 ns/op 2024 B/op 35 allocs/op
|
||||||
|
BenchmarkRivet_Param5 1000000 1914 ns/op 528 B/op 9 allocs/op
|
||||||
|
BenchmarkTango_Param5 1000000 3280 ns/op 944 B/op 18 allocs/op
|
||||||
|
BenchmarkTigerTonic_Param5 200000 11638 ns/op 2519 B/op 53 allocs/op
|
||||||
|
BenchmarkTraffic_Param5 200000 8941 ns/op 2280 B/op 31 allocs/op
|
||||||
|
BenchmarkVulcan_Param5 1000000 1279 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_Param5 1000000 1574 ns/op 416 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_Param20 1000000 1528 ns/op 640 B/op 1 allocs/op
|
||||||
|
BenchmarkBear_Param20 300000 4906 ns/op 1633 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_Param20 200000 10529 ns/op 3868 B/op 17 allocs/op
|
||||||
|
BenchmarkBone_Param20 300000 7362 ns/op 2539 B/op 5 allocs/op
|
||||||
|
BenchmarkDenco_Param20 1000000 1884 ns/op 640 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_Param20 2000000 689 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_Param20 3000000 545 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_Param20 200000 9437 ns/op 3804 B/op 16 allocs/op
|
||||||
|
BenchmarkGoji_Param20 500000 3987 ns/op 1246 B/op 2 allocs/op
|
||||||
|
BenchmarkGoJsonRest_Param20 100000 12799 ns/op 4492 B/op 21 allocs/op
|
||||||
|
BenchmarkGoRestful_Param20 100000 19451 ns/op 5244 B/op 33 allocs/op
|
||||||
|
BenchmarkGorillaMux_Param20 100000 12456 ns/op 3275 B/op 11 allocs/op
|
||||||
|
BenchmarkHttpRouter_Param20 1000000 1333 ns/op 640 B/op 1 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_Param20 300000 6490 ns/op 2187 B/op 4 allocs/op
|
||||||
|
BenchmarkKocha_Param20 300000 5335 ns/op 1808 B/op 27 allocs/op
|
||||||
|
BenchmarkMacaron_Param20 200000 11325 ns/op 4252 B/op 18 allocs/op
|
||||||
|
BenchmarkMartini_Param20 20000 64419 ns/op 3644 B/op 14 allocs/op
|
||||||
|
BenchmarkPat_Param20 50000 24672 ns/op 4888 B/op 151 allocs/op
|
||||||
|
BenchmarkPossum_Param20 1000000 2085 ns/op 624 B/op 7 allocs/op
|
||||||
|
BenchmarkR2router_Param20 300000 6809 ns/op 2283 B/op 8 allocs/op
|
||||||
|
BenchmarkRevel_Param20 100000 16600 ns/op 5551 B/op 54 allocs/op
|
||||||
|
BenchmarkRivet_Param20 200000 8428 ns/op 2620 B/op 26 allocs/op
|
||||||
|
BenchmarkTango_Param20 100000 16302 ns/op 8224 B/op 48 allocs/op
|
||||||
|
BenchmarkTigerTonic_Param20 30000 46828 ns/op 10538 B/op 178 allocs/op
|
||||||
|
BenchmarkTraffic_Param20 50000 28871 ns/op 7998 B/op 66 allocs/op
|
||||||
|
BenchmarkVulcan_Param20 1000000 2267 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_Param20 300000 6828 ns/op 2507 B/op 5 allocs/op
|
||||||
|
BenchmarkAce_ParamWrite 3000000 502 ns/op 40 B/op 2 allocs/op
|
||||||
|
BenchmarkBear_ParamWrite 1000000 1303 ns/op 424 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_ParamWrite 1000000 2489 ns/op 728 B/op 11 allocs/op
|
||||||
|
BenchmarkBone_ParamWrite 1000000 1181 ns/op 384 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_ParamWrite 5000000 315 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_ParamWrite 10000000 237 ns/op 8 B/op 1 allocs/op
|
||||||
|
BenchmarkGin_ParamWrite 5000000 336 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_ParamWrite 1000000 2079 ns/op 664 B/op 10 allocs/op
|
||||||
|
BenchmarkGoji_ParamWrite 1000000 1092 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGoJsonRest_ParamWrite 1000000 3329 ns/op 1136 B/op 19 allocs/op
|
||||||
|
BenchmarkGoRestful_ParamWrite 200000 9273 ns/op 2504 B/op 32 allocs/op
|
||||||
|
BenchmarkGorillaMux_ParamWrite 500000 3919 ns/op 792 B/op 10 allocs/op
|
||||||
|
BenchmarkHttpRouter_ParamWrite 10000000 223 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_ParamWrite 2000000 788 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkKocha_ParamWrite 3000000 549 ns/op 56 B/op 3 allocs/op
|
||||||
|
BenchmarkMacaron_ParamWrite 500000 4558 ns/op 1216 B/op 16 allocs/op
|
||||||
|
BenchmarkMartini_ParamWrite 200000 8850 ns/op 1256 B/op 16 allocs/op
|
||||||
|
BenchmarkPat_ParamWrite 500000 3679 ns/op 1088 B/op 19 allocs/op
|
||||||
|
BenchmarkPossum_ParamWrite 1000000 2114 ns/op 624 B/op 7 allocs/op
|
||||||
|
BenchmarkR2router_ParamWrite 1000000 1320 ns/op 432 B/op 6 allocs/op
|
||||||
|
BenchmarkRevel_ParamWrite 200000 8048 ns/op 2128 B/op 33 allocs/op
|
||||||
|
BenchmarkRivet_ParamWrite 1000000 1393 ns/op 472 B/op 6 allocs/op
|
||||||
|
BenchmarkTango_ParamWrite 2000000 819 ns/op 136 B/op 5 allocs/op
|
||||||
|
BenchmarkTigerTonic_ParamWrite 300000 5860 ns/op 1440 B/op 25 allocs/op
|
||||||
|
BenchmarkTraffic_ParamWrite 200000 7429 ns/op 2400 B/op 27 allocs/op
|
||||||
|
BenchmarkVulcan_ParamWrite 2000000 972 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_ParamWrite 1000000 1226 ns/op 368 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_GithubStatic 5000000 294 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GithubStatic 3000000 575 ns/op 88 B/op 3 allocs/op
|
||||||
|
BenchmarkBeego_GithubStatic 1000000 1561 ns/op 368 B/op 7 allocs/op
|
||||||
|
BenchmarkBone_GithubStatic 200000 12301 ns/op 2880 B/op 60 allocs/op
|
||||||
|
BenchmarkDenco_GithubStatic 20000000 74.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkEcho_GithubStatic 10000000 176 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GithubStatic 10000000 159 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GithubStatic 1000000 1116 ns/op 304 B/op 6 allocs/op
|
||||||
|
BenchmarkGoji_GithubStatic 5000000 413 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGoRestful_GithubStatic 30000 55200 ns/op 3520 B/op 36 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GithubStatic 1000000 1504 ns/op 337 B/op 12 allocs/op
|
||||||
|
BenchmarkGorillaMux_GithubStatic 100000 23620 ns/op 464 B/op 8 allocs/op
|
||||||
|
BenchmarkHttpRouter_GithubStatic 20000000 78.3 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GithubStatic 20000000 84.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkKocha_GithubStatic 20000000 111 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GithubStatic 1000000 2686 ns/op 752 B/op 8 allocs/op
|
||||||
|
BenchmarkMartini_GithubStatic 100000 22244 ns/op 832 B/op 11 allocs/op
|
||||||
|
BenchmarkPat_GithubStatic 100000 13278 ns/op 3648 B/op 76 allocs/op
|
||||||
|
BenchmarkPossum_GithubStatic 1000000 1429 ns/op 480 B/op 4 allocs/op
|
||||||
|
BenchmarkR2router_GithubStatic 2000000 726 ns/op 144 B/op 5 allocs/op
|
||||||
|
BenchmarkRevel_GithubStatic 300000 6271 ns/op 1288 B/op 25 allocs/op
|
||||||
|
BenchmarkRivet_GithubStatic 3000000 474 ns/op 112 B/op 2 allocs/op
|
||||||
|
BenchmarkTango_GithubStatic 1000000 1842 ns/op 256 B/op 10 allocs/op
|
||||||
|
BenchmarkTigerTonic_GithubStatic 5000000 361 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkTraffic_GithubStatic 30000 47197 ns/op 18920 B/op 149 allocs/op
|
||||||
|
BenchmarkVulcan_GithubStatic 1000000 1415 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_GithubStatic 1000000 2522 ns/op 512 B/op 11 allocs/op
|
||||||
|
BenchmarkAce_GithubParam 3000000 578 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkBear_GithubParam 1000000 1592 ns/op 464 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_GithubParam 1000000 2891 ns/op 784 B/op 11 allocs/op
|
||||||
|
BenchmarkBone_GithubParam 300000 6440 ns/op 1456 B/op 16 allocs/op
|
||||||
|
BenchmarkDenco_GithubParam 3000000 514 ns/op 128 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_GithubParam 5000000 292 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GithubParam 10000000 242 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GithubParam 1000000 2343 ns/op 720 B/op 10 allocs/op
|
||||||
|
BenchmarkGoji_GithubParam 1000000 1566 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GithubParam 1000000 2828 ns/op 721 B/op 15 allocs/op
|
||||||
|
BenchmarkGoRestful_GithubParam 10000 177711 ns/op 2816 B/op 35 allocs/op
|
||||||
|
BenchmarkGorillaMux_GithubParam 100000 13591 ns/op 816 B/op 9 allocs/op
|
||||||
|
BenchmarkHttpRouter_GithubParam 5000000 352 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GithubParam 2000000 973 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkKocha_GithubParam 2000000 889 ns/op 128 B/op 5 allocs/op
|
||||||
|
BenchmarkMacaron_GithubParam 500000 4047 ns/op 1168 B/op 12 allocs/op
|
||||||
|
BenchmarkMartini_GithubParam 50000 28982 ns/op 1184 B/op 12 allocs/op
|
||||||
|
BenchmarkPat_GithubParam 200000 8747 ns/op 2480 B/op 56 allocs/op
|
||||||
|
BenchmarkPossum_GithubParam 1000000 2158 ns/op 624 B/op 7 allocs/op
|
||||||
|
BenchmarkR2router_GithubParam 1000000 1352 ns/op 432 B/op 6 allocs/op
|
||||||
|
BenchmarkRevel_GithubParam 200000 7673 ns/op 1784 B/op 30 allocs/op
|
||||||
|
BenchmarkRivet_GithubParam 1000000 1573 ns/op 480 B/op 6 allocs/op
|
||||||
|
BenchmarkTango_GithubParam 1000000 2418 ns/op 480 B/op 13 allocs/op
|
||||||
|
BenchmarkTigerTonic_GithubParam 300000 6048 ns/op 1440 B/op 28 allocs/op
|
||||||
|
BenchmarkTraffic_GithubParam 100000 20143 ns/op 6024 B/op 55 allocs/op
|
||||||
|
BenchmarkVulcan_GithubParam 1000000 2224 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_GithubParam 500000 4156 ns/op 1312 B/op 12 allocs/op
|
||||||
|
BenchmarkAce_GithubAll 10000 109482 ns/op 13792 B/op 167 allocs/op
|
||||||
|
BenchmarkBear_GithubAll 10000 287490 ns/op 79952 B/op 943 allocs/op
|
||||||
|
BenchmarkBeego_GithubAll 3000 562184 ns/op 146272 B/op 2092 allocs/op
|
||||||
|
BenchmarkBone_GithubAll 500 2578716 ns/op 648016 B/op 8119 allocs/op
|
||||||
|
BenchmarkDenco_GithubAll 20000 94955 ns/op 20224 B/op 167 allocs/op
|
||||||
|
BenchmarkEcho_GithubAll 30000 58705 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GithubAll 30000 50991 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GithubAll 5000 449648 ns/op 133280 B/op 1889 allocs/op
|
||||||
|
BenchmarkGoji_GithubAll 2000 689748 ns/op 56113 B/op 334 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GithubAll 5000 537769 ns/op 135995 B/op 2940 allocs/op
|
||||||
|
BenchmarkGoRestful_GithubAll 100 18410628 ns/op 797236 B/op 7725 allocs/op
|
||||||
|
BenchmarkGorillaMux_GithubAll 200 8036360 ns/op 153137 B/op 1791 allocs/op
|
||||||
|
BenchmarkHttpRouter_GithubAll 20000 63506 ns/op 13792 B/op 167 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GithubAll 10000 165927 ns/op 56112 B/op 334 allocs/op
|
||||||
|
BenchmarkKocha_GithubAll 10000 171362 ns/op 23304 B/op 843 allocs/op
|
||||||
|
BenchmarkMacaron_GithubAll 2000 817008 ns/op 224960 B/op 2315 allocs/op
|
||||||
|
BenchmarkMartini_GithubAll 100 12609209 ns/op 237952 B/op 2686 allocs/op
|
||||||
|
BenchmarkPat_GithubAll 300 4830398 ns/op 1504101 B/op 32222 allocs/op
|
||||||
|
BenchmarkPossum_GithubAll 10000 301716 ns/op 97440 B/op 812 allocs/op
|
||||||
|
BenchmarkR2router_GithubAll 10000 270691 ns/op 77328 B/op 1182 allocs/op
|
||||||
|
BenchmarkRevel_GithubAll 1000 1491919 ns/op 345553 B/op 5918 allocs/op
|
||||||
|
BenchmarkRivet_GithubAll 10000 283860 ns/op 84272 B/op 1079 allocs/op
|
||||||
|
BenchmarkTango_GithubAll 5000 473821 ns/op 87078 B/op 2470 allocs/op
|
||||||
|
BenchmarkTigerTonic_GithubAll 2000 1120131 ns/op 241088 B/op 6052 allocs/op
|
||||||
|
BenchmarkTraffic_GithubAll 200 8708979 ns/op 2664762 B/op 22390 allocs/op
|
||||||
|
BenchmarkVulcan_GithubAll 5000 353392 ns/op 19894 B/op 609 allocs/op
|
||||||
|
BenchmarkZeus_GithubAll 2000 944234 ns/op 300688 B/op 2648 allocs/op
|
||||||
|
BenchmarkAce_GPlusStatic 5000000 251 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GPlusStatic 3000000 415 ns/op 72 B/op 3 allocs/op
|
||||||
|
BenchmarkBeego_GPlusStatic 1000000 1416 ns/op 352 B/op 7 allocs/op
|
||||||
|
BenchmarkBone_GPlusStatic 10000000 192 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkDenco_GPlusStatic 30000000 47.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkEcho_GPlusStatic 10000000 131 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GPlusStatic 10000000 131 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlusStatic 1000000 1035 ns/op 288 B/op 6 allocs/op
|
||||||
|
BenchmarkGoji_GPlusStatic 5000000 304 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlusStatic 1000000 1286 ns/op 337 B/op 12 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlusStatic 200000 9649 ns/op 2160 B/op 30 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlusStatic 1000000 2346 ns/op 464 B/op 8 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlusStatic 30000000 42.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlusStatic 30000000 49.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkKocha_GPlusStatic 20000000 74.8 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GPlusStatic 1000000 2520 ns/op 736 B/op 8 allocs/op
|
||||||
|
BenchmarkMartini_GPlusStatic 300000 5310 ns/op 832 B/op 11 allocs/op
|
||||||
|
BenchmarkPat_GPlusStatic 5000000 398 ns/op 96 B/op 2 allocs/op
|
||||||
|
BenchmarkPossum_GPlusStatic 1000000 1434 ns/op 480 B/op 4 allocs/op
|
||||||
|
BenchmarkR2router_GPlusStatic 2000000 646 ns/op 144 B/op 5 allocs/op
|
||||||
|
BenchmarkRevel_GPlusStatic 300000 6172 ns/op 1272 B/op 25 allocs/op
|
||||||
|
BenchmarkRivet_GPlusStatic 3000000 444 ns/op 112 B/op 2 allocs/op
|
||||||
|
BenchmarkTango_GPlusStatic 1000000 1400 ns/op 208 B/op 10 allocs/op
|
||||||
|
BenchmarkTigerTonic_GPlusStatic 10000000 213 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkTraffic_GPlusStatic 1000000 3091 ns/op 1208 B/op 16 allocs/op
|
||||||
|
BenchmarkVulcan_GPlusStatic 2000000 863 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_GPlusStatic 10000000 237 ns/op 16 B/op 1 allocs/op
|
||||||
|
BenchmarkAce_GPlusParam 3000000 435 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkBear_GPlusParam 1000000 1205 ns/op 448 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_GPlusParam 1000000 2494 ns/op 720 B/op 10 allocs/op
|
||||||
|
BenchmarkBone_GPlusParam 1000000 1126 ns/op 384 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_GPlusParam 5000000 325 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_GPlusParam 10000000 168 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GPlusParam 10000000 170 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlusParam 1000000 1895 ns/op 656 B/op 9 allocs/op
|
||||||
|
BenchmarkGoji_GPlusParam 1000000 1071 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlusParam 1000000 2282 ns/op 657 B/op 14 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlusParam 100000 19400 ns/op 2560 B/op 33 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlusParam 500000 5001 ns/op 784 B/op 9 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlusParam 10000000 240 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlusParam 2000000 797 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkKocha_GPlusParam 3000000 505 ns/op 56 B/op 3 allocs/op
|
||||||
|
BenchmarkMacaron_GPlusParam 1000000 3668 ns/op 1104 B/op 11 allocs/op
|
||||||
|
BenchmarkMartini_GPlusParam 200000 10672 ns/op 1152 B/op 12 allocs/op
|
||||||
|
BenchmarkPat_GPlusParam 1000000 2376 ns/op 704 B/op 14 allocs/op
|
||||||
|
BenchmarkPossum_GPlusParam 1000000 2090 ns/op 624 B/op 7 allocs/op
|
||||||
|
BenchmarkR2router_GPlusParam 1000000 1233 ns/op 432 B/op 6 allocs/op
|
||||||
|
BenchmarkRevel_GPlusParam 200000 6778 ns/op 1704 B/op 28 allocs/op
|
||||||
|
BenchmarkRivet_GPlusParam 1000000 1279 ns/op 464 B/op 5 allocs/op
|
||||||
|
BenchmarkTango_GPlusParam 1000000 1981 ns/op 272 B/op 10 allocs/op
|
||||||
|
BenchmarkTigerTonic_GPlusParam 500000 3893 ns/op 1064 B/op 19 allocs/op
|
||||||
|
BenchmarkTraffic_GPlusParam 200000 6585 ns/op 2000 B/op 23 allocs/op
|
||||||
|
BenchmarkVulcan_GPlusParam 1000000 1233 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_GPlusParam 1000000 1350 ns/op 368 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_GPlus2Params 3000000 512 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkBear_GPlus2Params 1000000 1564 ns/op 464 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_GPlus2Params 1000000 3043 ns/op 784 B/op 11 allocs/op
|
||||||
|
BenchmarkBone_GPlus2Params 1000000 3152 ns/op 736 B/op 7 allocs/op
|
||||||
|
BenchmarkDenco_GPlus2Params 3000000 431 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_GPlus2Params 5000000 247 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GPlus2Params 10000000 219 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlus2Params 1000000 2363 ns/op 720 B/op 10 allocs/op
|
||||||
|
BenchmarkGoji_GPlus2Params 1000000 1540 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlus2Params 1000000 2872 ns/op 721 B/op 15 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlus2Params 100000 23030 ns/op 2720 B/op 35 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlus2Params 200000 10516 ns/op 816 B/op 9 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlus2Params 5000000 273 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlus2Params 2000000 939 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkKocha_GPlus2Params 2000000 844 ns/op 128 B/op 5 allocs/op
|
||||||
|
BenchmarkMacaron_GPlus2Params 500000 3914 ns/op 1168 B/op 12 allocs/op
|
||||||
|
BenchmarkMartini_GPlus2Params 50000 35759 ns/op 1280 B/op 16 allocs/op
|
||||||
|
BenchmarkPat_GPlus2Params 200000 7089 ns/op 2304 B/op 41 allocs/op
|
||||||
|
BenchmarkPossum_GPlus2Params 1000000 2093 ns/op 624 B/op 7 allocs/op
|
||||||
|
BenchmarkR2router_GPlus2Params 1000000 1320 ns/op 432 B/op 6 allocs/op
|
||||||
|
BenchmarkRevel_GPlus2Params 200000 7351 ns/op 1800 B/op 30 allocs/op
|
||||||
|
BenchmarkRivet_GPlus2Params 1000000 1485 ns/op 480 B/op 6 allocs/op
|
||||||
|
BenchmarkTango_GPlus2Params 1000000 2111 ns/op 448 B/op 12 allocs/op
|
||||||
|
BenchmarkTigerTonic_GPlus2Params 300000 6271 ns/op 1528 B/op 28 allocs/op
|
||||||
|
BenchmarkTraffic_GPlus2Params 100000 14886 ns/op 3312 B/op 34 allocs/op
|
||||||
|
BenchmarkVulcan_GPlus2Params 1000000 1883 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkZeus_GPlus2Params 1000000 2686 ns/op 784 B/op 6 allocs/op
|
||||||
|
BenchmarkAce_GPlusAll 300000 5912 ns/op 640 B/op 11 allocs/op
|
||||||
|
BenchmarkBear_GPlusAll 100000 16448 ns/op 5072 B/op 61 allocs/op
|
||||||
|
BenchmarkBeego_GPlusAll 50000 32916 ns/op 8976 B/op 129 allocs/op
|
||||||
|
BenchmarkBone_GPlusAll 50000 25836 ns/op 6992 B/op 76 allocs/op
|
||||||
|
BenchmarkDenco_GPlusAll 500000 4462 ns/op 672 B/op 11 allocs/op
|
||||||
|
BenchmarkEcho_GPlusAll 500000 2806 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GPlusAll 500000 2579 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlusAll 50000 25223 ns/op 8144 B/op 116 allocs/op
|
||||||
|
BenchmarkGoji_GPlusAll 100000 14237 ns/op 3696 B/op 22 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlusAll 50000 29227 ns/op 8221 B/op 183 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlusAll 10000 203144 ns/op 36064 B/op 441 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlusAll 20000 80906 ns/op 9712 B/op 115 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlusAll 500000 3040 ns/op 640 B/op 11 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlusAll 200000 9627 ns/op 3696 B/op 22 allocs/op
|
||||||
|
BenchmarkKocha_GPlusAll 200000 8108 ns/op 976 B/op 43 allocs/op
|
||||||
|
BenchmarkMacaron_GPlusAll 30000 48083 ns/op 13968 B/op 142 allocs/op
|
||||||
|
BenchmarkMartini_GPlusAll 10000 196978 ns/op 15072 B/op 178 allocs/op
|
||||||
|
BenchmarkPat_GPlusAll 30000 58865 ns/op 16880 B/op 343 allocs/op
|
||||||
|
BenchmarkPossum_GPlusAll 100000 19685 ns/op 6240 B/op 52 allocs/op
|
||||||
|
BenchmarkR2router_GPlusAll 100000 16251 ns/op 5040 B/op 76 allocs/op
|
||||||
|
BenchmarkRevel_GPlusAll 20000 93489 ns/op 21656 B/op 368 allocs/op
|
||||||
|
BenchmarkRivet_GPlusAll 100000 16907 ns/op 5408 B/op 64 allocs/op
|
||||||
|
```
|
150
vendor/github.com/gin-gonic/gin/CHANGELOG.md
generated
vendored
Normal file
150
vendor/github.com/gin-gonic/gin/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#CHANGELOG
|
||||||
|
|
||||||
|
###Gin 1.0rc2 (...)
|
||||||
|
|
||||||
|
- [PERFORMANCE] Fast path for writing Content-Type.
|
||||||
|
- [PERFORMANCE] Much faster 404 routing
|
||||||
|
- [PERFORMANCE] Allocation optimizations
|
||||||
|
- [PERFORMANCE] Faster root tree lookup
|
||||||
|
- [PERFORMANCE] Zero overhead, String() and JSON() rendering.
|
||||||
|
- [PERFORMANCE] Faster ClientIP parsing
|
||||||
|
- [PERFORMANCE] Much faster SSE implementation
|
||||||
|
- [NEW] Benchmarks suite
|
||||||
|
- [NEW] Bind validation can be disabled and replaced with custom validators.
|
||||||
|
- [NEW] More flexible HTML render
|
||||||
|
- [NEW] Multipart and PostForm bindings
|
||||||
|
- [NEW] Adds method to return all the registered routes
|
||||||
|
- [NEW] Context.HandlerName() returns the main handler's name
|
||||||
|
- [NEW] Adds Error.IsType() helper
|
||||||
|
- [FIX] Binding multipart form
|
||||||
|
- [FIX] Integration tests
|
||||||
|
- [FIX] Crash when binding non struct object in Context.
|
||||||
|
- [FIX] RunTLS() implementation
|
||||||
|
- [FIX] Logger() unit tests
|
||||||
|
- [FIX] Adds SetHTMLTemplate() warning
|
||||||
|
- [FIX] Context.IsAborted()
|
||||||
|
- [FIX] More unit tests
|
||||||
|
- [FIX] JSON, XML, HTML renders accept custom content-types
|
||||||
|
- [FIX] gin.AbortIndex is unexported
|
||||||
|
- [FIX] Better approach to avoid directory listing in StaticFS()
|
||||||
|
- [FIX] Context.ClientIP() always returns the IP with trimmed spaces.
|
||||||
|
- [FIX] Better warning when running in debug mode.
|
||||||
|
- [FIX] Google App Engine integration. debugPrint does not use os.Stdout
|
||||||
|
- [FIX] Fixes integer overflow in error type
|
||||||
|
- [FIX] Error implements the json.Marshaller interface
|
||||||
|
- [FIX] MIT license in every file
|
||||||
|
|
||||||
|
|
||||||
|
###Gin 1.0rc1 (May 22, 2015)
|
||||||
|
|
||||||
|
- [PERFORMANCE] Zero allocation router
|
||||||
|
- [PERFORMANCE] Faster JSON, XML and text rendering
|
||||||
|
- [PERFORMANCE] Custom hand optimized HttpRouter for Gin
|
||||||
|
- [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations
|
||||||
|
- [NEW] Built-in support for golang.org/x/net/context
|
||||||
|
- [NEW] Any(path, handler). Create a route that matches any path
|
||||||
|
- [NEW] Refactored rendering pipeline (faster and static typeded)
|
||||||
|
- [NEW] Refactored errors API
|
||||||
|
- [NEW] IndentedJSON() prints pretty JSON
|
||||||
|
- [NEW] Added gin.DefaultWriter
|
||||||
|
- [NEW] UNIX socket support
|
||||||
|
- [NEW] RouterGroup.BasePath is exposed
|
||||||
|
- [NEW] JSON validation using go-validate-yourself (very powerful options)
|
||||||
|
- [NEW] Completed suite of unit tests
|
||||||
|
- [NEW] HTTP streaming with c.Stream()
|
||||||
|
- [NEW] StaticFile() creates a router for serving just one file.
|
||||||
|
- [NEW] StaticFS() has an option to disable directory listing.
|
||||||
|
- [NEW] StaticFS() for serving static files through virtual filesystems
|
||||||
|
- [NEW] Server-Sent Events native support
|
||||||
|
- [NEW] WrapF() and WrapH() helpers for wrapping http.HandlerFunc and http.Handler
|
||||||
|
- [NEW] Added LoggerWithWriter() middleware
|
||||||
|
- [NEW] Added RecoveryWithWriter() middleware
|
||||||
|
- [NEW] Added DefaultPostFormValue()
|
||||||
|
- [NEW] Added DefaultFormValue()
|
||||||
|
- [NEW] Added DefaultParamValue()
|
||||||
|
- [FIX] BasicAuth() when using custom realm
|
||||||
|
- [FIX] Bug when serving static files in nested routing group
|
||||||
|
- [FIX] Redirect using built-in http.Redirect()
|
||||||
|
- [FIX] Logger when printing the requested path
|
||||||
|
- [FIX] Documentation typos
|
||||||
|
- [FIX] Context.Engine renamed to Context.engine
|
||||||
|
- [FIX] Better debugging messages
|
||||||
|
- [FIX] ErrorLogger
|
||||||
|
- [FIX] Debug HTTP render
|
||||||
|
- [FIX] Refactored binding and render modules
|
||||||
|
- [FIX] Refactored Context initialization
|
||||||
|
- [FIX] Refactored BasicAuth()
|
||||||
|
- [FIX] NoMethod/NoRoute handlers
|
||||||
|
- [FIX] Hijacking http
|
||||||
|
- [FIX] Better support for Google App Engine (using log instead of fmt)
|
||||||
|
|
||||||
|
|
||||||
|
###Gin 0.6 (Mar 9, 2015)
|
||||||
|
|
||||||
|
- [NEW] Support multipart/form-data
|
||||||
|
- [NEW] NoMethod handler
|
||||||
|
- [NEW] Validate sub structures
|
||||||
|
- [NEW] Support for HTTP Realm Auth
|
||||||
|
- [FIX] Unsigned integers in binding
|
||||||
|
- [FIX] Improve color logger
|
||||||
|
|
||||||
|
|
||||||
|
###Gin 0.5 (Feb 7, 2015)
|
||||||
|
|
||||||
|
- [NEW] Content Negotiation
|
||||||
|
- [FIX] Solved security bug that allow a client to spoof ip
|
||||||
|
- [FIX] Fix unexported/ignored fields in binding
|
||||||
|
|
||||||
|
|
||||||
|
###Gin 0.4 (Aug 21, 2014)
|
||||||
|
|
||||||
|
- [NEW] Development mode
|
||||||
|
- [NEW] Unit tests
|
||||||
|
- [NEW] Add Content.Redirect()
|
||||||
|
- [FIX] Deferring WriteHeader()
|
||||||
|
- [FIX] Improved documentation for model binding
|
||||||
|
|
||||||
|
|
||||||
|
###Gin 0.3 (Jul 18, 2014)
|
||||||
|
|
||||||
|
- [PERFORMANCE] Normal log and error log are printed in the same call.
|
||||||
|
- [PERFORMANCE] Improve performance of NoRouter()
|
||||||
|
- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults.
|
||||||
|
- [NEW] Flexible rendering API
|
||||||
|
- [NEW] Add Context.File()
|
||||||
|
- [NEW] Add shorcut RunTLS() for http.ListenAndServeTLS
|
||||||
|
- [FIX] Rename NotFound404() to NoRoute()
|
||||||
|
- [FIX] Errors in context are purged
|
||||||
|
- [FIX] Adds HEAD method in Static file serving
|
||||||
|
- [FIX] Refactors Static() file serving
|
||||||
|
- [FIX] Using keyed initialization to fix app-engine integration
|
||||||
|
- [FIX] Can't unmarshal JSON array, #63
|
||||||
|
- [FIX] Renaming Context.Req to Context.Request
|
||||||
|
- [FIX] Check application/x-www-form-urlencoded when parsing form
|
||||||
|
|
||||||
|
|
||||||
|
###Gin 0.2b (Jul 08, 2014)
|
||||||
|
- [PERFORMANCE] Using sync.Pool to allocatio/gc overhead
|
||||||
|
- [NEW] Travis CI integration
|
||||||
|
- [NEW] Completely new logger
|
||||||
|
- [NEW] New API for serving static files. gin.Static()
|
||||||
|
- [NEW] gin.H() can be serialized into XML
|
||||||
|
- [NEW] Typed errors. Errors can be typed. Internet/external/custom.
|
||||||
|
- [NEW] Support for Godeps
|
||||||
|
- [NEW] Travis/Godocs badges in README
|
||||||
|
- [NEW] New Bind() and BindWith() methods for parsing request body.
|
||||||
|
- [NEW] Add Content.Copy()
|
||||||
|
- [NEW] Add context.LastError()
|
||||||
|
- [NEW] Add shorcut for OPTIONS HTTP method
|
||||||
|
- [FIX] Tons of README fixes
|
||||||
|
- [FIX] Header is written before body
|
||||||
|
- [FIX] BasicAuth() and changes API a little bit
|
||||||
|
- [FIX] Recovery() middleware only prints panics
|
||||||
|
- [FIX] Context.Get() does not panic anymore. Use MustGet() instead.
|
||||||
|
- [FIX] Multiple http.WriteHeader() in NotFound handlers
|
||||||
|
- [FIX] Engine.Run() panics if http server can't be setted up
|
||||||
|
- [FIX] Crash when route path doesn't start with '/'
|
||||||
|
- [FIX] Do not update header when status code is negative
|
||||||
|
- [FIX] Setting response headers before calling WriteHeader in context.String()
|
||||||
|
- [FIX] Add MIT license
|
||||||
|
- [FIX] Changes behaviour of ErrorLogger() and Logger()
|
21
vendor/github.com/gin-gonic/gin/LICENSE
generated
vendored
Normal file
21
vendor/github.com/gin-gonic/gin/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Manuel Martínez-Almeida
|
||||||
|
|
||||||
|
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.
|
733
vendor/github.com/gin-gonic/gin/README.md
generated
vendored
Normal file
733
vendor/github.com/gin-gonic/gin/README.md
generated
vendored
Normal file
@ -0,0 +1,733 @@
|
|||||||
|
|
||||||
|
#Gin Web Framework
|
||||||
|
|
||||||
|
<img align="right" src="https://raw.githubusercontent.com/gin-gonic/gin/master/logo.jpg">
|
||||||
|
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
|
||||||
|
[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
|
||||||
|
[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
|
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||||
|
|
||||||
|
![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cat test.go
|
||||||
|
```
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "gopkg.in/gin-gonic/gin.v1"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"message": "pong",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
r.Run() // listen and serve on 0.0.0.0:8080
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
|
||||||
|
|
||||||
|
[See all benchmarks](/BENCHMARKS.md)
|
||||||
|
|
||||||
|
|
||||||
|
Benchmark name | (1) | (2) | (3) | (4)
|
||||||
|
--------------------------------|----------:|----------:|----------:|------:
|
||||||
|
BenchmarkAce_GithubAll | 10000 | 109482 | 13792 | 167
|
||||||
|
BenchmarkBear_GithubAll | 10000 | 287490 | 79952 | 943
|
||||||
|
BenchmarkBeego_GithubAll | 3000 | 562184 | 146272 | 2092
|
||||||
|
BenchmarkBone_GithubAll | 500 | 2578716 | 648016 | 8119
|
||||||
|
BenchmarkDenco_GithubAll | 20000 | 94955 | 20224 | 167
|
||||||
|
BenchmarkEcho_GithubAll | 30000 | 58705 | 0 | 0
|
||||||
|
**BenchmarkGin_GithubAll** | **30000** | **50991** | **0** | **0**
|
||||||
|
BenchmarkGocraftWeb_GithubAll | 5000 | 449648 | 133280 | 1889
|
||||||
|
BenchmarkGoji_GithubAll | 2000 | 689748 | 56113 | 334
|
||||||
|
BenchmarkGoJsonRest_GithubAll | 5000 | 537769 | 135995 | 2940
|
||||||
|
BenchmarkGoRestful_GithubAll | 100 | 18410628 | 797236 | 7725
|
||||||
|
BenchmarkGorillaMux_GithubAll | 200 | 8036360 | 153137 | 1791
|
||||||
|
BenchmarkHttpRouter_GithubAll | 20000 | 63506 | 13792 | 167
|
||||||
|
BenchmarkHttpTreeMux_GithubAll | 10000 | 165927 | 56112 | 334
|
||||||
|
BenchmarkKocha_GithubAll | 10000 | 171362 | 23304 | 843
|
||||||
|
BenchmarkMacaron_GithubAll | 2000 | 817008 | 224960 | 2315
|
||||||
|
BenchmarkMartini_GithubAll | 100 | 12609209 | 237952 | 2686
|
||||||
|
BenchmarkPat_GithubAll | 300 | 4830398 | 1504101 | 32222
|
||||||
|
BenchmarkPossum_GithubAll | 10000 | 301716 | 97440 | 812
|
||||||
|
BenchmarkR2router_GithubAll | 10000 | 270691 | 77328 | 1182
|
||||||
|
BenchmarkRevel_GithubAll | 1000 | 1491919 | 345553 | 5918
|
||||||
|
BenchmarkRivet_GithubAll | 10000 | 283860 | 84272 | 1079
|
||||||
|
BenchmarkTango_GithubAll | 5000 | 473821 | 87078 | 2470
|
||||||
|
BenchmarkTigerTonic_GithubAll | 2000 | 1120131 | 241088 | 6052
|
||||||
|
BenchmarkTraffic_GithubAll | 200 | 8708979 | 2664762 | 22390
|
||||||
|
BenchmarkVulcan_GithubAll | 5000 | 353392 | 19894 | 609
|
||||||
|
BenchmarkZeus_GithubAll | 2000 | 944234 | 300688 | 2648
|
||||||
|
|
||||||
|
(1): Total Repetitions
|
||||||
|
(2): Single Repetition Duration (ns/op)
|
||||||
|
(3): Heap Memory (B/op)
|
||||||
|
(4): Average Allocations per Repetition (allocs/op)
|
||||||
|
|
||||||
|
## Gin v1. stable
|
||||||
|
|
||||||
|
- [x] Zero allocation router.
|
||||||
|
- [x] Still the fastest http router and framework. From routing to writing.
|
||||||
|
- [x] Complete suite of unit tests
|
||||||
|
- [x] Battle tested
|
||||||
|
- [x] API frozen, new releases will not break your code.
|
||||||
|
|
||||||
|
|
||||||
|
## Start using it
|
||||||
|
|
||||||
|
1. Download and install it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ go get gopkg.in/gin-gonic/gin.v1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Import it in your code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "gopkg.in/gin-gonic/gin.v1"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "net/http"
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Examples
|
||||||
|
|
||||||
|
#### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// Creates a gin router with default middleware:
|
||||||
|
// logger and recovery (crash-free) middleware
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
router.GET("/someGet", getting)
|
||||||
|
router.POST("/somePost", posting)
|
||||||
|
router.PUT("/somePut", putting)
|
||||||
|
router.DELETE("/someDelete", deleting)
|
||||||
|
router.PATCH("/somePatch", patching)
|
||||||
|
router.HEAD("/someHead", head)
|
||||||
|
router.OPTIONS("/someOptions", options)
|
||||||
|
|
||||||
|
// By default it serves on :8080 unless a
|
||||||
|
// PORT environment variable was defined.
|
||||||
|
router.Run()
|
||||||
|
// router.Run(":3000") for a hard coded port
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Parameters in path
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// This handler will match /user/john but will not match neither /user/ or /user
|
||||||
|
router.GET("/user/:name", func(c *gin.Context) {
|
||||||
|
name := c.Param("name")
|
||||||
|
c.String(http.StatusOK, "Hello %s", name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// However, this one will match /user/john/ and also /user/john/send
|
||||||
|
// If no other routers match /user/john, it will redirect to /user/john/
|
||||||
|
router.GET("/user/:name/*action", func(c *gin.Context) {
|
||||||
|
name := c.Param("name")
|
||||||
|
action := c.Param("action")
|
||||||
|
message := name + " is " + action
|
||||||
|
c.String(http.StatusOK, message)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Querystring parameters
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Query string parameters are parsed using the existing underlying request object.
|
||||||
|
// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe
|
||||||
|
router.GET("/welcome", func(c *gin.Context) {
|
||||||
|
firstname := c.DefaultQuery("firstname", "Guest")
|
||||||
|
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||||
|
|
||||||
|
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multipart/Urlencoded Form
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
router.POST("/form_post", func(c *gin.Context) {
|
||||||
|
message := c.PostForm("message")
|
||||||
|
nick := c.DefaultPostForm("nick", "anonymous")
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"status": "posted",
|
||||||
|
"message": message,
|
||||||
|
"nick": nick,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Another example: query + post form
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /post?id=1234&page=1 HTTP/1.1
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
|
||||||
|
name=manu&message=this_is_great
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
router.POST("/post", func(c *gin.Context) {
|
||||||
|
|
||||||
|
id := c.Query("id")
|
||||||
|
page := c.DefaultQuery("page", "0")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
message := c.PostForm("message")
|
||||||
|
|
||||||
|
fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
id: 1234; page: 1; name: manu; message: this_is_great
|
||||||
|
```
|
||||||
|
|
||||||
|
### Another example: upload file
|
||||||
|
|
||||||
|
References issue [#548](https://github.com/gin-gonic/gin/issues/548).
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
router.POST("/upload", func(c *gin.Context) {
|
||||||
|
|
||||||
|
file, header , err := c.Request.FormFile("upload")
|
||||||
|
filename := header.Filename
|
||||||
|
fmt.Println(header.Filename)
|
||||||
|
out, err := os.Create("./tmp/"+filename+".png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
_, err = io.Copy(out, file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Grouping routes
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Simple group: v1
|
||||||
|
v1 := router.Group("/v1")
|
||||||
|
{
|
||||||
|
v1.POST("/login", loginEndpoint)
|
||||||
|
v1.POST("/submit", submitEndpoint)
|
||||||
|
v1.POST("/read", readEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple group: v2
|
||||||
|
v2 := router.Group("/v2")
|
||||||
|
{
|
||||||
|
v2.POST("/login", loginEndpoint)
|
||||||
|
v2.POST("/submit", submitEndpoint)
|
||||||
|
v2.POST("/read", readEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Blank Gin without middleware by default
|
||||||
|
|
||||||
|
Use
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := gin.New()
|
||||||
|
```
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := gin.Default()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Using middleware
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
// Creates a router without any middleware by default
|
||||||
|
r := gin.New()
|
||||||
|
|
||||||
|
// Global middleware
|
||||||
|
r.Use(gin.Logger())
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
// Per route middleware, you can add as many as you desire.
|
||||||
|
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
|
||||||
|
|
||||||
|
// Authorization group
|
||||||
|
// authorized := r.Group("/", AuthRequired())
|
||||||
|
// exactly the same as:
|
||||||
|
authorized := r.Group("/")
|
||||||
|
// per group middleware! in this case we use the custom created
|
||||||
|
// AuthRequired() middleware just in the "authorized" group.
|
||||||
|
authorized.Use(AuthRequired())
|
||||||
|
{
|
||||||
|
authorized.POST("/login", loginEndpoint)
|
||||||
|
authorized.POST("/submit", submitEndpoint)
|
||||||
|
authorized.POST("/read", readEndpoint)
|
||||||
|
|
||||||
|
// nested group
|
||||||
|
testing := authorized.Group("testing")
|
||||||
|
testing.GET("/analytics", analyticsEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Model binding and validation
|
||||||
|
|
||||||
|
To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).
|
||||||
|
|
||||||
|
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
|
||||||
|
|
||||||
|
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use BindWith.
|
||||||
|
|
||||||
|
You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, the current request will fail with an error.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Binding from JSON
|
||||||
|
type Login struct {
|
||||||
|
User string `form:"user" json:"user" binding:"required"`
|
||||||
|
Password string `form:"password" json:"password" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
// Example for binding JSON ({"user": "manu", "password": "123"})
|
||||||
|
router.POST("/loginJSON", func(c *gin.Context) {
|
||||||
|
var json Login
|
||||||
|
if c.BindJSON(&json) == nil {
|
||||||
|
if json.User == "manu" && json.Password == "123" {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Example for binding a HTML form (user=manu&password=123)
|
||||||
|
router.POST("/loginForm", func(c *gin.Context) {
|
||||||
|
var form Login
|
||||||
|
// This will infer what binder to use depending on the content-type header.
|
||||||
|
if c.Bind(&form) == nil {
|
||||||
|
if form.User == "manu" && form.Password == "123" {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
###Multipart/Urlencoded binding
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gopkg.in/gin-gonic/gin.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginForm struct {
|
||||||
|
User string `form:"user" binding:"required"`
|
||||||
|
Password string `form:"password" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.POST("/login", func(c *gin.Context) {
|
||||||
|
// you can bind multipart form with explicit binding declaration:
|
||||||
|
// c.BindWith(&form, binding.Form)
|
||||||
|
// or you can simply use autobinding with Bind method:
|
||||||
|
var form LoginForm
|
||||||
|
// in this case proper binding will be automatically selected
|
||||||
|
if c.Bind(&form) == nil {
|
||||||
|
if form.User == "user" && form.Password == "password" {
|
||||||
|
c.JSON(200, gin.H{"status": "you are logged in"})
|
||||||
|
} else {
|
||||||
|
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it with:
|
||||||
|
```sh
|
||||||
|
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### XML, JSON and YAML rendering
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// gin.H is a shortcut for map[string]interface{}
|
||||||
|
r.GET("/someJSON", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/moreJSON", func(c *gin.Context) {
|
||||||
|
// You also can use a struct
|
||||||
|
var msg struct {
|
||||||
|
Name string `json:"user"`
|
||||||
|
Message string
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
msg.Name = "Lena"
|
||||||
|
msg.Message = "hey"
|
||||||
|
msg.Number = 123
|
||||||
|
// Note that msg.Name becomes "user" in the JSON
|
||||||
|
// Will output : {"user": "Lena", "Message": "hey", "Number": 123}
|
||||||
|
c.JSON(http.StatusOK, msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/someXML", func(c *gin.Context) {
|
||||||
|
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/someYAML", func(c *gin.Context) {
|
||||||
|
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
####Serving static files
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.Static("/assets", "./assets")
|
||||||
|
router.StaticFS("/more_static", http.Dir("my_file_system"))
|
||||||
|
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
####HTML rendering
|
||||||
|
|
||||||
|
Using LoadHTMLTemplates()
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.LoadHTMLGlob("templates/*")
|
||||||
|
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
|
||||||
|
router.GET("/index", func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||||
|
"title": "Main website",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
templates/index.tmpl
|
||||||
|
```html
|
||||||
|
<html>
|
||||||
|
<h1>
|
||||||
|
{{ .title }}
|
||||||
|
</h1>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
Using templates with same name in different directories
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.LoadHTMLGlob("templates/**/*")
|
||||||
|
router.GET("/posts/index", func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
|
||||||
|
"title": "Posts",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.GET("/users/index", func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
|
||||||
|
"title": "Users",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
templates/posts/index.tmpl
|
||||||
|
```html
|
||||||
|
{{ define "posts/index.tmpl" }}
|
||||||
|
<html><h1>
|
||||||
|
{{ .title }}
|
||||||
|
</h1>
|
||||||
|
<p>Using posts/index.tmpl</p>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
||||||
|
```
|
||||||
|
templates/users/index.tmpl
|
||||||
|
```html
|
||||||
|
{{ define "users/index.tmpl" }}
|
||||||
|
<html><h1>
|
||||||
|
{{ .title }}
|
||||||
|
</h1>
|
||||||
|
<p>Using users/index.tmpl</p>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use your own html template render
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "html/template"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
html := template.Must(template.ParseFiles("file1", "file2"))
|
||||||
|
router.SetHTMLTemplate(html)
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Redirects
|
||||||
|
|
||||||
|
Issuing a HTTP redirect is easy:
|
||||||
|
|
||||||
|
```go
|
||||||
|
r.GET("/test", func(c *gin.Context) {
|
||||||
|
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
|
||||||
|
})
|
||||||
|
```
|
||||||
|
Both internal and external locations are supported.
|
||||||
|
|
||||||
|
|
||||||
|
#### Custom Middleware
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Logger() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
t := time.Now()
|
||||||
|
|
||||||
|
// Set example variable
|
||||||
|
c.Set("example", "12345")
|
||||||
|
|
||||||
|
// before request
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// after request
|
||||||
|
latency := time.Since(t)
|
||||||
|
log.Print(latency)
|
||||||
|
|
||||||
|
// access the status we are sending
|
||||||
|
status := c.Writer.Status()
|
||||||
|
log.Println(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(Logger())
|
||||||
|
|
||||||
|
r.GET("/test", func(c *gin.Context) {
|
||||||
|
example := c.MustGet("example").(string)
|
||||||
|
|
||||||
|
// it would print: "12345"
|
||||||
|
log.Println(example)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using BasicAuth() middleware
|
||||||
|
```go
|
||||||
|
// simulate some private data
|
||||||
|
var secrets = gin.H{
|
||||||
|
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
|
||||||
|
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
|
||||||
|
"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
// Group using gin.BasicAuth() middleware
|
||||||
|
// gin.Accounts is a shortcut for map[string]string
|
||||||
|
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
|
||||||
|
"foo": "bar",
|
||||||
|
"austin": "1234",
|
||||||
|
"lena": "hello2",
|
||||||
|
"manu": "4321",
|
||||||
|
}))
|
||||||
|
|
||||||
|
// /admin/secrets endpoint
|
||||||
|
// hit "localhost:8080/admin/secrets
|
||||||
|
authorized.GET("/secrets", func(c *gin.Context) {
|
||||||
|
// get user, it was set by the BasicAuth middleware
|
||||||
|
user := c.MustGet(gin.AuthUserKey).(string)
|
||||||
|
if secret, ok := secrets[user]; ok {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Goroutines inside a middleware
|
||||||
|
When starting inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
r.GET("/long_async", func(c *gin.Context) {
|
||||||
|
// create copy to be used inside the goroutine
|
||||||
|
cCp := c.Copy()
|
||||||
|
go func() {
|
||||||
|
// simulate a long task with time.Sleep(). 5 seconds
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
// note that you are using the copied context "cCp", IMPORTANT
|
||||||
|
log.Println("Done! in path " + cCp.Request.URL.Path)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/long_sync", func(c *gin.Context) {
|
||||||
|
// simulate a long task with time.Sleep(). 5 seconds
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
// since we are NOT using a goroutine, we do not have to copy the context
|
||||||
|
log.Println("Done! in path " + c.Request.URL.Path)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Listen and serve on 0.0.0.0:8080
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom HTTP configuration
|
||||||
|
|
||||||
|
Use `http.ListenAndServe()` directly, like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
http.ListenAndServe(":8080", router)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
or
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: ":8080",
|
||||||
|
Handler: router,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
MaxHeaderBytes: 1 << 20,
|
||||||
|
}
|
||||||
|
s.ListenAndServe()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Graceful restart or stop
|
||||||
|
|
||||||
|
Do you want to graceful restart or stop your web server?
|
||||||
|
There are some ways this can be done.
|
||||||
|
|
||||||
|
We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.
|
||||||
|
|
||||||
|
```go
|
||||||
|
router := gin.Default()
|
||||||
|
router.GET("/", handler)
|
||||||
|
// [...]
|
||||||
|
endless.ListenAndServe(":4242", router)
|
||||||
|
```
|
||||||
|
|
||||||
|
An alternative to endless:
|
||||||
|
|
||||||
|
* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
- With issues:
|
||||||
|
- Use the search tool before opening a new issue.
|
||||||
|
- Please provide source code and commit sha if you found a bug.
|
||||||
|
- Review existing issues and provide feedback or react to them.
|
||||||
|
- With pull requests:
|
||||||
|
- Open your pull request against develop
|
||||||
|
- Your pull request should have no more than two commits, if not you should squash them.
|
||||||
|
- It should pass all tests in the available continuous integrations systems such as TravisCI.
|
||||||
|
- You should add/modify tests to cover your proposed code changes.
|
||||||
|
- If your pull request contains a new feature, please document it on the README.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
||||||
|
|
||||||
|
* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go
|
||||||
|
* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
|
92
vendor/github.com/gin-gonic/gin/auth.go
generated
vendored
Normal file
92
vendor/github.com/gin-gonic/gin/auth.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const AuthUserKey = "user"
|
||||||
|
|
||||||
|
type (
|
||||||
|
Accounts map[string]string
|
||||||
|
authPair struct {
|
||||||
|
Value string
|
||||||
|
User string
|
||||||
|
}
|
||||||
|
authPairs []authPair
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||||
|
if len(authValue) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for _, pair := range a {
|
||||||
|
if pair.Value == authValue {
|
||||||
|
return pair.User, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
|
||||||
|
// the key is the user name and the value is the password, as well as the name of the Realm.
|
||||||
|
// If the realm is empty, "Authorization Required" will be used by default.
|
||||||
|
// (see http://tools.ietf.org/html/rfc2617#section-1.2)
|
||||||
|
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
|
||||||
|
if realm == "" {
|
||||||
|
realm = "Authorization Required"
|
||||||
|
}
|
||||||
|
realm = "Basic realm=" + strconv.Quote(realm)
|
||||||
|
pairs := processAccounts(accounts)
|
||||||
|
return func(c *Context) {
|
||||||
|
// Search user in the slice of allowed credentials
|
||||||
|
user, found := pairs.searchCredential(c.Request.Header.Get("Authorization"))
|
||||||
|
if !found {
|
||||||
|
// Credentials doesn't match, we return 401 and abort handlers chain.
|
||||||
|
c.Header("WWW-Authenticate", realm)
|
||||||
|
c.AbortWithStatus(401)
|
||||||
|
} else {
|
||||||
|
// The user credentials was found, set user's id to key AuthUserKey in this context, the userId can be read later using
|
||||||
|
// c.MustGet(gin.AuthUserKey)
|
||||||
|
c.Set(AuthUserKey, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
|
||||||
|
// the key is the user name and the value is the password.
|
||||||
|
func BasicAuth(accounts Accounts) HandlerFunc {
|
||||||
|
return BasicAuthForRealm(accounts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func processAccounts(accounts Accounts) authPairs {
|
||||||
|
assert1(len(accounts) > 0, "Empty list of authorized credentials")
|
||||||
|
pairs := make(authPairs, 0, len(accounts))
|
||||||
|
for user, password := range accounts {
|
||||||
|
assert1(len(user) > 0, "User can not be empty")
|
||||||
|
value := authorizationHeader(user, password)
|
||||||
|
pairs = append(pairs, authPair{
|
||||||
|
Value: value,
|
||||||
|
User: user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizationHeader(user, password string) string {
|
||||||
|
base := user + ":" + password
|
||||||
|
return "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
|
||||||
|
}
|
||||||
|
|
||||||
|
func secureCompare(given, actual string) bool {
|
||||||
|
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
|
||||||
|
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
|
||||||
|
}
|
||||||
|
/* Securely compare actual to itself to keep constant time, but always return false */
|
||||||
|
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
|
||||||
|
}
|
67
vendor/github.com/gin-gonic/gin/binding/binding.go
generated
vendored
Normal file
67
vendor/github.com/gin-gonic/gin/binding/binding.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
const (
|
||||||
|
MIMEJSON = "application/json"
|
||||||
|
MIMEHTML = "text/html"
|
||||||
|
MIMEXML = "application/xml"
|
||||||
|
MIMEXML2 = "text/xml"
|
||||||
|
MIMEPlain = "text/plain"
|
||||||
|
MIMEPOSTForm = "application/x-www-form-urlencoded"
|
||||||
|
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||||
|
MIMEPROTOBUF = "application/x-protobuf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Binding interface {
|
||||||
|
Name() string
|
||||||
|
Bind(*http.Request, interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructValidator interface {
|
||||||
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
|
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
||||||
|
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||||
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
|
// Otherwise nil must be returned.
|
||||||
|
ValidateStruct(interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var Validator StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
JSON = jsonBinding{}
|
||||||
|
XML = xmlBinding{}
|
||||||
|
Form = formBinding{}
|
||||||
|
FormPost = formPostBinding{}
|
||||||
|
FormMultipart = formMultipartBinding{}
|
||||||
|
ProtoBuf = protobufBinding{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Default(method, contentType string) Binding {
|
||||||
|
if method == "GET" {
|
||||||
|
return Form
|
||||||
|
} else {
|
||||||
|
switch contentType {
|
||||||
|
case MIMEJSON:
|
||||||
|
return JSON
|
||||||
|
case MIMEXML, MIMEXML2:
|
||||||
|
return XML
|
||||||
|
case MIMEPROTOBUF:
|
||||||
|
return ProtoBuf
|
||||||
|
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
|
||||||
|
return Form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(obj interface{}) error {
|
||||||
|
if Validator == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Validator.ValidateStruct(obj)
|
||||||
|
}
|
41
vendor/github.com/gin-gonic/gin/binding/default_validator.go
generated
vendored
Normal file
41
vendor/github.com/gin-gonic/gin/binding/default_validator.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gopkg.in/go-playground/validator.v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type defaultValidator struct {
|
||||||
|
once sync.Once
|
||||||
|
validate *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||||
|
if kindOfData(obj) == reflect.Struct {
|
||||||
|
v.lazyinit()
|
||||||
|
if err := v.validate.Struct(obj); err != nil {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *defaultValidator) lazyinit() {
|
||||||
|
v.once.Do(func() {
|
||||||
|
config := &validator.Config{TagName: "binding"}
|
||||||
|
v.validate = validator.New(config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func kindOfData(data interface{}) reflect.Kind {
|
||||||
|
value := reflect.ValueOf(data)
|
||||||
|
valueType := value.Kind()
|
||||||
|
if valueType == reflect.Ptr {
|
||||||
|
valueType = value.Elem().Kind()
|
||||||
|
}
|
||||||
|
return valueType
|
||||||
|
}
|
54
vendor/github.com/gin-gonic/gin/binding/form.go
generated
vendored
Normal file
54
vendor/github.com/gin-gonic/gin/binding/form.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type formBinding struct{}
|
||||||
|
type formPostBinding struct{}
|
||||||
|
type formMultipartBinding struct{}
|
||||||
|
|
||||||
|
func (formBinding) Name() string {
|
||||||
|
return "form"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.ParseMultipartForm(32 << 10) // 32 MB
|
||||||
|
if err := mapForm(obj, req.Form); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formPostBinding) Name() string {
|
||||||
|
return "form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := mapForm(obj, req.PostForm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formMultipartBinding) Name() string {
|
||||||
|
return "multipart/form-data"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
if err := req.ParseMultipartForm(32 << 10); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := mapForm(obj, req.MultipartForm.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
150
vendor/github.com/gin-gonic/gin/binding/form_mapping.go
generated
vendored
Normal file
150
vendor/github.com/gin-gonic/gin/binding/form_mapping.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapForm(ptr interface{}, form map[string][]string) error {
|
||||||
|
typ := reflect.TypeOf(ptr).Elem()
|
||||||
|
val := reflect.ValueOf(ptr).Elem()
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
typeField := typ.Field(i)
|
||||||
|
structField := val.Field(i)
|
||||||
|
if !structField.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
structFieldKind := structField.Kind()
|
||||||
|
inputFieldName := typeField.Tag.Get("form")
|
||||||
|
if inputFieldName == "" {
|
||||||
|
inputFieldName = typeField.Name
|
||||||
|
|
||||||
|
// if "form" tag is nil, we inspect if the field is a struct.
|
||||||
|
// this would not make sense for JSON parsing but it does for a form
|
||||||
|
// since data is flatten
|
||||||
|
if structFieldKind == reflect.Struct {
|
||||||
|
err := mapForm(structField.Addr().Interface(), form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputValue, exists := form[inputFieldName]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
numElems := len(inputValue)
|
||||||
|
if structFieldKind == reflect.Slice && numElems > 0 {
|
||||||
|
sliceOf := structField.Type().Elem().Kind()
|
||||||
|
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
||||||
|
for i := 0; i < numElems; i++ {
|
||||||
|
if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val.Field(i).Set(slice)
|
||||||
|
} else {
|
||||||
|
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
||||||
|
switch valueKind {
|
||||||
|
case reflect.Int:
|
||||||
|
return setIntField(val, 0, structField)
|
||||||
|
case reflect.Int8:
|
||||||
|
return setIntField(val, 8, structField)
|
||||||
|
case reflect.Int16:
|
||||||
|
return setIntField(val, 16, structField)
|
||||||
|
case reflect.Int32:
|
||||||
|
return setIntField(val, 32, structField)
|
||||||
|
case reflect.Int64:
|
||||||
|
return setIntField(val, 64, structField)
|
||||||
|
case reflect.Uint:
|
||||||
|
return setUintField(val, 0, structField)
|
||||||
|
case reflect.Uint8:
|
||||||
|
return setUintField(val, 8, structField)
|
||||||
|
case reflect.Uint16:
|
||||||
|
return setUintField(val, 16, structField)
|
||||||
|
case reflect.Uint32:
|
||||||
|
return setUintField(val, 32, structField)
|
||||||
|
case reflect.Uint64:
|
||||||
|
return setUintField(val, 64, structField)
|
||||||
|
case reflect.Bool:
|
||||||
|
return setBoolField(val, structField)
|
||||||
|
case reflect.Float32:
|
||||||
|
return setFloatField(val, 32, structField)
|
||||||
|
case reflect.Float64:
|
||||||
|
return setFloatField(val, 64, structField)
|
||||||
|
case reflect.String:
|
||||||
|
structField.SetString(val)
|
||||||
|
default:
|
||||||
|
return errors.New("Unknown type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIntField(val string, bitSize int, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "0"
|
||||||
|
}
|
||||||
|
intVal, err := strconv.ParseInt(val, 10, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetInt(intVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUintField(val string, bitSize int, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "0"
|
||||||
|
}
|
||||||
|
uintVal, err := strconv.ParseUint(val, 10, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetUint(uintVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBoolField(val string, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "false"
|
||||||
|
}
|
||||||
|
boolVal, err := strconv.ParseBool(val)
|
||||||
|
if err == nil {
|
||||||
|
field.SetBool(boolVal)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFloatField(val string, bitSize int, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "0.0"
|
||||||
|
}
|
||||||
|
floatVal, err := strconv.ParseFloat(val, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetFloat(floatVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't pass in pointers to bind to. Can lead to bugs. See:
|
||||||
|
// https://github.com/codegangsta/martini-contrib/issues/40
|
||||||
|
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
|
||||||
|
func ensureNotPointer(obj interface{}) {
|
||||||
|
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
|
||||||
|
panic("Pointers are not accepted as binding models")
|
||||||
|
}
|
||||||
|
}
|
25
vendor/github.com/gin-gonic/gin/binding/json.go
generated
vendored
Normal file
25
vendor/github.com/gin-gonic/gin/binding/json.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type jsonBinding struct{}
|
||||||
|
|
||||||
|
func (jsonBinding) Name() string {
|
||||||
|
return "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
decoder := json.NewDecoder(req.Body)
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
35
vendor/github.com/gin-gonic/gin/binding/protobuf.go
generated
vendored
Normal file
35
vendor/github.com/gin-gonic/gin/binding/protobuf.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type protobufBinding struct{}
|
||||||
|
|
||||||
|
func (protobufBinding) Name() string {
|
||||||
|
return "protobuf"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct
|
||||||
|
//which automatically generate by gen-proto
|
||||||
|
return nil
|
||||||
|
//return validate(obj)
|
||||||
|
}
|
24
vendor/github.com/gin-gonic/gin/binding/xml.go
generated
vendored
Normal file
24
vendor/github.com/gin-gonic/gin/binding/xml.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xmlBinding struct{}
|
||||||
|
|
||||||
|
func (xmlBinding) Name() string {
|
||||||
|
return "xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
decoder := xml.NewDecoder(req.Body)
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
5
vendor/github.com/gin-gonic/gin/codecov.yml
generated
vendored
Normal file
5
vendor/github.com/gin-gonic/gin/codecov.yml
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
coverage:
|
||||||
|
notify:
|
||||||
|
gitter:
|
||||||
|
default:
|
||||||
|
url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165
|
601
vendor/github.com/gin-gonic/gin/context.go
generated
vendored
Normal file
601
vendor/github.com/gin-gonic/gin/context.go
generated
vendored
Normal file
@ -0,0 +1,601 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/gin-gonic/gin/render"
|
||||||
|
"github.com/manucorporat/sse"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Content-Type MIME of the most common data formats
|
||||||
|
const (
|
||||||
|
MIMEJSON = binding.MIMEJSON
|
||||||
|
MIMEHTML = binding.MIMEHTML
|
||||||
|
MIMEXML = binding.MIMEXML
|
||||||
|
MIMEXML2 = binding.MIMEXML2
|
||||||
|
MIMEPlain = binding.MIMEPlain
|
||||||
|
MIMEPOSTForm = binding.MIMEPOSTForm
|
||||||
|
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
||||||
|
)
|
||||||
|
|
||||||
|
const abortIndex int8 = math.MaxInt8 / 2
|
||||||
|
|
||||||
|
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
||||||
|
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
||||||
|
type Context struct {
|
||||||
|
writermem responseWriter
|
||||||
|
Request *http.Request
|
||||||
|
Writer ResponseWriter
|
||||||
|
|
||||||
|
Params Params
|
||||||
|
handlers HandlersChain
|
||||||
|
index int8
|
||||||
|
|
||||||
|
engine *Engine
|
||||||
|
Keys map[string]interface{}
|
||||||
|
Errors errorMsgs
|
||||||
|
Accepted []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ context.Context = &Context{}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/********** CONTEXT CREATION ********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
func (c *Context) reset() {
|
||||||
|
c.Writer = &c.writermem
|
||||||
|
c.Params = c.Params[0:0]
|
||||||
|
c.handlers = nil
|
||||||
|
c.index = -1
|
||||||
|
c.Keys = nil
|
||||||
|
c.Errors = c.Errors[0:0]
|
||||||
|
c.Accepted = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||||
|
// This have to be used then the context has to be passed to a goroutine.
|
||||||
|
func (c *Context) Copy() *Context {
|
||||||
|
var cp = *c
|
||||||
|
cp.writermem.ResponseWriter = nil
|
||||||
|
cp.Writer = &cp.writermem
|
||||||
|
cp.index = abortIndex
|
||||||
|
cp.handlers = nil
|
||||||
|
return &cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", this
|
||||||
|
// function will return "main.handleGetUsers"
|
||||||
|
func (c *Context) HandlerName() string {
|
||||||
|
return nameOfFunction(c.handlers.Last())
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/*********** FLOW CONTROL ***********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
// Next should be used only inside middleware.
|
||||||
|
// It executes the pending handlers in the chain inside the calling handler.
|
||||||
|
// See example in github.
|
||||||
|
func (c *Context) Next() {
|
||||||
|
c.index++
|
||||||
|
s := int8(len(c.handlers))
|
||||||
|
for ; c.index < s; c.index++ {
|
||||||
|
c.handlers[c.index](c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAborted returns true if the current context was aborted.
|
||||||
|
func (c *Context) IsAborted() bool {
|
||||||
|
return c.index >= abortIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
|
||||||
|
// Let's say you have an authorization middleware that validates that the current request is authorized. If the
|
||||||
|
// authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
|
||||||
|
// for this request are not called.
|
||||||
|
func (c *Context) Abort() {
|
||||||
|
c.index = abortIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.
|
||||||
|
// For example, a failed attempt to authentificate a request could use: context.AbortWithStatus(401).
|
||||||
|
func (c *Context) AbortWithStatus(code int) {
|
||||||
|
c.Status(code)
|
||||||
|
c.Writer.WriteHeaderNow()
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and
|
||||||
|
// pushes the specified error to `c.Errors`.
|
||||||
|
// See Context.Error() for more details.
|
||||||
|
func (c *Context) AbortWithError(code int, err error) *Error {
|
||||||
|
c.AbortWithStatus(code)
|
||||||
|
return c.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/********* ERROR MANAGEMENT *********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
// Attaches an error to the current context. The error is pushed to a list of errors.
|
||||||
|
// It's a good idea to call Error for each error that occurred during the resolution of a request.
|
||||||
|
// A middleware can be used to collect all the errors
|
||||||
|
// and push them to a database together, print a log, or append it in the HTTP response.
|
||||||
|
func (c *Context) Error(err error) *Error {
|
||||||
|
var parsedError *Error
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
parsedError = err.(*Error)
|
||||||
|
default:
|
||||||
|
parsedError = &Error{
|
||||||
|
Err: err,
|
||||||
|
Type: ErrorTypePrivate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Errors = append(c.Errors, parsedError)
|
||||||
|
return parsedError
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/******** METADATA MANAGEMENT********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
// Set is used to store a new key/value pair exclusivelly for this context.
|
||||||
|
// It also lazy initializes c.Keys if it was not used previously.
|
||||||
|
func (c *Context) Set(key string, value interface{}) {
|
||||||
|
if c.Keys == nil {
|
||||||
|
c.Keys = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
c.Keys[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value for the given key, ie: (value, true).
|
||||||
|
// If the value does not exists it returns (nil, false)
|
||||||
|
func (c *Context) Get(key string) (value interface{}, exists bool) {
|
||||||
|
if c.Keys != nil {
|
||||||
|
value, exists = c.Keys[key]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet returns the value for the given key if it exists, otherwise it panics.
|
||||||
|
func (c *Context) MustGet(key string) interface{} {
|
||||||
|
if value, exists := c.Get(key); exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
panic("Key \"" + key + "\" does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/************ INPUT DATA ************/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
// Param returns the value of the URL param.
|
||||||
|
// It is a shortcut for c.Params.ByName(key)
|
||||||
|
// router.GET("/user/:id", func(c *gin.Context) {
|
||||||
|
// // a GET request to /user/john
|
||||||
|
// id := c.Param("id") // id == "john"
|
||||||
|
// })
|
||||||
|
func (c *Context) Param(key string) string {
|
||||||
|
return c.Params.ByName(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns the keyed url query value if it exists,
|
||||||
|
// othewise it returns an empty string `("")`.
|
||||||
|
// It is shortcut for `c.Request.URL.Query().Get(key)`
|
||||||
|
// GET /path?id=1234&name=Manu&value=
|
||||||
|
// c.Query("id") == "1234"
|
||||||
|
// c.Query("name") == "Manu"
|
||||||
|
// c.Query("value") == ""
|
||||||
|
// c.Query("wtf") == ""
|
||||||
|
func (c *Context) Query(key string) string {
|
||||||
|
value, _ := c.GetQuery(key)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultQuery returns the keyed url query value if it exists,
|
||||||
|
// othewise it returns the specified defaultValue string.
|
||||||
|
// See: Query() and GetQuery() for further information.
|
||||||
|
// GET /?name=Manu&lastname=
|
||||||
|
// c.DefaultQuery("name", "unknown") == "Manu"
|
||||||
|
// c.DefaultQuery("id", "none") == "none"
|
||||||
|
// c.DefaultQuery("lastname", "none") == ""
|
||||||
|
func (c *Context) DefaultQuery(key, defaultValue string) string {
|
||||||
|
if value, ok := c.GetQuery(key); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQuery is like Query(), it returns the keyed url query value
|
||||||
|
// if it exists `(value, true)` (even when the value is an empty string),
|
||||||
|
// othewise it returns `("", false)`.
|
||||||
|
// It is shortcut for `c.Request.URL.Query().Get(key)`
|
||||||
|
// GET /?name=Manu&lastname=
|
||||||
|
// ("Manu", true) == c.GetQuery("name")
|
||||||
|
// ("", false) == c.GetQuery("id")
|
||||||
|
// ("", true) == c.GetQuery("lastname")
|
||||||
|
func (c *Context) GetQuery(key string) (string, bool) {
|
||||||
|
if values, ok := c.GetQueryArray(key); ok {
|
||||||
|
return values[0], ok
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryArray returns a slice of strings for a given query key.
|
||||||
|
// The length of the slice depends on the number of params with the given key.
|
||||||
|
func (c *Context) QueryArray(key string) []string {
|
||||||
|
values, _ := c.GetQueryArray(key)
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryArray returns a slice of strings for a given query key, plus
|
||||||
|
// a boolean value whether at least one value exists for the given key.
|
||||||
|
func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
||||||
|
req := c.Request
|
||||||
|
if values, ok := req.URL.Query()[key]; ok && len(values) > 0 {
|
||||||
|
return values, true
|
||||||
|
}
|
||||||
|
return []string{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||||
|
// when it exists, otherwise it returns an empty string `("")`.
|
||||||
|
func (c *Context) PostForm(key string) string {
|
||||||
|
value, _ := c.GetPostForm(key)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
|
||||||
|
// when it exists, otherwise it returns the specified defaultValue string.
|
||||||
|
// See: PostForm() and GetPostForm() for further information.
|
||||||
|
func (c *Context) DefaultPostForm(key, defaultValue string) string {
|
||||||
|
if value, ok := c.GetPostForm(key); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded
|
||||||
|
// form or multipart form when it exists `(value, true)` (even when the value is an empty string),
|
||||||
|
// otherwise it returns ("", false).
|
||||||
|
// For example, during a PATCH request to update the user's email:
|
||||||
|
// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
|
||||||
|
// email= --> ("", true) := GetPostForm("email") // set email to ""
|
||||||
|
// --> ("", false) := GetPostForm("email") // do nothing with email
|
||||||
|
func (c *Context) GetPostForm(key string) (string, bool) {
|
||||||
|
if values, ok := c.GetPostFormArray(key); ok {
|
||||||
|
return values[0], ok
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostFormArray returns a slice of strings for a given form key.
|
||||||
|
// The length of the slice depends on the number of params with the given key.
|
||||||
|
func (c *Context) PostFormArray(key string) []string {
|
||||||
|
values, _ := c.GetPostFormArray(key)
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPostFormArray returns a slice of strings for a given form key, plus
|
||||||
|
// a boolean value whether at least one value exists for the given key.
|
||||||
|
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||||
|
req := c.Request
|
||||||
|
req.ParseForm()
|
||||||
|
req.ParseMultipartForm(32 << 20) // 32 MB
|
||||||
|
if values := req.PostForm[key]; len(values) > 0 {
|
||||||
|
return values, true
|
||||||
|
}
|
||||||
|
if req.MultipartForm != nil && req.MultipartForm.File != nil {
|
||||||
|
if values := req.MultipartForm.Value[key]; len(values) > 0 {
|
||||||
|
return values, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []string{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind checks the Content-Type to select a binding engine automatically,
|
||||||
|
// Depending the "Content-Type" header different bindings are used:
|
||||||
|
// "application/json" --> JSON binding
|
||||||
|
// "application/xml" --> XML binding
|
||||||
|
// otherwise --> returns an error
|
||||||
|
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
||||||
|
// It decodes the json payload into the struct specified as a pointer.
|
||||||
|
// Like ParseBody() but this method also writes a 400 error if the json is not valid.
|
||||||
|
func (c *Context) Bind(obj interface{}) error {
|
||||||
|
b := binding.Default(c.Request.Method, c.ContentType())
|
||||||
|
return c.BindWith(obj, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindJSON is a shortcut for c.BindWith(obj, binding.JSON)
|
||||||
|
func (c *Context) BindJSON(obj interface{}) error {
|
||||||
|
return c.BindWith(obj, binding.JSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindWith binds the passed struct pointer using the specified binding engine.
|
||||||
|
// See the binding package.
|
||||||
|
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
||||||
|
if err := b.Bind(c.Request, obj); err != nil {
|
||||||
|
c.AbortWithError(400, err).SetType(ErrorTypeBind)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientIP implements a best effort algorithm to return the real client IP, it parses
|
||||||
|
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
|
||||||
|
func (c *Context) ClientIP() string {
|
||||||
|
if c.engine.ForwardedByClientIP {
|
||||||
|
clientIP := strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
return clientIP
|
||||||
|
}
|
||||||
|
clientIP = c.requestHeader("X-Forwarded-For")
|
||||||
|
if index := strings.IndexByte(clientIP, ','); index >= 0 {
|
||||||
|
clientIP = clientIP[0:index]
|
||||||
|
}
|
||||||
|
clientIP = strings.TrimSpace(clientIP)
|
||||||
|
if len(clientIP) > 0 {
|
||||||
|
return clientIP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType returns the Content-Type header of the request.
|
||||||
|
func (c *Context) ContentType() string {
|
||||||
|
return filterFlags(c.requestHeader("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) requestHeader(key string) string {
|
||||||
|
if values, _ := c.Request.Header[key]; len(values) > 0 {
|
||||||
|
return values[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/******** RESPONSE RENDERING ********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
func (c *Context) Status(code int) {
|
||||||
|
c.writermem.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value)
|
||||||
|
// It writes a header in the response.
|
||||||
|
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
|
||||||
|
func (c *Context) Header(key, value string) {
|
||||||
|
if len(value) == 0 {
|
||||||
|
c.Writer.Header().Del(key)
|
||||||
|
} else {
|
||||||
|
c.Writer.Header().Set(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SetCookie(
|
||||||
|
name string,
|
||||||
|
value string,
|
||||||
|
maxAge int,
|
||||||
|
path string,
|
||||||
|
domain string,
|
||||||
|
secure bool,
|
||||||
|
httpOnly bool,
|
||||||
|
) {
|
||||||
|
if path == "" {
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
http.SetCookie(c.Writer, &http.Cookie{
|
||||||
|
Name: name,
|
||||||
|
Value: url.QueryEscape(value),
|
||||||
|
MaxAge: maxAge,
|
||||||
|
Path: path,
|
||||||
|
Domain: domain,
|
||||||
|
Secure: secure,
|
||||||
|
HttpOnly: httpOnly,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Cookie(name string) (string, error) {
|
||||||
|
cookie, err := c.Request.Cookie(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
val, _ := url.QueryUnescape(cookie.Value)
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Render(code int, r render.Render) {
|
||||||
|
c.Status(code)
|
||||||
|
if err := r.Render(c.Writer); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML renders the HTTP template specified by its file name.
|
||||||
|
// It also updates the HTTP code and sets the Content-Type as "text/html".
|
||||||
|
// See http://golang.org/doc/articles/wiki/
|
||||||
|
func (c *Context) HTML(code int, name string, obj interface{}) {
|
||||||
|
instance := c.engine.HTMLRender.Instance(name, obj)
|
||||||
|
c.Render(code, instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.
|
||||||
|
// It also sets the Content-Type as "application/json".
|
||||||
|
// WARNING: we recommend to use this only for development propuses since printing pretty JSON is
|
||||||
|
// more CPU and bandwidth consuming. Use Context.JSON() instead.
|
||||||
|
func (c *Context) IndentedJSON(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.IndentedJSON{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON serializes the given struct as JSON into the response body.
|
||||||
|
// It also sets the Content-Type as "application/json".
|
||||||
|
func (c *Context) JSON(code int, obj interface{}) {
|
||||||
|
c.Status(code)
|
||||||
|
if err := render.WriteJSON(c.Writer, obj); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML serializes the given struct as XML into the response body.
|
||||||
|
// It also sets the Content-Type as "application/xml".
|
||||||
|
func (c *Context) XML(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.XML{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAML serializes the given struct as YAML into the response body.
|
||||||
|
func (c *Context) YAML(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.YAML{Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
|
// String writes the given string into the response body.
|
||||||
|
func (c *Context) String(code int, format string, values ...interface{}) {
|
||||||
|
c.Status(code)
|
||||||
|
render.WriteString(c.Writer, format, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect returns a HTTP redirect to the specific location.
|
||||||
|
func (c *Context) Redirect(code int, location string) {
|
||||||
|
c.Render(-1, render.Redirect{
|
||||||
|
Code: code,
|
||||||
|
Location: location,
|
||||||
|
Request: c.Request,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data writes some data into the body stream and updates the HTTP code.
|
||||||
|
func (c *Context) Data(code int, contentType string, data []byte) {
|
||||||
|
c.Render(code, render.Data{
|
||||||
|
ContentType: contentType,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// File writes the specified file into the body stream in a efficient way.
|
||||||
|
func (c *Context) File(filepath string) {
|
||||||
|
http.ServeFile(c.Writer, c.Request, filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSEvent writes a Server-Sent Event into the body stream.
|
||||||
|
func (c *Context) SSEvent(name string, message interface{}) {
|
||||||
|
c.Render(-1, sse.Event{
|
||||||
|
Event: name,
|
||||||
|
Data: message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Stream(step func(w io.Writer) bool) {
|
||||||
|
w := c.Writer
|
||||||
|
clientGone := w.CloseNotify()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-clientGone:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
keepOpen := step(w)
|
||||||
|
w.Flush()
|
||||||
|
if !keepOpen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/******** CONTENT NEGOTIATION *******/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
type Negotiate struct {
|
||||||
|
Offered []string
|
||||||
|
HTMLName string
|
||||||
|
HTMLData interface{}
|
||||||
|
JSONData interface{}
|
||||||
|
XMLData interface{}
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Negotiate(code int, config Negotiate) {
|
||||||
|
switch c.NegotiateFormat(config.Offered...) {
|
||||||
|
case binding.MIMEJSON:
|
||||||
|
data := chooseData(config.JSONData, config.Data)
|
||||||
|
c.JSON(code, data)
|
||||||
|
|
||||||
|
case binding.MIMEHTML:
|
||||||
|
data := chooseData(config.HTMLData, config.Data)
|
||||||
|
c.HTML(code, config.HTMLName, data)
|
||||||
|
|
||||||
|
case binding.MIMEXML:
|
||||||
|
data := chooseData(config.XMLData, config.Data)
|
||||||
|
c.XML(code, data)
|
||||||
|
|
||||||
|
default:
|
||||||
|
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) NegotiateFormat(offered ...string) string {
|
||||||
|
assert1(len(offered) > 0, "you must provide at least one offer")
|
||||||
|
|
||||||
|
if c.Accepted == nil {
|
||||||
|
c.Accepted = parseAccept(c.requestHeader("Accept"))
|
||||||
|
}
|
||||||
|
if len(c.Accepted) == 0 {
|
||||||
|
return offered[0]
|
||||||
|
}
|
||||||
|
for _, accepted := range c.Accepted {
|
||||||
|
for _, offert := range offered {
|
||||||
|
if accepted == offert {
|
||||||
|
return offert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) SetAccepted(formats ...string) {
|
||||||
|
c.Accepted = formats
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Done() <-chan struct{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Err() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) Value(key interface{}) interface{} {
|
||||||
|
if key == 0 {
|
||||||
|
return c.Request
|
||||||
|
}
|
||||||
|
if keyAsString, ok := key.(string); ok {
|
||||||
|
val, _ := c.Get(keyAsString)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
71
vendor/github.com/gin-gonic/gin/debug.go
generated
vendored
Normal file
71
vendor/github.com/gin-gonic/gin/debug.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
|
// Use SetMode(gin.Release) to switch to disable the debug mode.
|
||||||
|
func IsDebugging() bool {
|
||||||
|
return ginMode == debugCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
|
||||||
|
if IsDebugging() {
|
||||||
|
nuHandlers := len(handlers)
|
||||||
|
handlerName := nameOfFunction(handlers.Last())
|
||||||
|
debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintLoadTemplate(tmpl *template.Template) {
|
||||||
|
if IsDebugging() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, tmpl := range tmpl.Templates() {
|
||||||
|
buf.WriteString("\t- ")
|
||||||
|
buf.WriteString(tmpl.Name())
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
debugPrint("Loaded HTML Templates (%d): \n%s\n", len(tmpl.Templates()), buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrint(format string, values ...interface{}) {
|
||||||
|
if IsDebugging() {
|
||||||
|
log.Printf("[GIN-debug] "+format, values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintWARNINGNew() {
|
||||||
|
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
||||||
|
- using env: export GIN_MODE=release
|
||||||
|
- using code: gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintWARNINGSetHTMLTemplate() {
|
||||||
|
debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called
|
||||||
|
at initialization. ie. before any route is registered or the router is listening in a socket:
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
router.SetHTMLTemplate(template) // << good place
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
debugPrint("[ERROR] %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
12
vendor/github.com/gin-gonic/gin/deprecated.go
generated
vendored
Normal file
12
vendor/github.com/gin-gonic/gin/deprecated.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
func (c *Context) GetCookie(name string) (string, error) {
|
||||||
|
log.Println("GetCookie() method is deprecated. Use Cookie() instead.")
|
||||||
|
return c.Cookie(name)
|
||||||
|
}
|
159
vendor/github.com/gin-gonic/gin/errors.go
generated
vendored
Normal file
159
vendor/github.com/gin-gonic/gin/errors.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorType uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails
|
||||||
|
ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() fails
|
||||||
|
ErrorTypePrivate ErrorType = 1 << 0
|
||||||
|
ErrorTypePublic ErrorType = 1 << 1
|
||||||
|
|
||||||
|
ErrorTypeAny ErrorType = 1<<64 - 1
|
||||||
|
ErrorTypeNu = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Error struct {
|
||||||
|
Err error
|
||||||
|
Type ErrorType
|
||||||
|
Meta interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMsgs []*Error
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ error = &Error{}
|
||||||
|
|
||||||
|
func (msg *Error) SetType(flags ErrorType) *Error {
|
||||||
|
msg.Type = flags
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Error) SetMeta(data interface{}) *Error {
|
||||||
|
msg.Meta = data
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Error) JSON() interface{} {
|
||||||
|
json := H{}
|
||||||
|
if msg.Meta != nil {
|
||||||
|
value := reflect.ValueOf(msg.Meta)
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
return msg.Meta
|
||||||
|
case reflect.Map:
|
||||||
|
for _, key := range value.MapKeys() {
|
||||||
|
json[key.String()] = value.MapIndex(key).Interface()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
json["meta"] = msg.Meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := json["error"]; !ok {
|
||||||
|
json["error"] = msg.Error()
|
||||||
|
}
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaller interface
|
||||||
|
func (msg *Error) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(msg.JSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the error interface
|
||||||
|
func (msg *Error) Error() string {
|
||||||
|
return msg.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Error) IsType(flags ErrorType) bool {
|
||||||
|
return (msg.Type & flags) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a readonly copy filterd the byte.
|
||||||
|
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic
|
||||||
|
func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if typ == ErrorTypeAny {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
var result errorMsgs
|
||||||
|
for _, msg := range a {
|
||||||
|
if msg.IsType(typ) {
|
||||||
|
result = append(result, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the last error in the slice. It returns nil if the array is empty.
|
||||||
|
// Shortcut for errors[len(errors)-1]
|
||||||
|
func (a errorMsgs) Last() *Error {
|
||||||
|
length := len(a)
|
||||||
|
if length > 0 {
|
||||||
|
return a[length-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an array will all the error messages.
|
||||||
|
// Example:
|
||||||
|
// c.Error(errors.New("first"))
|
||||||
|
// c.Error(errors.New("second"))
|
||||||
|
// c.Error(errors.New("third"))
|
||||||
|
// c.Errors.Errors() // == []string{"first", "second", "third"}
|
||||||
|
func (a errorMsgs) Errors() []string {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errorStrings := make([]string, len(a))
|
||||||
|
for i, err := range a {
|
||||||
|
errorStrings[i] = err.Error()
|
||||||
|
}
|
||||||
|
return errorStrings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a errorMsgs) JSON() interface{} {
|
||||||
|
switch len(a) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return a.Last().JSON()
|
||||||
|
default:
|
||||||
|
json := make([]interface{}, len(a))
|
||||||
|
for i, err := range a {
|
||||||
|
json[i] = err.JSON()
|
||||||
|
}
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(a.JSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a errorMsgs) String() string {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for i, msg := range a {
|
||||||
|
fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err)
|
||||||
|
if msg.Meta != nil {
|
||||||
|
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
42
vendor/github.com/gin-gonic/gin/fs.go
generated
vendored
Normal file
42
vendor/github.com/gin-gonic/gin/fs.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
onlyfilesFS struct {
|
||||||
|
fs http.FileSystem
|
||||||
|
}
|
||||||
|
neuteredReaddirFile struct {
|
||||||
|
http.File
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used interally
|
||||||
|
// in router.Static().
|
||||||
|
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
|
||||||
|
// a filesystem that prevents http.FileServer() to list the directory files.
|
||||||
|
func Dir(root string, listDirectory bool) http.FileSystem {
|
||||||
|
fs := http.Dir(root)
|
||||||
|
if listDirectory {
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
return &onlyfilesFS{fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conforms to http.Filesystem
|
||||||
|
func (fs onlyfilesFS) Open(name string) (http.File, error) {
|
||||||
|
f, err := fs.fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return neuteredReaddirFile{f}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides the http.File default implementation
|
||||||
|
func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
// this disables directory listing
|
||||||
|
return nil, nil
|
||||||
|
}
|
370
vendor/github.com/gin-gonic/gin/gin.go
generated
vendored
Normal file
370
vendor/github.com/gin-gonic/gin/gin.go
generated
vendored
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version is Framework's version
|
||||||
|
const Version = "v1.0rc2"
|
||||||
|
|
||||||
|
var default404Body = []byte("404 page not found")
|
||||||
|
var default405Body = []byte("405 method not allowed")
|
||||||
|
|
||||||
|
type HandlerFunc func(*Context)
|
||||||
|
type HandlersChain []HandlerFunc
|
||||||
|
|
||||||
|
// Last returns the last handler in the chain. ie. the last handler is the main own.
|
||||||
|
func (c HandlersChain) Last() HandlerFunc {
|
||||||
|
length := len(c)
|
||||||
|
if length > 0 {
|
||||||
|
return c[length-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
RoutesInfo []RouteInfo
|
||||||
|
RouteInfo struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Handler string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||||
|
// Create an instance of Engine, by using New() or Default()
|
||||||
|
Engine struct {
|
||||||
|
RouterGroup
|
||||||
|
HTMLRender render.HTMLRender
|
||||||
|
allNoRoute HandlersChain
|
||||||
|
allNoMethod HandlersChain
|
||||||
|
noRoute HandlersChain
|
||||||
|
noMethod HandlersChain
|
||||||
|
pool sync.Pool
|
||||||
|
trees methodTrees
|
||||||
|
|
||||||
|
// Enables automatic redirection if the current route can't be matched but a
|
||||||
|
// handler for the path with (without) the trailing slash exists.
|
||||||
|
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||||
|
// client is redirected to /foo with http status code 301 for GET requests
|
||||||
|
// and 307 for all other request methods.
|
||||||
|
RedirectTrailingSlash bool
|
||||||
|
|
||||||
|
// If enabled, the router tries to fix the current request path, if no
|
||||||
|
// handle is registered for it.
|
||||||
|
// First superfluous path elements like ../ or // are removed.
|
||||||
|
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||||
|
// If a handle can be found for this route, the router makes a redirection
|
||||||
|
// to the corrected path with status code 301 for GET requests and 307 for
|
||||||
|
// all other request methods.
|
||||||
|
// For example /FOO and /..//Foo could be redirected to /foo.
|
||||||
|
// RedirectTrailingSlash is independent of this option.
|
||||||
|
RedirectFixedPath bool
|
||||||
|
|
||||||
|
// If enabled, the router checks if another method is allowed for the
|
||||||
|
// current route, if the current request can not be routed.
|
||||||
|
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||||
|
// and HTTP status code 405.
|
||||||
|
// If no other Method is allowed, the request is delegated to the NotFound
|
||||||
|
// handler.
|
||||||
|
HandleMethodNotAllowed bool
|
||||||
|
ForwardedByClientIP bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ IRouter = &Engine{}
|
||||||
|
|
||||||
|
// New returns a new blank Engine instance without any middleware attached.
|
||||||
|
// By default the configuration is:
|
||||||
|
// - RedirectTrailingSlash: true
|
||||||
|
// - RedirectFixedPath: false
|
||||||
|
// - HandleMethodNotAllowed: false
|
||||||
|
// - ForwardedByClientIP: true
|
||||||
|
func New() *Engine {
|
||||||
|
debugPrintWARNINGNew()
|
||||||
|
engine := &Engine{
|
||||||
|
RouterGroup: RouterGroup{
|
||||||
|
Handlers: nil,
|
||||||
|
basePath: "/",
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
RedirectTrailingSlash: true,
|
||||||
|
RedirectFixedPath: false,
|
||||||
|
HandleMethodNotAllowed: false,
|
||||||
|
ForwardedByClientIP: true,
|
||||||
|
trees: make(methodTrees, 0, 9),
|
||||||
|
}
|
||||||
|
engine.RouterGroup.engine = engine
|
||||||
|
engine.pool.New = func() interface{} {
|
||||||
|
return engine.allocateContext()
|
||||||
|
}
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
|
func Default() *Engine {
|
||||||
|
engine := New()
|
||||||
|
engine.Use(Logger(), Recovery())
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) allocateContext() *Context {
|
||||||
|
return &Context{engine: engine}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
|
if IsDebugging() {
|
||||||
|
debugPrintLoadTemplate(template.Must(template.ParseGlob(pattern)))
|
||||||
|
engine.HTMLRender = render.HTMLDebug{Glob: pattern}
|
||||||
|
} else {
|
||||||
|
templ := template.Must(template.ParseGlob(pattern))
|
||||||
|
engine.SetHTMLTemplate(templ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||||
|
if IsDebugging() {
|
||||||
|
engine.HTMLRender = render.HTMLDebug{Files: files}
|
||||||
|
} else {
|
||||||
|
templ := template.Must(template.ParseFiles(files...))
|
||||||
|
engine.SetHTMLTemplate(templ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
||||||
|
if len(engine.trees) > 0 {
|
||||||
|
debugPrintWARNINGSetHTMLTemplate()
|
||||||
|
}
|
||||||
|
engine.HTMLRender = render.HTMLProduction{Template: templ}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
|
||||||
|
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
||||||
|
engine.noRoute = handlers
|
||||||
|
engine.rebuild404Handlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoMethod sets the handlers called when... TODO
|
||||||
|
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||||
|
engine.noMethod = handlers
|
||||||
|
engine.rebuild405Handlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use attachs a global middleware to the router. ie. the middleware attached though Use() will be
|
||||||
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||||
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
|
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
|
engine.RouterGroup.Use(middleware...)
|
||||||
|
engine.rebuild404Handlers()
|
||||||
|
engine.rebuild405Handlers()
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) rebuild404Handlers() {
|
||||||
|
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) rebuild405Handlers() {
|
||||||
|
engine.allNoMethod = engine.combineHandlers(engine.noMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||||
|
assert1(path[0] == '/', "path must begin with '/'")
|
||||||
|
assert1(len(method) > 0, "HTTP method can not be empty")
|
||||||
|
assert1(len(handlers) > 0, "there must be at least one handler")
|
||||||
|
|
||||||
|
debugPrintRoute(method, path, handlers)
|
||||||
|
root := engine.trees.get(method)
|
||||||
|
if root == nil {
|
||||||
|
root = new(node)
|
||||||
|
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
||||||
|
}
|
||||||
|
root.addRoute(path, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns a slice of registered routes, including some useful information, such as:
|
||||||
|
// the http method, path and the handler name.
|
||||||
|
func (engine *Engine) Routes() (routes RoutesInfo) {
|
||||||
|
for _, tree := range engine.trees {
|
||||||
|
routes = iterate("", tree.method, routes, tree.root)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||||
|
path += root.path
|
||||||
|
if len(root.handlers) > 0 {
|
||||||
|
routes = append(routes, RouteInfo{
|
||||||
|
Method: method,
|
||||||
|
Path: path,
|
||||||
|
Handler: nameOfFunction(root.handlers.Last()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, child := range root.children {
|
||||||
|
routes = iterate(path, method, routes, child)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
||||||
|
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) Run(addr ...string) (err error) {
|
||||||
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
address := resolveAddress(addr)
|
||||||
|
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||||
|
err = http.ListenAndServe(address, engine)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
||||||
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) {
|
||||||
|
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
||||||
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||||
|
// through the specified unix socket (ie. a file).
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) RunUnix(file string) (err error) {
|
||||||
|
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
||||||
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
os.Remove(file)
|
||||||
|
listener, err := net.Listen("unix", file)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
err = http.Serve(listener, engine)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conforms to the http.Handler interface.
|
||||||
|
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
c := engine.pool.Get().(*Context)
|
||||||
|
c.writermem.reset(w)
|
||||||
|
c.Request = req
|
||||||
|
c.reset()
|
||||||
|
|
||||||
|
engine.handleHTTPRequest(c)
|
||||||
|
|
||||||
|
engine.pool.Put(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) handleHTTPRequest(context *Context) {
|
||||||
|
httpMethod := context.Request.Method
|
||||||
|
path := context.Request.URL.Path
|
||||||
|
|
||||||
|
// Find root of the tree for the given HTTP method
|
||||||
|
t := engine.trees
|
||||||
|
for i, tl := 0, len(t); i < tl; i++ {
|
||||||
|
if t[i].method == httpMethod {
|
||||||
|
root := t[i].root
|
||||||
|
// Find route in tree
|
||||||
|
handlers, params, tsr := root.getValue(path, context.Params)
|
||||||
|
if handlers != nil {
|
||||||
|
context.handlers = handlers
|
||||||
|
context.Params = params
|
||||||
|
context.Next()
|
||||||
|
context.writermem.WriteHeaderNow()
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if httpMethod != "CONNECT" && path != "/" {
|
||||||
|
if tsr && engine.RedirectTrailingSlash {
|
||||||
|
redirectTrailingSlash(context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: unit test
|
||||||
|
if engine.HandleMethodNotAllowed {
|
||||||
|
for _, tree := range engine.trees {
|
||||||
|
if tree.method != httpMethod {
|
||||||
|
if handlers, _, _ := tree.root.getValue(path, nil); handlers != nil {
|
||||||
|
context.handlers = engine.allNoMethod
|
||||||
|
serveError(context, 405, default405Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.handlers = engine.allNoRoute
|
||||||
|
serveError(context, 404, default404Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mimePlain = []string{MIMEPlain}
|
||||||
|
|
||||||
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||||
|
c.writermem.status = code
|
||||||
|
c.Next()
|
||||||
|
if !c.writermem.Written() {
|
||||||
|
if c.writermem.Status() == code {
|
||||||
|
c.writermem.Header()["Content-Type"] = mimePlain
|
||||||
|
c.Writer.Write(defaultMessage)
|
||||||
|
} else {
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectTrailingSlash(c *Context) {
|
||||||
|
req := c.Request
|
||||||
|
path := req.URL.Path
|
||||||
|
code := 301 // Permanent redirect, request with GET method
|
||||||
|
if req.Method != "GET" {
|
||||||
|
code = 307
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||||
|
req.URL.Path = path[:len(path)-1]
|
||||||
|
} else {
|
||||||
|
req.URL.Path = path + "/"
|
||||||
|
}
|
||||||
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||||
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||||
|
req := c.Request
|
||||||
|
path := req.URL.Path
|
||||||
|
|
||||||
|
fixedPath, found := root.findCaseInsensitivePath(
|
||||||
|
cleanPath(path),
|
||||||
|
trailingSlash,
|
||||||
|
)
|
||||||
|
if found {
|
||||||
|
code := 301 // Permanent redirect, request with GET method
|
||||||
|
if req.Method != "GET" {
|
||||||
|
code = 307
|
||||||
|
}
|
||||||
|
req.URL.Path = string(fixedPath)
|
||||||
|
debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String())
|
||||||
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
135
vendor/github.com/gin-gonic/gin/logger.go
generated
vendored
Normal file
135
vendor/github.com/gin-gonic/gin/logger.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||||
|
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||||
|
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
|
||||||
|
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||||
|
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||||
|
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||||
|
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||||
|
reset = string([]byte{27, 91, 48, 109})
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrorLogger() HandlerFunc {
|
||||||
|
return ErrorLoggerT(ErrorTypeAny)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||||
|
return func(c *Context) {
|
||||||
|
c.Next()
|
||||||
|
errors := c.Errors.ByType(typ)
|
||||||
|
if len(errors) > 0 {
|
||||||
|
c.JSON(-1, errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter
|
||||||
|
// By default gin.DefaultWriter = os.Stdout
|
||||||
|
func Logger() HandlerFunc {
|
||||||
|
return LoggerWithWriter(DefaultWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerWithWriter instance a Logger middleware with the specified writter buffer.
|
||||||
|
// Example: os.Stdout, a file opened in write mode, a socket...
|
||||||
|
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
||||||
|
isTerm := true
|
||||||
|
|
||||||
|
if w, ok := out.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
|
||||||
|
isTerm = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var skip map[string]struct{}
|
||||||
|
|
||||||
|
if length := len(notlogged); length > 0 {
|
||||||
|
skip = make(map[string]struct{}, length)
|
||||||
|
|
||||||
|
for _, path := range notlogged {
|
||||||
|
skip[path] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c *Context) {
|
||||||
|
// Start timer
|
||||||
|
start := time.Now()
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
|
||||||
|
// Process request
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// Log only when path is not being skipped
|
||||||
|
if _, ok := skip[path]; !ok {
|
||||||
|
// Stop timer
|
||||||
|
end := time.Now()
|
||||||
|
latency := end.Sub(start)
|
||||||
|
|
||||||
|
clientIP := c.ClientIP()
|
||||||
|
method := c.Request.Method
|
||||||
|
statusCode := c.Writer.Status()
|
||||||
|
var statusColor, methodColor string
|
||||||
|
if isTerm {
|
||||||
|
statusColor = colorForStatus(statusCode)
|
||||||
|
methodColor = colorForMethod(method)
|
||||||
|
}
|
||||||
|
comment := c.Errors.ByType(ErrorTypePrivate).String()
|
||||||
|
|
||||||
|
fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %s |%s %s %-7s %s\n%s",
|
||||||
|
end.Format("2006/01/02 - 15:04:05"),
|
||||||
|
statusColor, statusCode, reset,
|
||||||
|
latency,
|
||||||
|
clientIP,
|
||||||
|
methodColor, reset, method,
|
||||||
|
path,
|
||||||
|
comment,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorForStatus(code int) string {
|
||||||
|
switch {
|
||||||
|
case code >= 200 && code < 300:
|
||||||
|
return green
|
||||||
|
case code >= 300 && code < 400:
|
||||||
|
return white
|
||||||
|
case code >= 400 && code < 500:
|
||||||
|
return yellow
|
||||||
|
default:
|
||||||
|
return red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorForMethod(method string) string {
|
||||||
|
switch method {
|
||||||
|
case "GET":
|
||||||
|
return blue
|
||||||
|
case "POST":
|
||||||
|
return cyan
|
||||||
|
case "PUT":
|
||||||
|
return yellow
|
||||||
|
case "DELETE":
|
||||||
|
return red
|
||||||
|
case "PATCH":
|
||||||
|
return green
|
||||||
|
case "HEAD":
|
||||||
|
return magenta
|
||||||
|
case "OPTIONS":
|
||||||
|
return white
|
||||||
|
default:
|
||||||
|
return reset
|
||||||
|
}
|
||||||
|
}
|
BIN
vendor/github.com/gin-gonic/gin/logo.jpg
generated
vendored
Normal file
BIN
vendor/github.com/gin-gonic/gin/logo.jpg
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
69
vendor/github.com/gin-gonic/gin/mode.go
generated
vendored
Normal file
69
vendor/github.com/gin-gonic/gin/mode.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ENV_GIN_MODE = "GIN_MODE"
|
||||||
|
|
||||||
|
const (
|
||||||
|
DebugMode string = "debug"
|
||||||
|
ReleaseMode string = "release"
|
||||||
|
TestMode string = "test"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
debugCode = iota
|
||||||
|
releaseCode
|
||||||
|
testCode
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultWriter is the default io.Writer used the Gin for debug output and
|
||||||
|
// middleware output like Logger() or Recovery().
|
||||||
|
// Note that both Logger and Recovery provides custom ways to configure their
|
||||||
|
// output io.Writer.
|
||||||
|
// To support coloring in Windows use:
|
||||||
|
// import "github.com/mattn/go-colorable"
|
||||||
|
// gin.DefaultWriter = colorable.NewColorableStdout()
|
||||||
|
var DefaultWriter io.Writer = os.Stdout
|
||||||
|
var DefaultErrorWriter io.Writer = os.Stderr
|
||||||
|
|
||||||
|
var ginMode = debugCode
|
||||||
|
var modeName = DebugMode
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mode := os.Getenv(ENV_GIN_MODE)
|
||||||
|
if len(mode) == 0 {
|
||||||
|
SetMode(DebugMode)
|
||||||
|
} else {
|
||||||
|
SetMode(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetMode(value string) {
|
||||||
|
switch value {
|
||||||
|
case DebugMode:
|
||||||
|
ginMode = debugCode
|
||||||
|
case ReleaseMode:
|
||||||
|
ginMode = releaseCode
|
||||||
|
case TestMode:
|
||||||
|
ginMode = testCode
|
||||||
|
default:
|
||||||
|
panic("gin mode unknown: " + value)
|
||||||
|
}
|
||||||
|
modeName = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableBindValidation() {
|
||||||
|
binding.Validator = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mode() string {
|
||||||
|
return modeName
|
||||||
|
}
|
123
vendor/github.com/gin-gonic/gin/path.go
generated
vendored
Normal file
123
vendor/github.com/gin-gonic/gin/path.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Based on the path package, Copyright 2009 The Go Authors.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
|
||||||
|
// for p, eliminating . and .. elements.
|
||||||
|
//
|
||||||
|
// The following rules are applied iteratively until no further processing can
|
||||||
|
// be done:
|
||||||
|
// 1. Replace multiple slashes with a single slash.
|
||||||
|
// 2. Eliminate each . path name element (the current directory).
|
||||||
|
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||||
|
// along with the non-.. element that precedes it.
|
||||||
|
// 4. Eliminate .. elements that begin a rooted path:
|
||||||
|
// that is, replace "/.." by "/" at the beginning of a path.
|
||||||
|
//
|
||||||
|
// If the result of this process is an empty string, "/" is returned
|
||||||
|
func cleanPath(p string) string {
|
||||||
|
// Turn empty string into "/"
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(p)
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
// Invariants:
|
||||||
|
// reading from path; r is index of next byte to process.
|
||||||
|
// writing to buf; w is index of next byte to write.
|
||||||
|
|
||||||
|
// path must start with '/'
|
||||||
|
r := 1
|
||||||
|
w := 1
|
||||||
|
|
||||||
|
if p[0] != '/' {
|
||||||
|
r = 0
|
||||||
|
buf = make([]byte, n+1)
|
||||||
|
buf[0] = '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing := n > 2 && p[n-1] == '/'
|
||||||
|
|
||||||
|
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
||||||
|
// gets completely inlined (bufApp). So in contrast to the path package this
|
||||||
|
// loop has no expensive function calls (except 1x make)
|
||||||
|
|
||||||
|
for r < n {
|
||||||
|
switch {
|
||||||
|
case p[r] == '/':
|
||||||
|
// empty path element, trailing slash is added after the end
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && r+1 == n:
|
||||||
|
trailing = true
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '/':
|
||||||
|
// . element
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
||||||
|
// .. element: remove to last /
|
||||||
|
r += 2
|
||||||
|
|
||||||
|
if w > 1 {
|
||||||
|
// can backtrack
|
||||||
|
w--
|
||||||
|
|
||||||
|
if buf == nil {
|
||||||
|
for w > 1 && p[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for w > 1 && buf[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// real path element.
|
||||||
|
// add slash if needed
|
||||||
|
if w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy element
|
||||||
|
for r < n && p[r] != '/' {
|
||||||
|
bufApp(&buf, p, w, p[r])
|
||||||
|
w++
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-append trailing slash
|
||||||
|
if trailing && w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf == nil {
|
||||||
|
return p[:w]
|
||||||
|
}
|
||||||
|
return string(buf[:w])
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal helper to lazily create a buffer if necessary
|
||||||
|
func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||||
|
if *buf == nil {
|
||||||
|
if s[w] == c {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*buf = make([]byte, len(s))
|
||||||
|
copy(*buf, s[:w])
|
||||||
|
}
|
||||||
|
(*buf)[w] = c
|
||||||
|
}
|
108
vendor/github.com/gin-gonic/gin/recovery.go
generated
vendored
Normal file
108
vendor/github.com/gin-gonic/gin/recovery.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http/httputil"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dunno = []byte("???")
|
||||||
|
centerDot = []byte("·")
|
||||||
|
dot = []byte(".")
|
||||||
|
slash = []byte("/")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||||
|
func Recovery() HandlerFunc {
|
||||||
|
return RecoveryWithWriter(DefaultErrorWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||||
|
var logger *log.Logger
|
||||||
|
if out != nil {
|
||||||
|
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
|
||||||
|
}
|
||||||
|
return func(c *Context) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if logger != nil {
|
||||||
|
stack := stack(3)
|
||||||
|
httprequest, _ := httputil.DumpRequest(c.Request, false)
|
||||||
|
logger.Printf("[Recovery] panic recovered:\n%s\n%s\n%s%s", string(httprequest), err, stack, reset)
|
||||||
|
}
|
||||||
|
c.AbortWithStatus(500)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack returns a nicely formated stack frame, skipping skip frames
|
||||||
|
func stack(skip int) []byte {
|
||||||
|
buf := new(bytes.Buffer) // the returned data
|
||||||
|
// As we loop, we open files and read them. These variables record the currently
|
||||||
|
// loaded file.
|
||||||
|
var lines [][]byte
|
||||||
|
var lastFile string
|
||||||
|
for i := skip; ; i++ { // Skip the expected number of frames
|
||||||
|
pc, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Print this much at least. If we can't find the source, it won't show.
|
||||||
|
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
||||||
|
if file != lastFile {
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines = bytes.Split(data, []byte{'\n'})
|
||||||
|
lastFile = file
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// source returns a space-trimmed slice of the n'th line.
|
||||||
|
func source(lines [][]byte, n int) []byte {
|
||||||
|
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
||||||
|
if n < 0 || n >= len(lines) {
|
||||||
|
return dunno
|
||||||
|
}
|
||||||
|
return bytes.TrimSpace(lines[n])
|
||||||
|
}
|
||||||
|
|
||||||
|
// function returns, if possible, the name of the function containing the PC.
|
||||||
|
func function(pc uintptr) []byte {
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
return dunno
|
||||||
|
}
|
||||||
|
name := []byte(fn.Name())
|
||||||
|
// The name includes the path name to the package, which is unnecessary
|
||||||
|
// since the file name is already included. Plus, it has center dots.
|
||||||
|
// That is, we see
|
||||||
|
// runtime/debug.*T·ptrmethod
|
||||||
|
// and want
|
||||||
|
// *T.ptrmethod
|
||||||
|
// Also the package path might contains dot (e.g. code.google.com/...),
|
||||||
|
// so first eliminate the path prefix
|
||||||
|
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
||||||
|
name = name[lastslash+1:]
|
||||||
|
}
|
||||||
|
if period := bytes.Index(name, dot); period >= 0 {
|
||||||
|
name = name[period+1:]
|
||||||
|
}
|
||||||
|
name = bytes.Replace(name, centerDot, dot, -1)
|
||||||
|
return name
|
||||||
|
}
|
20
vendor/github.com/gin-gonic/gin/render/data.go
generated
vendored
Normal file
20
vendor/github.com/gin-gonic/gin/render/data.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
ContentType string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Data) Render(w http.ResponseWriter) error {
|
||||||
|
if len(r.ContentType) > 0 {
|
||||||
|
w.Header()["Content-Type"] = []string{r.ContentType}
|
||||||
|
}
|
||||||
|
w.Write(r.Data)
|
||||||
|
return nil
|
||||||
|
}
|
66
vendor/github.com/gin-gonic/gin/render/html.go
generated
vendored
Normal file
66
vendor/github.com/gin-gonic/gin/render/html.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
HTMLRender interface {
|
||||||
|
Instance(string, interface{}) Render
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLProduction struct {
|
||||||
|
Template *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLDebug struct {
|
||||||
|
Files []string
|
||||||
|
Glob string
|
||||||
|
}
|
||||||
|
|
||||||
|
HTML struct {
|
||||||
|
Template *template.Template
|
||||||
|
Name string
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var htmlContentType = []string{"text/html; charset=utf-8"}
|
||||||
|
|
||||||
|
func (r HTMLProduction) Instance(name string, data interface{}) Render {
|
||||||
|
return HTML{
|
||||||
|
Template: r.Template,
|
||||||
|
Name: name,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r HTMLDebug) Instance(name string, data interface{}) Render {
|
||||||
|
return HTML{
|
||||||
|
Template: r.loadTemplate(),
|
||||||
|
Name: name,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (r HTMLDebug) loadTemplate() *template.Template {
|
||||||
|
if len(r.Files) > 0 {
|
||||||
|
return template.Must(template.ParseFiles(r.Files...))
|
||||||
|
}
|
||||||
|
if len(r.Glob) > 0 {
|
||||||
|
return template.Must(template.ParseGlob(r.Glob))
|
||||||
|
}
|
||||||
|
panic("the HTML debug render was created without files or glob pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r HTML) Render(w http.ResponseWriter) error {
|
||||||
|
writeContentType(w, htmlContentType)
|
||||||
|
if len(r.Name) == 0 {
|
||||||
|
return r.Template.Execute(w, r.Data)
|
||||||
|
}
|
||||||
|
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
||||||
|
}
|
41
vendor/github.com/gin-gonic/gin/render/json.go
generated
vendored
Normal file
41
vendor/github.com/gin-gonic/gin/render/json.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
JSON struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
IndentedJSON struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||||
|
|
||||||
|
func (r JSON) Render(w http.ResponseWriter) error {
|
||||||
|
return WriteJSON(w, r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.Write(jsonBytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
return json.NewEncoder(w).Encode(obj)
|
||||||
|
}
|
24
vendor/github.com/gin-gonic/gin/render/redirect.go
generated
vendored
Normal file
24
vendor/github.com/gin-gonic/gin/render/redirect.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Redirect struct {
|
||||||
|
Code int
|
||||||
|
Request *http.Request
|
||||||
|
Location string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Redirect) Render(w http.ResponseWriter) error {
|
||||||
|
if (r.Code < 300 || r.Code > 308) && r.Code != 201 {
|
||||||
|
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
||||||
|
}
|
||||||
|
http.Redirect(w, r.Request, r.Location, r.Code)
|
||||||
|
return nil
|
||||||
|
}
|
31
vendor/github.com/gin-gonic/gin/render/render.go
generated
vendored
Normal file
31
vendor/github.com/gin-gonic/gin/render/render.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type Render interface {
|
||||||
|
Render(http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Render = JSON{}
|
||||||
|
_ Render = IndentedJSON{}
|
||||||
|
_ Render = XML{}
|
||||||
|
_ Render = String{}
|
||||||
|
_ Render = Redirect{}
|
||||||
|
_ Render = Data{}
|
||||||
|
_ Render = HTML{}
|
||||||
|
_ HTMLRender = HTMLDebug{}
|
||||||
|
_ HTMLRender = HTMLProduction{}
|
||||||
|
_ Render = YAML{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
header := w.Header()
|
||||||
|
if val := header["Content-Type"]; len(val) == 0 {
|
||||||
|
header["Content-Type"] = value
|
||||||
|
}
|
||||||
|
}
|
33
vendor/github.com/gin-gonic/gin/render/text.go
generated
vendored
Normal file
33
vendor/github.com/gin-gonic/gin/render/text.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type String struct {
|
||||||
|
Format string
|
||||||
|
Data []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var plainContentType = []string{"text/plain; charset=utf-8"}
|
||||||
|
|
||||||
|
func (r String) Render(w http.ResponseWriter) error {
|
||||||
|
WriteString(w, r.Format, r.Data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteString(w http.ResponseWriter, format string, data []interface{}) {
|
||||||
|
writeContentType(w, plainContentType)
|
||||||
|
|
||||||
|
if len(data) > 0 {
|
||||||
|
fmt.Fprintf(w, format, data...)
|
||||||
|
} else {
|
||||||
|
io.WriteString(w, format)
|
||||||
|
}
|
||||||
|
}
|
21
vendor/github.com/gin-gonic/gin/render/xml.go
generated
vendored
Normal file
21
vendor/github.com/gin-gonic/gin/render/xml.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XML struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var xmlContentType = []string{"application/xml; charset=utf-8"}
|
||||||
|
|
||||||
|
func (r XML) Render(w http.ResponseWriter) error {
|
||||||
|
writeContentType(w, xmlContentType)
|
||||||
|
return xml.NewEncoder(w).Encode(r.Data)
|
||||||
|
}
|
29
vendor/github.com/gin-gonic/gin/render/yaml.go
generated
vendored
Normal file
29
vendor/github.com/gin-gonic/gin/render/yaml.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type YAML struct {
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
|
||||||
|
|
||||||
|
func (r YAML) Render(w http.ResponseWriter) error {
|
||||||
|
writeContentType(w, yamlContentType)
|
||||||
|
|
||||||
|
bytes, err := yaml.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(bytes)
|
||||||
|
return nil
|
||||||
|
}
|
116
vendor/github.com/gin-gonic/gin/response_writer.go
generated
vendored
Normal file
116
vendor/github.com/gin-gonic/gin/response_writer.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
noWritten = -1
|
||||||
|
defaultStatus = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ResponseWriter interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
|
||||||
|
// Returns the HTTP response status code of the current request.
|
||||||
|
Status() int
|
||||||
|
|
||||||
|
// Returns the number of bytes already written into the response http body.
|
||||||
|
// See Written()
|
||||||
|
Size() int
|
||||||
|
|
||||||
|
// Writes the string into the response body.
|
||||||
|
WriteString(string) (int, error)
|
||||||
|
|
||||||
|
// Returns true if the response body was already written.
|
||||||
|
Written() bool
|
||||||
|
|
||||||
|
// Forces to write the http header (status code + headers).
|
||||||
|
WriteHeaderNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
size int
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ ResponseWriter = &responseWriter{}
|
||||||
|
|
||||||
|
func (w *responseWriter) reset(writer http.ResponseWriter) {
|
||||||
|
w.ResponseWriter = writer
|
||||||
|
w.size = noWritten
|
||||||
|
w.status = defaultStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) WriteHeader(code int) {
|
||||||
|
if code > 0 && w.status != code {
|
||||||
|
if w.Written() {
|
||||||
|
debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
|
||||||
|
}
|
||||||
|
w.status = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) WriteHeaderNow() {
|
||||||
|
if !w.Written() {
|
||||||
|
w.size = 0
|
||||||
|
w.ResponseWriter.WriteHeader(w.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Write(data []byte) (n int, err error) {
|
||||||
|
w.WriteHeaderNow()
|
||||||
|
n, err = w.ResponseWriter.Write(data)
|
||||||
|
w.size += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) WriteString(s string) (n int, err error) {
|
||||||
|
w.WriteHeaderNow()
|
||||||
|
n, err = io.WriteString(w.ResponseWriter, s)
|
||||||
|
w.size += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Status() int {
|
||||||
|
return w.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Size() int {
|
||||||
|
return w.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Written() bool {
|
||||||
|
return w.size != noWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the http.Hijacker interface
|
||||||
|
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if w.size < 0 {
|
||||||
|
w.size = 0
|
||||||
|
}
|
||||||
|
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the http.CloseNotify interface
|
||||||
|
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||||
|
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the http.Flush interface
|
||||||
|
func (w *responseWriter) Flush() {
|
||||||
|
w.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
}
|
215
vendor/github.com/gin-gonic/gin/routergroup.go
generated
vendored
Normal file
215
vendor/github.com/gin-gonic/gin/routergroup.go
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
IRouter interface {
|
||||||
|
IRoutes
|
||||||
|
Group(string, ...HandlerFunc) *RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
IRoutes interface {
|
||||||
|
Use(...HandlerFunc) IRoutes
|
||||||
|
|
||||||
|
Handle(string, string, ...HandlerFunc) IRoutes
|
||||||
|
Any(string, ...HandlerFunc) IRoutes
|
||||||
|
GET(string, ...HandlerFunc) IRoutes
|
||||||
|
POST(string, ...HandlerFunc) IRoutes
|
||||||
|
DELETE(string, ...HandlerFunc) IRoutes
|
||||||
|
PATCH(string, ...HandlerFunc) IRoutes
|
||||||
|
PUT(string, ...HandlerFunc) IRoutes
|
||||||
|
OPTIONS(string, ...HandlerFunc) IRoutes
|
||||||
|
HEAD(string, ...HandlerFunc) IRoutes
|
||||||
|
|
||||||
|
StaticFile(string, string) IRoutes
|
||||||
|
Static(string, string) IRoutes
|
||||||
|
StaticFS(string, http.FileSystem) IRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
|
||||||
|
// and an array of handlers (middleware)
|
||||||
|
RouterGroup struct {
|
||||||
|
Handlers HandlersChain
|
||||||
|
basePath string
|
||||||
|
engine *Engine
|
||||||
|
root bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ IRouter = &RouterGroup{}
|
||||||
|
|
||||||
|
// Use adds middleware to the group, see example code in github.
|
||||||
|
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
|
group.Handlers = append(group.Handlers, middleware...)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix.
|
||||||
|
// For example, all the routes that use a common middlware for authorization could be grouped.
|
||||||
|
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
|
||||||
|
return &RouterGroup{
|
||||||
|
Handlers: group.combineHandlers(handlers),
|
||||||
|
basePath: group.calculateAbsolutePath(relativePath),
|
||||||
|
engine: group.engine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) BasePath() string {
|
||||||
|
return group.basePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
|
||||||
|
absolutePath := group.calculateAbsolutePath(relativePath)
|
||||||
|
handlers = group.combineHandlers(handlers)
|
||||||
|
group.engine.addRoute(httpMethod, absolutePath, handlers)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new request handle and middleware with the given path and method.
|
||||||
|
// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
|
||||||
|
// See the example code in github.
|
||||||
|
//
|
||||||
|
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
||||||
|
// functions can be used.
|
||||||
|
//
|
||||||
|
// This function is intended for bulk loading and to allow the usage of less
|
||||||
|
// frequently used, non-standardized or custom methods (e.g. for internal
|
||||||
|
// communication with a proxy).
|
||||||
|
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
|
||||||
|
panic("http method " + httpMethod + " is not valid")
|
||||||
|
}
|
||||||
|
return group.handle(httpMethod, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST is a shortcut for router.Handle("POST", path, handle)
|
||||||
|
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle("POST", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET is a shortcut for router.Handle("GET", path, handle)
|
||||||
|
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle("GET", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
|
||||||
|
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle("DELETE", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
|
||||||
|
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle("PATCH", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT is a shortcut for router.Handle("PUT", path, handle)
|
||||||
|
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle("PUT", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
|
||||||
|
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle("OPTIONS", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
|
||||||
|
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle("HEAD", relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any registers a route that matches all the HTTP methods.
|
||||||
|
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE
|
||||||
|
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
group.handle("GET", relativePath, handlers)
|
||||||
|
group.handle("POST", relativePath, handlers)
|
||||||
|
group.handle("PUT", relativePath, handlers)
|
||||||
|
group.handle("PATCH", relativePath, handlers)
|
||||||
|
group.handle("HEAD", relativePath, handlers)
|
||||||
|
group.handle("OPTIONS", relativePath, handlers)
|
||||||
|
group.handle("DELETE", relativePath, handlers)
|
||||||
|
group.handle("CONNECT", relativePath, handlers)
|
||||||
|
group.handle("TRACE", relativePath, handlers)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticFile registers a single route in order to server a single file of the local filesystem.
|
||||||
|
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
|
||||||
|
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
|
||||||
|
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
||||||
|
panic("URL parameters can not be used when serving a static file")
|
||||||
|
}
|
||||||
|
handler := func(c *Context) {
|
||||||
|
c.File(filepath)
|
||||||
|
}
|
||||||
|
group.GET(relativePath, handler)
|
||||||
|
group.HEAD(relativePath, handler)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static serves files from the given file system root.
|
||||||
|
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
||||||
|
// of the Router's NotFound handler.
|
||||||
|
// To use the operating system's file system implementation,
|
||||||
|
// use :
|
||||||
|
// router.Static("/static", "/var/www")
|
||||||
|
func (group *RouterGroup) Static(relativePath, root string) IRoutes {
|
||||||
|
return group.StaticFS(relativePath, Dir(root, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead.
|
||||||
|
// Gin by default user: gin.Dir()
|
||||||
|
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
|
||||||
|
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
||||||
|
panic("URL parameters can not be used when serving a static folder")
|
||||||
|
}
|
||||||
|
handler := group.createStaticHandler(relativePath, fs)
|
||||||
|
urlPattern := path.Join(relativePath, "/*filepath")
|
||||||
|
|
||||||
|
// Register GET and HEAD handlers
|
||||||
|
group.GET(urlPattern, handler)
|
||||||
|
group.HEAD(urlPattern, handler)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
|
||||||
|
absolutePath := group.calculateAbsolutePath(relativePath)
|
||||||
|
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
||||||
|
_, nolisting := fs.(*onlyfilesFS)
|
||||||
|
return func(c *Context) {
|
||||||
|
if nolisting {
|
||||||
|
c.Writer.WriteHeader(404)
|
||||||
|
}
|
||||||
|
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
|
||||||
|
finalSize := len(group.Handlers) + len(handlers)
|
||||||
|
if finalSize >= int(abortIndex) {
|
||||||
|
panic("too many handlers")
|
||||||
|
}
|
||||||
|
mergedHandlers := make(HandlersChain, finalSize)
|
||||||
|
copy(mergedHandlers, group.Handlers)
|
||||||
|
copy(mergedHandlers[len(group.Handlers):], handlers)
|
||||||
|
return mergedHandlers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
|
||||||
|
return joinPaths(group.basePath, relativePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) returnObj() IRoutes {
|
||||||
|
if group.root {
|
||||||
|
return group.engine
|
||||||
|
}
|
||||||
|
return group
|
||||||
|
}
|
605
vendor/github.com/gin-gonic/gin/tree.go
generated
vendored
Normal file
605
vendor/github.com/gin-gonic/gin/tree.go
generated
vendored
Normal file
@ -0,0 +1,605 @@
|
|||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Param is a single URL parameter, consisting of a key and a value.
|
||||||
|
type Param struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Params is a Param-slice, as returned by the router.
|
||||||
|
// The slice is ordered, the first URL parameter is also the first slice value.
|
||||||
|
// It is therefore safe to read values by the index.
|
||||||
|
type Params []Param
|
||||||
|
|
||||||
|
// Get returns the value of the first Param which key matches the given name.
|
||||||
|
// If no matching Param is found, an empty string is returned.
|
||||||
|
func (ps Params) Get(name string) (string, bool) {
|
||||||
|
for _, entry := range ps {
|
||||||
|
if entry.Key == name {
|
||||||
|
return entry.Value, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByName returns the value of the first Param which key matches the given name.
|
||||||
|
// If no matching Param is found, an empty string is returned.
|
||||||
|
func (ps Params) ByName(name string) (va string) {
|
||||||
|
va, _ = ps.Get(name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodTree struct {
|
||||||
|
method string
|
||||||
|
root *node
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodTrees []methodTree
|
||||||
|
|
||||||
|
func (trees methodTrees) get(method string) *node {
|
||||||
|
for _, tree := range trees {
|
||||||
|
if tree.method == method {
|
||||||
|
return tree.root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a <= b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func countParams(path string) uint8 {
|
||||||
|
var n uint
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
if path[i] != ':' && path[i] != '*' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if n >= 255 {
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
return uint8(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
static nodeType = iota // default
|
||||||
|
root
|
||||||
|
param
|
||||||
|
catchAll
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
path string
|
||||||
|
wildChild bool
|
||||||
|
nType nodeType
|
||||||
|
maxParams uint8
|
||||||
|
indices string
|
||||||
|
children []*node
|
||||||
|
handlers HandlersChain
|
||||||
|
priority uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// increments priority of the given child and reorders if necessary
|
||||||
|
func (n *node) incrementChildPrio(pos int) int {
|
||||||
|
n.children[pos].priority++
|
||||||
|
prio := n.children[pos].priority
|
||||||
|
|
||||||
|
// adjust position (move to front)
|
||||||
|
newPos := pos
|
||||||
|
for newPos > 0 && n.children[newPos-1].priority < prio {
|
||||||
|
// swap node positions
|
||||||
|
tmpN := n.children[newPos-1]
|
||||||
|
n.children[newPos-1] = n.children[newPos]
|
||||||
|
n.children[newPos] = tmpN
|
||||||
|
|
||||||
|
newPos--
|
||||||
|
}
|
||||||
|
|
||||||
|
// build new index char string
|
||||||
|
if newPos != pos {
|
||||||
|
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
|
||||||
|
n.indices[pos:pos+1] + // the index char we move
|
||||||
|
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRoute adds a node with the given handle to the path.
|
||||||
|
// Not concurrency-safe!
|
||||||
|
func (n *node) addRoute(path string, handlers HandlersChain) {
|
||||||
|
fullPath := path
|
||||||
|
n.priority++
|
||||||
|
numParams := countParams(path)
|
||||||
|
|
||||||
|
// non-empty tree
|
||||||
|
if len(n.path) > 0 || len(n.children) > 0 {
|
||||||
|
walk:
|
||||||
|
for {
|
||||||
|
// Update maxParams of the current node
|
||||||
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the longest common prefix.
|
||||||
|
// This also implies that the common prefix contains no ':' or '*'
|
||||||
|
// since the existing key can't contain those chars.
|
||||||
|
i := 0
|
||||||
|
max := min(len(path), len(n.path))
|
||||||
|
for i < max && path[i] == n.path[i] {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split edge
|
||||||
|
if i < len(n.path) {
|
||||||
|
child := node{
|
||||||
|
path: n.path[i:],
|
||||||
|
wildChild: n.wildChild,
|
||||||
|
indices: n.indices,
|
||||||
|
children: n.children,
|
||||||
|
handlers: n.handlers,
|
||||||
|
priority: n.priority - 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update maxParams (max of all children)
|
||||||
|
for i := range child.children {
|
||||||
|
if child.children[i].maxParams > child.maxParams {
|
||||||
|
child.maxParams = child.children[i].maxParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.children = []*node{&child}
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices = string([]byte{n.path[i]})
|
||||||
|
n.path = path[:i]
|
||||||
|
n.handlers = nil
|
||||||
|
n.wildChild = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make new node a child of this node
|
||||||
|
if i < len(path) {
|
||||||
|
path = path[i:]
|
||||||
|
|
||||||
|
if n.wildChild {
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// Update maxParams of the child node
|
||||||
|
if numParams > n.maxParams {
|
||||||
|
n.maxParams = numParams
|
||||||
|
}
|
||||||
|
numParams--
|
||||||
|
|
||||||
|
// Check if the wildcard matches
|
||||||
|
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
|
||||||
|
// check for longer wildcard, e.g. :name and :names
|
||||||
|
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("path segment '" + path +
|
||||||
|
"' conflicts with existing wildcard '" + n.path +
|
||||||
|
"' in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
c := path[0]
|
||||||
|
|
||||||
|
// slash after param
|
||||||
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
|
n = n.children[0]
|
||||||
|
n.priority++
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a child with the next path byte exists
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if c == n.indices[i] {
|
||||||
|
i = n.incrementChildPrio(i)
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise insert it
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
// []byte for proper unicode char conversion, see #65
|
||||||
|
n.indices += string([]byte{c})
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
}
|
||||||
|
n.children = append(n.children, child)
|
||||||
|
n.incrementChildPrio(len(n.indices) - 1)
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
n.insertChild(numParams, path, fullPath, handlers)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if i == len(path) { // Make node a (in-path) leaf
|
||||||
|
if n.handlers != nil {
|
||||||
|
panic("handlers are already registered for path ''" + fullPath + "'")
|
||||||
|
}
|
||||||
|
n.handlers = handlers
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else { // Empty tree
|
||||||
|
n.insertChild(numParams, path, fullPath, handlers)
|
||||||
|
n.nType = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) {
|
||||||
|
var offset int // already handled bytes of the path
|
||||||
|
|
||||||
|
// find prefix until first wildcard (beginning with ':'' or '*'')
|
||||||
|
for i, max := 0, len(path); numParams > 0; i++ {
|
||||||
|
c := path[i]
|
||||||
|
if c != ':' && c != '*' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// find wildcard end (either '/' or path end)
|
||||||
|
end := i + 1
|
||||||
|
for end < max && path[end] != '/' {
|
||||||
|
switch path[end] {
|
||||||
|
// the wildcard name must not contain ':' and '*'
|
||||||
|
case ':', '*':
|
||||||
|
panic("only one wildcard per path segment is allowed, has: '" +
|
||||||
|
path[i:] + "' in path '" + fullPath + "'")
|
||||||
|
default:
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this Node existing children which would be
|
||||||
|
// unreachable if we insert the wildcard here
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
panic("wildcard route '" + path[i:end] +
|
||||||
|
"' conflicts with existing children in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the wildcard has a name
|
||||||
|
if end-i < 2 {
|
||||||
|
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == ':' { // param
|
||||||
|
// split path at the beginning of the wildcard
|
||||||
|
if i > 0 {
|
||||||
|
n.path = path[offset:i]
|
||||||
|
offset = i
|
||||||
|
}
|
||||||
|
|
||||||
|
child := &node{
|
||||||
|
nType: param,
|
||||||
|
maxParams: numParams,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n.wildChild = true
|
||||||
|
n = child
|
||||||
|
n.priority++
|
||||||
|
numParams--
|
||||||
|
|
||||||
|
// if the path doesn't end with the wildcard, then there
|
||||||
|
// will be another non-wildcard subpath starting with '/'
|
||||||
|
if end < max {
|
||||||
|
n.path = path[offset:end]
|
||||||
|
offset = end
|
||||||
|
|
||||||
|
child := &node{
|
||||||
|
maxParams: numParams,
|
||||||
|
priority: 1,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n = child
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // catchAll
|
||||||
|
if end != max || numParams > 1 {
|
||||||
|
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
|
||||||
|
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently fixed width 1 for '/'
|
||||||
|
i--
|
||||||
|
if path[i] != '/' {
|
||||||
|
panic("no / before catch-all in path '" + fullPath + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
n.path = path[offset:i]
|
||||||
|
|
||||||
|
// first node: catchAll node with empty path
|
||||||
|
child := &node{
|
||||||
|
wildChild: true,
|
||||||
|
nType: catchAll,
|
||||||
|
maxParams: 1,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
n.indices = string(path[i])
|
||||||
|
n = child
|
||||||
|
n.priority++
|
||||||
|
|
||||||
|
// second node: node holding the variable
|
||||||
|
child = &node{
|
||||||
|
path: path[i:],
|
||||||
|
nType: catchAll,
|
||||||
|
maxParams: 1,
|
||||||
|
handlers: handlers,
|
||||||
|
priority: 1,
|
||||||
|
}
|
||||||
|
n.children = []*node{child}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert remaining path part and handle to the leaf
|
||||||
|
n.path = path[offset:]
|
||||||
|
n.handlers = handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the handle registered with the given path (key). The values of
|
||||||
|
// wildcards are saved to a map.
|
||||||
|
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||||
|
// made if a handle exists with an extra (without the) trailing slash for the
|
||||||
|
// given path.
|
||||||
|
func (n *node) getValue(path string, po Params) (handlers HandlersChain, p Params, tsr bool) {
|
||||||
|
p = po
|
||||||
|
walk: // Outer loop for walking the tree
|
||||||
|
for {
|
||||||
|
if len(path) > len(n.path) {
|
||||||
|
if path[:len(n.path)] == n.path {
|
||||||
|
path = path[len(n.path):]
|
||||||
|
// If this node does not have a wildcard (param or catchAll)
|
||||||
|
// child, we can just look up the next child node and continue
|
||||||
|
// to walk down the tree
|
||||||
|
if !n.wildChild {
|
||||||
|
c := path[0]
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if c == n.indices[i] {
|
||||||
|
n = n.children[i]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found.
|
||||||
|
// We can recommend to redirect to the same URL without a
|
||||||
|
// trailing slash if a leaf exists for that path.
|
||||||
|
tsr = (path == "/" && n.handlers != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle wildcard child
|
||||||
|
n = n.children[0]
|
||||||
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
|
end := 0
|
||||||
|
for end < len(path) && path[end] != '/' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
// save param value
|
||||||
|
if cap(p) < int(n.maxParams) {
|
||||||
|
p = make(Params, 0, n.maxParams)
|
||||||
|
}
|
||||||
|
i := len(p)
|
||||||
|
p = p[:i+1] // expand slice within preallocated capacity
|
||||||
|
p[i].Key = n.path[1:]
|
||||||
|
p[i].Value = path[:end]
|
||||||
|
|
||||||
|
// we need to go deeper!
|
||||||
|
if end < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
path = path[end:]
|
||||||
|
n = n.children[0]
|
||||||
|
continue walk
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
tsr = (len(path) == end+1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if handlers = n.handlers; handlers != nil {
|
||||||
|
return
|
||||||
|
} else if len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists for TSR recommendation
|
||||||
|
n = n.children[0]
|
||||||
|
tsr = (n.path == "/" && n.handlers != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
// save param value
|
||||||
|
if cap(p) < int(n.maxParams) {
|
||||||
|
p = make(Params, 0, n.maxParams)
|
||||||
|
}
|
||||||
|
i := len(p)
|
||||||
|
p = p[:i+1] // expand slice within preallocated capacity
|
||||||
|
p[i].Key = n.path[2:]
|
||||||
|
p[i].Value = path
|
||||||
|
|
||||||
|
handlers = n.handlers
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if path == n.path {
|
||||||
|
// We should have reached the node containing the handle.
|
||||||
|
// Check if this node has a handle registered.
|
||||||
|
if handlers = n.handlers; handlers != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "/" && n.wildChild && n.nType != root {
|
||||||
|
tsr = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists for trailing slash recommendation
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == '/' {
|
||||||
|
n = n.children[i]
|
||||||
|
tsr = (len(n.path) == 1 && n.handlers != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handlers != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found. We can recommend to redirect to the same URL with an
|
||||||
|
// extra trailing slash if a leaf exists for that path
|
||||||
|
tsr = (path == "/") ||
|
||||||
|
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
|
||||||
|
path == n.path[:len(n.path)-1] && n.handlers != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes a case-insensitive lookup of the given path and tries to find a handler.
|
||||||
|
// It can optionally also fix trailing slashes.
|
||||||
|
// It returns the case-corrected path and a bool indicating whether the lookup
|
||||||
|
// was successful.
|
||||||
|
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
|
||||||
|
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
|
||||||
|
|
||||||
|
// Outer loop for walking the tree
|
||||||
|
for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) {
|
||||||
|
path = path[len(n.path):]
|
||||||
|
ciPath = append(ciPath, n.path...)
|
||||||
|
|
||||||
|
if len(path) > 0 {
|
||||||
|
// If this node does not have a wildcard (param or catchAll) child,
|
||||||
|
// we can just look up the next child node and continue to walk down
|
||||||
|
// the tree
|
||||||
|
if !n.wildChild {
|
||||||
|
r := unicode.ToLower(rune(path[0]))
|
||||||
|
for i, index := range n.indices {
|
||||||
|
// must use recursive approach since both index and
|
||||||
|
// ToLower(index) could exist. We must check both.
|
||||||
|
if r == unicode.ToLower(index) {
|
||||||
|
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
|
||||||
|
if found {
|
||||||
|
return append(ciPath, out...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found. We can recommend to redirect to the same URL
|
||||||
|
// without a trailing slash if a leaf exists for that path
|
||||||
|
found = (fixTrailingSlash && path == "/" && n.handlers != nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n = n.children[0]
|
||||||
|
switch n.nType {
|
||||||
|
case param:
|
||||||
|
// find param end (either '/' or path end)
|
||||||
|
k := 0
|
||||||
|
for k < len(path) && path[k] != '/' {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
// add param value to case insensitive path
|
||||||
|
ciPath = append(ciPath, path[:k]...)
|
||||||
|
|
||||||
|
// we need to go deeper!
|
||||||
|
if k < len(path) {
|
||||||
|
if len(n.children) > 0 {
|
||||||
|
path = path[k:]
|
||||||
|
n = n.children[0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... but we can't
|
||||||
|
if fixTrailingSlash && len(path) == k+1 {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.handlers != nil {
|
||||||
|
return ciPath, true
|
||||||
|
} else if fixTrailingSlash && len(n.children) == 1 {
|
||||||
|
// No handle found. Check if a handle for this path + a
|
||||||
|
// trailing slash exists
|
||||||
|
n = n.children[0]
|
||||||
|
if n.path == "/" && n.handlers != nil {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case catchAll:
|
||||||
|
return append(ciPath, path...), true
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We should have reached the node containing the handle.
|
||||||
|
// Check if this node has a handle registered.
|
||||||
|
if n.handlers != nil {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handle found.
|
||||||
|
// Try to fix the path by adding a trailing slash
|
||||||
|
if fixTrailingSlash {
|
||||||
|
for i := 0; i < len(n.indices); i++ {
|
||||||
|
if n.indices[i] == '/' {
|
||||||
|
n = n.children[i]
|
||||||
|
if (len(n.path) == 1 && n.handlers != nil) ||
|
||||||
|
(n.nType == catchAll && n.children[0].handlers != nil) {
|
||||||
|
return append(ciPath, '/'), true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found.
|
||||||
|
// Try to fix the path by adding / removing a trailing slash
|
||||||
|
if fixTrailingSlash {
|
||||||
|
if path == "/" {
|
||||||
|
return ciPath, true
|
||||||
|
}
|
||||||
|
if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
|
||||||
|
strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) &&
|
||||||
|
n.handlers != nil {
|
||||||
|
return append(ciPath, n.path...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
154
vendor/github.com/gin-gonic/gin/utils.go
generated
vendored
Normal file
154
vendor/github.com/gin-gonic/gin/utils.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BindKey = "_gin-gonic/gin/bindkey"
|
||||||
|
|
||||||
|
func Bind(val interface{}) HandlerFunc {
|
||||||
|
value := reflect.ValueOf(val)
|
||||||
|
if value.Kind() == reflect.Ptr {
|
||||||
|
panic(`Bind struct can not be a pointer. Example:
|
||||||
|
Use: gin.Bind(Struct{}) instead of gin.Bind(&Struct{})
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
typ := value.Type()
|
||||||
|
|
||||||
|
return func(c *Context) {
|
||||||
|
obj := reflect.New(typ).Interface()
|
||||||
|
if c.Bind(obj) == nil {
|
||||||
|
c.Set(BindKey, obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapF(f http.HandlerFunc) HandlerFunc {
|
||||||
|
return func(c *Context) {
|
||||||
|
f(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapH(h http.Handler) HandlerFunc {
|
||||||
|
return func(c *Context) {
|
||||||
|
h.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type H map[string]interface{}
|
||||||
|
|
||||||
|
// MarshalXML allows type H to be used with xml.Marshal
|
||||||
|
func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
|
start.Name = xml.Name{
|
||||||
|
Space: "",
|
||||||
|
Local: "map",
|
||||||
|
}
|
||||||
|
if err := e.EncodeToken(start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for key, value := range h {
|
||||||
|
elem := xml.StartElement{
|
||||||
|
Name: xml.Name{Space: "", Local: key},
|
||||||
|
Attr: []xml.Attr{},
|
||||||
|
}
|
||||||
|
if err := e.EncodeElement(value, elem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assert1(guard bool, text string) {
|
||||||
|
if !guard {
|
||||||
|
panic(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterFlags(content string) string {
|
||||||
|
for i, char := range content {
|
||||||
|
if char == ' ' || char == ';' {
|
||||||
|
return content[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
func chooseData(custom, wildcard interface{}) interface{} {
|
||||||
|
if custom == nil {
|
||||||
|
if wildcard == nil {
|
||||||
|
panic("negotiation config is invalid")
|
||||||
|
}
|
||||||
|
return wildcard
|
||||||
|
}
|
||||||
|
return custom
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAccept(acceptHeader string) []string {
|
||||||
|
parts := strings.Split(acceptHeader, ",")
|
||||||
|
out := make([]string, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
index := strings.IndexByte(part, ';')
|
||||||
|
if index >= 0 {
|
||||||
|
part = part[0:index]
|
||||||
|
}
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if len(part) > 0 {
|
||||||
|
out = append(out, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastChar(str string) uint8 {
|
||||||
|
size := len(str)
|
||||||
|
if size == 0 {
|
||||||
|
panic("The length of the string can't be 0")
|
||||||
|
}
|
||||||
|
return str[size-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameOfFunction(f interface{}) string {
|
||||||
|
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinPaths(absolutePath, relativePath string) string {
|
||||||
|
if len(relativePath) == 0 {
|
||||||
|
return absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
finalPath := path.Join(absolutePath, relativePath)
|
||||||
|
appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
|
||||||
|
if appendSlash {
|
||||||
|
return finalPath + "/"
|
||||||
|
}
|
||||||
|
return finalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveAddress(addr []string) string {
|
||||||
|
switch len(addr) {
|
||||||
|
case 0:
|
||||||
|
if port := os.Getenv("PORT"); len(port) > 0 {
|
||||||
|
debugPrint("Environment variable PORT=\"%s\"", port)
|
||||||
|
return ":" + port
|
||||||
|
}
|
||||||
|
debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
|
||||||
|
return ":8080"
|
||||||
|
case 1:
|
||||||
|
return addr[0]
|
||||||
|
default:
|
||||||
|
panic("too much parameters")
|
||||||
|
}
|
||||||
|
}
|
1
vendor/github.com/gin-gonic/gin/wercker.yml
generated
vendored
Normal file
1
vendor/github.com/gin-gonic/gin/wercker.yml
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
box: wercker/default
|
3
vendor/github.com/golang/protobuf/AUTHORS
generated
vendored
Normal file
3
vendor/github.com/golang/protobuf/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
3
vendor/github.com/golang/protobuf/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/github.com/golang/protobuf/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
31
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
31
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
|
||||||
|
Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
https://github.com/golang/protobuf
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
43
vendor/github.com/golang/protobuf/proto/Makefile
generated
vendored
Normal file
43
vendor/github.com/golang/protobuf/proto/Makefile
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
#
|
||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# https://github.com/golang/protobuf
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following disclaimer
|
||||||
|
# in the documentation and/or other materials provided with the
|
||||||
|
# distribution.
|
||||||
|
# * Neither the name of Google Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
install:
|
||||||
|
go install
|
||||||
|
|
||||||
|
test: install generate-test-pbs
|
||||||
|
go test
|
||||||
|
|
||||||
|
|
||||||
|
generate-test-pbs:
|
||||||
|
make install
|
||||||
|
make -C testdata
|
||||||
|
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
|
||||||
|
make
|
229
vendor/github.com/golang/protobuf/proto/clone.go
generated
vendored
Normal file
229
vendor/github.com/golang/protobuf/proto/clone.go
generated
vendored
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Protocol buffer deep copy and merge.
|
||||||
|
// TODO: RawMessage.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clone returns a deep copy of a protocol buffer.
|
||||||
|
func Clone(pb Message) Message {
|
||||||
|
in := reflect.ValueOf(pb)
|
||||||
|
if in.IsNil() {
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
out := reflect.New(in.Type().Elem())
|
||||||
|
// out is empty so a merge is a deep copy.
|
||||||
|
mergeStruct(out.Elem(), in.Elem())
|
||||||
|
return out.Interface().(Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges src into dst.
|
||||||
|
// Required and optional fields that are set in src will be set to that value in dst.
|
||||||
|
// Elements of repeated fields will be appended.
|
||||||
|
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||||
|
func Merge(dst, src Message) {
|
||||||
|
in := reflect.ValueOf(src)
|
||||||
|
out := reflect.ValueOf(dst)
|
||||||
|
if out.IsNil() {
|
||||||
|
panic("proto: nil destination")
|
||||||
|
}
|
||||||
|
if in.Type() != out.Type() {
|
||||||
|
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||||
|
panic("proto: type mismatch")
|
||||||
|
}
|
||||||
|
if in.IsNil() {
|
||||||
|
// Merging nil into non-nil is a quiet no-op
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mergeStruct(out.Elem(), in.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeStruct(out, in reflect.Value) {
|
||||||
|
sprop := GetProperties(in.Type())
|
||||||
|
for i := 0; i < in.NumField(); i++ {
|
||||||
|
f := in.Type().Field(i)
|
||||||
|
if strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if emIn, ok := extendable(in.Addr().Interface()); ok {
|
||||||
|
emOut, _ := extendable(out.Addr().Interface())
|
||||||
|
mIn, muIn := emIn.extensionsRead()
|
||||||
|
if mIn != nil {
|
||||||
|
mOut := emOut.extensionsWrite()
|
||||||
|
muIn.Lock()
|
||||||
|
mergeExtension(mOut, mIn)
|
||||||
|
muIn.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uf := in.FieldByName("XXX_unrecognized")
|
||||||
|
if !uf.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uin := uf.Bytes()
|
||||||
|
if len(uin) > 0 {
|
||||||
|
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeAny performs a merge between two values of the same type.
|
||||||
|
// viaPtr indicates whether the values were indirected through a pointer (implying proto2).
|
||||||
|
// prop is set if this is a struct field (it may be nil).
|
||||||
|
func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) {
|
||||||
|
if in.Type() == protoMessageType {
|
||||||
|
if !in.IsNil() {
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
|
||||||
|
} else {
|
||||||
|
Merge(out.Interface().(Message), in.Interface().(Message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch in.Kind() {
|
||||||
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||||
|
if !viaPtr && isProto3Zero(in) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.Set(in)
|
||||||
|
case reflect.Interface:
|
||||||
|
// Probably a oneof field; copy non-nil values.
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Allocate destination if it is not set, or set to a different type.
|
||||||
|
// Otherwise we will merge as normal.
|
||||||
|
if out.IsNil() || out.Elem().Type() != in.Elem().Type() {
|
||||||
|
out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T)
|
||||||
|
}
|
||||||
|
mergeAny(out.Elem(), in.Elem(), false, nil)
|
||||||
|
case reflect.Map:
|
||||||
|
if in.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.MakeMap(in.Type()))
|
||||||
|
}
|
||||||
|
// For maps with value types of *T or []byte we need to deep copy each value.
|
||||||
|
elemKind := in.Type().Elem().Kind()
|
||||||
|
for _, key := range in.MapKeys() {
|
||||||
|
var val reflect.Value
|
||||||
|
switch elemKind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
val = reflect.New(in.Type().Elem().Elem())
|
||||||
|
mergeAny(val, in.MapIndex(key), false, nil)
|
||||||
|
case reflect.Slice:
|
||||||
|
val = in.MapIndex(key)
|
||||||
|
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||||
|
default:
|
||||||
|
val = in.MapIndex(key)
|
||||||
|
}
|
||||||
|
out.SetMapIndex(key, val)
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.New(in.Elem().Type()))
|
||||||
|
}
|
||||||
|
mergeAny(out.Elem(), in.Elem(), true, nil)
|
||||||
|
case reflect.Slice:
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if in.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
// []byte is a scalar bytes field, not a repeated field.
|
||||||
|
|
||||||
|
// Edge case: if this is in a proto3 message, a zero length
|
||||||
|
// bytes field is considered the zero value, and should not
|
||||||
|
// be merged.
|
||||||
|
if prop != nil && prop.proto3 && in.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a deep copy.
|
||||||
|
// Append to []byte{} instead of []byte(nil) so that we never end up
|
||||||
|
// with a nil result.
|
||||||
|
out.SetBytes(append([]byte{}, in.Bytes()...))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n := in.Len()
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.MakeSlice(in.Type(), 0, n))
|
||||||
|
}
|
||||||
|
switch in.Type().Elem().Kind() {
|
||||||
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||||
|
out.Set(reflect.AppendSlice(out, in))
|
||||||
|
default:
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
x := reflect.Indirect(reflect.New(in.Type().Elem()))
|
||||||
|
mergeAny(x, in.Index(i), false, nil)
|
||||||
|
out.Set(reflect.Append(out, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
mergeStruct(out, in)
|
||||||
|
default:
|
||||||
|
// unknown type, so not a protocol buffer
|
||||||
|
log.Printf("proto: don't know how to copy %v", in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeExtension(out, in map[int32]Extension) {
|
||||||
|
for extNum, eIn := range in {
|
||||||
|
eOut := Extension{desc: eIn.desc}
|
||||||
|
if eIn.value != nil {
|
||||||
|
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
|
||||||
|
mergeAny(v, reflect.ValueOf(eIn.value), false, nil)
|
||||||
|
eOut.value = v.Interface()
|
||||||
|
}
|
||||||
|
if eIn.enc != nil {
|
||||||
|
eOut.enc = make([]byte, len(eIn.enc))
|
||||||
|
copy(eOut.enc, eIn.enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
out[extNum] = eOut
|
||||||
|
}
|
||||||
|
}
|
970
vendor/github.com/golang/protobuf/proto/decode.go
generated
vendored
Normal file
970
vendor/github.com/golang/protobuf/proto/decode.go
generated
vendored
Normal file
@ -0,0 +1,970 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Routines for decoding protocol buffer data to construct in-memory representations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errOverflow is returned when an integer is too large to be represented.
|
||||||
|
var errOverflow = errors.New("proto: integer overflow")
|
||||||
|
|
||||||
|
// ErrInternalBadWireType is returned by generated code when an incorrect
|
||||||
|
// wire type is encountered. It does not get returned to user code.
|
||||||
|
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||||
|
|
||||||
|
// The fundamental decoders that interpret bytes on the wire.
|
||||||
|
// Those that take integer types all return uint64 and are
|
||||||
|
// therefore of type valueDecoder.
|
||||||
|
|
||||||
|
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||||
|
// It returns the integer and the number of bytes consumed, or
|
||||||
|
// zero if there is not enough.
|
||||||
|
// This is the format for the
|
||||||
|
// int32, int64, uint32, uint64, bool, and enum
|
||||||
|
// protocol buffer types.
|
||||||
|
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||||
|
for shift := uint(0); shift < 64; shift += 7 {
|
||||||
|
if n >= len(buf) {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
b := uint64(buf[n])
|
||||||
|
n++
|
||||||
|
x |= (b & 0x7F) << shift
|
||||||
|
if (b & 0x80) == 0 {
|
||||||
|
return x, n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number is too large to represent in a 64-bit value.
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Buffer) decodeVarintSlow() (x uint64, err error) {
|
||||||
|
i := p.index
|
||||||
|
l := len(p.buf)
|
||||||
|
|
||||||
|
for shift := uint(0); shift < 64; shift += 7 {
|
||||||
|
if i >= l {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := p.buf[i]
|
||||||
|
i++
|
||||||
|
x |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
p.index = i
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number is too large to represent in a 64-bit value.
|
||||||
|
err = errOverflow
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVarint reads a varint-encoded integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// int32, int64, uint32, uint64, bool, and enum
|
||||||
|
// protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
||||||
|
i := p.index
|
||||||
|
buf := p.buf
|
||||||
|
|
||||||
|
if i >= len(buf) {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
} else if buf[i] < 0x80 {
|
||||||
|
p.index++
|
||||||
|
return uint64(buf[i]), nil
|
||||||
|
} else if len(buf)-i < 10 {
|
||||||
|
return p.decodeVarintSlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
var b uint64
|
||||||
|
// we already checked the first byte
|
||||||
|
x = uint64(buf[i]) - 0x80
|
||||||
|
i++
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 7
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 7
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 14
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 14
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 21
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 21
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 28
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 28
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 35
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 35
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 42
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 42
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 49
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 49
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 56
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 56
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 63
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
// x -= 0x80 << 63 // Always zero.
|
||||||
|
|
||||||
|
return 0, errOverflow
|
||||||
|
|
||||||
|
done:
|
||||||
|
p.index = i
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed64 reads a 64-bit integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// fixed64, sfixed64, and double protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
|
||||||
|
// x, err already 0
|
||||||
|
i := p.index + 8
|
||||||
|
if i < 0 || i > len(p.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.index = i
|
||||||
|
|
||||||
|
x = uint64(p.buf[i-8])
|
||||||
|
x |= uint64(p.buf[i-7]) << 8
|
||||||
|
x |= uint64(p.buf[i-6]) << 16
|
||||||
|
x |= uint64(p.buf[i-5]) << 24
|
||||||
|
x |= uint64(p.buf[i-4]) << 32
|
||||||
|
x |= uint64(p.buf[i-3]) << 40
|
||||||
|
x |= uint64(p.buf[i-2]) << 48
|
||||||
|
x |= uint64(p.buf[i-1]) << 56
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed32 reads a 32-bit integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// fixed32, sfixed32, and float protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
|
||||||
|
// x, err already 0
|
||||||
|
i := p.index + 4
|
||||||
|
if i < 0 || i > len(p.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.index = i
|
||||||
|
|
||||||
|
x = uint64(p.buf[i-4])
|
||||||
|
x |= uint64(p.buf[i-3]) << 8
|
||||||
|
x |= uint64(p.buf[i-2]) << 16
|
||||||
|
x |= uint64(p.buf[i-1]) << 24
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
|
||||||
|
// from the Buffer.
|
||||||
|
// This is the format used for the sint64 protocol buffer type.
|
||||||
|
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
|
||||||
|
x, err = p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
|
||||||
|
// from the Buffer.
|
||||||
|
// This is the format used for the sint32 protocol buffer type.
|
||||||
|
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
||||||
|
x, err = p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||||
|
// bytes, embedded messages
|
||||||
|
|
||||||
|
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||||
|
// This is the format used for the bytes protocol buffer
|
||||||
|
// type and for embedded messages.
|
||||||
|
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
|
||||||
|
n, err := p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nb := int(n)
|
||||||
|
if nb < 0 {
|
||||||
|
return nil, fmt.Errorf("proto: bad byte length %d", nb)
|
||||||
|
}
|
||||||
|
end := p.index + nb
|
||||||
|
if end < p.index || end > len(p.buf) {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alloc {
|
||||||
|
// todo: check if can get more uses of alloc=false
|
||||||
|
buf = p.buf[p.index:end]
|
||||||
|
p.index += nb
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = make([]byte, nb)
|
||||||
|
copy(buf, p.buf[p.index:])
|
||||||
|
p.index += nb
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeStringBytes reads an encoded string from the Buffer.
|
||||||
|
// This is the format used for the proto2 string type.
|
||||||
|
func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
||||||
|
buf, err := p.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||||
|
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||||
|
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||||
|
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||||
|
oi := o.index
|
||||||
|
|
||||||
|
err := o.skip(t, tag, wire)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unrecField.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := structPointer_Bytes(base, unrecField)
|
||||||
|
|
||||||
|
// Add the skipped field to struct field
|
||||||
|
obuf := o.buf
|
||||||
|
|
||||||
|
o.buf = *ptr
|
||||||
|
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||||
|
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||||
|
|
||||||
|
o.buf = obuf
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||||
|
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||||
|
|
||||||
|
var u uint64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch wire {
|
||||||
|
case WireVarint:
|
||||||
|
_, err = o.DecodeVarint()
|
||||||
|
case WireFixed64:
|
||||||
|
_, err = o.DecodeFixed64()
|
||||||
|
case WireBytes:
|
||||||
|
_, err = o.DecodeRawBytes(false)
|
||||||
|
case WireFixed32:
|
||||||
|
_, err = o.DecodeFixed32()
|
||||||
|
case WireStartGroup:
|
||||||
|
for {
|
||||||
|
u, err = o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fwire := int(u & 0x7)
|
||||||
|
if fwire == WireEndGroup {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ftag := int(u >> 3)
|
||||||
|
err = o.skip(t, ftag, fwire)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is the interface representing objects that can
|
||||||
|
// unmarshal themselves. The method should reset the receiver before
|
||||||
|
// decoding starts. The argument points to data that may be
|
||||||
|
// overwritten, so implementations should not keep references to the
|
||||||
|
// buffer.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
Unmarshal([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||||
|
// decoded result in pb. If the struct underlying pb does not match
|
||||||
|
// the data in buf, the results can be unpredictable.
|
||||||
|
//
|
||||||
|
// Unmarshal resets pb before starting to unmarshal, so any
|
||||||
|
// existing data in pb is always removed. Use UnmarshalMerge
|
||||||
|
// to preserve and append to existing data.
|
||||||
|
func Unmarshal(buf []byte, pb Message) error {
|
||||||
|
pb.Reset()
|
||||||
|
return UnmarshalMerge(buf, pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||||
|
// writes the decoded result to pb. If the struct underlying pb does not match
|
||||||
|
// the data in buf, the results can be unpredictable.
|
||||||
|
//
|
||||||
|
// UnmarshalMerge merges into existing data in pb.
|
||||||
|
// Most code should use Unmarshal instead.
|
||||||
|
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if u, ok := pb.(Unmarshaler); ok {
|
||||||
|
return u.Unmarshal(buf)
|
||||||
|
}
|
||||||
|
return NewBuffer(buf).Unmarshal(pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMessage reads a count-delimited message from the Buffer.
|
||||||
|
func (p *Buffer) DecodeMessage(pb Message) error {
|
||||||
|
enc, err := p.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return NewBuffer(enc).Unmarshal(pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeGroup reads a tag-delimited group from the Buffer.
|
||||||
|
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||||
|
typ, base, err := getbase(pb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the protocol buffer representation in the
|
||||||
|
// Buffer and places the decoded result in pb. If the struct
|
||||||
|
// underlying pb does not match the data in the buffer, the results can be
|
||||||
|
// unpredictable.
|
||||||
|
//
|
||||||
|
// Unlike proto.Unmarshal, this does not reset pb before starting to unmarshal.
|
||||||
|
func (p *Buffer) Unmarshal(pb Message) error {
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if u, ok := pb.(Unmarshaler); ok {
|
||||||
|
err := u.Unmarshal(p.buf[p.index:])
|
||||||
|
p.index = len(p.buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
typ, base, err := getbase(pb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||||
|
|
||||||
|
if collectStats {
|
||||||
|
stats.Decode++
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalType does the work of unmarshaling a structure.
|
||||||
|
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||||
|
var state errorState
|
||||||
|
required, reqFields := prop.reqCount, uint64(0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for err == nil && o.index < len(o.buf) {
|
||||||
|
oi := o.index
|
||||||
|
var u uint64
|
||||||
|
u, err = o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
wire := int(u & 0x7)
|
||||||
|
if wire == WireEndGroup {
|
||||||
|
if is_group {
|
||||||
|
if required > 0 {
|
||||||
|
// Not enough information to determine the exact field.
|
||||||
|
// (See below.)
|
||||||
|
return &RequiredNotSetError{"{Unknown}"}
|
||||||
|
}
|
||||||
|
return nil // input is satisfied
|
||||||
|
}
|
||||||
|
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||||
|
}
|
||||||
|
tag := int(u >> 3)
|
||||||
|
if tag <= 0 {
|
||||||
|
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||||
|
}
|
||||||
|
fieldnum, ok := prop.decoderTags.get(tag)
|
||||||
|
if !ok {
|
||||||
|
// Maybe it's an extension?
|
||||||
|
if prop.extendable {
|
||||||
|
if e, _ := extendable(structPointer_Interface(base, st)); isExtensionField(e, int32(tag)) {
|
||||||
|
if err = o.skip(st, tag, wire); err == nil {
|
||||||
|
extmap := e.extensionsWrite()
|
||||||
|
ext := extmap[int32(tag)] // may be missing
|
||||||
|
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||||
|
extmap[int32(tag)] = ext
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Maybe it's a oneof?
|
||||||
|
if prop.oneofUnmarshaler != nil {
|
||||||
|
m := structPointer_Interface(base, st).(Message)
|
||||||
|
// First return value indicates whether tag is a oneof field.
|
||||||
|
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
|
||||||
|
if err == ErrInternalBadWireType {
|
||||||
|
// Map the error to something more descriptive.
|
||||||
|
// Do the formatting here to save generated code space.
|
||||||
|
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := prop.Prop[fieldnum]
|
||||||
|
|
||||||
|
if p.dec == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dec := p.dec
|
||||||
|
if wire != WireStartGroup && wire != p.WireType {
|
||||||
|
if wire == WireBytes && p.packedDec != nil {
|
||||||
|
// a packable field
|
||||||
|
dec = p.packedDec
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decErr := dec(o, p, base)
|
||||||
|
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||||
|
err = decErr
|
||||||
|
}
|
||||||
|
if err == nil && p.Required {
|
||||||
|
// Successfully decoded a required field.
|
||||||
|
if tag <= 64 {
|
||||||
|
// use bitmap for fields 1-64 to catch field reuse.
|
||||||
|
var mask uint64 = 1 << uint64(tag-1)
|
||||||
|
if reqFields&mask == 0 {
|
||||||
|
// new required field
|
||||||
|
reqFields |= mask
|
||||||
|
required--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is imprecise. It can be fooled by a required field
|
||||||
|
// with a tag > 64 that is encoded twice; that's very rare.
|
||||||
|
// A fully correct implementation would require allocating
|
||||||
|
// a data structure, which we would like to avoid.
|
||||||
|
required--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if is_group {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if state.err != nil {
|
||||||
|
return state.err
|
||||||
|
}
|
||||||
|
if required > 0 {
|
||||||
|
// Not enough information to determine the exact field. If we use extra
|
||||||
|
// CPU, we could determine the field only if the missing required field
|
||||||
|
// has a tag <= 64 and we check reqFields.
|
||||||
|
return &RequiredNotSetError{"{Unknown}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Individual type decoders
|
||||||
|
// For each,
|
||||||
|
// u is the decoded value,
|
||||||
|
// v is a pointer to the field (pointer) in the struct
|
||||||
|
|
||||||
|
// Sizes of the pools to allocate inside the Buffer.
|
||||||
|
// The goal is modest amortization and allocation
|
||||||
|
// on at least 16-byte boundaries.
|
||||||
|
const (
|
||||||
|
boolPoolSize = 16
|
||||||
|
uint32PoolSize = 8
|
||||||
|
uint64PoolSize = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode a bool.
|
||||||
|
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(o.bools) == 0 {
|
||||||
|
o.bools = make([]bool, boolPoolSize)
|
||||||
|
}
|
||||||
|
o.bools[0] = u != 0
|
||||||
|
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||||
|
o.bools = o.bools[1:]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_BoolVal(base, p.field) = u != 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an int32.
|
||||||
|
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an int64.
|
||||||
|
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a string.
|
||||||
|
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_String(base, p.field) = &s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_StringVal(base, p.field) = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bytes ([]byte).
|
||||||
|
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||||
|
b, err := o.DecodeRawBytes(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_Bytes(base, p.field) = b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bools ([]bool).
|
||||||
|
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_BoolSlice(base, p.field)
|
||||||
|
*v = append(*v, u != 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bools ([]bool) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_BoolSlice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded bools
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
y := *v
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
y = append(y, u != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
*v = y
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int32s ([]int32).
|
||||||
|
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int32s ([]int32) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_Word32Slice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded int32s
|
||||||
|
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Append(uint32(u))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int64s ([]int64).
|
||||||
|
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
structPointer_Word64Slice(base, p.field).Append(u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int64s ([]int64) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_Word64Slice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded int64s
|
||||||
|
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Append(u)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of strings ([]string).
|
||||||
|
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_StringSlice(base, p.field)
|
||||||
|
*v = append(*v, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of slice of bytes ([][]byte).
|
||||||
|
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||||
|
b, err := o.DecodeRawBytes(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_BytesSlice(base, p.field)
|
||||||
|
*v = append(*v, b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a map field.
|
||||||
|
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||||
|
raw, err := o.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oi := o.index // index at the end of this map entry
|
||||||
|
o.index -= len(raw) // move buffer back to start of map entry
|
||||||
|
|
||||||
|
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
|
||||||
|
if mptr.Elem().IsNil() {
|
||||||
|
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||||
|
}
|
||||||
|
v := mptr.Elem() // map[K]V
|
||||||
|
|
||||||
|
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||||
|
// See enc_new_map for why.
|
||||||
|
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||||
|
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||||
|
|
||||||
|
var valbase structPointer
|
||||||
|
var valptr reflect.Value
|
||||||
|
switch p.mtype.Elem().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// []byte
|
||||||
|
var dummy []byte
|
||||||
|
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||||
|
valbase = toStructPointer(valptr) // *[]byte
|
||||||
|
case reflect.Ptr:
|
||||||
|
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||||
|
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||||
|
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||||
|
valbase = toStructPointer(valptr)
|
||||||
|
default:
|
||||||
|
// everything else
|
||||||
|
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||||
|
valbase = toStructPointer(valptr.Addr()) // **V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode.
|
||||||
|
// This parses a restricted wire format, namely the encoding of a message
|
||||||
|
// with two fields. See enc_new_map for the format.
|
||||||
|
for o.index < oi {
|
||||||
|
// tagcode for key and value properties are always a single byte
|
||||||
|
// because they have tags 1 and 2.
|
||||||
|
tagcode := o.buf[o.index]
|
||||||
|
o.index++
|
||||||
|
switch tagcode {
|
||||||
|
case p.mkeyprop.tagcode[0]:
|
||||||
|
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case p.mvalprop.tagcode[0]:
|
||||||
|
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: Should we silently skip this instead?
|
||||||
|
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyelem, valelem := keyptr.Elem(), valptr.Elem()
|
||||||
|
if !keyelem.IsValid() {
|
||||||
|
keyelem = reflect.Zero(p.mtype.Key())
|
||||||
|
}
|
||||||
|
if !valelem.IsValid() {
|
||||||
|
valelem = reflect.Zero(p.mtype.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
v.SetMapIndex(keyelem, valelem)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a group.
|
||||||
|
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||||
|
bas := structPointer_GetStructPointer(base, p.field)
|
||||||
|
if structPointer_IsNil(bas) {
|
||||||
|
// allocate new nested message
|
||||||
|
bas = toStructPointer(reflect.New(p.stype))
|
||||||
|
structPointer_SetStructPointer(base, p.field, bas)
|
||||||
|
}
|
||||||
|
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an embedded message.
|
||||||
|
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||||
|
raw, e := o.DecodeRawBytes(false)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
bas := structPointer_GetStructPointer(base, p.field)
|
||||||
|
if structPointer_IsNil(bas) {
|
||||||
|
// allocate new nested message
|
||||||
|
bas = toStructPointer(reflect.New(p.stype))
|
||||||
|
structPointer_SetStructPointer(base, p.field, bas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if p.isUnmarshaler {
|
||||||
|
iv := structPointer_Interface(bas, p.stype)
|
||||||
|
return iv.(Unmarshaler).Unmarshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
obuf := o.buf
|
||||||
|
oi := o.index
|
||||||
|
o.buf = raw
|
||||||
|
o.index = 0
|
||||||
|
|
||||||
|
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||||
|
o.buf = obuf
|
||||||
|
o.index = oi
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of embedded messages.
|
||||||
|
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||||
|
return o.dec_slice_struct(p, false, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of embedded groups.
|
||||||
|
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||||
|
return o.dec_slice_struct(p, true, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of structs ([]*struct).
|
||||||
|
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||||
|
v := reflect.New(p.stype)
|
||||||
|
bas := toStructPointer(v)
|
||||||
|
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||||
|
|
||||||
|
if is_group {
|
||||||
|
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := o.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if p.isUnmarshaler {
|
||||||
|
iv := v.Interface()
|
||||||
|
return iv.(Unmarshaler).Unmarshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
obuf := o.buf
|
||||||
|
oi := o.index
|
||||||
|
o.buf = raw
|
||||||
|
o.index = 0
|
||||||
|
|
||||||
|
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||||
|
|
||||||
|
o.buf = obuf
|
||||||
|
o.index = oi
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
1362
vendor/github.com/golang/protobuf/proto/encode.go
generated
vendored
Normal file
1362
vendor/github.com/golang/protobuf/proto/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
300
vendor/github.com/golang/protobuf/proto/equal.go
generated
vendored
Normal file
300
vendor/github.com/golang/protobuf/proto/equal.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Protocol buffer comparison.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Equal returns true iff protocol buffers a and b are equal.
|
||||||
|
The arguments must both be pointers to protocol buffer structs.
|
||||||
|
|
||||||
|
Equality is defined in this way:
|
||||||
|
- Two messages are equal iff they are the same type,
|
||||||
|
corresponding fields are equal, unknown field sets
|
||||||
|
are equal, and extensions sets are equal.
|
||||||
|
- Two set scalar fields are equal iff their values are equal.
|
||||||
|
If the fields are of a floating-point type, remember that
|
||||||
|
NaN != x for all x, including NaN. If the message is defined
|
||||||
|
in a proto3 .proto file, fields are not "set"; specifically,
|
||||||
|
zero length proto3 "bytes" fields are equal (nil == {}).
|
||||||
|
- Two repeated fields are equal iff their lengths are the same,
|
||||||
|
and their corresponding elements are equal. Note a "bytes" field,
|
||||||
|
although represented by []byte, is not a repeated field and the
|
||||||
|
rule for the scalar fields described above applies.
|
||||||
|
- Two unset fields are equal.
|
||||||
|
- Two unknown field sets are equal if their current
|
||||||
|
encoded state is equal.
|
||||||
|
- Two extension sets are equal iff they have corresponding
|
||||||
|
elements that are pairwise equal.
|
||||||
|
- Two map fields are equal iff their lengths are the same,
|
||||||
|
and they contain the same set of elements. Zero-length map
|
||||||
|
fields are equal.
|
||||||
|
- Every other combination of things are not equal.
|
||||||
|
|
||||||
|
The return value is undefined if a and b are not protocol buffers.
|
||||||
|
*/
|
||||||
|
func Equal(a, b Message) bool {
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b)
|
||||||
|
if v1.Type() != v2.Type() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v1.Kind() == reflect.Ptr {
|
||||||
|
if v1.IsNil() {
|
||||||
|
return v2.IsNil()
|
||||||
|
}
|
||||||
|
if v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v1, v2 = v1.Elem(), v2.Elem()
|
||||||
|
}
|
||||||
|
if v1.Kind() != reflect.Struct {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalStruct(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1 and v2 are known to have the same type.
|
||||||
|
func equalStruct(v1, v2 reflect.Value) bool {
|
||||||
|
sprop := GetProperties(v1.Type())
|
||||||
|
for i := 0; i < v1.NumField(); i++ {
|
||||||
|
f := v1.Type().Field(i)
|
||||||
|
if strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f1, f2 := v1.Field(i), v2.Field(i)
|
||||||
|
if f.Type.Kind() == reflect.Ptr {
|
||||||
|
if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 {
|
||||||
|
// both unset
|
||||||
|
continue
|
||||||
|
} else if n1 != n2 {
|
||||||
|
// set/unset mismatch
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b1, ok := f1.Interface().(raw)
|
||||||
|
if ok {
|
||||||
|
b2 := f2.Interface().(raw)
|
||||||
|
// RawMessage
|
||||||
|
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f1, f2 = f1.Elem(), f2.Elem()
|
||||||
|
}
|
||||||
|
if !equalAny(f1, f2, sprop.Prop[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if em1 := v1.FieldByName("XXX_InternalExtensions"); em1.IsValid() {
|
||||||
|
em2 := v2.FieldByName("XXX_InternalExtensions")
|
||||||
|
if !equalExtensions(v1.Type(), em1.Interface().(XXX_InternalExtensions), em2.Interface().(XXX_InternalExtensions)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() {
|
||||||
|
em2 := v2.FieldByName("XXX_extensions")
|
||||||
|
if !equalExtMap(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uf := v1.FieldByName("XXX_unrecognized")
|
||||||
|
if !uf.IsValid() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
u1 := uf.Bytes()
|
||||||
|
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||||
|
if !bytes.Equal(u1, u2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1 and v2 are known to have the same type.
|
||||||
|
// prop may be nil.
|
||||||
|
func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
|
||||||
|
if v1.Type() == protoMessageType {
|
||||||
|
m1, _ := v1.Interface().(Message)
|
||||||
|
m2, _ := v2.Interface().(Message)
|
||||||
|
return Equal(m1, m2)
|
||||||
|
}
|
||||||
|
switch v1.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return v1.Bool() == v2.Bool()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v1.Float() == v2.Float()
|
||||||
|
case reflect.Int32, reflect.Int64:
|
||||||
|
return v1.Int() == v2.Int()
|
||||||
|
case reflect.Interface:
|
||||||
|
// Probably a oneof field; compare the inner values.
|
||||||
|
n1, n2 := v1.IsNil(), v2.IsNil()
|
||||||
|
if n1 || n2 {
|
||||||
|
return n1 == n2
|
||||||
|
}
|
||||||
|
e1, e2 := v1.Elem(), v2.Elem()
|
||||||
|
if e1.Type() != e2.Type() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalAny(e1, e2, nil)
|
||||||
|
case reflect.Map:
|
||||||
|
if v1.Len() != v2.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, key := range v1.MapKeys() {
|
||||||
|
val2 := v2.MapIndex(key)
|
||||||
|
if !val2.IsValid() {
|
||||||
|
// This key was not found in the second map.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !equalAny(v1.MapIndex(key), val2, nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Maps may have nil values in them, so check for nil.
|
||||||
|
if v1.IsNil() && v2.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v1.IsNil() != v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalAny(v1.Elem(), v2.Elem(), prop)
|
||||||
|
case reflect.Slice:
|
||||||
|
if v1.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
// short circuit: []byte
|
||||||
|
|
||||||
|
// Edge case: if this is in a proto3 message, a zero length
|
||||||
|
// bytes field is considered the zero value.
|
||||||
|
if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v1.IsNil() != v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
if v1.Len() != v2.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < v1.Len(); i++ {
|
||||||
|
if !equalAny(v1.Index(i), v2.Index(i), prop) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return v1.Interface().(string) == v2.Interface().(string)
|
||||||
|
case reflect.Struct:
|
||||||
|
return equalStruct(v1, v2)
|
||||||
|
case reflect.Uint32, reflect.Uint64:
|
||||||
|
return v1.Uint() == v2.Uint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknown type, so not a protocol buffer
|
||||||
|
log.Printf("proto: don't know how to compare %v", v1)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// base is the struct type that the extensions are based on.
|
||||||
|
// x1 and x2 are InternalExtensions.
|
||||||
|
func equalExtensions(base reflect.Type, x1, x2 XXX_InternalExtensions) bool {
|
||||||
|
em1, _ := x1.extensionsRead()
|
||||||
|
em2, _ := x2.extensionsRead()
|
||||||
|
return equalExtMap(base, em1, em2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
||||||
|
if len(em1) != len(em2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for extNum, e1 := range em1 {
|
||||||
|
e2, ok := em2[extNum]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m1, m2 := e1.value, e2.value
|
||||||
|
|
||||||
|
if m1 != nil && m2 != nil {
|
||||||
|
// Both are unencoded.
|
||||||
|
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one is encoded. To do a semantically correct comparison
|
||||||
|
// we need to unmarshal them first.
|
||||||
|
var desc *ExtensionDesc
|
||||||
|
if m := extensionMaps[base]; m != nil {
|
||||||
|
desc = m[extNum]
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if m1 == nil {
|
||||||
|
m1, err = decodeExtension(e1.enc, desc)
|
||||||
|
}
|
||||||
|
if m2 == nil && err == nil {
|
||||||
|
m2, err = decodeExtension(e2.enc, desc)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// The encoded form is invalid.
|
||||||
|
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user