mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-19 12:48:18 +08:00
Compare commits
138 Commits
v1.2.2
...
tools/goct
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6eb1a9670 | ||
|
|
e4ab518576 | ||
|
|
dfc67b5fac | ||
|
|
62266d8f91 | ||
|
|
b8ea16a88e | ||
|
|
23deaf50e6 | ||
|
|
38a36ed8d3 | ||
|
|
49bab23c54 | ||
|
|
78ba00d3a7 | ||
|
|
787b046a70 | ||
|
|
f827a7b985 | ||
|
|
f5f2097d14 | ||
|
|
cfcfb87fd4 | ||
|
|
1d223fc114 | ||
|
|
c0647f0719 | ||
|
|
8745ed9c61 | ||
|
|
836726e710 | ||
|
|
a67c118dcf | ||
|
|
cd289465fd | ||
|
|
263e426ae1 | ||
|
|
d5e493383a | ||
|
|
6f1d27354a | ||
|
|
26101732d2 | ||
|
|
71d40e0c08 | ||
|
|
4ba2ff7cdd | ||
|
|
2cdf5e7395 | ||
|
|
8315a55b3f | ||
|
|
d1c2a31af7 | ||
|
|
3e6c217408 | ||
|
|
b299f350be | ||
|
|
8fd16c17dc | ||
|
|
5979b2aa0f | ||
|
|
0b17e0e5d9 | ||
|
|
3d8ad5e4f6 | ||
|
|
ff1752dd39 | ||
|
|
1becaeb7be | ||
|
|
171afaadb9 | ||
|
|
776e6e647d | ||
|
|
4ccdf4ec72 | ||
|
|
a7bd993c0c | ||
|
|
a290ff4486 | ||
|
|
490ef13822 | ||
|
|
1b14de2ff9 | ||
|
|
914692cc82 | ||
|
|
07191dc430 | ||
|
|
af3fb2b04d | ||
|
|
0240fa131a | ||
|
|
e96577dd38 | ||
|
|
403dd7367a | ||
|
|
8086ad120b | ||
|
|
87a445689c | ||
|
|
b6bda54870 | ||
|
|
9d528dddd6 | ||
|
|
543d590710 | ||
|
|
f1d70eb6b2 | ||
|
|
d828c3f37e | ||
|
|
038491b7bc | ||
|
|
cf683411ee | ||
|
|
de5ed6a677 | ||
|
|
3dda557410 | ||
|
|
c800f6f723 | ||
|
|
0395ba1816 | ||
|
|
86f9f63b46 | ||
|
|
a7a6753118 | ||
|
|
2e80d12d6a | ||
|
|
417a96cbf2 | ||
|
|
2d4c29ea7c | ||
|
|
67db40ed4f | ||
|
|
11c485a5ed | ||
|
|
b0573af9a9 | ||
|
|
09eb53f308 | ||
|
|
11f85d1b80 | ||
|
|
0cb86c6990 | ||
|
|
57d2f22c24 | ||
|
|
fa0c364982 | ||
|
|
a6c8113419 | ||
|
|
4f5c30e083 | ||
|
|
9d0b51fa26 | ||
|
|
ba5f8045a2 | ||
|
|
3a510a9138 | ||
|
|
d3bfa16813 | ||
|
|
28409791fa | ||
|
|
c1abe87953 | ||
|
|
f8367856e8 | ||
|
|
a72b0a689b | ||
|
|
69a4d213a3 | ||
|
|
c28e01fed3 | ||
|
|
e8efcef108 | ||
|
|
d011316997 | ||
|
|
4d22b0c497 | ||
|
|
539215d7df | ||
|
|
3ede597a15 | ||
|
|
01786c5e63 | ||
|
|
6aba5f74fc | ||
|
|
3c894a3fb7 | ||
|
|
1ece3a498f | ||
|
|
b76c7ae55d | ||
|
|
91b10bd3b9 | ||
|
|
7e3fe77e7b | ||
|
|
ba43214dae | ||
|
|
ebc90720ea | ||
|
|
785d100be9 | ||
|
|
f13e6f1149 | ||
|
|
8be0f77d96 | ||
|
|
429f85a9de | ||
|
|
b4d1c6da2c | ||
|
|
3c1cfd4c1e | ||
|
|
a71a210704 | ||
|
|
769d06c8ab | ||
|
|
cd1f8da13f | ||
|
|
8230474667 | ||
|
|
27f553bf84 | ||
|
|
d48bff8c8b | ||
|
|
59b9687f31 | ||
|
|
c1a8ccda11 | ||
|
|
9df6786b09 | ||
|
|
bef5bd4e4f | ||
|
|
68acfb1891 | ||
|
|
9fd3f752d1 | ||
|
|
9c48e9ceab | ||
|
|
bd26783b33 | ||
|
|
eda8230521 | ||
|
|
462ddbb145 | ||
|
|
496a2f341e | ||
|
|
7109d6d635 | ||
|
|
ca72241fa3 | ||
|
|
a6bdffd225 | ||
|
|
5636bf4955 | ||
|
|
a944a7fd7e | ||
|
|
a40fa405e4 | ||
|
|
eab77e21dd | ||
|
|
d41163f5c1 | ||
|
|
265b1f2459 | ||
|
|
c92ea59228 | ||
|
|
afddfea093 | ||
|
|
fa4dc151ca | ||
|
|
44202acb18 | ||
|
|
cf00786209 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
|
||||
30
.github/workflows/release.yaml
vendored
Normal file
30
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
releases-matrix:
|
||||
name: Release goctl binary
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# build and publish in parallel: linux/386, linux/amd64, linux/arm64,
|
||||
# windows/386, windows/amd64, windows/arm64, darwin/amd64, darwin/arm64
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, arm64]
|
||||
exclude:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
- goarch: "386"
|
||||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: wangyoucao577/go-release-action@v1.22
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
goversion: "https://dl.google.com/go/go1.17.5.linux-amd64.tar.gz"
|
||||
project_path: "tools/goctl"
|
||||
binary_name: "goctl"
|
||||
extra_files: tools/goctl/goctl.md
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,6 +15,9 @@
|
||||
**/.DS_Store
|
||||
**/logs
|
||||
|
||||
# for test purpose
|
||||
adhoc
|
||||
|
||||
# gitlab ci
|
||||
.cache
|
||||
|
||||
|
||||
10
ROADMAP.md
10
ROADMAP.md
@@ -10,13 +10,17 @@ We hope that the items listed below will inspire further engagement from the com
|
||||
|
||||
## 2021 Q3
|
||||
- [x] Support `goctl model pg` to support PostgreSQL code generation
|
||||
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
|
||||
- [ ] Adapt builtin tracing mechanism to opentracing solutions
|
||||
- [x] Adapt builtin tracing mechanism to opentracing solutions
|
||||
|
||||
## 2021 Q4
|
||||
- [x] Support `username/password` authentication in ETCD
|
||||
- [x] Support `SSL/TLS` in `zRPC`
|
||||
- [x] Support `TLS` in redis connections
|
||||
|
||||
## 2022
|
||||
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
|
||||
- [ ] Add `httpx.Client` with governance, like circuit breaker etc.
|
||||
- [ ] Support `goctl doctor` command to report potential issues for given service
|
||||
- [ ] Support `context` in redis related methods for timeout and tracing
|
||||
- [ ] Support `context` in sql related methods for timeout and tracing
|
||||
- [ ] Support `context` in mongodb related methods for timeout and tracing
|
||||
- [ ] Support TLS in redis connections
|
||||
|
||||
7
core/discov/accountregistry.go
Normal file
7
core/discov/accountregistry.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package discov
|
||||
|
||||
import "github.com/tal-tech/go-zero/core/discov/internal"
|
||||
|
||||
func RegisterAccount(endpoints []string, user, pass string) {
|
||||
internal.AddAccount(endpoints, user, pass)
|
||||
}
|
||||
21
core/discov/accountregistry_test.go
Normal file
21
core/discov/accountregistry_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package discov
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/discov/internal"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
func TestRegisterAccount(t *testing.T) {
|
||||
endpoints := []string{
|
||||
"localhost:2379",
|
||||
}
|
||||
user := "foo" + stringx.Rand()
|
||||
RegisterAccount(endpoints, user, "bar")
|
||||
account, ok := internal.GetAccount(endpoints)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, user, account.User)
|
||||
assert.Equal(t, "bar", account.Pass)
|
||||
}
|
||||
@@ -6,6 +6,13 @@ import "errors"
|
||||
type EtcdConf struct {
|
||||
Hosts []string
|
||||
Key string
|
||||
User string `json:",optional"`
|
||||
Pass string `json:",optional"`
|
||||
}
|
||||
|
||||
// HasAccount returns if account provided.
|
||||
func (c EtcdConf) HasAccount() bool {
|
||||
return len(c.User) > 0 && len(c.Pass) > 0
|
||||
}
|
||||
|
||||
// Validate validates c.
|
||||
|
||||
@@ -44,3 +44,39 @@ func TestConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdConf_HasAccount(t *testing.T) {
|
||||
tests := []struct {
|
||||
EtcdConf
|
||||
hasAccount bool
|
||||
}{
|
||||
{
|
||||
EtcdConf: EtcdConf{
|
||||
Hosts: []string{"any"},
|
||||
Key: "key",
|
||||
},
|
||||
hasAccount: false,
|
||||
},
|
||||
{
|
||||
EtcdConf: EtcdConf{
|
||||
Hosts: []string{"any"},
|
||||
Key: "key",
|
||||
User: "foo",
|
||||
},
|
||||
hasAccount: false,
|
||||
},
|
||||
{
|
||||
EtcdConf: EtcdConf{
|
||||
Hosts: []string{"any"},
|
||||
Key: "key",
|
||||
User: "foo",
|
||||
Pass: "bar",
|
||||
},
|
||||
hasAccount: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.hasAccount, test.EtcdConf.HasAccount())
|
||||
}
|
||||
}
|
||||
|
||||
31
core/discov/internal/accountmanager.go
Normal file
31
core/discov/internal/accountmanager.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package internal
|
||||
|
||||
import "sync"
|
||||
|
||||
type Account struct {
|
||||
User string
|
||||
Pass string
|
||||
}
|
||||
|
||||
var (
|
||||
accounts = make(map[string]Account)
|
||||
lock sync.RWMutex
|
||||
)
|
||||
|
||||
func AddAccount(endpoints []string, user, pass string) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
accounts[getClusterKey(endpoints)] = Account{
|
||||
User: user,
|
||||
Pass: pass,
|
||||
}
|
||||
}
|
||||
|
||||
func GetAccount(endpoints []string) (Account, bool) {
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
|
||||
account, ok := accounts[getClusterKey(endpoints)]
|
||||
return account, ok
|
||||
}
|
||||
34
core/discov/internal/accountmanager_test.go
Normal file
34
core/discov/internal/accountmanager_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
func TestAccount(t *testing.T) {
|
||||
endpoints := []string{
|
||||
"192.168.0.2:2379",
|
||||
"192.168.0.3:2379",
|
||||
"192.168.0.4:2379",
|
||||
}
|
||||
username := "foo" + stringx.Rand()
|
||||
password := "bar"
|
||||
anotherPassword := "any"
|
||||
|
||||
_, ok := GetAccount(endpoints)
|
||||
assert.False(t, ok)
|
||||
|
||||
AddAccount(endpoints, username, password)
|
||||
account, ok := GetAccount(endpoints)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, username, account.User)
|
||||
assert.Equal(t, password, account.Pass)
|
||||
|
||||
AddAccount(endpoints, username, anotherPassword)
|
||||
account, ok = GetAccount(endpoints)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, username, account.User)
|
||||
assert.Equal(t, anotherPassword, account.Pass)
|
||||
}
|
||||
@@ -37,25 +37,35 @@ func GetRegistry() *Registry {
|
||||
|
||||
// GetConn returns an etcd client connection associated with given endpoints.
|
||||
func (r *Registry) GetConn(endpoints []string) (EtcdClient, error) {
|
||||
return r.getCluster(endpoints).getClient()
|
||||
c, _ := r.getCluster(endpoints)
|
||||
return c.getClient()
|
||||
}
|
||||
|
||||
// Monitor monitors the key on given etcd endpoints, notify with the given UpdateListener.
|
||||
func (r *Registry) Monitor(endpoints []string, key string, l UpdateListener) error {
|
||||
return r.getCluster(endpoints).monitor(key, l)
|
||||
c, exists := r.getCluster(endpoints)
|
||||
// if exists, the existing values should be updated to the listener.
|
||||
if exists {
|
||||
kvs := c.getCurrent(key)
|
||||
for _, kv := range kvs {
|
||||
l.OnAdd(kv)
|
||||
}
|
||||
}
|
||||
|
||||
return c.monitor(key, l)
|
||||
}
|
||||
|
||||
func (r *Registry) getCluster(endpoints []string) *cluster {
|
||||
func (r *Registry) getCluster(endpoints []string) (c *cluster, exists bool) {
|
||||
clusterKey := getClusterKey(endpoints)
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
c, ok := r.clusters[clusterKey]
|
||||
if !ok {
|
||||
c, exists = r.clusters[clusterKey]
|
||||
if !exists {
|
||||
c = newCluster(endpoints)
|
||||
r.clusters[clusterKey] = c
|
||||
}
|
||||
|
||||
return c
|
||||
return
|
||||
}
|
||||
|
||||
type cluster struct {
|
||||
@@ -94,6 +104,21 @@ func (c *cluster) getClient() (EtcdClient, error) {
|
||||
return val.(EtcdClient), nil
|
||||
}
|
||||
|
||||
func (c *cluster) getCurrent(key string) []KV {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var kvs []KV
|
||||
for k, v := range c.values[key] {
|
||||
kvs = append(kvs, KV{
|
||||
Key: k,
|
||||
Val: v,
|
||||
})
|
||||
}
|
||||
|
||||
return kvs
|
||||
}
|
||||
|
||||
func (c *cluster) handleChanges(key string, kvs []KV) {
|
||||
var add []KV
|
||||
var remove []KV
|
||||
@@ -197,14 +222,12 @@ func (c *cluster) load(cli EtcdClient, key string) {
|
||||
}
|
||||
|
||||
var kvs []KV
|
||||
c.lock.Lock()
|
||||
for _, ev := range resp.Kvs {
|
||||
kvs = append(kvs, KV{
|
||||
Key: string(ev.Key),
|
||||
Val: string(ev.Value),
|
||||
})
|
||||
}
|
||||
c.lock.Unlock()
|
||||
|
||||
c.handleChanges(key, kvs)
|
||||
}
|
||||
@@ -302,14 +325,20 @@ func (c *cluster) watchConnState(cli EtcdClient) {
|
||||
|
||||
// DialClient dials an etcd cluster with given endpoints.
|
||||
func DialClient(endpoints []string) (EtcdClient, error) {
|
||||
return clientv3.New(clientv3.Config{
|
||||
cfg := clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
AutoSyncInterval: autoSyncInterval,
|
||||
DialTimeout: DialTimeout,
|
||||
DialKeepAliveTime: dialKeepAliveTime,
|
||||
DialKeepAliveTimeout: DialTimeout,
|
||||
RejectOldCluster: true,
|
||||
})
|
||||
}
|
||||
if account, ok := GetAccount(endpoints); ok {
|
||||
cfg.Username = account.User
|
||||
cfg.Password = account.Pass
|
||||
}
|
||||
|
||||
return clientv3.New(cfg)
|
||||
}
|
||||
|
||||
func getClusterKey(endpoints []string) string {
|
||||
|
||||
@@ -33,9 +33,10 @@ func setMockClient(cli EtcdClient) func() {
|
||||
}
|
||||
|
||||
func TestGetCluster(t *testing.T) {
|
||||
c1 := GetRegistry().getCluster([]string{"first"})
|
||||
c2 := GetRegistry().getCluster([]string{"second"})
|
||||
c3 := GetRegistry().getCluster([]string{"first"})
|
||||
AddAccount([]string{"first"}, "foo", "bar")
|
||||
c1, _ := GetRegistry().getCluster([]string{"first"})
|
||||
c2, _ := GetRegistry().getCluster([]string{"second"})
|
||||
c3, _ := GetRegistry().getCluster([]string{"first"})
|
||||
assert.Equal(t, c1, c3)
|
||||
assert.NotEqual(t, c1, c2)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
// PublisherOption defines the method to customize a Publisher.
|
||||
PublisherOption func(client *Publisher)
|
||||
// PubOption defines the method to customize a Publisher.
|
||||
PubOption func(client *Publisher)
|
||||
|
||||
// A Publisher can be used to publish the value to an etcd cluster on the given key.
|
||||
Publisher struct {
|
||||
@@ -32,7 +32,7 @@ type (
|
||||
// endpoints is the hosts of the etcd cluster.
|
||||
// key:value are a pair to be published.
|
||||
// opts are used to customize the Publisher.
|
||||
func NewPublisher(endpoints []string, key, value string, opts ...PublisherOption) *Publisher {
|
||||
func NewPublisher(endpoints []string, key, value string, opts ...PubOption) *Publisher {
|
||||
publisher := &Publisher{
|
||||
endpoints: endpoints,
|
||||
key: key,
|
||||
@@ -145,8 +145,15 @@ func (p *Publisher) revoke(cli internal.EtcdClient) {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPubEtcdAccount provides the etcd username/password.
|
||||
func WithPubEtcdAccount(user, pass string) PubOption {
|
||||
return func(pub *Publisher) {
|
||||
internal.AddAccount(pub.endpoints, user, pass)
|
||||
}
|
||||
}
|
||||
|
||||
// WithId customizes a Publisher with the id.
|
||||
func WithId(id int64) PublisherOption {
|
||||
func WithId(id int64) PubOption {
|
||||
return func(publisher *Publisher) {
|
||||
publisher.id = id
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/discov/internal"
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,8 @@ func TestPublisher_register(t *testing.T) {
|
||||
ID: id,
|
||||
}, nil)
|
||||
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", id), "thevalue", gomock.Any())
|
||||
pub := NewPublisher(nil, "thekey", "thevalue")
|
||||
pub := NewPublisher(nil, "thekey", "thevalue",
|
||||
WithPubEtcdAccount(stringx.Rand(), "bar"))
|
||||
_, err := pub.register(cli)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
@@ -9,16 +9,14 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
subOptions struct {
|
||||
exclusive bool
|
||||
}
|
||||
|
||||
// SubOption defines the method to customize a Subscriber.
|
||||
SubOption func(opts *subOptions)
|
||||
SubOption func(sub *Subscriber)
|
||||
|
||||
// A Subscriber is used to subscribe the given key on a etcd cluster.
|
||||
Subscriber struct {
|
||||
items *container
|
||||
endpoints []string
|
||||
exclusive bool
|
||||
items *container
|
||||
}
|
||||
)
|
||||
|
||||
@@ -27,14 +25,14 @@ type (
|
||||
// key is the key to subscribe.
|
||||
// opts are used to customize the Subscriber.
|
||||
func NewSubscriber(endpoints []string, key string, opts ...SubOption) (*Subscriber, error) {
|
||||
var subOpts subOptions
|
||||
for _, opt := range opts {
|
||||
opt(&subOpts)
|
||||
}
|
||||
|
||||
sub := &Subscriber{
|
||||
items: newContainer(subOpts.exclusive),
|
||||
endpoints: endpoints,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(sub)
|
||||
}
|
||||
sub.items = newContainer(sub.exclusive)
|
||||
|
||||
if err := internal.GetRegistry().Monitor(endpoints, key, sub.items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -55,8 +53,14 @@ func (s *Subscriber) Values() []string {
|
||||
// Exclusive means that key value can only be 1:1,
|
||||
// which means later added value will remove the keys associated with the same value previously.
|
||||
func Exclusive() SubOption {
|
||||
return func(opts *subOptions) {
|
||||
opts.exclusive = true
|
||||
return func(sub *Subscriber) {
|
||||
sub.exclusive = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithSubEtcdAccount(user, pass string) SubOption {
|
||||
return func(sub *Subscriber) {
|
||||
internal.AddAccount(sub.endpoints, user, pass)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/discov/internal"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -201,11 +202,9 @@ func TestContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubscriber(t *testing.T) {
|
||||
var opt subOptions
|
||||
Exclusive()(&opt)
|
||||
|
||||
sub := new(Subscriber)
|
||||
sub.items = newContainer(opt.exclusive)
|
||||
Exclusive()(sub)
|
||||
sub.items = newContainer(sub.exclusive)
|
||||
var count int32
|
||||
sub.AddListener(func() {
|
||||
atomic.AddInt32(&count, 1)
|
||||
@@ -214,3 +213,15 @@ func TestSubscriber(t *testing.T) {
|
||||
assert.Empty(t, sub.Values())
|
||||
assert.Equal(t, int32(1), atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
func TestWithSubEtcdAccount(t *testing.T) {
|
||||
endpoints := []string{"localhost:2379"}
|
||||
user := stringx.Rand()
|
||||
WithSubEtcdAccount(user, "bar")(&Subscriber{
|
||||
endpoints: endpoints,
|
||||
})
|
||||
account, ok := internal.GetAccount(endpoints)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, user, account.User)
|
||||
assert.Equal(t, "bar", account.Pass)
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ func (s Stream) Head(n int64) Stream {
|
||||
}
|
||||
if n == 0 {
|
||||
// let successive method go ASAP even we have more items to skip
|
||||
// why we don't just break the loop, because if break,
|
||||
// why we don't just break the loop, because if breaks,
|
||||
// this former goroutine will block forever, which will cause goroutine leak.
|
||||
close(source)
|
||||
}
|
||||
|
||||
@@ -74,12 +74,12 @@ func TestConsistentHashIncrementalTransfer(t *testing.T) {
|
||||
laterCh := create()
|
||||
laterCh.AddWithWeight(node, 10*(i+1))
|
||||
|
||||
for i := 0; i < requestSize; i++ {
|
||||
key, ok := laterCh.Get(requestSize + i)
|
||||
for j := 0; j < requestSize; j++ {
|
||||
key, ok := laterCh.Get(requestSize + j)
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, key)
|
||||
value := key.(string)
|
||||
assert.True(t, value == keys[i] || value == node)
|
||||
assert.True(t, value == keys[j] || value == node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -106,3 +107,20 @@ func TestMilliTime_UnmarshalJSON(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalWithError(t *testing.T) {
|
||||
var mt MilliTime
|
||||
assert.NotNil(t, mt.UnmarshalJSON([]byte("hello")))
|
||||
}
|
||||
|
||||
func TestSetBSON(t *testing.T) {
|
||||
data, err := bson.Marshal(time.Now())
|
||||
assert.Nil(t, err)
|
||||
|
||||
var raw bson.Raw
|
||||
assert.Nil(t, bson.Unmarshal(data, &raw))
|
||||
|
||||
var mt MilliTime
|
||||
assert.Nil(t, mt.SetBSON(raw))
|
||||
assert.NotNil(t, mt.SetBSON(bson.Raw{}))
|
||||
}
|
||||
|
||||
@@ -79,8 +79,10 @@ func (l *durationLogger) WithDuration(duration time.Duration) Logger {
|
||||
}
|
||||
|
||||
func (l *durationLogger) write(writer io.Writer, level string, val interface{}) {
|
||||
l.Timestamp = getTimestamp()
|
||||
l.Level = level
|
||||
l.Content = val
|
||||
outputJson(writer, l)
|
||||
outputJson(writer, &durationLogger{
|
||||
Timestamp: getTimestamp(),
|
||||
Level: level,
|
||||
Content: val,
|
||||
Duration: l.Duration,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,13 @@ func TestWithDurationErrorf(t *testing.T) {
|
||||
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
|
||||
}
|
||||
|
||||
func TestWithDurationErrorv(t *testing.T) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
WithDuration(time.Second).Errorv("foo")
|
||||
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
|
||||
}
|
||||
|
||||
func TestWithDurationInfo(t *testing.T) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
@@ -37,6 +44,13 @@ func TestWithDurationInfof(t *testing.T) {
|
||||
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
|
||||
}
|
||||
|
||||
func TestWithDurationInfov(t *testing.T) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
WithDuration(time.Second).Infov("foo")
|
||||
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
|
||||
}
|
||||
|
||||
func TestWithDurationSlow(t *testing.T) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
@@ -50,3 +64,10 @@ func TestWithDurationSlowf(t *testing.T) {
|
||||
WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo")
|
||||
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
|
||||
}
|
||||
|
||||
func TestWithDurationSlowv(t *testing.T) {
|
||||
var builder strings.Builder
|
||||
log.SetOutput(&builder)
|
||||
WithDuration(time.Second).WithDuration(time.Hour).Slowv("foo")
|
||||
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
|
||||
}
|
||||
|
||||
62
core/logx/limitedexecutor_test.go
Normal file
62
core/logx/limitedexecutor_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/timex"
|
||||
)
|
||||
|
||||
func TestLimitedExecutor_logOrDiscard(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
threshold time.Duration
|
||||
lastTime time.Duration
|
||||
discarded uint32
|
||||
executed bool
|
||||
}{
|
||||
{
|
||||
name: "nil executor",
|
||||
executed: true,
|
||||
},
|
||||
{
|
||||
name: "regular",
|
||||
threshold: time.Hour,
|
||||
lastTime: timex.Now(),
|
||||
discarded: 10,
|
||||
executed: false,
|
||||
},
|
||||
{
|
||||
name: "slow",
|
||||
threshold: time.Duration(1),
|
||||
lastTime: -1000,
|
||||
discarded: 10,
|
||||
executed: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
executor := newLimitedExecutor(0)
|
||||
executor.threshold = test.threshold
|
||||
executor.discarded = test.discarded
|
||||
executor.lastTime.Set(test.lastTime)
|
||||
|
||||
var run int32
|
||||
executor.logOrDiscard(func() {
|
||||
atomic.AddInt32(&run, 1)
|
||||
})
|
||||
if test.executed {
|
||||
assert.Equal(t, int32(1), atomic.LoadInt32(&run))
|
||||
} else {
|
||||
assert.Equal(t, int32(0), atomic.LoadInt32(&run))
|
||||
assert.Equal(t, test.discarded+1, atomic.LoadUint32(&executor.discarded))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func ErrorCaller(callDepth int, v ...interface{}) {
|
||||
|
||||
// ErrorCallerf writes v with context in format into error log.
|
||||
func ErrorCallerf(callDepth int, format string, v ...interface{}) {
|
||||
errorTextSync(fmt.Sprintf(format, v...), callDepth+callerInnerDepth)
|
||||
errorTextSync(fmt.Errorf(format, v...).Error(), callDepth+callerInnerDepth)
|
||||
}
|
||||
|
||||
// Errorf writes v with format into error log.
|
||||
|
||||
@@ -2,6 +2,7 @@ package logx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -242,6 +243,16 @@ func TestSetLevelWithDuration(t *testing.T) {
|
||||
assert.Equal(t, 0, writer.builder.Len())
|
||||
}
|
||||
|
||||
func TestErrorfWithWrappedError(t *testing.T) {
|
||||
SetLevel(ErrorLevel)
|
||||
const message = "there"
|
||||
writer := new(mockWriter)
|
||||
errorLog = writer
|
||||
atomic.StoreUint32(&initialized, 1)
|
||||
Errorf("hello %w", errors.New(message))
|
||||
assert.True(t, strings.Contains(writer.builder.String(), "hello there"))
|
||||
}
|
||||
|
||||
func TestMustNil(t *testing.T) {
|
||||
Must(nil)
|
||||
}
|
||||
|
||||
@@ -77,12 +77,16 @@ func (l *traceLogger) WithDuration(duration time.Duration) Logger {
|
||||
}
|
||||
|
||||
func (l *traceLogger) write(writer io.Writer, level string, val interface{}) {
|
||||
l.Timestamp = getTimestamp()
|
||||
l.Level = level
|
||||
l.Content = val
|
||||
l.Trace = traceIdFromContext(l.ctx)
|
||||
l.Span = spanIdFromContext(l.ctx)
|
||||
outputJson(writer, l)
|
||||
outputJson(writer, &traceLogger{
|
||||
logEntry: logEntry{
|
||||
Timestamp: getTimestamp(),
|
||||
Level: level,
|
||||
Duration: l.Duration,
|
||||
Content: val,
|
||||
},
|
||||
Trace: traceIdFromContext(l.ctx),
|
||||
Span: spanIdFromContext(l.ctx),
|
||||
})
|
||||
}
|
||||
|
||||
// WithContext sets ctx to log, for keeping tracing information.
|
||||
|
||||
@@ -51,6 +51,10 @@ func TestTraceError(t *testing.T) {
|
||||
l.WithDuration(time.Second).Errorf(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
l.WithDuration(time.Second).Errorv(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
func TestTraceInfo(t *testing.T) {
|
||||
@@ -72,6 +76,10 @@ func TestTraceInfo(t *testing.T) {
|
||||
l.WithDuration(time.Second).Infof(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
l.WithDuration(time.Second).Infov(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
func TestTraceSlow(t *testing.T) {
|
||||
@@ -93,6 +101,10 @@ func TestTraceSlow(t *testing.T) {
|
||||
l.WithDuration(time.Second).Slowf(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
buf.Reset()
|
||||
l.WithDuration(time.Second).Slowv(testlog)
|
||||
assert.True(t, strings.Contains(buf.String(), traceKey))
|
||||
assert.True(t, strings.Contains(buf.String(), spanKey))
|
||||
}
|
||||
|
||||
func TestTraceWithoutContext(t *testing.T) {
|
||||
|
||||
@@ -15,6 +15,11 @@ func UnmarshalJsonBytes(content []byte, v interface{}) error {
|
||||
return unmarshalJsonBytes(content, v, jsonUnmarshaler)
|
||||
}
|
||||
|
||||
// UnmarshalJsonMap unmarshals content from m into v.
|
||||
func UnmarshalJsonMap(m map[string]interface{}, v interface{}) error {
|
||||
return jsonUnmarshaler.Unmarshal(m, v)
|
||||
}
|
||||
|
||||
// UnmarshalJsonReader unmarshals content from reader into v.
|
||||
func UnmarshalJsonReader(reader io.Reader, v interface{}) error {
|
||||
return unmarshalJsonReader(reader, v, jsonUnmarshaler)
|
||||
|
||||
@@ -871,3 +871,50 @@ func TestUnmarshalReaderError(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), payload))
|
||||
}
|
||||
|
||||
func TestUnmarshalMap(t *testing.T) {
|
||||
t.Run("nil map and valid", func(t *testing.T) {
|
||||
var m map[string]interface{}
|
||||
var v struct {
|
||||
Any string `json:",optional"`
|
||||
}
|
||||
|
||||
err := UnmarshalJsonMap(m, &v)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(v.Any) == 0)
|
||||
})
|
||||
|
||||
t.Run("empty map but not valid", func(t *testing.T) {
|
||||
m := map[string]interface{}{}
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalJsonMap(m, &v)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
t.Run("empty map and valid", func(t *testing.T) {
|
||||
m := map[string]interface{}{}
|
||||
var v struct {
|
||||
Any string `json:",optional"`
|
||||
}
|
||||
|
||||
err := UnmarshalJsonMap(m, &v)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(v.Any) == 0)
|
||||
})
|
||||
|
||||
t.Run("valid map", func(t *testing.T) {
|
||||
m := map[string]interface{}{
|
||||
"Any": "foo",
|
||||
}
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalJsonMap(m, &v)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "foo", v.Any)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/jsonx"
|
||||
@@ -25,15 +24,17 @@ var (
|
||||
errValueNotSettable = errors.New("value is not settable")
|
||||
errValueNotStruct = errors.New("value type is not struct")
|
||||
keyUnmarshaler = NewUnmarshaler(defaultKeyName)
|
||||
cacheKeys atomic.Value
|
||||
cacheKeysLock sync.Mutex
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
cacheKeys map[string][]string
|
||||
cacheKeysLock sync.Mutex
|
||||
defaultCache map[string]interface{}
|
||||
defaultCacheLock sync.Mutex
|
||||
emptyMap = map[string]interface{}{}
|
||||
emptyValue = reflect.ValueOf(lang.Placeholder)
|
||||
)
|
||||
|
||||
type (
|
||||
// A Unmarshaler is used to unmarshal with given tag key.
|
||||
// Unmarshaler is used to unmarshal with given tag key.
|
||||
Unmarshaler struct {
|
||||
key string
|
||||
opts unmarshalOptions
|
||||
@@ -46,12 +47,11 @@ type (
|
||||
fromString bool
|
||||
canonicalKey func(key string) string
|
||||
}
|
||||
|
||||
keyCache map[string][]string
|
||||
)
|
||||
|
||||
func init() {
|
||||
cacheKeys.Store(make(keyCache))
|
||||
cacheKeys = make(map[string][]string)
|
||||
defaultCache = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// NewUnmarshaler returns a Unmarshaler.
|
||||
@@ -207,6 +207,8 @@ func (u *Unmarshaler) processFieldNotFromString(field reflect.StructField, value
|
||||
switch {
|
||||
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
||||
return u.processFieldStruct(field, value, mapValue, fullName)
|
||||
case valueKind == reflect.Map && typeKind == reflect.Map:
|
||||
return u.fillMap(field, value, mapValue)
|
||||
case valueKind == reflect.String && typeKind == reflect.Slice:
|
||||
return u.fillSliceFromString(fieldType, value, mapValue)
|
||||
case valueKind == reflect.String && derefedFieldType == durationType:
|
||||
@@ -386,7 +388,13 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(field reflect.StructField, v
|
||||
if derefedType == durationType {
|
||||
return fillDurationValue(fieldKind, value, defaultValue)
|
||||
}
|
||||
return setValue(fieldKind, value, defaultValue)
|
||||
|
||||
switch fieldKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return u.fillSliceWithDefault(derefedType, value, defaultValue)
|
||||
default:
|
||||
return setValue(fieldKind, value, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
switch fieldKind {
|
||||
@@ -500,7 +508,8 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, baseKind reflect.Kind, value interface{}) error {
|
||||
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
||||
baseKind reflect.Kind, value interface{}) error {
|
||||
ithVal := slice.Index(index)
|
||||
switch v := value.(type) {
|
||||
case json.Number:
|
||||
@@ -529,6 +538,28 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, baseKind re
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value reflect.Value,
|
||||
defaultValue string) error {
|
||||
baseFieldType := Deref(derefedType.Elem())
|
||||
baseFieldKind := baseFieldType.Kind()
|
||||
defaultCacheLock.Lock()
|
||||
slice, ok := defaultCache[defaultValue]
|
||||
defaultCacheLock.Unlock()
|
||||
if !ok {
|
||||
if baseFieldKind == reflect.String {
|
||||
slice = parseGroupedSegments(defaultValue)
|
||||
} else if err := jsonx.UnmarshalFromString(defaultValue, &slice); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultCacheLock.Lock()
|
||||
defaultCache[defaultValue] = slice
|
||||
defaultCacheLock.Unlock()
|
||||
}
|
||||
|
||||
return u.fillSlice(derefedType, value, slice)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue interface{}) (reflect.Value, error) {
|
||||
mapType := reflect.MapOf(keyType, elemType)
|
||||
valueType := reflect.TypeOf(mapValue)
|
||||
@@ -584,6 +615,8 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue inter
|
||||
targetValue.SetMapIndex(key, innerValue)
|
||||
default:
|
||||
switch v := keythData.(type) {
|
||||
case bool:
|
||||
targetValue.SetMapIndex(key, reflect.ValueOf(v))
|
||||
case string:
|
||||
targetValue.SetMapIndex(key, reflect.ValueOf(v))
|
||||
case json.Number:
|
||||
@@ -720,20 +753,6 @@ func getValueWithChainedKeys(m Valuer, keys []string) (interface{}, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func insertKeys(key string, cache []string) {
|
||||
cacheKeysLock.Lock()
|
||||
defer cacheKeysLock.Unlock()
|
||||
|
||||
keys := cacheKeys.Load().(keyCache)
|
||||
// copy the contents into the new map, to guarantee the old map is immutable
|
||||
newKeys := make(keyCache)
|
||||
for k, v := range keys {
|
||||
newKeys[k] = v
|
||||
}
|
||||
newKeys[key] = cache
|
||||
cacheKeys.Store(newKeys)
|
||||
}
|
||||
|
||||
func join(elem ...string) string {
|
||||
var builder strings.Builder
|
||||
|
||||
@@ -764,15 +783,19 @@ func newTypeMismatchError(name string) error {
|
||||
}
|
||||
|
||||
func readKeys(key string) []string {
|
||||
cache := cacheKeys.Load().(keyCache)
|
||||
if keys, ok := cache[key]; ok {
|
||||
cacheKeysLock.Lock()
|
||||
keys, ok := cacheKeys[key]
|
||||
cacheKeysLock.Unlock()
|
||||
if ok {
|
||||
return keys
|
||||
}
|
||||
|
||||
keys := strings.FieldsFunc(key, func(c rune) bool {
|
||||
keys = strings.FieldsFunc(key, func(c rune) bool {
|
||||
return c == delimiter
|
||||
})
|
||||
insertKeys(key, keys)
|
||||
cacheKeysLock.Lock()
|
||||
cacheKeys[key] = keys
|
||||
cacheKeysLock.Unlock()
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
@@ -198,6 +198,66 @@ func TestUnmarshalIntWithDefault(t *testing.T) {
|
||||
assert.Equal(t, 1, in.Int)
|
||||
}
|
||||
|
||||
func TestUnmarshalBoolSliceWithDefault(t *testing.T) {
|
||||
type inner struct {
|
||||
Bools []bool `key:"bools,default=[true,false]"`
|
||||
}
|
||||
|
||||
var in inner
|
||||
assert.Nil(t, UnmarshalKey(nil, &in))
|
||||
assert.ElementsMatch(t, []bool{true, false}, in.Bools)
|
||||
}
|
||||
|
||||
func TestUnmarshalIntSliceWithDefault(t *testing.T) {
|
||||
type inner struct {
|
||||
Ints []int `key:"ints,default=[1,2,3]"`
|
||||
}
|
||||
|
||||
var in inner
|
||||
assert.Nil(t, UnmarshalKey(nil, &in))
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, in.Ints)
|
||||
}
|
||||
|
||||
func TestUnmarshalIntSliceWithDefaultHasSpaces(t *testing.T) {
|
||||
type inner struct {
|
||||
Ints []int `key:"ints,default=[1, 2, 3]"`
|
||||
}
|
||||
|
||||
var in inner
|
||||
assert.Nil(t, UnmarshalKey(nil, &in))
|
||||
assert.ElementsMatch(t, []int{1, 2, 3}, in.Ints)
|
||||
}
|
||||
|
||||
func TestUnmarshalFloatSliceWithDefault(t *testing.T) {
|
||||
type inner struct {
|
||||
Floats []float32 `key:"floats,default=[1.1,2.2,3.3]"`
|
||||
}
|
||||
|
||||
var in inner
|
||||
assert.Nil(t, UnmarshalKey(nil, &in))
|
||||
assert.ElementsMatch(t, []float32{1.1, 2.2, 3.3}, in.Floats)
|
||||
}
|
||||
|
||||
func TestUnmarshalStringSliceWithDefault(t *testing.T) {
|
||||
type inner struct {
|
||||
Strs []string `key:"strs,default=[foo,bar,woo]"`
|
||||
}
|
||||
|
||||
var in inner
|
||||
assert.Nil(t, UnmarshalKey(nil, &in))
|
||||
assert.ElementsMatch(t, []string{"foo", "bar", "woo"}, in.Strs)
|
||||
}
|
||||
|
||||
func TestUnmarshalStringSliceWithDefaultHasSpaces(t *testing.T) {
|
||||
type inner struct {
|
||||
Strs []string `key:"strs,default=[foo, bar, woo]"`
|
||||
}
|
||||
|
||||
var in inner
|
||||
assert.Nil(t, UnmarshalKey(nil, &in))
|
||||
assert.ElementsMatch(t, []string{"foo", "bar", "woo"}, in.Strs)
|
||||
}
|
||||
|
||||
func TestUnmarshalUint(t *testing.T) {
|
||||
type inner struct {
|
||||
Uint uint `key:"uint"`
|
||||
@@ -861,10 +921,12 @@ func TestUnmarshalSliceOfStruct(t *testing.T) {
|
||||
func TestUnmarshalWithStringOptionsCorrect(t *testing.T) {
|
||||
type inner struct {
|
||||
Value string `key:"value,options=first|second"`
|
||||
Foo string `key:"foo,options=[bar,baz]"`
|
||||
Correct string `key:"correct,options=1|2"`
|
||||
}
|
||||
m := map[string]interface{}{
|
||||
"value": "first",
|
||||
"foo": "bar",
|
||||
"correct": "2",
|
||||
}
|
||||
|
||||
@@ -872,6 +934,7 @@ func TestUnmarshalWithStringOptionsCorrect(t *testing.T) {
|
||||
ast := assert.New(t)
|
||||
ast.Nil(UnmarshalKey(m, &in))
|
||||
ast.Equal("first", in.Value)
|
||||
ast.Equal("bar", in.Foo)
|
||||
ast.Equal("2", in.Correct)
|
||||
}
|
||||
|
||||
@@ -943,6 +1006,22 @@ func TestUnmarshalStringOptionsWithStringOptionsIncorrect(t *testing.T) {
|
||||
ast.NotNil(unmarshaler.Unmarshal(m, &in))
|
||||
}
|
||||
|
||||
func TestUnmarshalStringOptionsWithStringOptionsIncorrectGrouped(t *testing.T) {
|
||||
type inner struct {
|
||||
Value string `key:"value,options=[first,second]"`
|
||||
Correct string `key:"correct,options=1|2"`
|
||||
}
|
||||
m := map[string]interface{}{
|
||||
"value": "third",
|
||||
"correct": "2",
|
||||
}
|
||||
|
||||
var in inner
|
||||
unmarshaler := NewUnmarshaler(defaultKeyName, WithStringValues())
|
||||
ast := assert.New(t)
|
||||
ast.NotNil(unmarshaler.Unmarshal(m, &in))
|
||||
}
|
||||
|
||||
func TestUnmarshalWithStringOptionsIncorrect(t *testing.T) {
|
||||
type inner struct {
|
||||
Value string `key:"value,options=first|second"`
|
||||
@@ -2518,3 +2597,29 @@ func TestUnmarshalJsonReaderPtrArray(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(res.B))
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonWithoutKey(t *testing.T) {
|
||||
payload := `{"A": "1", "B": "2"}`
|
||||
var res struct {
|
||||
A string `json:""`
|
||||
B string `json:","`
|
||||
}
|
||||
reader := strings.NewReader(payload)
|
||||
err := UnmarshalJsonReader(reader, &res)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "1", res.A)
|
||||
assert.Equal(t, "2", res.B)
|
||||
}
|
||||
|
||||
func BenchmarkDefaultValue(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var a struct {
|
||||
Ints []int `json:"ints,default=[1,2,3]"`
|
||||
Strs []string `json:"strs,default=[foo,bar,baz]"`
|
||||
}
|
||||
_ = UnmarshalJsonMap(nil, &a)
|
||||
if len(a.Strs) != 3 || len(a.Ints) != 3 {
|
||||
b.Fatal("failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,19 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultOption = "default"
|
||||
stringOption = "string"
|
||||
optionalOption = "optional"
|
||||
optionsOption = "options"
|
||||
rangeOption = "range"
|
||||
optionSeparator = "|"
|
||||
equalToken = "="
|
||||
defaultOption = "default"
|
||||
stringOption = "string"
|
||||
optionalOption = "optional"
|
||||
optionsOption = "options"
|
||||
rangeOption = "range"
|
||||
optionSeparator = "|"
|
||||
equalToken = "="
|
||||
escapeChar = '\\'
|
||||
leftBracket = '('
|
||||
rightBracket = ')'
|
||||
leftSquareBracket = '['
|
||||
rightSquareBracket = ']'
|
||||
segmentSeparator = ','
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -118,7 +124,7 @@ func convertType(kind reflect.Kind, str string) (interface{}, error) {
|
||||
}
|
||||
|
||||
func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fieldOptions, error) {
|
||||
segments := strings.Split(value, ",")
|
||||
segments := parseSegments(value)
|
||||
key := strings.TrimSpace(segments[0])
|
||||
options := segments[1:]
|
||||
|
||||
@@ -198,6 +204,16 @@ func maybeNewValue(field reflect.StructField, value reflect.Value) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseGroupedSegments(val string) []string {
|
||||
val = strings.TrimLeftFunc(val, func(r rune) bool {
|
||||
return r == leftBracket || r == leftSquareBracket
|
||||
})
|
||||
val = strings.TrimRightFunc(val, func(r rune) bool {
|
||||
return r == rightBracket || r == rightSquareBracket
|
||||
})
|
||||
return parseSegments(val)
|
||||
}
|
||||
|
||||
// don't modify returned fieldOptions, it's cached and shared among different calls.
|
||||
func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fieldOptions, error) {
|
||||
value := field.Tag.Get(tagName)
|
||||
@@ -309,7 +325,7 @@ func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||
return fmt.Errorf("field %s has wrong options", fieldName)
|
||||
}
|
||||
|
||||
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
|
||||
fieldOpts.Options = parseOptions(segs[1])
|
||||
case strings.HasPrefix(option, defaultOption):
|
||||
segs := strings.Split(option, equalToken)
|
||||
if len(segs) != 2 {
|
||||
@@ -334,6 +350,69 @@ func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseOptions parses the given options in tag.
|
||||
// for example: `json:"name,options=foo|bar"` or `json:"name,options=[foo,bar]"`
|
||||
func parseOptions(val string) []string {
|
||||
if len(val) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if val[0] == leftSquareBracket {
|
||||
return parseGroupedSegments(val)
|
||||
}
|
||||
|
||||
return strings.Split(val, optionSeparator)
|
||||
}
|
||||
|
||||
func parseSegments(val string) []string {
|
||||
var segments []string
|
||||
var escaped, grouped bool
|
||||
var buf strings.Builder
|
||||
|
||||
for _, ch := range val {
|
||||
if escaped {
|
||||
buf.WriteRune(ch)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
switch ch {
|
||||
case segmentSeparator:
|
||||
if grouped {
|
||||
buf.WriteRune(ch)
|
||||
} else {
|
||||
// need to trim spaces, but we cannot ignore empty string,
|
||||
// because the first segment stands for the key might be empty.
|
||||
// if ignored, the later tag will be used as the key.
|
||||
segments = append(segments, strings.TrimSpace(buf.String()))
|
||||
buf.Reset()
|
||||
}
|
||||
case escapeChar:
|
||||
if grouped {
|
||||
buf.WriteRune(ch)
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
case leftBracket, leftSquareBracket:
|
||||
buf.WriteRune(ch)
|
||||
grouped = true
|
||||
case rightBracket, rightSquareBracket:
|
||||
buf.WriteRune(ch)
|
||||
grouped = false
|
||||
default:
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
last := strings.TrimSpace(buf.String())
|
||||
// ignore last empty string
|
||||
if len(last) > 0 {
|
||||
segments = append(segments, last)
|
||||
}
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
func reprOfValue(val reflect.Value) string {
|
||||
switch vt := val.Interface().(type) {
|
||||
case bool:
|
||||
|
||||
@@ -90,6 +90,82 @@ func TestParseKeyAndOptionWithTagAndOption(t *testing.T) {
|
||||
assert.True(t, options.FromString)
|
||||
}
|
||||
|
||||
func TestParseSegments(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
expect: []string{},
|
||||
},
|
||||
{
|
||||
input: ",",
|
||||
expect: []string{""},
|
||||
},
|
||||
{
|
||||
input: "foo,",
|
||||
expect: []string{"foo"},
|
||||
},
|
||||
{
|
||||
input: ",foo",
|
||||
// the first empty string cannot be ignored, it's the key.
|
||||
expect: []string{"", "foo"},
|
||||
},
|
||||
{
|
||||
input: "foo",
|
||||
expect: []string{"foo"},
|
||||
},
|
||||
{
|
||||
input: "foo,bar",
|
||||
expect: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
input: "foo,bar,baz",
|
||||
expect: []string{"foo", "bar", "baz"},
|
||||
},
|
||||
{
|
||||
input: "foo,options=a|b",
|
||||
expect: []string{"foo", "options=a|b"},
|
||||
},
|
||||
{
|
||||
input: "foo,bar,default=[baz,qux]",
|
||||
expect: []string{"foo", "bar", "default=[baz,qux]"},
|
||||
},
|
||||
{
|
||||
input: "foo,bar,options=[baz,qux]",
|
||||
expect: []string{"foo", "bar", "options=[baz,qux]"},
|
||||
},
|
||||
{
|
||||
input: `foo\,bar,options=[baz,qux]`,
|
||||
expect: []string{`foo,bar`, "options=[baz,qux]"},
|
||||
},
|
||||
{
|
||||
input: `foo,bar,options=\[baz,qux]`,
|
||||
expect: []string{"foo", "bar", "options=[baz", "qux]"},
|
||||
},
|
||||
{
|
||||
input: `foo,bar,options=[baz\,qux]`,
|
||||
expect: []string{"foo", "bar", `options=[baz\,qux]`},
|
||||
},
|
||||
{
|
||||
input: `foo\,bar,options=[baz,qux],default=baz`,
|
||||
expect: []string{`foo,bar`, "options=[baz,qux]", "default=baz"},
|
||||
},
|
||||
{
|
||||
input: `foo\,bar,options=[baz,qux, quux],default=[qux, baz]`,
|
||||
expect: []string{`foo,bar`, "options=[baz,qux, quux]", "default=[qux, baz]"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
assert.ElementsMatch(t, test.expect, parseSegments(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePtrWithNonPtr(t *testing.T) {
|
||||
var foo string
|
||||
rve := reflect.ValueOf(foo)
|
||||
@@ -209,6 +285,12 @@ func TestRepr(t *testing.T) {
|
||||
newMockPtr(),
|
||||
"mockptr",
|
||||
},
|
||||
{
|
||||
&mockOpacity{
|
||||
val: 1,
|
||||
},
|
||||
"{1}",
|
||||
},
|
||||
{
|
||||
true,
|
||||
"true",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/io"
|
||||
)
|
||||
|
||||
func TestUnmarshalYamlBytes(t *testing.T) {
|
||||
@@ -18,6 +19,22 @@ func TestUnmarshalYamlBytes(t *testing.T) {
|
||||
assert.Equal(t, "liao", c.Name)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesErrorInput(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
}
|
||||
content := []byte(`liao`)
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesEmptyInput(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
}
|
||||
content := []byte(``)
|
||||
assert.NotNil(t, UnmarshalYamlBytes(content, &c))
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBytesOptional(t *testing.T) {
|
||||
var c struct {
|
||||
Name string
|
||||
@@ -918,3 +935,82 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
|
||||
err := UnmarshalYamlReader(reader, &v)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlBadReader(t *testing.T) {
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalYamlReader(new(badReader), &v)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlMapBool(t *testing.T) {
|
||||
text := `machine:
|
||||
node1: true
|
||||
node2: true
|
||||
node3: true
|
||||
`
|
||||
var v struct {
|
||||
Machine map[string]bool `json:"machine,optional"`
|
||||
}
|
||||
reader := strings.NewReader(text)
|
||||
assert.Nil(t, UnmarshalYamlReader(reader, &v))
|
||||
assert.True(t, v.Machine["node1"])
|
||||
assert.True(t, v.Machine["node2"])
|
||||
assert.True(t, v.Machine["node3"])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlMapInt(t *testing.T) {
|
||||
text := `machine:
|
||||
node1: 1
|
||||
node2: 2
|
||||
node3: 3
|
||||
`
|
||||
var v struct {
|
||||
Machine map[string]int `json:"machine,optional"`
|
||||
}
|
||||
reader := strings.NewReader(text)
|
||||
assert.Nil(t, UnmarshalYamlReader(reader, &v))
|
||||
assert.Equal(t, 1, v.Machine["node1"])
|
||||
assert.Equal(t, 2, v.Machine["node2"])
|
||||
assert.Equal(t, 3, v.Machine["node3"])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlMapByte(t *testing.T) {
|
||||
text := `machine:
|
||||
node1: 1
|
||||
node2: 2
|
||||
node3: 3
|
||||
`
|
||||
var v struct {
|
||||
Machine map[string]byte `json:"machine,optional"`
|
||||
}
|
||||
reader := strings.NewReader(text)
|
||||
assert.Nil(t, UnmarshalYamlReader(reader, &v))
|
||||
assert.Equal(t, byte(1), v.Machine["node1"])
|
||||
assert.Equal(t, byte(2), v.Machine["node2"])
|
||||
assert.Equal(t, byte(3), v.Machine["node3"])
|
||||
}
|
||||
|
||||
func TestUnmarshalYamlMapRune(t *testing.T) {
|
||||
text := `machine:
|
||||
node1: 1
|
||||
node2: 2
|
||||
node3: 3
|
||||
`
|
||||
var v struct {
|
||||
Machine map[string]rune `json:"machine,optional"`
|
||||
}
|
||||
reader := strings.NewReader(text)
|
||||
assert.Nil(t, UnmarshalYamlReader(reader, &v))
|
||||
assert.Equal(t, rune(1), v.Machine["node1"])
|
||||
assert.Equal(t, rune(2), v.Machine["node2"])
|
||||
assert.Equal(t, rune(3), v.Machine["node3"])
|
||||
}
|
||||
|
||||
type badReader struct{}
|
||||
|
||||
func (b *badReader) Read(p []byte) (n int, err error) {
|
||||
return 0, io.ErrLimitReached
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
@@ -43,6 +44,7 @@ type (
|
||||
Option func(opts *mapReduceOptions)
|
||||
|
||||
mapReduceOptions struct {
|
||||
ctx context.Context
|
||||
workers int
|
||||
}
|
||||
|
||||
@@ -95,14 +97,15 @@ func Map(generate GenerateFunc, mapper MapFunc, opts ...Option) chan interface{}
|
||||
collector := make(chan interface{}, options.workers)
|
||||
done := syncx.NewDoneChan()
|
||||
|
||||
go executeMappers(mapper, source, collector, done.Done(), options.workers)
|
||||
go executeMappers(options.ctx, mapper, source, collector, done.Done(), options.workers)
|
||||
|
||||
return collector
|
||||
}
|
||||
|
||||
// MapReduce maps all elements generated from given generate func,
|
||||
// and reduces the output elements with given reducer.
|
||||
func MapReduce(generate GenerateFunc, mapper MapperFunc, reducer ReducerFunc, opts ...Option) (interface{}, error) {
|
||||
func MapReduce(generate GenerateFunc, mapper MapperFunc, reducer ReducerFunc,
|
||||
opts ...Option) (interface{}, error) {
|
||||
source := buildSource(generate)
|
||||
return MapReduceWithSource(source, mapper, reducer, opts...)
|
||||
}
|
||||
@@ -120,7 +123,7 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
||||
|
||||
collector := make(chan interface{}, options.workers)
|
||||
done := syncx.NewDoneChan()
|
||||
writer := newGuardedWriter(output, done.Done())
|
||||
writer := newGuardedWriter(options.ctx, output, done.Done())
|
||||
var closeOnce sync.Once
|
||||
var retErr errorx.AtomicError
|
||||
finish := func() {
|
||||
@@ -154,7 +157,7 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
|
||||
reducer(collector, writer, cancel)
|
||||
}()
|
||||
|
||||
go executeMappers(func(item interface{}, w Writer) {
|
||||
go executeMappers(options.ctx, func(item interface{}, w Writer) {
|
||||
mapper(item, w, cancel)
|
||||
}, source, collector, done.Done(), options.workers)
|
||||
|
||||
@@ -187,6 +190,13 @@ func MapVoid(generate GenerateFunc, mapper VoidMapFunc, opts ...Option) {
|
||||
}, opts...))
|
||||
}
|
||||
|
||||
// WithContext customizes a mapreduce processing accepts a given ctx.
|
||||
func WithContext(ctx context.Context) Option {
|
||||
return func(opts *mapReduceOptions) {
|
||||
opts.ctx = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// WithWorkers customizes a mapreduce processing with given workers.
|
||||
func WithWorkers(workers int) Option {
|
||||
return func(opts *mapReduceOptions) {
|
||||
@@ -224,8 +234,8 @@ func drain(channel <-chan interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func executeMappers(mapper MapFunc, input <-chan interface{}, collector chan<- interface{},
|
||||
done <-chan lang.PlaceholderType, workers int) {
|
||||
func executeMappers(ctx context.Context, mapper MapFunc, input <-chan interface{},
|
||||
collector chan<- interface{}, done <-chan lang.PlaceholderType, workers int) {
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
wg.Wait()
|
||||
@@ -233,9 +243,11 @@ func executeMappers(mapper MapFunc, input <-chan interface{}, collector chan<- i
|
||||
}()
|
||||
|
||||
pool := make(chan lang.PlaceholderType, workers)
|
||||
writer := newGuardedWriter(collector, done)
|
||||
writer := newGuardedWriter(ctx, collector, done)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-done:
|
||||
return
|
||||
case pool <- lang.Placeholder:
|
||||
@@ -261,6 +273,7 @@ func executeMappers(mapper MapFunc, input <-chan interface{}, collector chan<- i
|
||||
|
||||
func newOptions() *mapReduceOptions {
|
||||
return &mapReduceOptions{
|
||||
ctx: context.Background(),
|
||||
workers: defaultWorkers,
|
||||
}
|
||||
}
|
||||
@@ -275,12 +288,15 @@ func once(fn func(error)) func(error) {
|
||||
}
|
||||
|
||||
type guardedWriter struct {
|
||||
ctx context.Context
|
||||
channel chan<- interface{}
|
||||
done <-chan lang.PlaceholderType
|
||||
}
|
||||
|
||||
func newGuardedWriter(channel chan<- interface{}, done <-chan lang.PlaceholderType) guardedWriter {
|
||||
func newGuardedWriter(ctx context.Context, channel chan<- interface{},
|
||||
done <-chan lang.PlaceholderType) guardedWriter {
|
||||
return guardedWriter{
|
||||
ctx: ctx,
|
||||
channel: channel,
|
||||
done: done,
|
||||
}
|
||||
@@ -288,6 +304,8 @@ func newGuardedWriter(channel chan<- interface{}, done <-chan lang.PlaceholderTy
|
||||
|
||||
func (gw guardedWriter) Write(v interface{}) {
|
||||
select {
|
||||
case <-gw.ctx.Done():
|
||||
return
|
||||
case <-gw.done:
|
||||
return
|
||||
default:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -410,6 +411,50 @@ func TestMapReduceWithoutReducerWrite(t *testing.T) {
|
||||
assert.Nil(t, res)
|
||||
}
|
||||
|
||||
func TestMapReduceVoidPanicInReducer(t *testing.T) {
|
||||
const message = "foo"
|
||||
var done syncx.AtomicBool
|
||||
err := MapReduceVoid(func(source chan<- interface{}) {
|
||||
for i := 0; i < defaultWorkers*2; i++ {
|
||||
source <- i
|
||||
}
|
||||
done.Set(true)
|
||||
}, func(item interface{}, writer Writer, cancel func(error)) {
|
||||
i := item.(int)
|
||||
writer.Write(i)
|
||||
}, func(pipe <-chan interface{}, cancel func(error)) {
|
||||
panic(message)
|
||||
}, WithWorkers(1))
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, message, err.Error())
|
||||
assert.True(t, done.True())
|
||||
}
|
||||
|
||||
func TestMapReduceWithContext(t *testing.T) {
|
||||
var done syncx.AtomicBool
|
||||
var result []int
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
err := MapReduceVoid(func(source chan<- interface{}) {
|
||||
for i := 0; i < defaultWorkers*2; i++ {
|
||||
source <- i
|
||||
}
|
||||
done.Set(true)
|
||||
}, func(item interface{}, writer Writer, c func(error)) {
|
||||
i := item.(int)
|
||||
if i == defaultWorkers/2 {
|
||||
cancel()
|
||||
}
|
||||
writer.Write(i)
|
||||
}, func(pipe <-chan interface{}, cancel func(error)) {
|
||||
for item := range pipe {
|
||||
i := item.(int)
|
||||
result = append(result, i)
|
||||
}
|
||||
}, WithContext(ctx))
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, ErrReduceNoOutput, err)
|
||||
}
|
||||
|
||||
func BenchmarkMapReduce(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/threading"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -46,10 +47,10 @@ func gracefulStop(signals chan os.Signal) {
|
||||
signal.Stop(signals)
|
||||
|
||||
logx.Info("Got signal SIGTERM, shutting down...")
|
||||
wrapUpListeners.notifyListeners()
|
||||
go wrapUpListeners.notifyListeners()
|
||||
|
||||
time.Sleep(wrapUpTime)
|
||||
shutdownListeners.notifyListeners()
|
||||
go shutdownListeners.notifyListeners()
|
||||
|
||||
time.Sleep(delayTimeBeforeForceQuit - wrapUpTime)
|
||||
logx.Infof("Still alive after %v, going to force kill the process...", delayTimeBeforeForceQuit)
|
||||
@@ -81,7 +82,9 @@ func (lm *listenerManager) notifyListeners() {
|
||||
lm.lock.Lock()
|
||||
defer lm.lock.Unlock()
|
||||
|
||||
group := threading.NewRoutineGroup()
|
||||
for _, listener := range lm.listeners {
|
||||
listener()
|
||||
group.RunSafe(listener)
|
||||
}
|
||||
group.Wait()
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ func Enabled() bool {
|
||||
|
||||
// StartAgent starts a prometheus agent.
|
||||
func StartAgent(c Config) {
|
||||
once.Do(func() {
|
||||
if len(c.Host) == 0 {
|
||||
return
|
||||
}
|
||||
if len(c.Host) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
once.Do(func() {
|
||||
enabled.Set(true)
|
||||
threading.GoSafe(func() {
|
||||
http.Handle(c.Path, promhttp.Handler())
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
type mockedRoute struct {
|
||||
@@ -139,11 +142,9 @@ func TestStrictSearchSibling(t *testing.T) {
|
||||
tree.Add(r.route, r.value)
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
result, ok := tree.Search(query)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 3, result.Item.(int))
|
||||
}
|
||||
result, ok := tree.Search(query)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 3, result.Item.(int))
|
||||
}
|
||||
|
||||
func TestAddDuplicate(t *testing.T) {
|
||||
@@ -185,3 +186,41 @@ func TestSearchInvalidItem(t *testing.T) {
|
||||
err := tree.Add("/", nil)
|
||||
assert.Equal(t, errEmptyItem, err)
|
||||
}
|
||||
|
||||
func BenchmarkSearchTree(b *testing.B) {
|
||||
const (
|
||||
avgLen = 1000
|
||||
entries = 10000
|
||||
)
|
||||
|
||||
tree := NewTree()
|
||||
generate := func() string {
|
||||
var buf strings.Builder
|
||||
size := rand.Intn(avgLen) + avgLen/2
|
||||
val := stringx.Randn(size)
|
||||
prev := 0
|
||||
for j := rand.Intn(9) + 1; j < size; j += rand.Intn(9) + 1 {
|
||||
buf.WriteRune('/')
|
||||
buf.WriteString(val[prev:j])
|
||||
prev = j
|
||||
}
|
||||
if prev < size {
|
||||
buf.WriteRune('/')
|
||||
buf.WriteString(val[prev:])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
index := rand.Intn(entries)
|
||||
var query string
|
||||
for i := 0; i < entries; i++ {
|
||||
val := generate()
|
||||
if i == index {
|
||||
query = val
|
||||
}
|
||||
tree.Add(val, i)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
tree.Search(query)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ type (
|
||||
}
|
||||
|
||||
// A ServiceGroup is a group of services.
|
||||
// Attention: the starting order of the added services is not guaranteed.
|
||||
ServiceGroup struct {
|
||||
services []Service
|
||||
stopOnce func()
|
||||
@@ -41,7 +42,8 @@ func NewServiceGroup() *ServiceGroup {
|
||||
|
||||
// Add adds service into sg.
|
||||
func (sg *ServiceGroup) Add(service Service) {
|
||||
sg.services = append(sg.services, service)
|
||||
// push front, stop with reverse order.
|
||||
sg.services = append([]Service{service}, sg.services...)
|
||||
}
|
||||
|
||||
// Start starts the ServiceGroup.
|
||||
|
||||
63
core/stores/builder/builder.go
Normal file
63
core/stores/builder/builder.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const dbTag = "db"
|
||||
|
||||
// RawFieldNames converts golang struct field into slice string.
|
||||
func RawFieldNames(in interface{}, postgresSql ...bool) []string {
|
||||
out := make([]string, 0)
|
||||
v := reflect.ValueOf(in)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
var pg bool
|
||||
if len(postgresSql) > 0 {
|
||||
pg = postgresSql[0]
|
||||
}
|
||||
|
||||
// we only accept structs
|
||||
if v.Kind() != reflect.Struct {
|
||||
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
|
||||
}
|
||||
|
||||
typ := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
// gets us a StructField
|
||||
fi := typ.Field(i)
|
||||
if tagv := fi.Tag.Get(dbTag); tagv != "" {
|
||||
if pg {
|
||||
out = append(out, tagv)
|
||||
} else {
|
||||
out = append(out, fmt.Sprintf("`%s`", tagv))
|
||||
}
|
||||
} else {
|
||||
if pg {
|
||||
out = append(out, fi.Name)
|
||||
} else {
|
||||
out = append(out, fmt.Sprintf("`%s`", fi.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// PostgreSqlJoin concatenates the given elements into a string.
|
||||
func PostgreSqlJoin(elems []string) string {
|
||||
b := new(strings.Builder)
|
||||
for index, e := range elems {
|
||||
b.WriteString(fmt.Sprintf("%s = $%d, ", e, index+2))
|
||||
}
|
||||
|
||||
if b.Len() == 0 {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
return b.String()[0 : b.Len()-2]
|
||||
}
|
||||
24
core/stores/builder/builder_test.go
Normal file
24
core/stores/builder/builder_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockedUser struct {
|
||||
ID string `db:"id" json:"id,omitempty"`
|
||||
UserName string `db:"user_name" json:"userName,omitempty"`
|
||||
Sex int `db:"sex" json:"sex,omitempty"`
|
||||
UUID string `db:"uuid" uuid:"uuid,omitempty"`
|
||||
Age int `db:"age" json:"age"`
|
||||
}
|
||||
|
||||
func TestFieldNames(t *testing.T) {
|
||||
t.Run("new", func(t *testing.T) {
|
||||
var u mockedUser
|
||||
out := RawFieldNames(&u)
|
||||
expected := []string{"`id`", "`user_name`", "`sex`", "`uuid`", "`age`"}
|
||||
assert.Equal(t, expected, out)
|
||||
})
|
||||
}
|
||||
2
core/stores/cache/cachenode_test.go
vendored
2
core/stores/cache/cachenode_test.go
vendored
@@ -53,8 +53,8 @@ func TestCacheNode_DelCache(t *testing.T) {
|
||||
func TestCacheNode_DelCacheWithErrors(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
store.Type = redis.ClusterType
|
||||
clean()
|
||||
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
|
||||
@@ -16,6 +16,8 @@ var ErrNoRedisNode = errors.New("no redis node")
|
||||
type (
|
||||
// Store interface represents a KV store.
|
||||
Store interface {
|
||||
Decr(key string) (int64, error)
|
||||
Decrby(key string, increment int64) (int64, error)
|
||||
Del(keys ...string) (int, error)
|
||||
Eval(script, key string, args ...interface{}) (interface{}, error)
|
||||
Exists(key string) (bool, error)
|
||||
@@ -36,6 +38,7 @@ type (
|
||||
Hvals(key string) ([]string, error)
|
||||
Incr(key string) (int64, error)
|
||||
Incrby(key string, increment int64) (int64, error)
|
||||
Lindex(key string, index int64) (string, error)
|
||||
Llen(key string) (int, error)
|
||||
Lpop(key string) (string, error)
|
||||
Lpush(key string, values ...interface{}) (int, error)
|
||||
@@ -102,6 +105,24 @@ func NewStore(c KvConf) Store {
|
||||
}
|
||||
}
|
||||
|
||||
func (cs clusterStore) Decr(key string) (int64, error) {
|
||||
node, err := cs.getRedis(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return node.Decr(key)
|
||||
}
|
||||
|
||||
func (cs clusterStore) Decrby(key string, increment int64) (int64, error) {
|
||||
node, err := cs.getRedis(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return node.Decrby(key, increment)
|
||||
}
|
||||
|
||||
func (cs clusterStore) Del(keys ...string) (int, error) {
|
||||
var val int
|
||||
var be errorx.BatchError
|
||||
@@ -303,6 +324,15 @@ func (cs clusterStore) Llen(key string) (int, error) {
|
||||
return node.Llen(key)
|
||||
}
|
||||
|
||||
func (cs clusterStore) Lindex(key string, index int64) (string, error) {
|
||||
node, err := cs.getRedis(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return node.Lindex(key, index)
|
||||
}
|
||||
|
||||
func (cs clusterStore) Lpop(key string) (string, error) {
|
||||
node, err := cs.getRedis(key)
|
||||
if err != nil {
|
||||
|
||||
@@ -17,6 +17,36 @@ var (
|
||||
s2, _ = miniredis.Run()
|
||||
)
|
||||
|
||||
func TestRedis_Decr(t *testing.T) {
|
||||
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||
_, err := store.Decr("a")
|
||||
assert.NotNil(t, err)
|
||||
|
||||
runOnCluster(t, func(client Store) {
|
||||
val, err := client.Decr("a")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-1), val)
|
||||
val, err = client.Decr("a")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-2), val)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedis_DecrBy(t *testing.T) {
|
||||
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||
_, err := store.Incrby("a", 2)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
runOnCluster(t, func(client Store) {
|
||||
val, err := client.Decrby("a", 2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-2), val)
|
||||
val, err = client.Decrby("a", 3)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-5), val)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedis_Exists(t *testing.T) {
|
||||
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||
_, err := store.Exists("foo")
|
||||
@@ -234,6 +264,8 @@ func TestRedis_List(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
_, err = store.Lrem("key", 0, "val")
|
||||
assert.NotNil(t, err)
|
||||
_, err = store.Lindex("key", 0)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
runOnCluster(t, func(client Store) {
|
||||
val, err := client.Lpush("key", "value1", "value2")
|
||||
@@ -245,6 +277,9 @@ func TestRedis_List(t *testing.T) {
|
||||
val, err = client.Llen("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, val)
|
||||
value, err := client.Lindex("key", 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "value2", value)
|
||||
vals, err := client.Lrange("key", 0, 10)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"value2", "value1", "value3", "value4"}, vals)
|
||||
@@ -667,10 +702,12 @@ func TestRedis_HyperLogLog(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
|
||||
runOnCluster(t, func(cluster Store) {
|
||||
_, err := cluster.Pfadd("key")
|
||||
assert.NotNil(t, err)
|
||||
_, err = cluster.Pfcount("key")
|
||||
assert.NotNil(t, err)
|
||||
ok, err := cluster.Pfadd("key", "value")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
val, err := cluster.Pfcount("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), val)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const slowThreshold = time.Millisecond * 500
|
||||
const defaultSlowThreshold = time.Millisecond * 500
|
||||
|
||||
// ErrNotFound is an alias of mgo.ErrNotFound.
|
||||
var ErrNotFound = mgo.ErrNotFound
|
||||
@@ -203,7 +203,7 @@ func (c *decoratedCollection) logDuration(method string, duration time.Duration,
|
||||
if e != nil {
|
||||
logx.Error(err)
|
||||
} else if err != nil {
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
|
||||
c.name, method, err.Error(), string(content))
|
||||
} else {
|
||||
@@ -211,7 +211,7 @@ func (c *decoratedCollection) logDuration(method string, duration time.Duration,
|
||||
c.name, method, err.Error(), string(content))
|
||||
}
|
||||
} else {
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
|
||||
c.name, method, string(content))
|
||||
} else {
|
||||
|
||||
@@ -8,23 +8,14 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/breaker"
|
||||
)
|
||||
|
||||
type (
|
||||
options struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// Option defines the method to customize a mongo model.
|
||||
Option func(opts *options)
|
||||
|
||||
// A Model is a mongo model.
|
||||
Model struct {
|
||||
session *concurrentSession
|
||||
db *mgo.Database
|
||||
collection string
|
||||
brk breaker.Breaker
|
||||
opts []Option
|
||||
}
|
||||
)
|
||||
// A Model is a mongo model.
|
||||
type Model struct {
|
||||
session *concurrentSession
|
||||
db *mgo.Database
|
||||
collection string
|
||||
brk breaker.Breaker
|
||||
opts []Option
|
||||
}
|
||||
|
||||
// MustNewModel returns a Model, exits on errors.
|
||||
func MustNewModel(url, collection string, opts ...Option) *Model {
|
||||
|
||||
14
core/stores/mongo/model_test.go
Normal file
14
core/stores/mongo/model_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWithTimeout(t *testing.T) {
|
||||
o := defaultOptions()
|
||||
WithTimeout(time.Second)(o)
|
||||
assert.Equal(t, time.Second, o.timeout)
|
||||
}
|
||||
29
core/stores/mongo/options.go
Normal file
29
core/stores/mongo/options.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/syncx"
|
||||
)
|
||||
|
||||
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
||||
|
||||
type (
|
||||
options struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// Option defines the method to customize a mongo model.
|
||||
Option func(opts *options)
|
||||
)
|
||||
|
||||
// SetSlowThreshold sets the slow threshold.
|
||||
func SetSlowThreshold(threshold time.Duration) {
|
||||
slowThreshold.Set(threshold)
|
||||
}
|
||||
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
timeout: defaultTimeout,
|
||||
}
|
||||
}
|
||||
14
core/stores/mongo/options_test.go
Normal file
14
core/stores/mongo/options_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetSlowThreshold(t *testing.T) {
|
||||
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
|
||||
SetSlowThreshold(time.Second)
|
||||
assert.Equal(t, time.Second, slowThreshold.Load())
|
||||
}
|
||||
@@ -57,9 +57,7 @@ func (cs *concurrentSession) putSession(session *mgo.Session) {
|
||||
}
|
||||
|
||||
func (cs *concurrentSession) takeSession(opts ...Option) (*mgo.Session, error) {
|
||||
o := &options{
|
||||
timeout: defaultTimeout,
|
||||
}
|
||||
o := defaultOptions()
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
|
||||
@@ -36,20 +36,23 @@ func MustNewModel(url, collection string, c cache.CacheConf, opts ...cache.Optio
|
||||
return model
|
||||
}
|
||||
|
||||
// NewNodeModel returns a Model with a cache node.
|
||||
func NewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
|
||||
c := cache.NewNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
// NewModel returns a Model with a cache cluster.
|
||||
func NewModel(url, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
|
||||
c := cache.New(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
return NewModelWithCache(url, collection, c)
|
||||
}
|
||||
|
||||
// NewModelWithCache returns a Model with a custom cache.
|
||||
func NewModelWithCache(url, collection string, c cache.Cache) (*Model, error) {
|
||||
return createModel(url, collection, c, func(collection mongo.Collection) CachedCollection {
|
||||
return newCollection(collection, c)
|
||||
})
|
||||
}
|
||||
|
||||
// NewModel returns a Model with a cache cluster.
|
||||
func NewModel(url, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
|
||||
c := cache.New(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
return createModel(url, collection, c, func(collection mongo.Collection) CachedCollection {
|
||||
return newCollection(collection, c)
|
||||
})
|
||||
// NewNodeModel returns a Model with a cache node.
|
||||
func NewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
|
||||
c := cache.NewNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
|
||||
return NewModelWithCache(url, collection, c)
|
||||
}
|
||||
|
||||
// Count returns the count of given query.
|
||||
|
||||
@@ -17,7 +17,7 @@ type (
|
||||
Host string
|
||||
Type string `json:",default=node,options=node|cluster"`
|
||||
Pass string `json:",optional"`
|
||||
Tls bool `json:",default=false,options=true|false"`
|
||||
Tls bool `json:",optional"`
|
||||
}
|
||||
|
||||
// A RedisKeyConf is a redis config with key.
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
"github.com/tal-tech/go-zero/core/timex"
|
||||
)
|
||||
|
||||
func process(proc func(red.Cmder) error) func(red.Cmder) error {
|
||||
func checkDuration(proc func(red.Cmder) error) func(red.Cmder) error {
|
||||
return func(cmd red.Cmder) error {
|
||||
start := timex.Now()
|
||||
|
||||
defer func() {
|
||||
duration := timex.Since(start)
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
var buf strings.Builder
|
||||
for i, arg := range cmd.Args() {
|
||||
if i > 0 {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
red "github.com/go-redis/redis"
|
||||
"github.com/tal-tech/go-zero/core/breaker"
|
||||
"github.com/tal-tech/go-zero/core/mapping"
|
||||
"github.com/tal-tech/go-zero/core/syncx"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,12 +22,14 @@ const (
|
||||
|
||||
blockingQueryTimeout = 5 * time.Second
|
||||
readWriteTimeout = 2 * time.Second
|
||||
|
||||
slowThreshold = time.Millisecond * 100
|
||||
defaultSlowThreshold = time.Millisecond * 100
|
||||
)
|
||||
|
||||
// ErrNilNode is an error that indicates a nil redis node.
|
||||
var ErrNilNode = errors.New("nil redis node")
|
||||
var (
|
||||
// ErrNilNode is an error that indicates a nil redis node.
|
||||
ErrNilNode = errors.New("nil redis node")
|
||||
slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
||||
)
|
||||
|
||||
type (
|
||||
// Option defines the method to customize a Redis.
|
||||
@@ -235,6 +238,36 @@ func (s *Redis) BlpopEx(redisNode RedisNode, key string) (string, bool, error) {
|
||||
return vals[1], true, nil
|
||||
}
|
||||
|
||||
// Decr is the implementation of redis decr command.
|
||||
func (s *Redis) Decr(key string) (val int64, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val, err = conn.Decr(key).Result()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Decrby is the implementation of redis decrby command.
|
||||
func (s *Redis) Decrby(key string, increment int64) (val int64, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val, err = conn.DecrBy(key, increment).Result()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Del deletes keys.
|
||||
func (s *Redis) Del(keys ...string) (val int, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
@@ -762,6 +795,21 @@ func (s *Redis) Llen(key string) (val int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Lindex is the implementation of redis lindex command.
|
||||
func (s *Redis) Lindex(key string, index int64) (val string, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
conn, err := getRedis(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val, err = conn.LIndex(key, index).Result()
|
||||
return err
|
||||
}, acceptable)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Lpop is the implementation of redis lpop command.
|
||||
func (s *Redis) Lpop(key string) (val string, err error) {
|
||||
err = s.brk.DoWithAcceptable(func() error {
|
||||
@@ -1758,6 +1806,11 @@ func Cluster() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// SetSlowThreshold sets the slow threshold.
|
||||
func SetSlowThreshold(threshold time.Duration) {
|
||||
slowThreshold.Set(threshold)
|
||||
}
|
||||
|
||||
// WithPass customizes the given Redis with given password.
|
||||
func WithPass(pass string) Option {
|
||||
return func(r *Redis) {
|
||||
|
||||
@@ -11,8 +11,35 @@ import (
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
red "github.com/go-redis/redis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
func TestRedis_Decr(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := New(client.Addr, badType()).Decr("a")
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Decr("a")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-1), val)
|
||||
val, err = client.Decr("a")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-2), val)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedis_DecrBy(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := New(client.Addr, badType()).Decrby("a", 2)
|
||||
assert.NotNil(t, err)
|
||||
val, err := client.Decrby("a", 2)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-2), val)
|
||||
val, err = client.Decrby("a", 3)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(-5), val)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedis_Exists(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
_, err := New(client.Addr, badType()).Exists("a")
|
||||
@@ -186,7 +213,7 @@ func TestRedis_Hscan(t *testing.T) {
|
||||
key := "hash:test"
|
||||
fieldsAndValues := make(map[string]string)
|
||||
for i := 0; i < 1550; i++ {
|
||||
fieldsAndValues["filed_"+strconv.Itoa(i)] = randomStr(i)
|
||||
fieldsAndValues["filed_"+strconv.Itoa(i)] = stringx.Randn(i)
|
||||
}
|
||||
err := client.Hmset(key, fieldsAndValues)
|
||||
assert.Nil(t, err)
|
||||
@@ -256,13 +283,24 @@ func TestRedis_Keys(t *testing.T) {
|
||||
func TestRedis_HyperLogLog(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
client.Ping()
|
||||
r := New(client.Addr, badType())
|
||||
_, err := r.Pfadd("key1")
|
||||
assert.NotNil(t, err)
|
||||
_, err = r.Pfcount("*")
|
||||
assert.NotNil(t, err)
|
||||
err = r.Pfmerge("*")
|
||||
assert.NotNil(t, err)
|
||||
r := New(client.Addr)
|
||||
ok, err := r.Pfadd("key1", "val1")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
val, err := r.Pfcount("key1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), val)
|
||||
ok, err = r.Pfadd("key2", "val2")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
val, err = r.Pfcount("key2")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), val)
|
||||
err = r.Pfmerge("key1", "key2")
|
||||
assert.Nil(t, err)
|
||||
val, err = r.Pfcount("key1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(2), val)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -283,6 +321,11 @@ func TestRedis_List(t *testing.T) {
|
||||
val, err = client.Llen("key")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 4, val)
|
||||
_, err = New(client.Addr, badType()).Lindex("key", 1)
|
||||
assert.NotNil(t, err)
|
||||
value, err := client.Lindex("key", 0)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "value2", value)
|
||||
vals, err := client.Lrange("key", 0, 10)
|
||||
assert.Nil(t, err)
|
||||
assert.EqualValues(t, []string{"value2", "value1", "value3", "value4"}, vals)
|
||||
@@ -539,7 +582,7 @@ func TestRedis_Sscan(t *testing.T) {
|
||||
key := "list"
|
||||
var list []string
|
||||
for i := 0; i < 1550; i++ {
|
||||
list = append(list, randomStr(i))
|
||||
list = append(list, stringx.Randn(i))
|
||||
}
|
||||
lens, err := client.Sadd(key, list)
|
||||
assert.Nil(t, err)
|
||||
@@ -1073,6 +1116,12 @@ func TestRedisGeo(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetSlowThreshold(t *testing.T) {
|
||||
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
|
||||
SetSlowThreshold(time.Second)
|
||||
assert.Equal(t, time.Second, slowThreshold.Load())
|
||||
}
|
||||
|
||||
func TestRedis_WithPass(t *testing.T) {
|
||||
runOnRedis(t, func(client *Redis) {
|
||||
err := New(client.Addr, WithPass("any")).Ping()
|
||||
|
||||
@@ -32,7 +32,7 @@ func getClient(r *Redis) (*red.Client, error) {
|
||||
MinIdleConns: idleConns,
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
store.WrapProcess(process)
|
||||
store.WrapProcess(checkDuration)
|
||||
return store, nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -25,7 +25,7 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
|
||||
MinIdleConns: idleConns,
|
||||
TLSConfig: tlsConfig,
|
||||
})
|
||||
store.WrapProcess(process)
|
||||
store.WrapProcess(checkDuration)
|
||||
|
||||
return store, nil
|
||||
})
|
||||
|
||||
@@ -2,36 +2,28 @@ package redis
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
red "github.com/go-redis/redis"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
|
||||
return "OK"
|
||||
else
|
||||
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
|
||||
end`
|
||||
delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("DEL", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end`
|
||||
randomLen = 16
|
||||
tolerance = 500 // milliseconds
|
||||
millisPerSecond = 1000
|
||||
randomLen = 16
|
||||
)
|
||||
|
||||
// A RedisLock is a redis lock.
|
||||
type RedisLock struct {
|
||||
store *Redis
|
||||
seconds uint32
|
||||
count int32
|
||||
key string
|
||||
id string
|
||||
}
|
||||
@@ -45,36 +37,41 @@ func NewRedisLock(store *Redis, key string) *RedisLock {
|
||||
return &RedisLock{
|
||||
store: store,
|
||||
key: key,
|
||||
id: randomStr(randomLen),
|
||||
id: stringx.Randn(randomLen),
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire acquires the lock.
|
||||
func (rl *RedisLock) Acquire() (bool, error) {
|
||||
seconds := atomic.LoadUint32(&rl.seconds)
|
||||
resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
|
||||
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
|
||||
})
|
||||
if err == red.Nil {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
|
||||
return false, err
|
||||
} else if resp == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
reply, ok := resp.(string)
|
||||
if ok && reply == "OK" {
|
||||
newCount := atomic.AddInt32(&rl.count, 1)
|
||||
if newCount > 1 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
|
||||
return false, nil
|
||||
seconds := atomic.LoadUint32(&rl.seconds)
|
||||
ok, err := rl.store.SetnxEx(rl.key, rl.id, int(seconds+1)) // +1s for tolerance
|
||||
if err == red.Nil {
|
||||
atomic.AddInt32(&rl.count, -1)
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
atomic.AddInt32(&rl.count, -1)
|
||||
logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
|
||||
return false, err
|
||||
} else if !ok {
|
||||
atomic.AddInt32(&rl.count, -1)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Release releases the lock.
|
||||
func (rl *RedisLock) Release() (bool, error) {
|
||||
newCount := atomic.AddInt32(&rl.count, -1)
|
||||
if newCount > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -88,15 +85,7 @@ func (rl *RedisLock) Release() (bool, error) {
|
||||
return reply == 1, nil
|
||||
}
|
||||
|
||||
// SetExpire sets the expire.
|
||||
// SetExpire sets the expiration.
|
||||
func (rl *RedisLock) SetExpire(seconds int) {
|
||||
atomic.StoreUint32(&rl.seconds, uint32(seconds))
|
||||
}
|
||||
|
||||
func randomStr(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
@@ -29,5 +29,25 @@ func TestRedisLock(t *testing.T) {
|
||||
endAcquire, err := secondLock.Acquire()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, endAcquire)
|
||||
|
||||
endAcquire, err = secondLock.Acquire()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, endAcquire)
|
||||
|
||||
release, err = secondLock.Release()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, release)
|
||||
|
||||
againAcquire, err = firstLock.Acquire()
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, againAcquire)
|
||||
|
||||
release, err = secondLock.Release()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, release)
|
||||
|
||||
firstAcquire, err = firstLock.Acquire()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, firstAcquire)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,20 +39,24 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// NewNodeConn returns a CachedConn with a redis node cache.
|
||||
func NewNodeConn(db sqlx.SqlConn, rds *redis.Redis, opts ...cache.Option) CachedConn {
|
||||
// NewConn returns a CachedConn with a redis cluster cache.
|
||||
func NewConn(db sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) CachedConn {
|
||||
cc := cache.New(c, exclusiveCalls, stats, sql.ErrNoRows, opts...)
|
||||
return NewConnWithCache(db, cc)
|
||||
}
|
||||
|
||||
// NewConnWithCache returns a CachedConn with a custom cache.
|
||||
func NewConnWithCache(db sqlx.SqlConn, c cache.Cache) CachedConn {
|
||||
return CachedConn{
|
||||
db: db,
|
||||
cache: cache.NewNode(rds, exclusiveCalls, stats, sql.ErrNoRows, opts...),
|
||||
cache: c,
|
||||
}
|
||||
}
|
||||
|
||||
// NewConn returns a CachedConn with a redis cluster cache.
|
||||
func NewConn(db sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) CachedConn {
|
||||
return CachedConn{
|
||||
db: db,
|
||||
cache: cache.New(c, exclusiveCalls, stats, sql.ErrNoRows, opts...),
|
||||
}
|
||||
// NewNodeConn returns a CachedConn with a redis node cache.
|
||||
func NewNodeConn(db sqlx.SqlConn, rds *redis.Redis, opts ...cache.Option) CachedConn {
|
||||
c := cache.NewNode(rds, exclusiveCalls, stats, sql.ErrNoRows, opts...)
|
||||
return NewConnWithCache(db, c)
|
||||
}
|
||||
|
||||
// DelCache deletes cache with keys.
|
||||
|
||||
@@ -562,6 +562,18 @@ func TestQueryRowNoCache(t *testing.T) {
|
||||
assert.True(t, ran)
|
||||
}
|
||||
|
||||
func TestNewConnWithCache(t *testing.T) {
|
||||
r, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
|
||||
var conn trackedConn
|
||||
c := NewConnWithCache(&conn, cache.NewNode(r, exclusiveCalls, stats, sql.ErrNoRows))
|
||||
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, conn.execValue)
|
||||
}
|
||||
|
||||
func resetStats() {
|
||||
atomic.StoreUint64(&stats.Total, 0)
|
||||
atomic.StoreUint64(&stats.Hit, 0)
|
||||
|
||||
@@ -25,6 +25,7 @@ type (
|
||||
SqlConn interface {
|
||||
Session
|
||||
// RawDB is for other ORM to operate with, use it with caution.
|
||||
// Notice: don't close it.
|
||||
RawDB() (*sql.DB, error)
|
||||
Transact(func(session Session) error) error
|
||||
}
|
||||
|
||||
@@ -5,10 +5,18 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/syncx"
|
||||
"github.com/tal-tech/go-zero/core/timex"
|
||||
)
|
||||
|
||||
const slowThreshold = time.Millisecond * 500
|
||||
const defaultSlowThreshold = time.Millisecond * 500
|
||||
|
||||
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
||||
|
||||
// SetSlowThreshold sets the slow threshold.
|
||||
func SetSlowThreshold(threshold time.Duration) {
|
||||
slowThreshold.Set(threshold)
|
||||
}
|
||||
|
||||
func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
|
||||
stmt, err := format(q, args...)
|
||||
@@ -19,7 +27,7 @@ func exec(conn sessionConn, q string, args ...interface{}) (sql.Result, error) {
|
||||
startTime := timex.Now()
|
||||
result, err := conn.Exec(q, args...)
|
||||
duration := timex.Since(startTime)
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[SQL] exec: slowcall - %s", stmt)
|
||||
} else {
|
||||
logx.WithDuration(duration).Infof("sql exec: %s", stmt)
|
||||
@@ -40,7 +48,7 @@ func execStmt(conn stmtConn, q string, args ...interface{}) (sql.Result, error)
|
||||
startTime := timex.Now()
|
||||
result, err := conn.Exec(args...)
|
||||
duration := timex.Since(startTime)
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[SQL] execStmt: slowcall - %s", stmt)
|
||||
} else {
|
||||
logx.WithDuration(duration).Infof("sql execStmt: %s", stmt)
|
||||
@@ -61,7 +69,7 @@ func query(conn sessionConn, scanner func(*sql.Rows) error, q string, args ...in
|
||||
startTime := timex.Now()
|
||||
rows, err := conn.Query(q, args...)
|
||||
duration := timex.Since(startTime)
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[SQL] query: slowcall - %s", stmt)
|
||||
} else {
|
||||
logx.WithDuration(duration).Infof("sql query: %s", stmt)
|
||||
@@ -84,7 +92,7 @@ func queryStmt(conn stmtConn, scanner func(*sql.Rows) error, q string, args ...i
|
||||
startTime := timex.Now()
|
||||
rows, err := conn.Query(args...)
|
||||
duration := timex.Since(startTime)
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
logx.WithDuration(duration).Slowf("[SQL] queryStmt: slowcall - %s", stmt)
|
||||
} else {
|
||||
logx.WithDuration(duration).Infof("sql queryStmt: %s", stmt)
|
||||
|
||||
@@ -171,6 +171,12 @@ func TestStmt_query(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSlowThreshold(t *testing.T) {
|
||||
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
|
||||
SetSlowThreshold(time.Second)
|
||||
assert.Equal(t, time.Second, slowThreshold.Load())
|
||||
}
|
||||
|
||||
type mockedSessionConn struct {
|
||||
lastInsertId int64
|
||||
rowsAffected int64
|
||||
@@ -180,7 +186,7 @@ type mockedSessionConn struct {
|
||||
|
||||
func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
if m.delay {
|
||||
time.Sleep(slowThreshold + time.Millisecond)
|
||||
time.Sleep(defaultSlowThreshold + time.Millisecond)
|
||||
}
|
||||
return mockedResult{
|
||||
lastInsertId: m.lastInsertId,
|
||||
@@ -190,7 +196,7 @@ func (m *mockedSessionConn) Exec(query string, args ...interface{}) (sql.Result,
|
||||
|
||||
func (m *mockedSessionConn) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
if m.delay {
|
||||
time.Sleep(slowThreshold + time.Millisecond)
|
||||
time.Sleep(defaultSlowThreshold + time.Millisecond)
|
||||
}
|
||||
|
||||
err := errMockedPlaceholder
|
||||
@@ -209,7 +215,7 @@ type mockedStmtConn struct {
|
||||
|
||||
func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
|
||||
if m.delay {
|
||||
time.Sleep(slowThreshold + time.Millisecond)
|
||||
time.Sleep(defaultSlowThreshold + time.Millisecond)
|
||||
}
|
||||
return mockedResult{
|
||||
lastInsertId: m.lastInsertId,
|
||||
@@ -219,7 +225,7 @@ func (m *mockedStmtConn) Exec(args ...interface{}) (sql.Result, error) {
|
||||
|
||||
func (m *mockedStmtConn) Query(args ...interface{}) (*sql.Rows, error) {
|
||||
if m.delay {
|
||||
time.Sleep(slowThreshold + time.Millisecond)
|
||||
time.Sleep(defaultSlowThreshold + time.Millisecond)
|
||||
}
|
||||
|
||||
err := errMockedPlaceholder
|
||||
|
||||
@@ -19,6 +19,12 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// NewSessionFromTx returns a Session with the given sql.Tx.
|
||||
// Use it with caution, it's provided for other ORM to interact with.
|
||||
func NewSessionFromTx(tx *sql.Tx) Session {
|
||||
return txSession{Tx: tx}
|
||||
}
|
||||
|
||||
func (t txSession) Exec(q string, args ...interface{}) (sql.Result, error) {
|
||||
return exec(t.Tx, q, args...)
|
||||
}
|
||||
|
||||
@@ -41,3 +41,10 @@ func TestFakeTicker(t *testing.T) {
|
||||
assert.Nil(t, ticker.Wait(time.Second))
|
||||
assert.Equal(t, int32(total), atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
func TestFakeTickerTimeout(t *testing.T) {
|
||||
ticker := NewFakeTicker()
|
||||
defer ticker.Stop()
|
||||
|
||||
assert.NotNil(t, ticker.Wait(time.Millisecond))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/lang"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/jaeger"
|
||||
@@ -19,17 +20,31 @@ const (
|
||||
kindZipkin = "zipkin"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var (
|
||||
agents = make(map[string]lang.PlaceholderType)
|
||||
lock sync.Mutex
|
||||
)
|
||||
|
||||
// StartAgent starts a opentelemetry agent.
|
||||
func StartAgent(c Config) {
|
||||
once.Do(func() {
|
||||
startAgent(c)
|
||||
})
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
_, ok := agents[c.Endpoint]
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
// if error happens, let later calls run.
|
||||
if err := startAgent(c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
agents[c.Endpoint] = lang.Placeholder
|
||||
}
|
||||
|
||||
func createExporter(c Config) (sdktrace.SpanExporter, error) {
|
||||
// Just support jaeger now, more for later
|
||||
// Just support jaeger and zipkin now, more for later
|
||||
switch c.Batcher {
|
||||
case kindJaeger:
|
||||
return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(c.Endpoint)))
|
||||
@@ -40,7 +55,7 @@ func createExporter(c Config) (sdktrace.SpanExporter, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func startAgent(c Config) {
|
||||
func startAgent(c Config) error {
|
||||
opts := []sdktrace.TracerProviderOption{
|
||||
// Set the sampling rate based on the parent span to 100%
|
||||
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(c.Sampler))),
|
||||
@@ -52,7 +67,7 @@ func startAgent(c Config) {
|
||||
exp, err := createExporter(c)
|
||||
if err != nil {
|
||||
logx.Error(err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
// Always be sure to batch in production.
|
||||
@@ -66,4 +81,6 @@ func startAgent(c Config) {
|
||||
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
|
||||
logx.Errorf("[otel] error: %v", err)
|
||||
}))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
54
core/trace/agent_test.go
Normal file
54
core/trace/agent_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestStartAgent(t *testing.T) {
|
||||
logx.Disable()
|
||||
|
||||
const (
|
||||
endpoint1 = "localhost:1234"
|
||||
endpoint2 = "remotehost:1234"
|
||||
endpoint3 = "localhost:1235"
|
||||
)
|
||||
c1 := Config{
|
||||
Name: "foo",
|
||||
}
|
||||
c2 := Config{
|
||||
Name: "bar",
|
||||
Endpoint: endpoint1,
|
||||
Batcher: kindJaeger,
|
||||
}
|
||||
c3 := Config{
|
||||
Name: "any",
|
||||
Endpoint: endpoint2,
|
||||
Batcher: kindZipkin,
|
||||
}
|
||||
c4 := Config{
|
||||
Name: "bla",
|
||||
Endpoint: endpoint3,
|
||||
Batcher: "otlp",
|
||||
}
|
||||
|
||||
StartAgent(c1)
|
||||
StartAgent(c1)
|
||||
StartAgent(c2)
|
||||
StartAgent(c3)
|
||||
StartAgent(c4)
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
// because remotehost cannot be resolved
|
||||
assert.Equal(t, 2, len(agents))
|
||||
_, ok := agents[""]
|
||||
assert.True(t, ok)
|
||||
_, ok = agents[endpoint1]
|
||||
assert.True(t, ok)
|
||||
_, ok = agents[endpoint2]
|
||||
assert.False(t, ok)
|
||||
}
|
||||
@@ -34,7 +34,7 @@ var (
|
||||
RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED")
|
||||
)
|
||||
|
||||
// StatusCodeAttr returns a attribute.KeyValue that represents the give c.
|
||||
// StatusCodeAttr returns an attribute.KeyValue that represents the give c.
|
||||
func StatusCodeAttr(c gcodes.Code) attribute.KeyValue {
|
||||
return GRPCStatusCodeKey.Int64(int64(c))
|
||||
}
|
||||
|
||||
12
core/trace/attributes_test.go
Normal file
12
core/trace/attributes_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
gcodes "google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
func TestStatusCodeAttr(t *testing.T) {
|
||||
assert.Equal(t, GRPCStatusCodeKey.Int(int(gcodes.DataLoss)), StatusCodeAttr(gcodes.DataLoss))
|
||||
}
|
||||
@@ -157,7 +157,8 @@ func TestExtractValidTraceContext(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
}
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
||||
propagation.TraceContext{}, propagation.Baggage{}))
|
||||
propagator := otel.GetTextMapPropagator()
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -242,7 +243,8 @@ func TestExtractInvalidTraceContext(t *testing.T) {
|
||||
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-",
|
||||
},
|
||||
}
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
||||
propagation.TraceContext{}, propagation.Baggage{}))
|
||||
propagator := otel.GetTextMapPropagator()
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -308,7 +310,8 @@ func TestInjectValidTraceContext(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
}
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
||||
propagation.TraceContext{}, propagation.Baggage{}))
|
||||
propagator := otel.GetTextMapPropagator()
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -325,6 +328,11 @@ func TestInjectValidTraceContext(t *testing.T) {
|
||||
md := metadata.MD{}
|
||||
Inject(ctx, propagator, &md)
|
||||
assert.Equal(t, want, md)
|
||||
|
||||
mm := &metadataSupplier{
|
||||
metadata: &md,
|
||||
}
|
||||
assert.NotEmpty(t, mm.Keys())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -334,7 +342,8 @@ func TestInvalidSpanContextDropped(t *testing.T) {
|
||||
require.False(t, invalidSC.IsValid())
|
||||
ctx := trace.ContextWithRemoteSpanContext(context.Background(), invalidSC)
|
||||
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
||||
propagation.TraceContext{}, propagation.Baggage{}))
|
||||
propagator := otel.GetTextMapPropagator()
|
||||
|
||||
md := metadata.MD{}
|
||||
@@ -342,5 +351,6 @@ func TestInvalidSpanContextDropped(t *testing.T) {
|
||||
mm := &metadataSupplier{
|
||||
metadata: &md,
|
||||
}
|
||||
assert.Empty(t, mm.Keys())
|
||||
assert.Equal(t, "", mm.Get("traceparent"), "injected invalid SpanContext")
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const localhost = "127.0.0.1"
|
||||
// PeerFromCtx returns the peer from ctx.
|
||||
func PeerFromCtx(ctx context.Context) string {
|
||||
p, ok := peer.FromContext(ctx)
|
||||
if !ok {
|
||||
if !ok || p == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func ParseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
|
||||
func PeerAttr(addr string) []attribute.KeyValue {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return []attribute.KeyValue(nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(host) == 0 {
|
||||
|
||||
@@ -1,13 +1,53 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||
"google.golang.org/grpc/peer"
|
||||
)
|
||||
|
||||
func TestPeerFromContext(t *testing.T) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
assert.Nil(t, err)
|
||||
assert.NotEmpty(t, addrs)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
empty bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
ctx: context.Background(),
|
||||
empty: true,
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
ctx: peer.NewContext(context.Background(), nil),
|
||||
empty: true,
|
||||
},
|
||||
{
|
||||
name: "with value",
|
||||
ctx: peer.NewContext(context.Background(), &peer.Peer{
|
||||
Addr: addrs[0],
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
addr := PeerFromCtx(test.ctx)
|
||||
assert.Equal(t, test.empty, len(addr) == 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFullMethod(t *testing.T) {
|
||||
tests := []struct {
|
||||
fullMethod string
|
||||
@@ -68,3 +108,46 @@ func TestParseFullMethod(t *testing.T) {
|
||||
assert.Equal(t, test.attr, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpanInfo(t *testing.T) {
|
||||
val, kvs := SpanInfo("/fullMethod", "remote")
|
||||
assert.Equal(t, "fullMethod", val)
|
||||
assert.NotEmpty(t, kvs)
|
||||
}
|
||||
|
||||
func TestPeerAttr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
addr string
|
||||
expect []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
},
|
||||
{
|
||||
name: "port only",
|
||||
addr: ":8080",
|
||||
expect: []attribute.KeyValue{
|
||||
semconv.NetPeerIPKey.String(localhost),
|
||||
semconv.NetPeerPortKey.String("8080"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "port only",
|
||||
addr: "192.168.0.2:8080",
|
||||
expect: []attribute.KeyValue{
|
||||
semconv.NetPeerIPKey.String("192.168.0.2"),
|
||||
semconv.NetPeerPortKey.String("8080"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
kvs := PeerAttr(test.addr)
|
||||
assert.EqualValues(t, test.expect, kvs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
60
go.mod
60
go.mod
@@ -3,55 +3,43 @@ module github.com/tal-tech/go-zero
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go v1.4.3
|
||||
github.com/DATA-DOG/go-sqlmock v1.4.1
|
||||
github.com/alicebob/miniredis/v2 v2.14.1
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/emicklei/proto v1.9.0
|
||||
github.com/ClickHouse/clickhouse-go v1.5.1
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/alicebob/miniredis/v2 v2.16.0
|
||||
github.com/fatih/color v1.9.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/frankban/quicktest v1.7.2 // indirect
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
|
||||
github.com/go-redis/redis v6.15.7+incompatible
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-xorm/builder v0.3.4
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible
|
||||
github.com/golang/mock v1.4.3
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/iancoleman/strcase v0.1.2
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/lib/pq v1.10.3
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pierrec/lz4 v2.5.1+incompatible // indirect
|
||||
github.com/openzipkin/zipkin-go v0.3.0 // indirect
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/urfave/cli v1.22.5
|
||||
github.com/zeromicro/antlr v0.0.1
|
||||
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348
|
||||
github.com/zeromicro/protobuf v0.0.0-20210921042113-636cd51f0c35
|
||||
go.etcd.io/etcd/api/v3 v3.5.0
|
||||
go.etcd.io/etcd/client/v3 v3.5.0
|
||||
go.opentelemetry.io/otel v1.0.1
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.0.1
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.0.1
|
||||
go.opentelemetry.io/otel/sdk v1.0.1
|
||||
go.opentelemetry.io/otel/trace v1.0.1
|
||||
go.uber.org/automaxprocs v1.3.0
|
||||
go.etcd.io/etcd/api/v3 v3.5.1
|
||||
go.etcd.io/etcd/client/v3 v3.5.1
|
||||
go.opentelemetry.io/otel v1.1.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.1.0
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.1.0
|
||||
go.opentelemetry.io/otel/sdk v1.1.0
|
||||
go.opentelemetry.io/otel/trace v1.1.0
|
||||
go.uber.org/automaxprocs v1.4.0
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b // indirect
|
||||
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
|
||||
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 // indirect
|
||||
google.golang.org/grpc v1.41.0
|
||||
google.golang.org/grpc v1.42.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||
gopkg.in/h2non/gock.v1 v1.0.15
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.20.10
|
||||
k8s.io/apimachinery v0.20.10
|
||||
k8s.io/client-go v0.20.10
|
||||
k8s.io/api v0.20.12
|
||||
k8s.io/apimachinery v0.20.12
|
||||
k8s.io/client-go v0.20.12
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
|
||||
)
|
||||
|
||||
199
go.sum
199
go.sum
@@ -32,16 +32,18 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/ClickHouse/clickhouse-go v1.4.3 h1:iAFMa2UrQdR5bHJ2/yaSLffZkxpcOYQMCUuKeNXGdqc=
|
||||
github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/DATA-DOG/go-sqlmock v1.4.1 h1:ThlnYciV1iM/V0OSF/dtkqWb6xo5qITT1TJBG1MRDJM=
|
||||
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.1 h1:I8zVFZTz80crCs0FFEBJooIxsPcV0xfthzK1YrkpJTc=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -49,11 +51,9 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs=
|
||||
github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
|
||||
github.com/alicebob/miniredis/v2 v2.16.0 h1:ALkyFg7bSTEd1Mkrb4ppq4fnwjklA59dVtIehXCUZkU=
|
||||
github.com/alicebob/miniredis/v2 v2.16.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
@@ -74,15 +74,17 @@ github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -91,12 +93,11 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/proto v1.9.0 h1:l0QiNT6Qs7Yj0Mb4X6dnWBQer4ebei2BFcgQLbGqUDc=
|
||||
github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -108,11 +109,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
|
||||
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@@ -139,16 +139,13 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
|
||||
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
|
||||
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -165,8 +162,8 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -184,6 +181,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@@ -207,28 +205,36 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
|
||||
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/strcase v0.1.2 h1:gnomlvw9tnV3ITTAxzKSgTF+8kFWcU/f+TgttpXGz1U=
|
||||
github.com/iancoleman/strcase v0.1.2/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -243,21 +249,22 @@ github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
|
||||
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
@@ -285,24 +292,34 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
|
||||
github.com/openzipkin/zipkin-go v0.3.0 h1:XtuXmOLIXLjiU2XduuWREDT0LOKtSgos/g7i7RYyoZQ=
|
||||
github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4 v2.5.1+incompatible h1:Yq0up0149Hh5Ekhm/91lgkZuD1ZDnXNM26bycpTzYBM=
|
||||
github.com/pierrec/lz4 v2.5.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -329,16 +346,17 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
@@ -358,44 +376,40 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
|
||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
|
||||
github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
|
||||
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348 h1:OhxL9tn28gDeJVzreIUiE5oVxZCjL3tBJ0XBNw8p5R8=
|
||||
github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
|
||||
github.com/zeromicro/protobuf v0.0.0-20210921042113-636cd51f0c35 h1:LKLiozf78evAX4+82AHw/rG7TvefD7TJII7XB7Oip7o=
|
||||
github.com/zeromicro/protobuf v0.0.0-20210921042113-636cd51f0c35/go.mod h1:CJvyESptK/6uU5003fPJQ9Hmubl3vRuGqaLgzUU0R7Y=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
|
||||
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
|
||||
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
|
||||
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.1 h1:oImGuV5LGKjCqXdjkMHCyWa5OO1gYKCnC/1sgdfj1Uk=
|
||||
go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.0.1 h1:4XKyXmfqJLOQ7feyV5DB6gsBFZ0ltB8vLtp6pj4JIcc=
|
||||
go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.0.1 h1:fg9udWIWWJMAT+Gq2ATFd/DFy3OZvKEZy9VK2amxvkw=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.0.1/go.mod h1:85Ym3qknJdIdfRzYS9Ofy9NeLi9gKPFzFDBEHCKpfXI=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.0.1 h1:Li6OvM1Po5qrP+HnXlZa+FyLkMun7JG4R0vTAch12qs=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.0.1/go.mod h1:KXb2W6IVINSd/rKugSARqP3TsByxngvea3B1vm5ju74=
|
||||
go.opentelemetry.io/otel/sdk v1.0.1 h1:wXxFEWGo7XfXupPwVJvTBOaPBC9FEg0wB8hMNrKk+cA=
|
||||
go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI=
|
||||
go.opentelemetry.io/otel/trace v1.0.1 h1:StTeIH6Q3G4r0Fiw34LTokUFESZgIDUr0qIJ7mKmAfw=
|
||||
go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk=
|
||||
go.opentelemetry.io/otel v1.1.0 h1:8p0uMLcyyIx0KHNTgO8o3CW8A1aA+dJZJW6PvnMz0Wc=
|
||||
go.opentelemetry.io/otel v1.1.0/go.mod h1:7cww0OW51jQ8IaZChIEdqLwgh+44+7uiTdWsAL0wQpA=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.1.0 h1:VRF+Hf3EePFO6ab7/wfPoyWzSY4z5X0tTvQtV9/Mq8Y=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.1.0/go.mod h1:D/GIBwAdrFTTqCy1iITpC9nh5rgJpIbFVgkhlz2vCXk=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.1.0 h1:NfP5auMWoVOYnAeQPY+fxNG8UMAu94heSL4rtOL8Bsg=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.1.0/go.mod h1:LZwDnf1mVGTPMq9hdRUHfFBH30SuQvZ1BJaVywpg0VI=
|
||||
go.opentelemetry.io/otel/sdk v1.1.0 h1:j/1PngUJIDOddkCILQYTevrTIbWd494djgGkSsMit+U=
|
||||
go.opentelemetry.io/otel/sdk v1.1.0/go.mod h1:3aQvM6uLm6C4wJpHtT8Od3vNzeZ34Pqc6bps8MywWzo=
|
||||
go.opentelemetry.io/otel/trace v1.1.0 h1:N25T9qCL0+7IpOT8RrRy0WYlL7y6U0WiUJzXcVdXY/o=
|
||||
go.opentelemetry.io/otel/trace v1.1.0/go.mod h1:i47XtdcBQiktu5IsrPqOHe8w+sBmnLwwHt8wiUsWGTI=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0=
|
||||
go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
|
||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
|
||||
@@ -407,8 +421,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8=
|
||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -463,11 +479,15 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -501,9 +521,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -520,6 +542,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -527,15 +550,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d h1:SABT8Vei3iTiu+Gy8KOzpSNz+W1EQ5YBCRtiEETxF+0=
|
||||
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 h1:Ywe/f3fNleF8I6F6qv3MeFoSZ6CTf2zBMMa/7qVML8M=
|
||||
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
@@ -578,7 +602,9 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -637,8 +663,9 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -655,15 +682,15 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
@@ -686,12 +713,12 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.20.10 h1:kAdgi1zcyenV88/uVEzS9B/fn1m4KRbmdKB0Lxl6z/M=
|
||||
k8s.io/api v0.20.10/go.mod h1:0kei3F6biGjtRQBo5dUeujq6Ji3UCh9aOSfp/THYd7I=
|
||||
k8s.io/apimachinery v0.20.10 h1:GcFwz5hsGgKLohcNgv8GrInk60vUdFgBXW7uOY1i1YM=
|
||||
k8s.io/apimachinery v0.20.10/go.mod h1:kQa//VOAwyVwJ2+L9kOREbsnryfsGSkSM1przND4+mw=
|
||||
k8s.io/client-go v0.20.10 h1:TgAL2pqcNWMH4eZoS9Uw0BLh2lu5a2A4pmegjp5pmsk=
|
||||
k8s.io/client-go v0.20.10/go.mod h1:fFg+aLoasv/R+xiVaWjxeqGFYltzgQcOQzkFaSRfnJ0=
|
||||
k8s.io/api v0.20.12 h1:LfRpmRkJLwPP8eaYehsVVmIIfg1yCBIIUHaSsdqCgHA=
|
||||
k8s.io/api v0.20.12/go.mod h1:A2brwyEkVLM3wQGNnzoAa5JsQRzHK0uoOQ+bsnv7V68=
|
||||
k8s.io/apimachinery v0.20.12 h1:2c0LIVNMvB8k2Ozstmhl2zGeCEcPazznuLYEwxFdNjM=
|
||||
k8s.io/apimachinery v0.20.12/go.mod h1:uM7hCI0NyBymUwgshMgZyte475lxhr+QH6h3cvdnzEc=
|
||||
k8s.io/client-go v0.20.12 h1:U75SxTC31BHT9i7CbX/hL4v+U1Wkzy/E1vt5ClDPp3I=
|
||||
k8s.io/client-go v0.20.12/go.mod h1:NBJj6Evp73Xy/4v/O/RDRaH0+3JoxNfjRxkyRgrdbsA=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
|
||||
|
||||
17
readme-cn.md
17
readme-cn.md
@@ -104,10 +104,10 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
|
||||
|
||||
```shell
|
||||
# Go 1.15 及之前版本
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl@cli
|
||||
|
||||
# Go 1.16 及以后版本
|
||||
go install github.com/tal-tech/go-zero/tools/goctl@latest
|
||||
GOPROXY=https://goproxy.cn/,direct go install github.com/tal-tech/go-zero/tools/goctl@cli
|
||||
```
|
||||
|
||||
确保 goctl 可执行
|
||||
@@ -141,7 +141,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/
|
||||
|
||||
编写业务代码:
|
||||
|
||||
* api 文件定义了服务对外 HTTP 接口,可参考 [api 规范](https://github.com/zeromicro/zero-doc/blob/main/doc/goctl.md)
|
||||
* api 文件定义了服务对外 HTTP 接口,可参考 [api 规范](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/goctl-api.md)
|
||||
* 可以在 `servicecontext.go` 里面传递依赖给 logic,比如 mysql, redis 等
|
||||
* 在 api 定义的 `get/post/put/delete` 等请求对应的 logic 里增加业务处理逻辑
|
||||
|
||||
@@ -226,6 +226,15 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
||||
>40. 马鞍山百助网络科技有限公司
|
||||
>41. 上海阿莫尔科技有限公司
|
||||
>42. 发明者量化
|
||||
>43. 济南超级盟网络科技有限公司
|
||||
>44. 苏州互盟信息存储技术有限公司
|
||||
>45. 成都艾途教育科技集团有限公司
|
||||
>46. 上海游族网络
|
||||
>47. 深信服
|
||||
>48. 中免日上科技互联有限公司
|
||||
>48. ECLOUDVALLEY TECHNOLOGY (HK) LIMITED
|
||||
>48. 馨科智(深圳)科技有限公司
|
||||
>48. 成都松珀科技有限公司
|
||||
|
||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||
|
||||
@@ -242,7 +251,7 @@ go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape
|
||||
|
||||
`go-zero` 相关文章和视频都会在 `微服务实践` 公众号整理呈现,欢迎扫码关注 👏
|
||||
|
||||
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/wechat-micro.jpg" alt="wechat" width="300" />
|
||||
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/zeromicro.jpg" alt="wechat" width="600" />
|
||||
|
||||
## 11. 微信交流群
|
||||
|
||||
|
||||
@@ -107,10 +107,10 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
```shell
|
||||
# for Go 1.15 and earlier
|
||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
|
||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl@cli
|
||||
|
||||
# for Go 1.16 and later
|
||||
go install github.com/tal-tech/go-zero/tools/goctl@latest
|
||||
go install github.com/tal-tech/go-zero/tools/goctl@cli
|
||||
```
|
||||
|
||||
make sure goctl is executable.
|
||||
@@ -221,7 +221,7 @@ go get -u github.com/tal-tech/go-zero
|
||||
|
||||
## 9. Chat group
|
||||
|
||||
Join the chat via https://join.slack.com/t/go-zero/shared_invite/zt-ulzixfgi-NAkZjq856TewLY2KQSxHCw
|
||||
Join the chat via https://join.slack.com/t/go-zero/shared_invite/zt-10ruju779-BE4y6lQNB_R21samtyKTgA
|
||||
|
||||
## 10. Cloud Native Landscape
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ type (
|
||||
KeyFile string `json:",optional"`
|
||||
Verbose bool `json:",optional"`
|
||||
MaxConns int `json:",default=10000"`
|
||||
MaxBytes int64 `json:",default=1048576,range=[0:33554432]"`
|
||||
MaxBytes int64 `json:",default=1048576"`
|
||||
// milliseconds
|
||||
Timeout int64 `json:",default=3000"`
|
||||
CpuThreshold int64 `json:",default=900,range=[0:1000]"`
|
||||
|
||||
136
rest/engine.go
136
rest/engine.go
@@ -1,6 +1,7 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"github.com/tal-tech/go-zero/rest/handler"
|
||||
"github.com/tal-tech/go-zero/rest/httpx"
|
||||
"github.com/tal-tech/go-zero/rest/internal"
|
||||
"github.com/tal-tech/go-zero/rest/router"
|
||||
)
|
||||
|
||||
// use 1000m to represent 100%
|
||||
@@ -30,6 +30,7 @@ type engine struct {
|
||||
middlewares []Middleware
|
||||
shedder load.Shedder
|
||||
priorityShedder load.Shedder
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func newEngine(c RestConf) *engine {
|
||||
@@ -45,58 +46,34 @@ func newEngine(c RestConf) *engine {
|
||||
return srv
|
||||
}
|
||||
|
||||
func (s *engine) AddRoutes(r featuredRoutes) {
|
||||
s.routes = append(s.routes, r)
|
||||
func (ng *engine) addRoutes(r featuredRoutes) {
|
||||
ng.routes = append(ng.routes, r)
|
||||
}
|
||||
|
||||
func (s *engine) SetUnauthorizedCallback(callback handler.UnauthorizedCallback) {
|
||||
s.unauthorizedCallback = callback
|
||||
}
|
||||
|
||||
func (s *engine) SetUnsignedCallback(callback handler.UnsignedCallback) {
|
||||
s.unsignedCallback = callback
|
||||
}
|
||||
|
||||
func (s *engine) Start() error {
|
||||
return s.StartWithRouter(router.NewRouter())
|
||||
}
|
||||
|
||||
func (s *engine) StartWithRouter(router httpx.Router) error {
|
||||
if err := s.bindRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(s.conf.CertFile) == 0 && len(s.conf.KeyFile) == 0 {
|
||||
return internal.StartHttp(s.conf.Host, s.conf.Port, router)
|
||||
}
|
||||
|
||||
return internal.StartHttps(s.conf.Host, s.conf.Port, s.conf.CertFile, s.conf.KeyFile, router)
|
||||
}
|
||||
|
||||
func (s *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
|
||||
func (ng *engine) appendAuthHandler(fr featuredRoutes, chain alice.Chain,
|
||||
verifier func(alice.Chain) alice.Chain) alice.Chain {
|
||||
if fr.jwt.enabled {
|
||||
if len(fr.jwt.prevSecret) == 0 {
|
||||
chain = chain.Append(handler.Authorize(fr.jwt.secret,
|
||||
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
|
||||
handler.WithUnauthorizedCallback(ng.unauthorizedCallback)))
|
||||
} else {
|
||||
chain = chain.Append(handler.Authorize(fr.jwt.secret,
|
||||
handler.WithPrevSecret(fr.jwt.prevSecret),
|
||||
handler.WithUnauthorizedCallback(s.unauthorizedCallback)))
|
||||
handler.WithUnauthorizedCallback(ng.unauthorizedCallback)))
|
||||
}
|
||||
}
|
||||
|
||||
return verifier(chain)
|
||||
}
|
||||
|
||||
func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
|
||||
verifier, err := s.signatureVerifier(fr.signature)
|
||||
func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
|
||||
verifier, err := ng.signatureVerifier(fr.signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, route := range fr.routes {
|
||||
if err := s.bindRoute(fr, router, metrics, route, verifier); err != nil {
|
||||
if err := ng.bindRoute(fr, router, metrics, route, verifier); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -104,24 +81,24 @@ func (s *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
|
||||
func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
|
||||
route Route, verifier func(chain alice.Chain) alice.Chain) error {
|
||||
chain := alice.New(
|
||||
handler.TracingHandler(s.conf.Name, route.Path),
|
||||
s.getLogHandler(),
|
||||
handler.TracingHandler(ng.conf.Name, route.Path),
|
||||
ng.getLogHandler(),
|
||||
handler.PrometheusHandler(route.Path),
|
||||
handler.MaxConns(s.conf.MaxConns),
|
||||
handler.MaxConns(ng.conf.MaxConns),
|
||||
handler.BreakerHandler(route.Method, route.Path, metrics),
|
||||
handler.SheddingHandler(s.getShedder(fr.priority), metrics),
|
||||
handler.TimeoutHandler(time.Duration(s.conf.Timeout)*time.Millisecond),
|
||||
handler.SheddingHandler(ng.getShedder(fr.priority), metrics),
|
||||
handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
|
||||
handler.RecoverHandler,
|
||||
handler.MetricHandler(metrics),
|
||||
handler.MaxBytesHandler(s.conf.MaxBytes),
|
||||
handler.MaxBytesHandler(ng.conf.MaxBytes),
|
||||
handler.GunzipHandler,
|
||||
)
|
||||
chain = s.appendAuthHandler(fr, chain, verifier)
|
||||
chain = ng.appendAuthHandler(fr, chain, verifier)
|
||||
|
||||
for _, middleware := range s.middlewares {
|
||||
for _, middleware := range ng.middlewares {
|
||||
chain = chain.Append(convertMiddleware(middleware))
|
||||
}
|
||||
handle := chain.ThenFunc(route.Handler)
|
||||
@@ -129,11 +106,11 @@ func (s *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat
|
||||
return router.Handle(route.Method, route.Path, handle)
|
||||
}
|
||||
|
||||
func (s *engine) bindRoutes(router httpx.Router) error {
|
||||
metrics := s.createMetrics()
|
||||
func (ng *engine) bindRoutes(router httpx.Router) error {
|
||||
metrics := ng.createMetrics()
|
||||
|
||||
for _, fr := range s.routes {
|
||||
if err := s.bindFeaturedRoutes(router, fr, metrics); err != nil {
|
||||
for _, fr := range ng.routes {
|
||||
if err := ng.bindFeaturedRoutes(router, fr, metrics); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -141,35 +118,55 @@ func (s *engine) bindRoutes(router httpx.Router) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *engine) createMetrics() *stat.Metrics {
|
||||
func (ng *engine) checkedTimeout(timeout time.Duration) time.Duration {
|
||||
if timeout > 0 {
|
||||
return timeout
|
||||
}
|
||||
|
||||
return time.Duration(ng.conf.Timeout) * time.Millisecond
|
||||
}
|
||||
|
||||
func (ng *engine) createMetrics() *stat.Metrics {
|
||||
var metrics *stat.Metrics
|
||||
|
||||
if len(s.conf.Name) > 0 {
|
||||
metrics = stat.NewMetrics(s.conf.Name)
|
||||
if len(ng.conf.Name) > 0 {
|
||||
metrics = stat.NewMetrics(ng.conf.Name)
|
||||
} else {
|
||||
metrics = stat.NewMetrics(fmt.Sprintf("%s:%d", s.conf.Host, s.conf.Port))
|
||||
metrics = stat.NewMetrics(fmt.Sprintf("%s:%d", ng.conf.Host, ng.conf.Port))
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
func (s *engine) getLogHandler() func(http.Handler) http.Handler {
|
||||
if s.conf.Verbose {
|
||||
func (ng *engine) getLogHandler() func(http.Handler) http.Handler {
|
||||
if ng.conf.Verbose {
|
||||
return handler.DetailedLogHandler
|
||||
}
|
||||
|
||||
return handler.LogHandler
|
||||
}
|
||||
|
||||
func (s *engine) getShedder(priority bool) load.Shedder {
|
||||
if priority && s.priorityShedder != nil {
|
||||
return s.priorityShedder
|
||||
func (ng *engine) getShedder(priority bool) load.Shedder {
|
||||
if priority && ng.priorityShedder != nil {
|
||||
return ng.priorityShedder
|
||||
}
|
||||
|
||||
return s.shedder
|
||||
return ng.shedder
|
||||
}
|
||||
|
||||
func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice.Chain) alice.Chain, error) {
|
||||
func (ng *engine) setTlsConfig(cfg *tls.Config) {
|
||||
ng.tlsConfig = cfg
|
||||
}
|
||||
|
||||
func (ng *engine) setUnauthorizedCallback(callback handler.UnauthorizedCallback) {
|
||||
ng.unauthorizedCallback = callback
|
||||
}
|
||||
|
||||
func (ng *engine) setUnsignedCallback(callback handler.UnsignedCallback) {
|
||||
ng.unsignedCallback = callback
|
||||
}
|
||||
|
||||
func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain alice.Chain) alice.Chain, error) {
|
||||
if !signature.enabled {
|
||||
return func(chain alice.Chain) alice.Chain {
|
||||
return chain
|
||||
@@ -199,9 +196,9 @@ func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice
|
||||
}
|
||||
|
||||
return func(chain alice.Chain) alice.Chain {
|
||||
if s.unsignedCallback != nil {
|
||||
if ng.unsignedCallback != nil {
|
||||
return chain.Append(handler.ContentSecurityHandler(
|
||||
decrypters, signature.Expiry, signature.Strict, s.unsignedCallback))
|
||||
decrypters, signature.Expiry, signature.Strict, ng.unsignedCallback))
|
||||
}
|
||||
|
||||
return chain.Append(handler.ContentSecurityHandler(
|
||||
@@ -209,8 +206,25 @@ func (s *engine) signatureVerifier(signature signatureSetting) (func(chain alice
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *engine) use(middleware Middleware) {
|
||||
s.middlewares = append(s.middlewares, middleware)
|
||||
func (ng *engine) start(router httpx.Router) error {
|
||||
if err := ng.bindRoutes(router); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {
|
||||
return internal.StartHttp(ng.conf.Host, ng.conf.Port, router)
|
||||
}
|
||||
|
||||
return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
|
||||
ng.conf.KeyFile, router, func(srv *http.Server) {
|
||||
if ng.tlsConfig != nil {
|
||||
srv.TLSConfig = ng.tlsConfig
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (ng *engine) use(middleware Middleware) {
|
||||
ng.middlewares = append(ng.middlewares, middleware)
|
||||
}
|
||||
|
||||
func convertMiddleware(ware Middleware) func(http.Handler) http.Handler {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/conf"
|
||||
@@ -143,17 +144,52 @@ Verbose: true
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(yaml), &cnf))
|
||||
ng := newEngine(cnf)
|
||||
ng.AddRoutes(route)
|
||||
ng.addRoutes(route)
|
||||
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
assert.NotNil(t, ng.StartWithRouter(mockedRouter{}))
|
||||
assert.NotNil(t, ng.start(mockedRouter{}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngine_checkedTimeout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
timeout time.Duration
|
||||
expect time.Duration
|
||||
}{
|
||||
{
|
||||
name: "not set",
|
||||
expect: time.Second,
|
||||
},
|
||||
{
|
||||
name: "less",
|
||||
timeout: time.Millisecond * 500,
|
||||
expect: time.Millisecond * 500,
|
||||
},
|
||||
{
|
||||
name: "equal",
|
||||
timeout: time.Second,
|
||||
expect: time.Second,
|
||||
},
|
||||
{
|
||||
name: "more",
|
||||
timeout: time.Millisecond * 1500,
|
||||
expect: time.Millisecond * 1500,
|
||||
},
|
||||
}
|
||||
|
||||
ng := newEngine(RestConf{
|
||||
Timeout: 1000,
|
||||
})
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.expect, ng.checkedTimeout(test.timeout))
|
||||
}
|
||||
}
|
||||
|
||||
type mockedRouter struct{}
|
||||
|
||||
func (m mockedRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/tal-tech/go-zero/core/iox"
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/syncx"
|
||||
"github.com/tal-tech/go-zero/core/timex"
|
||||
"github.com/tal-tech/go-zero/core/utils"
|
||||
"github.com/tal-tech/go-zero/rest/httpx"
|
||||
@@ -23,10 +24,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
limitBodyBytes = 1024
|
||||
slowThreshold = time.Millisecond * 500
|
||||
limitBodyBytes = 1024
|
||||
defaultSlowThreshold = time.Millisecond * 500
|
||||
)
|
||||
|
||||
var slowThreshold = syncx.ForAtomicDuration(defaultSlowThreshold)
|
||||
|
||||
type loggedResponseWriter struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
@@ -140,6 +143,11 @@ func DetailedLogHandler(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// SetSlowThreshold sets the slow threshold.
|
||||
func SetSlowThreshold(threshold time.Duration) {
|
||||
slowThreshold.Set(threshold)
|
||||
}
|
||||
|
||||
func dumpRequest(r *http.Request) string {
|
||||
reqContent, err := httputil.DumpRequest(r, true)
|
||||
if err != nil {
|
||||
@@ -155,7 +163,7 @@ func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *intern
|
||||
logger := logx.WithContext(r.Context())
|
||||
buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s - %s - %s",
|
||||
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration)))
|
||||
if duration > slowThreshold {
|
||||
if duration > slowThreshold.Load() {
|
||||
logger.Slowf("[HTTP] %s - %d - %s - %s - %s - slowcall(%s)",
|
||||
r.Method, code, r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(), timex.ReprOfDuration(duration))
|
||||
}
|
||||
@@ -188,12 +196,13 @@ func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *ut
|
||||
logs *internal.LogCollector) {
|
||||
var buf bytes.Buffer
|
||||
duration := timer.Duration()
|
||||
code := response.writer.code
|
||||
logger := logx.WithContext(r.Context())
|
||||
buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s\n=> %s\n",
|
||||
r.Method, response.writer.code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)))
|
||||
if duration > slowThreshold {
|
||||
r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r)))
|
||||
if duration > defaultSlowThreshold {
|
||||
logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n",
|
||||
r.Method, response.writer.code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r))
|
||||
r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r))
|
||||
}
|
||||
|
||||
body := logs.Flush()
|
||||
@@ -206,7 +215,11 @@ func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *ut
|
||||
buf.WriteString(fmt.Sprintf("<= %s", respBuf))
|
||||
}
|
||||
|
||||
logger.Info(buf.String())
|
||||
if isOkResponse(code) {
|
||||
logger.Info(buf.String())
|
||||
} else {
|
||||
logger.Error(buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func isOkResponse(code int) bool {
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestLogHandlerSlow(t *testing.T) {
|
||||
for _, logHandler := range handlers {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
handler := logHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(slowThreshold + time.Millisecond*50)
|
||||
time.Sleep(defaultSlowThreshold + time.Millisecond*50)
|
||||
}))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
@@ -100,6 +100,12 @@ func TestDetailedLogHandler_Hijack(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetSlowThreshold(t *testing.T) {
|
||||
assert.Equal(t, defaultSlowThreshold, slowThreshold.Load())
|
||||
SetSlowThreshold(time.Second)
|
||||
assert.Equal(t, time.Second, slowThreshold.Load())
|
||||
}
|
||||
|
||||
func BenchmarkLogHandler(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
@@ -1,19 +1,192 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/rest/httpx"
|
||||
"github.com/tal-tech/go-zero/rest/internal"
|
||||
)
|
||||
|
||||
const reason = "Request Timeout"
|
||||
const (
|
||||
statusClientClosedRequest = 499
|
||||
reason = "Request Timeout"
|
||||
)
|
||||
|
||||
// TimeoutHandler returns the handler with given timeout.
|
||||
// If client closed request, code 499 will be logged.
|
||||
// Notice: even if canceled in server side, 499 will be logged as well.
|
||||
func TimeoutHandler(duration time.Duration) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
if duration > 0 {
|
||||
return http.TimeoutHandler(next, duration, reason)
|
||||
return &timeoutHandler{
|
||||
handler: next,
|
||||
dt: duration,
|
||||
}
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
}
|
||||
|
||||
// timeoutHandler is the handler that controls the request timeout.
|
||||
// Why we implement it on our own, because the stdlib implementation
|
||||
// treats the ClientClosedRequest as http.StatusServiceUnavailable.
|
||||
// And we write the codes in logs as code 499, which is defined by nginx.
|
||||
type timeoutHandler struct {
|
||||
handler http.Handler
|
||||
dt time.Duration
|
||||
}
|
||||
|
||||
func (h *timeoutHandler) errorBody() string {
|
||||
return reason
|
||||
}
|
||||
|
||||
func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancelCtx := context.WithTimeout(r.Context(), h.dt)
|
||||
defer cancelCtx()
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
done := make(chan struct{})
|
||||
tw := &timeoutWriter{
|
||||
w: w,
|
||||
h: make(http.Header),
|
||||
req: r,
|
||||
}
|
||||
panicChan := make(chan interface{}, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
panicChan <- p
|
||||
}
|
||||
}()
|
||||
h.handler.ServeHTTP(tw, r)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case p := <-panicChan:
|
||||
panic(p)
|
||||
case <-done:
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
dst := w.Header()
|
||||
for k, vv := range tw.h {
|
||||
dst[k] = vv
|
||||
}
|
||||
if !tw.wroteHeader {
|
||||
tw.code = http.StatusOK
|
||||
}
|
||||
w.WriteHeader(tw.code)
|
||||
w.Write(tw.wbuf.Bytes())
|
||||
case <-ctx.Done():
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
// there isn't any user-defined middleware before TimoutHandler,
|
||||
// so we can guarantee that cancelation in biz related code won't come here.
|
||||
httpx.Error(w, ctx.Err(), func(w http.ResponseWriter, err error) {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
w.WriteHeader(statusClientClosedRequest)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
io.WriteString(w, h.errorBody())
|
||||
})
|
||||
tw.timedOut = true
|
||||
}
|
||||
}
|
||||
|
||||
type timeoutWriter struct {
|
||||
w http.ResponseWriter
|
||||
h http.Header
|
||||
wbuf bytes.Buffer
|
||||
req *http.Request
|
||||
|
||||
mu sync.Mutex
|
||||
timedOut bool
|
||||
wroteHeader bool
|
||||
code int
|
||||
}
|
||||
|
||||
var _ http.Pusher = (*timeoutWriter)(nil)
|
||||
|
||||
// Push implements the Pusher interface.
|
||||
func (tw *timeoutWriter) Push(target string, opts *http.PushOptions) error {
|
||||
if pusher, ok := tw.w.(http.Pusher); ok {
|
||||
return pusher.Push(target, opts)
|
||||
}
|
||||
return http.ErrNotSupported
|
||||
}
|
||||
|
||||
func (tw *timeoutWriter) Header() http.Header { return tw.h }
|
||||
|
||||
func (tw *timeoutWriter) Write(p []byte) (int, error) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
|
||||
if tw.timedOut {
|
||||
return 0, http.ErrHandlerTimeout
|
||||
}
|
||||
|
||||
if !tw.wroteHeader {
|
||||
tw.writeHeaderLocked(http.StatusOK)
|
||||
}
|
||||
return tw.wbuf.Write(p)
|
||||
}
|
||||
|
||||
func (tw *timeoutWriter) writeHeaderLocked(code int) {
|
||||
checkWriteHeaderCode(code)
|
||||
|
||||
switch {
|
||||
case tw.timedOut:
|
||||
return
|
||||
case tw.wroteHeader:
|
||||
if tw.req != nil {
|
||||
caller := relevantCaller()
|
||||
internal.Errorf(tw.req, "http: superfluous response.WriteHeader call from %s (%s:%d)",
|
||||
caller.Function, path.Base(caller.File), caller.Line)
|
||||
}
|
||||
default:
|
||||
tw.wroteHeader = true
|
||||
tw.code = code
|
||||
}
|
||||
}
|
||||
|
||||
func (tw *timeoutWriter) WriteHeader(code int) {
|
||||
tw.mu.Lock()
|
||||
defer tw.mu.Unlock()
|
||||
tw.writeHeaderLocked(code)
|
||||
}
|
||||
|
||||
func checkWriteHeaderCode(code int) {
|
||||
if code < 100 || code > 599 {
|
||||
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
|
||||
}
|
||||
}
|
||||
|
||||
// relevantCaller searches the call stack for the first function outside of net/http.
|
||||
// The purpose of this function is to provide more helpful error messages.
|
||||
func relevantCaller() runtime.Frame {
|
||||
pc := make([]uintptr, 16)
|
||||
n := runtime.Callers(1, pc)
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
var frame runtime.Frame
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
if !strings.HasPrefix(frame.Function, "net/http.") {
|
||||
return frame
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -39,6 +40,20 @@ func TestWithinTimeout(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
}
|
||||
|
||||
func TestWithTimeoutTimedout(t *testing.T) {
|
||||
timeoutHandler := TimeoutHandler(time.Millisecond)
|
||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
w.Write([]byte(`foo`))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
handler.ServeHTTP(resp, req)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
|
||||
}
|
||||
|
||||
func TestWithoutTimeout(t *testing.T) {
|
||||
timeoutHandler := TimeoutHandler(0)
|
||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -50,3 +65,91 @@ func TestWithoutTimeout(t *testing.T) {
|
||||
handler.ServeHTTP(resp, req)
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
}
|
||||
|
||||
func TestTimeoutPanic(t *testing.T) {
|
||||
timeoutHandler := TimeoutHandler(time.Minute)
|
||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("foo")
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
assert.Panics(t, func() {
|
||||
handler.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimeoutWroteHeaderTwice(t *testing.T) {
|
||||
timeoutHandler := TimeoutHandler(time.Minute)
|
||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`hello`))
|
||||
w.Header().Set("foo", "bar")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
handler.ServeHTTP(resp, req)
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
}
|
||||
|
||||
func TestTimeoutWriteBadCode(t *testing.T) {
|
||||
timeoutHandler := TimeoutHandler(time.Minute)
|
||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(1000)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
assert.Panics(t, func() {
|
||||
handler.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimeoutClientClosed(t *testing.T) {
|
||||
timeoutHandler := TimeoutHandler(time.Minute)
|
||||
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(1000)
|
||||
}))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req = req.WithContext(ctx)
|
||||
cancel()
|
||||
resp := httptest.NewRecorder()
|
||||
handler.ServeHTTP(resp, req)
|
||||
assert.Equal(t, statusClientClosedRequest, resp.Code)
|
||||
}
|
||||
|
||||
func TestTimeoutPusher(t *testing.T) {
|
||||
handler := &timeoutWriter{
|
||||
w: mockedPusher{},
|
||||
}
|
||||
|
||||
assert.Panics(t, func() {
|
||||
handler.Push("any", nil)
|
||||
})
|
||||
|
||||
handler = &timeoutWriter{
|
||||
w: httptest.NewRecorder(),
|
||||
}
|
||||
assert.Equal(t, http.ErrNotSupported, handler.Push("any", nil))
|
||||
}
|
||||
|
||||
type mockedPusher struct{}
|
||||
|
||||
func (m mockedPusher) Header() http.Header {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m mockedPusher) Write(bytes []byte) (int, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m mockedPusher) WriteHeader(statusCode int) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (m mockedPusher) Push(target string, opts *http.PushOptions) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package rest
|
||||
|
||||
import "net/http"
|
||||
|
||||
const (
|
||||
allowOrigin = "Access-Control-Allow-Origin"
|
||||
allOrigins = "*"
|
||||
allowMethods = "Access-Control-Allow-Methods"
|
||||
allowHeaders = "Access-Control-Allow-Headers"
|
||||
headers = "Content-Type, Content-Length, Origin"
|
||||
methods = "GET, HEAD, POST, PATCH, PUT, DELETE"
|
||||
)
|
||||
|
||||
// CorsHandler handles cross domain OPTIONS requests.
|
||||
// At most one origin can be specified, other origins are ignored if given.
|
||||
func CorsHandler(origins ...string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if len(origins) > 0 {
|
||||
w.Header().Set(allowOrigin, origins[0])
|
||||
} else {
|
||||
w.Header().Set(allowOrigin, allOrigins)
|
||||
}
|
||||
w.Header().Set(allowMethods, methods)
|
||||
w.Header().Set(allowHeaders, headers)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCorsHandlerWithOrigins(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
origins []string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "allow all origins",
|
||||
expect: allOrigins,
|
||||
},
|
||||
{
|
||||
name: "allow one origin",
|
||||
origins: []string{"local"},
|
||||
expect: "local",
|
||||
},
|
||||
{
|
||||
name: "allow many origins",
|
||||
origins: []string{"local", "remote"},
|
||||
expect: "local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
handler := CorsHandler(test.origins...)
|
||||
handler.ServeHTTP(w, nil)
|
||||
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
|
||||
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ const (
|
||||
formKey = "form"
|
||||
pathKey = "path"
|
||||
headerKey = "header"
|
||||
emptyJson = "{}"
|
||||
maxMemory = 32 << 20 // 32MB
|
||||
maxBodyLen = 8 << 20 // 8MB
|
||||
separator = ";"
|
||||
@@ -106,14 +105,12 @@ func ParseHeader(headerValue string) map[string]string {
|
||||
|
||||
// ParseJsonBody parses the post request which contains json in body.
|
||||
func ParseJsonBody(r *http.Request, v interface{}) error {
|
||||
var reader io.Reader
|
||||
if withJsonBody(r) {
|
||||
reader = io.LimitReader(r.Body, maxBodyLen)
|
||||
} else {
|
||||
reader = strings.NewReader(emptyJson)
|
||||
reader := io.LimitReader(r.Body, maxBodyLen)
|
||||
return mapping.UnmarshalJsonReader(reader, v)
|
||||
}
|
||||
|
||||
return mapping.UnmarshalJsonReader(reader, v)
|
||||
return mapping.UnmarshalJsonMap(nil, v)
|
||||
}
|
||||
|
||||
// ParsePath parses the symbols reside in url path.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/rest/pathvar"
|
||||
)
|
||||
|
||||
func TestParseForm(t *testing.T) {
|
||||
@@ -17,7 +18,7 @@ func TestParseForm(t *testing.T) {
|
||||
Percent float64 `form:"percent,optional"`
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil)
|
||||
r, err := http.NewRequest(http.MethodGet, "/a?name=hello&age=18&percent=3.4", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, Parse(r, &v))
|
||||
assert.Equal(t, "hello", v.Name)
|
||||
@@ -25,11 +26,83 @@ func TestParseForm(t *testing.T) {
|
||||
assert.Equal(t, 3.4, v.Percent)
|
||||
}
|
||||
|
||||
func TestParseForm_Error(t *testing.T) {
|
||||
var v struct {
|
||||
Name string `form:"name"`
|
||||
Age int `form:"age"`
|
||||
}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/a?name=hello;", nil)
|
||||
assert.NotNil(t, ParseForm(r, &v))
|
||||
}
|
||||
|
||||
func TestParseHeader(t *testing.T) {
|
||||
m := ParseHeader("key=value;")
|
||||
assert.EqualValues(t, map[string]string{
|
||||
"key": "value",
|
||||
}, m)
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
expect map[string]string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
value: "",
|
||||
expect: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "regular",
|
||||
value: "key=value",
|
||||
expect: map[string]string{"key": "value"},
|
||||
},
|
||||
{
|
||||
name: "next empty",
|
||||
value: "key=value;",
|
||||
expect: map[string]string{"key": "value"},
|
||||
},
|
||||
{
|
||||
name: "regular",
|
||||
value: "key=value;foo",
|
||||
expect: map[string]string{"key": "value"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := ParseHeader(test.value)
|
||||
assert.EqualValues(t, test.expect, m)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePath(t *testing.T) {
|
||||
var v struct {
|
||||
Name string `path:"name"`
|
||||
Age int `path:"age"`
|
||||
}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r = pathvar.WithVars(r, map[string]string{
|
||||
"name": "foo",
|
||||
"age": "18",
|
||||
})
|
||||
err := Parse(r, &v)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "foo", v.Name)
|
||||
assert.Equal(t, 18, v.Age)
|
||||
}
|
||||
|
||||
func TestParsePath_Error(t *testing.T) {
|
||||
var v struct {
|
||||
Name string `path:"name"`
|
||||
Age int `path:"age"`
|
||||
}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
r = pathvar.WithVars(r, map[string]string{
|
||||
"name": "foo",
|
||||
})
|
||||
assert.NotNil(t, Parse(r, &v))
|
||||
}
|
||||
|
||||
func TestParseFormOutOfRange(t *testing.T) {
|
||||
@@ -42,23 +115,23 @@ func TestParseFormOutOfRange(t *testing.T) {
|
||||
pass bool
|
||||
}{
|
||||
{
|
||||
url: "http://hello.com/a?age=5",
|
||||
url: "/a?age=5",
|
||||
pass: false,
|
||||
},
|
||||
{
|
||||
url: "http://hello.com/a?age=10",
|
||||
url: "/a?age=10",
|
||||
pass: true,
|
||||
},
|
||||
{
|
||||
url: "http://hello.com/a?age=15",
|
||||
url: "/a?age=15",
|
||||
pass: true,
|
||||
},
|
||||
{
|
||||
url: "http://hello.com/a?age=20",
|
||||
url: "/a?age=20",
|
||||
pass: false,
|
||||
},
|
||||
{
|
||||
url: "http://hello.com/a?age=28",
|
||||
url: "/a?age=28",
|
||||
pass: false,
|
||||
},
|
||||
}
|
||||
@@ -92,7 +165,7 @@ Content-Disposition: form-data; name="age"
|
||||
18
|
||||
----------------------------220477612388154780019383--`, "\n", "\r\n", -1)
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
|
||||
|
||||
assert.Nil(t, Parse(r, &v))
|
||||
@@ -116,25 +189,39 @@ Content-Disposition: form-data; name="age"
|
||||
18
|
||||
----------------------------22047761238815478001938--`, "\n", "\r\n", -1)
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
r.Header.Set(ContentType, "multipart/form-data; boundary=--------------------------220477612388154780019383")
|
||||
|
||||
assert.NotNil(t, Parse(r, &v))
|
||||
}
|
||||
|
||||
func TestParseJsonBody(t *testing.T) {
|
||||
var v struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
t.Run("has body", func(t *testing.T) {
|
||||
var v struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
body := `{"name":"kevin", "age": 18}`
|
||||
r := httptest.NewRequest(http.MethodPost, "http://localhost:3333/", strings.NewReader(body))
|
||||
r.Header.Set(ContentType, ApplicationJson)
|
||||
body := `{"name":"kevin", "age": 18}`
|
||||
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body))
|
||||
r.Header.Set(ContentType, ApplicationJson)
|
||||
|
||||
assert.Nil(t, Parse(r, &v))
|
||||
assert.Equal(t, "kevin", v.Name)
|
||||
assert.Equal(t, 18, v.Age)
|
||||
assert.Nil(t, Parse(r, &v))
|
||||
assert.Equal(t, "kevin", v.Name)
|
||||
assert.Equal(t, 18, v.Age)
|
||||
})
|
||||
|
||||
t.Run("hasn't body", func(t *testing.T) {
|
||||
var v struct {
|
||||
Name string `json:"name,optional"`
|
||||
Age int `json:"age,optional"`
|
||||
}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
assert.Nil(t, Parse(r, &v))
|
||||
assert.Equal(t, "", v.Name)
|
||||
assert.Equal(t, 0, v.Age)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseRequired(t *testing.T) {
|
||||
@@ -143,7 +230,7 @@ func TestParseRequired(t *testing.T) {
|
||||
Percent float64 `form:"percent"`
|
||||
}{}
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello", nil)
|
||||
r, err := http.NewRequest(http.MethodGet, "/a?name=hello", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, Parse(r, &v))
|
||||
}
|
||||
@@ -153,11 +240,57 @@ func TestParseOptions(t *testing.T) {
|
||||
Position int8 `form:"pos,options=1|2"`
|
||||
}{}
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?pos=4", nil)
|
||||
r, err := http.NewRequest(http.MethodGet, "/a?pos=4", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, Parse(r, &v))
|
||||
}
|
||||
|
||||
func TestParseHeaders(t *testing.T) {
|
||||
type AnonymousStruct struct {
|
||||
XRealIP string `header:"x-real-ip"`
|
||||
Accept string `header:"Accept,optional"`
|
||||
}
|
||||
v := struct {
|
||||
Name string `header:"name,optional"`
|
||||
Percent string `header:"percent"`
|
||||
Addrs []string `header:"addrs"`
|
||||
XForwardedFor string `header:"X-Forwarded-For,optional"`
|
||||
AnonymousStruct
|
||||
}{}
|
||||
request, err := http.NewRequest("POST", "/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request.Header.Set("name", "chenquan")
|
||||
request.Header.Set("percent", "1")
|
||||
request.Header.Add("addrs", "addr1")
|
||||
request.Header.Add("addrs", "addr2")
|
||||
request.Header.Add("X-Forwarded-For", "10.0.10.11")
|
||||
request.Header.Add("x-real-ip", "10.0.11.10")
|
||||
request.Header.Add("Accept", "application/json")
|
||||
err = ParseHeaders(request, &v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "chenquan", v.Name)
|
||||
assert.Equal(t, "1", v.Percent)
|
||||
assert.Equal(t, []string{"addr1", "addr2"}, v.Addrs)
|
||||
assert.Equal(t, "10.0.10.11", v.XForwardedFor)
|
||||
assert.Equal(t, "10.0.11.10", v.XRealIP)
|
||||
assert.Equal(t, "application/json", v.Accept)
|
||||
}
|
||||
|
||||
func TestParseHeaders_Error(t *testing.T) {
|
||||
v := struct {
|
||||
Name string `header:"name"`
|
||||
Age int `header:"age"`
|
||||
}{}
|
||||
|
||||
r := httptest.NewRequest("POST", "/", nil)
|
||||
r.Header.Set("name", "foo")
|
||||
assert.NotNil(t, Parse(r, &v))
|
||||
}
|
||||
|
||||
func BenchmarkParseRaw(b *testing.B) {
|
||||
r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", nil)
|
||||
if err != nil {
|
||||
@@ -201,38 +334,3 @@ func BenchmarkParseAuto(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHeaders(t *testing.T) {
|
||||
type AnonymousStruct struct {
|
||||
XRealIP string `header:"x-real-ip"`
|
||||
Accept string `header:"Accept,optional"`
|
||||
}
|
||||
v := struct {
|
||||
Name string `header:"name,optional"`
|
||||
Percent string `header:"percent"`
|
||||
Addrs []string `header:"addrs"`
|
||||
XForwardedFor string `header:"X-Forwarded-For,optional"`
|
||||
AnonymousStruct
|
||||
}{}
|
||||
request, err := http.NewRequest("POST", "http://hello.com/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request.Header.Set("name", "chenquan")
|
||||
request.Header.Set("percent", "1")
|
||||
request.Header.Add("addrs", "addr1")
|
||||
request.Header.Add("addrs", "addr2")
|
||||
request.Header.Add("X-Forwarded-For", "10.0.10.11")
|
||||
request.Header.Add("x-real-ip", "10.0.11.10")
|
||||
request.Header.Add("Accept", "application/json")
|
||||
err = ParseHeaders(request, &v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "chenquan", v.Name)
|
||||
assert.Equal(t, "1", v.Percent)
|
||||
assert.Equal(t, []string{"addr1", "addr2"}, v.Addrs)
|
||||
assert.Equal(t, "10.0.10.11", v.XForwardedFor)
|
||||
assert.Equal(t, "10.0.11.10", v.XRealIP)
|
||||
assert.Equal(t, "application/json", v.Accept)
|
||||
}
|
||||
|
||||
@@ -14,17 +14,21 @@ var (
|
||||
)
|
||||
|
||||
// Error writes err into w.
|
||||
func Error(w http.ResponseWriter, err error) {
|
||||
func Error(w http.ResponseWriter, err error, fns ...func(w http.ResponseWriter, err error)) {
|
||||
lock.RLock()
|
||||
handler := errorHandler
|
||||
lock.RUnlock()
|
||||
|
||||
if handler == nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
if len(fns) > 0 {
|
||||
fns[0](w, err)
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
code, body := errorHandler(err)
|
||||
code, body := handler(err)
|
||||
if body == nil {
|
||||
w.WriteHeader(code)
|
||||
return
|
||||
|
||||
@@ -95,6 +95,18 @@ func TestError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorWithHandler(t *testing.T) {
|
||||
w := tracedResponseWriter{
|
||||
headers: make(map[string][]string),
|
||||
}
|
||||
Error(&w, errors.New("foo"), func(w http.ResponseWriter, err error) {
|
||||
http.Error(w, err.Error(), 499)
|
||||
})
|
||||
assert.Equal(t, 499, w.code)
|
||||
assert.True(t, w.hasBody)
|
||||
assert.Equal(t, "foo", strings.TrimSpace(w.builder.String()))
|
||||
}
|
||||
|
||||
func TestOk(t *testing.T) {
|
||||
w := tracedResponseWriter{
|
||||
headers: make(map[string][]string),
|
||||
|
||||
@@ -10,5 +10,6 @@ func GetRemoteAddr(r *http.Request) string {
|
||||
if len(v) > 0 {
|
||||
return v
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
@@ -16,3 +16,10 @@ func TestGetRemoteAddr(t *testing.T) {
|
||||
r.Header.Set(xForwardedFor, host)
|
||||
assert.Equal(t, host, GetRemoteAddr(r))
|
||||
}
|
||||
|
||||
func TestGetRemoteAddrNoHeader(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "/", strings.NewReader(""))
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.True(t, len(GetRemoteAddr(r)) == 0)
|
||||
}
|
||||
|
||||
150
rest/internal/cors/handlers.go
Normal file
150
rest/internal/cors/handlers.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
allowOrigin = "Access-Control-Allow-Origin"
|
||||
allOrigins = "*"
|
||||
allowMethods = "Access-Control-Allow-Methods"
|
||||
allowHeaders = "Access-Control-Allow-Headers"
|
||||
allowCredentials = "Access-Control-Allow-Credentials"
|
||||
exposeHeaders = "Access-Control-Expose-Headers"
|
||||
requestMethod = "Access-Control-Request-Method"
|
||||
requestHeaders = "Access-Control-Request-Headers"
|
||||
allowHeadersVal = "Content-Type, Origin, X-CSRF-Token, Authorization, AccessToken, Token, Range"
|
||||
exposeHeadersVal = "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers"
|
||||
methods = "GET, HEAD, POST, PATCH, PUT, DELETE"
|
||||
allowTrue = "true"
|
||||
maxAgeHeader = "Access-Control-Max-Age"
|
||||
maxAgeHeaderVal = "86400"
|
||||
varyHeader = "Vary"
|
||||
originHeader = "Origin"
|
||||
)
|
||||
|
||||
// NotAllowedHandler handles cross domain not allowed requests.
|
||||
// At most one origin can be specified, other origins are ignored if given, default to be *.
|
||||
func NotAllowedHandler(fn func(w http.ResponseWriter), origins ...string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gw := &guardedResponseWriter{w: w}
|
||||
checkAndSetHeaders(gw, r, origins)
|
||||
if fn != nil {
|
||||
fn(gw)
|
||||
}
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
gw.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
gw.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Middleware returns a middleware that adds CORS headers to the response.
|
||||
func Middleware(fn func(w http.Header), origins ...string) func(http.HandlerFunc) http.HandlerFunc {
|
||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
checkAndSetHeaders(w, r, origins)
|
||||
if fn != nil {
|
||||
fn(w.Header())
|
||||
}
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type guardedResponseWriter struct {
|
||||
w http.ResponseWriter
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (w *guardedResponseWriter) Flush() {
|
||||
if flusher, ok := w.w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *guardedResponseWriter) Header() http.Header {
|
||||
return w.w.Header()
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface.
|
||||
// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
|
||||
func (w *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if hijacked, ok := w.w.(http.Hijacker); ok {
|
||||
return hijacked.Hijack()
|
||||
}
|
||||
|
||||
return nil, nil, errors.New("server doesn't support hijacking")
|
||||
}
|
||||
|
||||
func (w *guardedResponseWriter) Write(bytes []byte) (int, error) {
|
||||
return w.w.Write(bytes)
|
||||
}
|
||||
|
||||
func (w *guardedResponseWriter) WriteHeader(code int) {
|
||||
if w.wroteHeader {
|
||||
return
|
||||
}
|
||||
|
||||
w.w.WriteHeader(code)
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
func checkAndSetHeaders(w http.ResponseWriter, r *http.Request, origins []string) {
|
||||
setVaryHeaders(w, r)
|
||||
|
||||
if len(origins) == 0 {
|
||||
setHeader(w, allOrigins)
|
||||
return
|
||||
}
|
||||
|
||||
origin := r.Header.Get(originHeader)
|
||||
if isOriginAllowed(origins, origin) {
|
||||
setHeader(w, origin)
|
||||
}
|
||||
}
|
||||
|
||||
func isOriginAllowed(allows []string, origin string) bool {
|
||||
for _, o := range allows {
|
||||
if o == allOrigins {
|
||||
return true
|
||||
}
|
||||
|
||||
if o == origin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func setHeader(w http.ResponseWriter, origin string) {
|
||||
header := w.Header()
|
||||
header.Set(allowOrigin, origin)
|
||||
header.Set(allowMethods, methods)
|
||||
header.Set(allowHeaders, allowHeadersVal)
|
||||
header.Set(exposeHeaders, exposeHeadersVal)
|
||||
if origin != allOrigins {
|
||||
header.Set(allowCredentials, allowTrue)
|
||||
}
|
||||
header.Set(maxAgeHeader, maxAgeHeaderVal)
|
||||
}
|
||||
|
||||
func setVaryHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
header := w.Header()
|
||||
header.Add(varyHeader, originHeader)
|
||||
if r.Method == http.MethodOptions {
|
||||
header.Add(varyHeader, requestMethod)
|
||||
header.Add(varyHeader, requestHeaders)
|
||||
}
|
||||
}
|
||||
178
rest/internal/cors/handlers_test.go
Normal file
178
rest/internal/cors/handlers_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCorsHandlerWithOrigins(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
origins []string
|
||||
reqOrigin string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "allow all origins",
|
||||
expect: allOrigins,
|
||||
},
|
||||
{
|
||||
name: "allow one origin",
|
||||
origins: []string{"http://local"},
|
||||
reqOrigin: "http://local",
|
||||
expect: "http://local",
|
||||
},
|
||||
{
|
||||
name: "allow many origins",
|
||||
origins: []string{"http://local", "http://remote"},
|
||||
reqOrigin: "http://local",
|
||||
expect: "http://local",
|
||||
},
|
||||
{
|
||||
name: "allow all origins",
|
||||
reqOrigin: "http://local",
|
||||
expect: "*",
|
||||
},
|
||||
{
|
||||
name: "allow many origins with all mark",
|
||||
origins: []string{"http://local", "http://remote", "*"},
|
||||
reqOrigin: "http://another",
|
||||
expect: "http://another",
|
||||
},
|
||||
{
|
||||
name: "not allow origin",
|
||||
origins: []string{"http://local", "http://remote"},
|
||||
reqOrigin: "http://another",
|
||||
},
|
||||
}
|
||||
|
||||
methods := []string{
|
||||
http.MethodOptions,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for _, method := range methods {
|
||||
test := test
|
||||
t.Run(test.name+"-handler", func(t *testing.T) {
|
||||
r := httptest.NewRequest(method, "http://localhost", nil)
|
||||
r.Header.Set(originHeader, test.reqOrigin)
|
||||
w := httptest.NewRecorder()
|
||||
handler := NotAllowedHandler(nil, test.origins...)
|
||||
handler.ServeHTTP(w, r)
|
||||
if method == http.MethodOptions {
|
||||
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
|
||||
} else {
|
||||
assert.Equal(t, http.StatusNotFound, w.Result().StatusCode)
|
||||
}
|
||||
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
|
||||
})
|
||||
t.Run(test.name+"-handler-custom", func(t *testing.T) {
|
||||
r := httptest.NewRequest(method, "http://localhost", nil)
|
||||
r.Header.Set(originHeader, test.reqOrigin)
|
||||
w := httptest.NewRecorder()
|
||||
handler := NotAllowedHandler(func(w http.ResponseWriter) {
|
||||
w.Header().Set("foo", "bar")
|
||||
}, test.origins...)
|
||||
handler.ServeHTTP(w, r)
|
||||
if method == http.MethodOptions {
|
||||
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
|
||||
} else {
|
||||
assert.Equal(t, http.StatusNotFound, w.Result().StatusCode)
|
||||
}
|
||||
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
|
||||
assert.Equal(t, "bar", w.Header().Get("foo"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for _, method := range methods {
|
||||
test := test
|
||||
t.Run(test.name+"-middleware", func(t *testing.T) {
|
||||
r := httptest.NewRequest(method, "http://localhost", nil)
|
||||
r.Header.Set(originHeader, test.reqOrigin)
|
||||
w := httptest.NewRecorder()
|
||||
handler := Middleware(nil, test.origins...)(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
handler.ServeHTTP(w, r)
|
||||
if method == http.MethodOptions {
|
||||
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
|
||||
} else {
|
||||
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
}
|
||||
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
|
||||
})
|
||||
t.Run(test.name+"-middleware-custom", func(t *testing.T) {
|
||||
r := httptest.NewRequest(method, "http://localhost", nil)
|
||||
r.Header.Set(originHeader, test.reqOrigin)
|
||||
w := httptest.NewRecorder()
|
||||
handler := Middleware(func(header http.Header) {
|
||||
header.Set("foo", "bar")
|
||||
}, test.origins...)(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
handler.ServeHTTP(w, r)
|
||||
if method == http.MethodOptions {
|
||||
assert.Equal(t, http.StatusNoContent, w.Result().StatusCode)
|
||||
} else {
|
||||
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
|
||||
}
|
||||
assert.Equal(t, test.expect, w.Header().Get(allowOrigin))
|
||||
assert.Equal(t, "bar", w.Header().Get("foo"))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuardedResponseWriter_Flush(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||
handler := NotAllowedHandler(func(w http.ResponseWriter) {
|
||||
w.Header().Set("X-Test", "test")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
_, err := w.Write([]byte("content"))
|
||||
assert.Nil(t, err)
|
||||
|
||||
flusher, ok := w.(http.Flusher)
|
||||
assert.True(t, ok)
|
||||
flusher.Flush()
|
||||
}, "foo.com")
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
handler.ServeHTTP(resp, req)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
|
||||
assert.Equal(t, "test", resp.Header().Get("X-Test"))
|
||||
assert.Equal(t, "content", resp.Body.String())
|
||||
}
|
||||
|
||||
func TestGuardedResponseWriter_Hijack(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
writer := &guardedResponseWriter{
|
||||
w: resp,
|
||||
}
|
||||
assert.NotPanics(t, func() {
|
||||
writer.Hijack()
|
||||
})
|
||||
|
||||
writer = &guardedResponseWriter{
|
||||
w: mockedHijackable{resp},
|
||||
}
|
||||
assert.NotPanics(t, func() {
|
||||
writer.Hijack()
|
||||
})
|
||||
}
|
||||
|
||||
type mockedHijackable struct {
|
||||
*httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (m mockedHijackable) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -119,16 +119,13 @@ func VerifySignature(r *http.Request, securityHeader *ContentSecurityHeader, tol
|
||||
}, "\n")
|
||||
actualSignature := codec.HmacBase64(securityHeader.Key, signContent)
|
||||
|
||||
passed := securityHeader.Signature == actualSignature
|
||||
if !passed {
|
||||
logx.Infof("signature different, expect: %s, actual: %s",
|
||||
securityHeader.Signature, actualSignature)
|
||||
}
|
||||
|
||||
if passed {
|
||||
if securityHeader.Signature == actualSignature {
|
||||
return httpx.CodeSignaturePass
|
||||
}
|
||||
|
||||
logx.Infof("signature different, expect: %s, actual: %s",
|
||||
securityHeader.Signature, actualSignature)
|
||||
|
||||
return httpx.CodeSignatureInvalidToken
|
||||
}
|
||||
|
||||
|
||||
@@ -5,31 +5,43 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/core/proc"
|
||||
)
|
||||
|
||||
// StartOption defines the method to customize http.Server.
|
||||
type StartOption func(srv *http.Server)
|
||||
|
||||
// StartHttp starts a http server.
|
||||
func StartHttp(host string, port int, handler http.Handler) error {
|
||||
func StartHttp(host string, port int, handler http.Handler, opts ...StartOption) error {
|
||||
return start(host, port, handler, func(srv *http.Server) error {
|
||||
return srv.ListenAndServe()
|
||||
})
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// StartHttps starts a https server.
|
||||
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler) error {
|
||||
func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler,
|
||||
opts ...StartOption) error {
|
||||
return start(host, port, handler, func(srv *http.Server) error {
|
||||
// certFile and keyFile are set in buildHttpsServer
|
||||
return srv.ListenAndServeTLS(certFile, keyFile)
|
||||
})
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error) (err error) {
|
||||
func start(host string, port int, handler http.Handler, run func(srv *http.Server) error,
|
||||
opts ...StartOption) (err error) {
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||
Handler: handler,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(server)
|
||||
}
|
||||
|
||||
waitForCalled := proc.AddWrapUpListener(func() {
|
||||
server.Shutdown(context.Background())
|
||||
if e := server.Shutdown(context.Background()); err != nil {
|
||||
logx.Error(e)
|
||||
}
|
||||
})
|
||||
defer func() {
|
||||
if err == http.ErrServerClosed {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package pathvar
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -16,7 +15,7 @@ func TestVars(t *testing.T) {
|
||||
}
|
||||
r, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
assert.Nil(t, err)
|
||||
r = r.WithContext(context.WithValue(context.Background(), pathVars, expect))
|
||||
r = WithVars(r, expect)
|
||||
assert.EqualValues(t, expect, Vars(r))
|
||||
}
|
||||
|
||||
|
||||
@@ -105,19 +105,22 @@ func TestPatRouter(t *testing.T) {
|
||||
t.Run(test.method+":"+test.path, func(t *testing.T) {
|
||||
routed := false
|
||||
router := NewRouter()
|
||||
err := router.Handle(test.method, "/a/:b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
routed = true
|
||||
assert.Equal(t, 1, len(pathvar.Vars(r)))
|
||||
}))
|
||||
err := router.Handle(test.method, "/a/:b", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
routed = true
|
||||
assert.Equal(t, 1, len(pathvar.Vars(r)))
|
||||
}))
|
||||
assert.Nil(t, err)
|
||||
err = router.Handle(test.method, "/a/b/c", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
routed = true
|
||||
assert.Nil(t, pathvar.Vars(r))
|
||||
}))
|
||||
err = router.Handle(test.method, "/a/b/c", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
routed = true
|
||||
assert.Nil(t, pathvar.Vars(r))
|
||||
}))
|
||||
assert.Nil(t, err)
|
||||
err = router.Handle(test.method, "/b/c", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
routed = true
|
||||
}))
|
||||
err = router.Handle(test.method, "/b/c", http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
routed = true
|
||||
}))
|
||||
assert.Nil(t, err)
|
||||
|
||||
w := new(mockedResponseWriter)
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/tal-tech/go-zero/core/logx"
|
||||
"github.com/tal-tech/go-zero/rest/handler"
|
||||
"github.com/tal-tech/go-zero/rest/httpx"
|
||||
"github.com/tal-tech/go-zero/rest/internal/cors"
|
||||
"github.com/tal-tech/go-zero/rest/router"
|
||||
)
|
||||
|
||||
type (
|
||||
runOptions struct {
|
||||
start func(*engine) error
|
||||
}
|
||||
|
||||
// RunOption defines the method to customize a Server.
|
||||
RunOption func(*Server)
|
||||
|
||||
// A Server is a http server.
|
||||
Server struct {
|
||||
ngin *engine
|
||||
opts runOptions
|
||||
ngin *engine
|
||||
router httpx.Router
|
||||
}
|
||||
)
|
||||
|
||||
@@ -45,12 +45,8 @@ func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
ngin: newEngine(c),
|
||||
opts: runOptions{
|
||||
start: func(srv *engine) error {
|
||||
return srv.Start()
|
||||
},
|
||||
},
|
||||
ngin: newEngine(c),
|
||||
router: router.NewRouter(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -68,7 +64,7 @@ func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {
|
||||
for _, opt := range opts {
|
||||
opt(&r)
|
||||
}
|
||||
s.ngin.AddRoutes(r)
|
||||
s.ngin.addRoutes(r)
|
||||
}
|
||||
|
||||
// AddRoute adds given route into the Server.
|
||||
@@ -80,7 +76,7 @@ func (s *Server) AddRoute(r Route, opts ...RouteOption) {
|
||||
// Graceful shutdown is enabled by default.
|
||||
// Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
|
||||
func (s *Server) Start() {
|
||||
handleError(s.opts.start(s.ngin))
|
||||
handleError(s.ngin.start(s.router))
|
||||
}
|
||||
|
||||
// Stop stops the Server.
|
||||
@@ -100,6 +96,24 @@ func ToMiddleware(handler func(next http.Handler) http.Handler) Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
// WithCors returns a func to enable CORS for given origin, or default to all origins (*).
|
||||
func WithCors(origin ...string) RunOption {
|
||||
return func(server *Server) {
|
||||
server.router.SetNotAllowedHandler(cors.NotAllowedHandler(nil, origin...))
|
||||
server.Use(cors.Middleware(nil, origin...))
|
||||
}
|
||||
}
|
||||
|
||||
// WithCustomCors returns a func to enable CORS for given origin, or default to all origins (*),
|
||||
// fn lets caller customizing the response.
|
||||
func WithCustomCors(middlewareFn func(header http.Header), notAllowedFn func(http.ResponseWriter),
|
||||
origin ...string) RunOption {
|
||||
return func(server *Server) {
|
||||
server.router.SetNotAllowedHandler(cors.NotAllowedHandler(notAllowedFn, origin...))
|
||||
server.Use(cors.Middleware(middlewareFn, origin...))
|
||||
}
|
||||
}
|
||||
|
||||
// WithJwt returns a func to enable jwt authentication in given route.
|
||||
func WithJwt(secret string) RouteOption {
|
||||
return func(r *featuredRoutes) {
|
||||
@@ -148,16 +162,32 @@ func WithMiddleware(middleware Middleware, rs ...Route) []Route {
|
||||
|
||||
// WithNotFoundHandler returns a RunOption with not found handler set to given handler.
|
||||
func WithNotFoundHandler(handler http.Handler) RunOption {
|
||||
rt := router.NewRouter()
|
||||
rt.SetNotFoundHandler(handler)
|
||||
return WithRouter(rt)
|
||||
return func(server *Server) {
|
||||
server.router.SetNotFoundHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotAllowedHandler returns a RunOption with not allowed handler set to given handler.
|
||||
func WithNotAllowedHandler(handler http.Handler) RunOption {
|
||||
rt := router.NewRouter()
|
||||
rt.SetNotAllowedHandler(handler)
|
||||
return WithRouter(rt)
|
||||
return func(server *Server) {
|
||||
server.router.SetNotAllowedHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix adds group as a prefix to the route paths.
|
||||
func WithPrefix(group string) RouteOption {
|
||||
return func(r *featuredRoutes) {
|
||||
var routes []Route
|
||||
for _, rt := range r.routes {
|
||||
p := path.Join(group, rt.Path)
|
||||
routes = append(routes, Route{
|
||||
Method: rt.Method,
|
||||
Path: p,
|
||||
Handler: rt.Handler,
|
||||
})
|
||||
}
|
||||
r.routes = routes
|
||||
}
|
||||
}
|
||||
|
||||
// WithPriority returns a RunOption with priority.
|
||||
@@ -170,9 +200,7 @@ func WithPriority() RouteOption {
|
||||
// WithRouter returns a RunOption that make server run with given router.
|
||||
func WithRouter(router httpx.Router) RunOption {
|
||||
return func(server *Server) {
|
||||
server.opts.start = func(srv *engine) error {
|
||||
return srv.StartWithRouter(router)
|
||||
}
|
||||
server.router = router
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,17 +214,31 @@ func WithSignature(signature SignatureConf) RouteOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithTimeout returns a RouteOption to set timeout with given value.
|
||||
func WithTimeout(timeout time.Duration) RouteOption {
|
||||
return func(r *featuredRoutes) {
|
||||
r.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithTLSConfig returns a RunOption that with given tls config.
|
||||
func WithTLSConfig(cfg *tls.Config) RunOption {
|
||||
return func(srv *Server) {
|
||||
srv.ngin.setTlsConfig(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithUnauthorizedCallback returns a RunOption that with given unauthorized callback set.
|
||||
func WithUnauthorizedCallback(callback handler.UnauthorizedCallback) RunOption {
|
||||
return func(engine *Server) {
|
||||
engine.ngin.SetUnauthorizedCallback(callback)
|
||||
return func(srv *Server) {
|
||||
srv.ngin.setUnauthorizedCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
// WithUnsignedCallback returns a RunOption that with given unsigned callback set.
|
||||
func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
|
||||
return func(engine *Server) {
|
||||
engine.ngin.SetUnsignedCallback(callback)
|
||||
return func(srv *Server) {
|
||||
srv.ngin.setUnsignedCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tal-tech/go-zero/core/conf"
|
||||
@@ -20,11 +22,6 @@ Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
failStart := func(server *Server) {
|
||||
server.opts.start = func(e *engine) error {
|
||||
return http.ErrServerClosed
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
c RestConf
|
||||
@@ -33,38 +30,40 @@ Port: 54321
|
||||
}{
|
||||
{
|
||||
c: RestConf{},
|
||||
opts: []RunOption{failStart},
|
||||
opts: []RunOption{WithRouter(mockedRouter{}), WithCors()},
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
c: cnf,
|
||||
opts: []RunOption{failStart},
|
||||
opts: []RunOption{WithRouter(mockedRouter{})},
|
||||
},
|
||||
{
|
||||
c: cnf,
|
||||
opts: []RunOption{WithNotAllowedHandler(nil), failStart},
|
||||
opts: []RunOption{WithRouter(mockedRouter{}), WithNotAllowedHandler(nil)},
|
||||
},
|
||||
{
|
||||
c: cnf,
|
||||
opts: []RunOption{WithNotFoundHandler(nil), failStart},
|
||||
opts: []RunOption{WithNotFoundHandler(nil), WithRouter(mockedRouter{})},
|
||||
},
|
||||
{
|
||||
c: cnf,
|
||||
opts: []RunOption{WithUnauthorizedCallback(nil), failStart},
|
||||
opts: []RunOption{WithUnauthorizedCallback(nil), WithRouter(mockedRouter{})},
|
||||
},
|
||||
{
|
||||
c: cnf,
|
||||
opts: []RunOption{WithUnsignedCallback(nil), failStart},
|
||||
opts: []RunOption{WithUnsignedCallback(nil), WithRouter(mockedRouter{})},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
srv, err := NewServer(test.c, test.opts...)
|
||||
var srv *Server
|
||||
var err error
|
||||
if test.fail {
|
||||
_, err = NewServer(test.c, test.opts...)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
srv = MustNewServer(test.c, test.opts...)
|
||||
}
|
||||
|
||||
srv.Use(ToMiddleware(func(next http.Handler) http.Handler {
|
||||
@@ -78,8 +77,21 @@ Port: 54321
|
||||
Handler: nil,
|
||||
}, WithJwt("thesecret"), WithSignature(SignatureConf{}),
|
||||
WithJwtTransition("preivous", "thenewone"))
|
||||
srv.Start()
|
||||
srv.Stop()
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
p := recover()
|
||||
switch v := p.(type) {
|
||||
case error:
|
||||
assert.Equal(t, "foo", v.Error())
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
}()
|
||||
|
||||
srv.Start()
|
||||
srv.Stop()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +190,9 @@ func TestMultiMiddlewares(t *testing.T) {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
},
|
||||
ToMiddleware(func(next http.Handler) http.Handler {
|
||||
return next
|
||||
}),
|
||||
}, Route{
|
||||
Method: http.MethodGet,
|
||||
Path: "/first/:name/:year",
|
||||
@@ -212,8 +227,105 @@ func TestMultiMiddlewares(t *testing.T) {
|
||||
}, m)
|
||||
}
|
||||
|
||||
func TestWithPrefix(t *testing.T) {
|
||||
fr := featuredRoutes{
|
||||
routes: []Route{
|
||||
{
|
||||
Path: "/hello",
|
||||
},
|
||||
{
|
||||
Path: "/world",
|
||||
},
|
||||
},
|
||||
}
|
||||
WithPrefix("/api")(&fr)
|
||||
var vals []string
|
||||
for _, r := range fr.routes {
|
||||
vals = append(vals, r.Path)
|
||||
}
|
||||
assert.EqualValues(t, []string{"/api/hello", "/api/world"}, vals)
|
||||
}
|
||||
|
||||
func TestWithPriority(t *testing.T) {
|
||||
var fr featuredRoutes
|
||||
WithPriority()(&fr)
|
||||
assert.True(t, fr.priority)
|
||||
}
|
||||
|
||||
func TestWithTimeout(t *testing.T) {
|
||||
var fr featuredRoutes
|
||||
WithTimeout(time.Hour)(&fr)
|
||||
assert.Equal(t, time.Hour, fr.timeout)
|
||||
}
|
||||
|
||||
func TestWithTLSConfig(t *testing.T) {
|
||||
const configYaml = `
|
||||
Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
|
||||
testConfig := &tls.Config{
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
c RestConf
|
||||
opts []RunOption
|
||||
res *tls.Config
|
||||
}{
|
||||
{
|
||||
c: cnf,
|
||||
opts: []RunOption{WithTLSConfig(testConfig)},
|
||||
res: testConfig,
|
||||
},
|
||||
{
|
||||
c: cnf,
|
||||
opts: []RunOption{WithUnsignedCallback(nil)},
|
||||
res: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
srv, err := NewServer(testCase.c, testCase.opts...)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, srv.ngin.tlsConfig, testCase.res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithCors(t *testing.T) {
|
||||
const configYaml = `
|
||||
Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
rt := router.NewRouter()
|
||||
srv, err := NewServer(cnf, WithRouter(rt))
|
||||
assert.Nil(t, err)
|
||||
|
||||
opt := WithCors("local")
|
||||
opt(srv)
|
||||
}
|
||||
|
||||
func TestWithCustomCors(t *testing.T) {
|
||||
const configYaml = `
|
||||
Name: foo
|
||||
Port: 54321
|
||||
`
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
|
||||
rt := router.NewRouter()
|
||||
srv, err := NewServer(cnf, WithRouter(rt))
|
||||
assert.Nil(t, err)
|
||||
|
||||
opt := WithCustomCors(func(header http.Header) {
|
||||
header.Set("foo", "bar")
|
||||
}, func(w http.ResponseWriter) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}, "local")
|
||||
opt(srv)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user