Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
70adfeab0d | |||
a45b672c98 | |||
4509323cae | |||
b3f4c670d5 | |||
778dd449e2 | |||
1d16983b67 | |||
f386bffd37 | |||
772bde7938 | |||
ea16f5f825 | |||
c2f34df493 | |||
efe215cd60 | |||
b4f332bf0d | |||
f47fbb1030 | |||
1e8e57a708 | |||
|
5d0959b0a1 | ||
fa8fb3aed7 | |||
cfd2d53a79 | |||
d306f77ffc | |||
e5b0a7e20d | |||
9a5b158b4d |
@@ -55,7 +55,7 @@ type Auth interface {
|
|||||||
type Account struct {
|
type Account struct {
|
||||||
// Metadata any other associated metadata
|
// Metadata any other associated metadata
|
||||||
Metadata metadata.Metadata `json:"metadata"`
|
Metadata metadata.Metadata `json:"metadata"`
|
||||||
// ID of the account e.g. email or uuid
|
// ID of the account e.g. email or id
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
// Type of the account, e.g. service
|
// Type of the account, e.g. service
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
type noopAuth struct {
|
type noopAuth struct {
|
||||||
@@ -61,11 +61,11 @@ func (n *noopAuth) Verify(acc *Account, res *Resource, opts ...VerifyOption) err
|
|||||||
|
|
||||||
// Inspect a token
|
// Inspect a token
|
||||||
func (n *noopAuth) Inspect(token string) (*Account, error) {
|
func (n *noopAuth) Inspect(token string) (*Account, error) {
|
||||||
uid, err := uuid.NewRandom()
|
id, err := id.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Account{ID: uid.String(), Issuer: n.Options().Issuer}, nil
|
return &Account{ID: id, Issuer: n.Options().Issuer}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token generation using an account id and secret
|
// Token generation using an account id and secret
|
||||||
|
@@ -4,10 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"github.com/unistack-org/micro/v3/metadata"
|
||||||
maddr "github.com/unistack-org/micro/v3/util/addr"
|
maddr "github.com/unistack-org/micro/v3/util/addr"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
mnet "github.com/unistack-org/micro/v3/util/net"
|
mnet "github.com/unistack-org/micro/v3/util/net"
|
||||||
"github.com/unistack-org/micro/v3/util/rand"
|
"github.com/unistack-org/micro/v3/util/rand"
|
||||||
)
|
)
|
||||||
@@ -224,7 +224,7 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler
|
|||||||
}
|
}
|
||||||
m.RUnlock()
|
m.RUnlock()
|
||||||
|
|
||||||
id, err := uuid.NewRandom()
|
sid, err := id.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler
|
|||||||
|
|
||||||
sub := &memorySubscriber{
|
sub := &memorySubscriber{
|
||||||
exit: make(chan bool, 1),
|
exit: make(chan bool, 1),
|
||||||
id: id.String(),
|
id: sid,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
batchhandler: handler,
|
batchhandler: handler,
|
||||||
opts: options,
|
opts: options,
|
||||||
@@ -269,7 +269,7 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
|
|||||||
}
|
}
|
||||||
m.RUnlock()
|
m.RUnlock()
|
||||||
|
|
||||||
id, err := uuid.NewRandom()
|
sid, err := id.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -278,7 +278,7 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
|
|||||||
|
|
||||||
sub := &memorySubscriber{
|
sub := &memorySubscriber{
|
||||||
exit: make(chan bool, 1),
|
exit: make(chan bool, 1),
|
||||||
id: id.String(),
|
id: sid,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
opts: options,
|
opts: options,
|
||||||
|
@@ -232,7 +232,7 @@ func SubscribeContext(ctx context.Context) SubscribeOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DisableAutoAck disables auto ack
|
// DisableAutoAck disables auto ack
|
||||||
// DEPRECATED
|
// Deprecated
|
||||||
func DisableAutoAck() SubscribeOption {
|
func DisableAutoAck() SubscribeOption {
|
||||||
return func(o *SubscribeOptions) {
|
return func(o *SubscribeOptions) {
|
||||||
o.AutoAck = false
|
o.AutoAck = false
|
||||||
|
@@ -173,7 +173,7 @@ func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {
|
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {
|
||||||
options := NewMessageOptions(opts...)
|
options := NewMessageOptions(append([]MessageOption{MessageContentType(n.opts.ContentType)}, opts...)...)
|
||||||
return &noopMessage{topic: topic, payload: msg, opts: options}
|
return &noopMessage{topic: topic, payload: msg, opts: options}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -373,7 +373,7 @@ func DialTimeout(d time.Duration) Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithExchange sets the exchange to route a message through
|
// WithExchange sets the exchange to route a message through
|
||||||
// DEPRECATED
|
// Deprecated
|
||||||
func WithExchange(e string) PublishOption {
|
func WithExchange(e string) PublishOption {
|
||||||
return func(o *PublishOptions) {
|
return func(o *PublishOptions) {
|
||||||
o.Exchange = e
|
o.Exchange = e
|
||||||
@@ -514,7 +514,7 @@ func WithSelectOptions(sops ...selector.SelectOption) CallOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithMessageContentType sets the message content type
|
// WithMessageContentType sets the message content type
|
||||||
// DEPRECATED
|
// Deprecated
|
||||||
func WithMessageContentType(ct string) MessageOption {
|
func WithMessageContentType(ct string) MessageOption {
|
||||||
return func(o *MessageOptions) {
|
return func(o *MessageOptions) {
|
||||||
o.ContentType = ct
|
o.ContentType = ct
|
||||||
|
@@ -4,11 +4,18 @@ package config
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultConfig default config
|
// DefaultConfig default config
|
||||||
var DefaultConfig Config = NewConfig()
|
var DefaultConfig Config = NewConfig()
|
||||||
|
|
||||||
|
// DefaultWatcherMinInterval default min interval for poll changes
|
||||||
|
var DefaultWatcherMinInterval = 5 * time.Second
|
||||||
|
|
||||||
|
// DefaultWatcherMinInterval default max interval for poll changes
|
||||||
|
var DefaultWatcherMaxInterval = 9 * time.Second
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrCodecMissing is returned when codec needed and not specified
|
// ErrCodecMissing is returned when codec needed and not specified
|
||||||
ErrCodecMissing = errors.New("codec missing")
|
ErrCodecMissing = errors.New("codec missing")
|
||||||
@@ -30,15 +37,17 @@ type Config interface {
|
|||||||
Load(context.Context, ...LoadOption) error
|
Load(context.Context, ...LoadOption) error
|
||||||
// Save config to sources
|
// Save config to sources
|
||||||
Save(context.Context, ...SaveOption) error
|
Save(context.Context, ...SaveOption) error
|
||||||
// Watch a value for changes
|
// Watch a config for changes
|
||||||
//Watch(context.Context) (Watcher, error)
|
Watch(context.Context, ...WatchOption) (Watcher, error)
|
||||||
// String returns config type name
|
// String returns config type name
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watcher is the config watcher
|
// Watcher is the config watcher
|
||||||
type Watcher interface {
|
type Watcher interface {
|
||||||
// Next() (, error)
|
// Next blocks until update happens or error returned
|
||||||
|
Next() (map[string]interface{}, error)
|
||||||
|
// Stop stops watcher
|
||||||
Stop() error
|
Stop() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -41,11 +42,15 @@ func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
|
|||||||
mopts = append(mopts, mergo.WithAppendSlice)
|
mopts = append(mopts, mergo.WithAppendSlice)
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := rutil.Zero(c.opts.Struct)
|
dst := c.opts.Struct
|
||||||
|
if options.Struct != nil {
|
||||||
|
dst = options.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := rutil.Zero(dst)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
valueOf := reflect.ValueOf(src)
|
if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil {
|
||||||
if err = c.fillValues(valueOf); err == nil {
|
err = mergo.Merge(dst, src, mopts...)
|
||||||
err = mergo.Merge(c.opts.Struct, src, mopts...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +68,7 @@ func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
|
func fillValue(value reflect.Value, val string) error {
|
||||||
if !rutil.IsEmpty(value) {
|
if !rutil.IsEmpty(value) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -80,10 +85,10 @@ func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
|
|||||||
kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' })
|
kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' })
|
||||||
mkey := reflect.Indirect(reflect.New(kt))
|
mkey := reflect.Indirect(reflect.New(kt))
|
||||||
mval := reflect.Indirect(reflect.New(et))
|
mval := reflect.Indirect(reflect.New(et))
|
||||||
if err := c.fillValue(mkey, kv[0]); err != nil {
|
if err := fillValue(mkey, kv[0]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := c.fillValue(mval, kv[1]); err != nil {
|
if err := fillValue(mval, kv[1]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
value.SetMapIndex(mkey, mval)
|
value.SetMapIndex(mkey, mval)
|
||||||
@@ -93,7 +98,7 @@ func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
|
|||||||
value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals)))
|
value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals)))
|
||||||
for idx, nval := range nvals {
|
for idx, nval := range nvals {
|
||||||
nvalue := reflect.Indirect(reflect.New(value.Type().Elem()))
|
nvalue := reflect.Indirect(reflect.New(value.Type().Elem()))
|
||||||
if err := c.fillValue(nvalue, nval); err != nil {
|
if err := fillValue(nvalue, nval); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
value.Index(idx).Set(nvalue)
|
value.Index(idx).Set(nvalue)
|
||||||
@@ -182,7 +187,7 @@ func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultConfig) fillValues(valueOf reflect.Value) error {
|
func fillValues(valueOf reflect.Value, tname string) error {
|
||||||
var values reflect.Value
|
var values reflect.Value
|
||||||
|
|
||||||
if valueOf.Kind() == reflect.Ptr {
|
if valueOf.Kind() == reflect.Ptr {
|
||||||
@@ -209,7 +214,7 @@ func (c *defaultConfig) fillValues(valueOf reflect.Value) error {
|
|||||||
switch value.Kind() {
|
switch value.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
value.Set(reflect.Indirect(reflect.New(value.Type())))
|
value.Set(reflect.Indirect(reflect.New(value.Type())))
|
||||||
if err := c.fillValues(value); err != nil {
|
if err := fillValues(value, tname); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -223,17 +228,17 @@ func (c *defaultConfig) fillValues(valueOf reflect.Value) error {
|
|||||||
value.Set(reflect.New(value.Type().Elem()))
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
}
|
}
|
||||||
value = value.Elem()
|
value = value.Elem()
|
||||||
if err := c.fillValues(value); err != nil {
|
if err := fillValues(value, tname); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tag, ok := field.Tag.Lookup(c.opts.StructTag)
|
tag, ok := field.Tag.Lookup(tname)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.fillValue(value, tag); err != nil {
|
if err := fillValue(value, tag); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,6 +270,10 @@ func (c *defaultConfig) Name() string {
|
|||||||
return c.opts.Name
|
return c.opts.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// NewConfig returns new default config source
|
// NewConfig returns new default config source
|
||||||
func NewConfig(opts ...Option) Config {
|
func NewConfig(opts ...Option) Config {
|
||||||
options := NewOptions(opts...)
|
options := NewOptions(opts...)
|
||||||
|
@@ -47,6 +47,6 @@ func TestDefault(t *testing.T) {
|
|||||||
if conf.StringValue != "after_load" {
|
if conf.StringValue != "after_load" {
|
||||||
t.Fatal("AfterLoad option not working")
|
t.Fatal("AfterLoad option not working")
|
||||||
}
|
}
|
||||||
|
_ = conf
|
||||||
t.Logf("%#+v\n", conf)
|
//t.Logf("%#+v\n", conf)
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"github.com/unistack-org/micro/v3/codec"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
@@ -62,6 +63,7 @@ type LoadOption func(o *LoadOptions)
|
|||||||
|
|
||||||
// LoadOptions struct
|
// LoadOptions struct
|
||||||
type LoadOptions struct {
|
type LoadOptions struct {
|
||||||
|
Struct interface{}
|
||||||
Override bool
|
Override bool
|
||||||
Append bool
|
Append bool
|
||||||
}
|
}
|
||||||
@@ -88,13 +90,29 @@ func LoadAppend(b bool) LoadOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadStruct override struct for loading
|
||||||
|
func LoadStruct(src interface{}) LoadOption {
|
||||||
|
return func(o *LoadOptions) {
|
||||||
|
o.Struct = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SaveOption function signature
|
// SaveOption function signature
|
||||||
type SaveOption func(o *SaveOptions)
|
type SaveOption func(o *SaveOptions)
|
||||||
|
|
||||||
// SaveOptions struct
|
// SaveOptions struct
|
||||||
type SaveOptions struct {
|
type SaveOptions struct {
|
||||||
|
Struct interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveStruct override struct for save to config
|
||||||
|
func SaveStruct(src interface{}) SaveOption {
|
||||||
|
return func(o *SaveOptions) {
|
||||||
|
o.Struct = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSaveOptions fill SaveOptions struct
|
||||||
func NewSaveOptions(opts ...SaveOption) SaveOptions {
|
func NewSaveOptions(opts ...SaveOption) SaveOptions {
|
||||||
options := SaveOptions{}
|
options := SaveOptions{}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
@@ -186,3 +204,60 @@ func Name(n string) Option {
|
|||||||
o.Name = n
|
o.Name = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WatchOptions struuct
|
||||||
|
type WatchOptions struct {
|
||||||
|
// Context used by non default options
|
||||||
|
Context context.Context
|
||||||
|
// Coalesce multiple events to one
|
||||||
|
Coalesce bool
|
||||||
|
// MinInterval specifies the min time.Duration interval for poll changes
|
||||||
|
MinInterval time.Duration
|
||||||
|
// MaxInterval specifies the max time.Duration interval for poll changes
|
||||||
|
MaxInterval time.Duration
|
||||||
|
// Struct for filling
|
||||||
|
Struct interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WatchOption func(*WatchOptions)
|
||||||
|
|
||||||
|
func NewWatchOptions(opts ...WatchOption) WatchOptions {
|
||||||
|
options := WatchOptions{
|
||||||
|
Context: context.Background(),
|
||||||
|
MinInterval: DefaultWatcherMinInterval,
|
||||||
|
MaxInterval: DefaultWatcherMaxInterval,
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchContext pass context
|
||||||
|
func WatchContext(ctx context.Context) WatchOption {
|
||||||
|
return func(o *WatchOptions) {
|
||||||
|
o.Context = ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchCoalesce controls watch event combining
|
||||||
|
func WatchCoalesce(b bool) WatchOption {
|
||||||
|
return func(o *WatchOptions) {
|
||||||
|
o.Coalesce = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchInterval specifies min and max time.Duration for pulling changes
|
||||||
|
func WatchInterval(min, max time.Duration) WatchOption {
|
||||||
|
return func(o *WatchOptions) {
|
||||||
|
o.MinInterval = min
|
||||||
|
o.MaxInterval = max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchStruct overrides struct for fill
|
||||||
|
func WatchStruct(src interface{}) WatchOption {
|
||||||
|
return func(o *WatchOptions) {
|
||||||
|
o.Struct = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
package config_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
rutil "github.com/unistack-org/micro/v3/util/reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
SubConfig *SubConfig
|
|
||||||
Config *Config
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubConfig struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReflect(t *testing.T) {
|
|
||||||
cfg1 := &Config{Value: "cfg1", Config: &Config{Value: "cfg1_1"}, SubConfig: &SubConfig{Value: "cfg1"}}
|
|
||||||
cfg2, err := rutil.Zero(cfg1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Logf("dst: %#+v\n", cfg2)
|
|
||||||
}
|
|
@@ -37,7 +37,7 @@ var (
|
|||||||
|
|
||||||
// Error type
|
// Error type
|
||||||
type Error struct {
|
type Error struct {
|
||||||
// Id holds error id or service, usually someting like my_service or uuid
|
// Id holds error id or service, usually someting like my_service or id
|
||||||
Id string
|
Id string
|
||||||
// Detail holds some useful details about error
|
// Detail holds some useful details about error
|
||||||
Detail string
|
Detail string
|
||||||
|
@@ -6,13 +6,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/silas/dag"
|
"github.com/silas/dag"
|
||||||
"github.com/unistack-org/micro/v3/client"
|
"github.com/unistack-org/micro/v3/client"
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"github.com/unistack-org/micro/v3/codec"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"github.com/unistack-org/micro/v3/metadata"
|
||||||
"github.com/unistack-org/micro/v3/store"
|
"github.com/unistack-org/micro/v3/store"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
type microFlow struct {
|
type microFlow struct {
|
||||||
@@ -149,18 +149,18 @@ func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) {
|
|||||||
return steps, nil
|
return steps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *microWorkflow) Abort(ctx context.Context, eid string) error {
|
func (w *microWorkflow) Abort(ctx context.Context, id string) error {
|
||||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id))
|
||||||
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
|
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *microWorkflow) Suspend(ctx context.Context, eid string) error {
|
func (w *microWorkflow) Suspend(ctx context.Context, id string) error {
|
||||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id))
|
||||||
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
|
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *microWorkflow) Resume(ctx context.Context, eid string) error {
|
func (w *microWorkflow) Resume(ctx context.Context, id string) error {
|
||||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id))
|
||||||
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
|
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,11 +176,10 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
|
|||||||
}
|
}
|
||||||
w.Unlock()
|
w.Unlock()
|
||||||
|
|
||||||
uid, err := uuid.NewRandom()
|
eid, err := id.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
eid := uid.String()
|
|
||||||
|
|
||||||
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid))
|
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid))
|
||||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
||||||
@@ -330,7 +329,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
|
|||||||
close(cherr)
|
close(cherr)
|
||||||
case <-chstatus:
|
case <-chstatus:
|
||||||
close(chstatus)
|
close(chstatus)
|
||||||
return uid.String(), nil
|
return eid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@@ -351,7 +350,7 @@ func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...Execu
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return uid.String(), err
|
return eid, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFlow(opts ...Option) Flow {
|
func NewFlow(opts ...Option) Flow {
|
||||||
|
@@ -116,11 +116,11 @@ type Workflow interface {
|
|||||||
// Steps returns steps slice where parallel steps returned on the same level
|
// Steps returns steps slice where parallel steps returned on the same level
|
||||||
Steps() ([][]Step, error)
|
Steps() ([][]Step, error)
|
||||||
// Suspend suspends execution
|
// Suspend suspends execution
|
||||||
Suspend(ctx context.Context, eid string) error
|
Suspend(ctx context.Context, id string) error
|
||||||
// Resume resumes execution
|
// Resume resumes execution
|
||||||
Resume(ctx context.Context, eid string) error
|
Resume(ctx context.Context, id string) error
|
||||||
// Abort abort execution
|
// Abort abort execution
|
||||||
Abort(ctx context.Context, eid string) error
|
Abort(ctx context.Context, id string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flow the base interface to interact with workflows
|
// Flow the base interface to interact with workflows
|
||||||
|
5
go.mod
5
go.mod
@@ -3,11 +3,12 @@ module github.com/unistack-org/micro/v3
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
|
||||||
github.com/ef-ds/deque v1.0.4
|
github.com/ef-ds/deque v1.0.4
|
||||||
github.com/google/uuid v1.3.0
|
github.com/golang-jwt/jwt/v4 v4.0.0
|
||||||
github.com/imdario/mergo v0.3.12
|
github.com/imdario/mergo v0.3.12
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
|
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
github.com/unistack-org/micro-proto v0.0.5
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed
|
||||||
)
|
)
|
||||||
|
26
go.sum
26
go.sum
@@ -1,15 +1,25 @@
|
|||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
|
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
|
||||||
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
|
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI=
|
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI=
|
||||||
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
|
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/unistack-org/micro-proto v0.0.5 h1:DIC97Hufa2nGjuvTsfToD9laEOKddWMRTzeCfBwJ1j8=
|
||||||
|
github.com/unistack-org/micro-proto v0.0.5/go.mod h1:EuI7UlfGXmT1hy6WacULib9LbNgRnDYQvTCFoLgKM2I=
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -17,6 +27,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@@ -40,7 +40,6 @@ func (l *defaultLogger) Init(opts ...Option) error {
|
|||||||
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
|
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
|
||||||
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
|
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Unlock()
|
l.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -56,26 +55,20 @@ func (l *defaultLogger) V(level Level) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
|
func (l *defaultLogger) Fields(fields ...interface{}) Logger {
|
||||||
nl := &defaultLogger{opts: l.opts, enc: l.enc}
|
nl := &defaultLogger{opts: l.opts, enc: l.enc}
|
||||||
nl.opts.Fields = make(map[string]interface{}, len(l.opts.Fields)+len(fields))
|
if len(fields) == 0 {
|
||||||
l.RLock()
|
return nl
|
||||||
for k, v := range l.opts.Fields {
|
} else if len(fields)%2 != 0 {
|
||||||
nl.opts.Fields[k] = v
|
fields = fields[:len(fields)-1]
|
||||||
}
|
|
||||||
l.RUnlock()
|
|
||||||
|
|
||||||
for k, v := range fields {
|
|
||||||
nl.opts.Fields[k] = v
|
|
||||||
}
|
}
|
||||||
|
nl.opts.Fields = append(l.opts.Fields, fields...)
|
||||||
return nl
|
return nl
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFields(src map[string]interface{}) map[string]interface{} {
|
func copyFields(src []interface{}) []interface{} {
|
||||||
dst := make(map[string]interface{}, len(src))
|
dst := make([]interface{}, len(src))
|
||||||
for k, v := range src {
|
copy(dst, src)
|
||||||
dst[k] = v
|
|
||||||
}
|
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,19 +155,23 @@ func (l *defaultLogger) Log(ctx context.Context, level Level, args ...interface{
|
|||||||
fields := copyFields(l.opts.Fields)
|
fields := copyFields(l.opts.Fields)
|
||||||
l.RUnlock()
|
l.RUnlock()
|
||||||
|
|
||||||
fields["level"] = level.String()
|
fields = append(fields, "level", level.String())
|
||||||
|
|
||||||
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
|
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
|
||||||
fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
|
fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
|
||||||
}
|
}
|
||||||
|
fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
fields["msg"] = fmt.Sprint(args...)
|
fields = append(fields, "msg", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out := make(map[string]interface{}, len(fields)/2)
|
||||||
|
for i := 0; i < len(fields); i += 2 {
|
||||||
|
out[fields[i].(string)] = fields[i+1]
|
||||||
|
}
|
||||||
l.RLock()
|
l.RLock()
|
||||||
_ = l.enc.Encode(fields)
|
_ = l.enc.Encode(out)
|
||||||
l.RUnlock()
|
l.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,30 +184,30 @@ func (l *defaultLogger) Logf(ctx context.Context, level Level, msg string, args
|
|||||||
fields := copyFields(l.opts.Fields)
|
fields := copyFields(l.opts.Fields)
|
||||||
l.RUnlock()
|
l.RUnlock()
|
||||||
|
|
||||||
fields["level"] = level.String()
|
fields = append(fields, "level", level.String())
|
||||||
|
|
||||||
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
|
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
|
||||||
fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
|
fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
|
||||||
}
|
}
|
||||||
|
|
||||||
fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
|
fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
fields["msg"] = fmt.Sprintf(msg, args...)
|
fields = append(fields, "msg", fmt.Sprintf(msg, args...))
|
||||||
} else if msg != "" {
|
} else if msg != "" {
|
||||||
fields["msg"] = msg
|
fields = append(fields, "msg", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]interface{}, len(fields)/2)
|
||||||
|
for i := 0; i < len(fields); i += 2 {
|
||||||
|
out[fields[i].(string)] = fields[i+1]
|
||||||
}
|
}
|
||||||
l.RLock()
|
l.RLock()
|
||||||
_ = l.enc.Encode(fields)
|
_ = l.enc.Encode(out)
|
||||||
l.RUnlock()
|
l.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *defaultLogger) Options() Options {
|
func (l *defaultLogger) Options() Options {
|
||||||
// not guard against options Context values
|
return l.opts
|
||||||
l.RLock()
|
|
||||||
opts := l.opts
|
|
||||||
opts.Fields = copyFields(l.opts.Fields)
|
|
||||||
l.RUnlock()
|
|
||||||
return opts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLogger builds a new logger based on options
|
// NewLogger builds a new logger based on options
|
||||||
|
@@ -20,8 +20,8 @@ type Logger interface {
|
|||||||
V(level Level) bool
|
V(level Level) bool
|
||||||
// The Logger options
|
// The Logger options
|
||||||
Options() Options
|
Options() Options
|
||||||
// Fields set fields to always be logged
|
// Fields set fields to always be logged with keyval pairs
|
||||||
Fields(fields map[string]interface{}) Logger
|
Fields(fields ...interface{}) Logger
|
||||||
// Info level message
|
// Info level message
|
||||||
Info(ctx context.Context, args ...interface{})
|
Info(ctx context.Context, args ...interface{})
|
||||||
// Trace level message
|
// Trace level message
|
||||||
@@ -54,6 +54,9 @@ type Logger interface {
|
|||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Field contains keyval pair
|
||||||
|
type Field interface{}
|
||||||
|
|
||||||
// Info writes msg to default logger on info level
|
// Info writes msg to default logger on info level
|
||||||
func Info(ctx context.Context, args ...interface{}) {
|
func Info(ctx context.Context, args ...interface{}) {
|
||||||
DefaultLogger.Info(ctx, args...)
|
DefaultLogger.Info(ctx, args...)
|
||||||
@@ -125,6 +128,6 @@ func Init(opts ...Option) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fields create logger with specific fields
|
// Fields create logger with specific fields
|
||||||
func Fields(fields map[string]interface{}) Logger {
|
func Fields(fields ...interface{}) Logger {
|
||||||
return DefaultLogger.Fields(fields)
|
return DefaultLogger.Fields(fields...)
|
||||||
}
|
}
|
||||||
|
@@ -3,9 +3,37 @@ package logger
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestRedirectStdLogger(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
|
||||||
|
if err := l.Init(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fn := RedirectStdLogger(l, ErrorLevel)
|
||||||
|
defer fn()
|
||||||
|
log.Print("test")
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
|
||||||
|
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStdLogger(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
|
||||||
|
if err := l.Init(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
lg := NewStdLogger(l, ErrorLevel)
|
||||||
|
lg.Print("test")
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
|
||||||
|
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLogger(t *testing.T) {
|
func TestLogger(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
@@ -15,7 +43,7 @@ func TestLogger(t *testing.T) {
|
|||||||
}
|
}
|
||||||
l.Trace(ctx, "trace_msg1")
|
l.Trace(ctx, "trace_msg1")
|
||||||
l.Warn(ctx, "warn_msg1")
|
l.Warn(ctx, "warn_msg1")
|
||||||
l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message")
|
l.Fields("error", "test").Info(ctx, "error message")
|
||||||
l.Warn(ctx, "first", " ", "second")
|
l.Warn(ctx, "first", " ", "second")
|
||||||
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
|
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
|
||||||
t.Fatalf("logger error, buf %s", buf.Bytes())
|
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||||
|
@@ -16,7 +16,7 @@ type Options struct {
|
|||||||
// Context holds exernal options
|
// Context holds exernal options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
// Fields holds additional metadata
|
// Fields holds additional metadata
|
||||||
Fields map[string]interface{}
|
Fields []interface{}
|
||||||
// Name holds the logger name
|
// Name holds the logger name
|
||||||
Name string
|
Name string
|
||||||
// CallerSkipCount number of frmaes to skip
|
// CallerSkipCount number of frmaes to skip
|
||||||
@@ -31,7 +31,7 @@ type Options struct {
|
|||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
Level: DefaultLevel,
|
Level: DefaultLevel,
|
||||||
Fields: make(map[string]interface{}),
|
Fields: make([]interface{}, 0, 6),
|
||||||
Out: os.Stderr,
|
Out: os.Stderr,
|
||||||
CallerSkipCount: DefaultCallerSkipCount,
|
CallerSkipCount: DefaultCallerSkipCount,
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
@@ -43,7 +43,7 @@ func NewOptions(opts ...Option) Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithFields set default fields for the logger
|
// WithFields set default fields for the logger
|
||||||
func WithFields(fields map[string]interface{}) Option {
|
func WithFields(fields ...interface{}) Option {
|
||||||
return func(o *Options) {
|
return func(o *Options) {
|
||||||
o.Fields = fields
|
o.Fields = fields
|
||||||
}
|
}
|
||||||
|
35
logger/stdlogger.go
Normal file
35
logger/stdlogger.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stdLogger struct {
|
||||||
|
l Logger
|
||||||
|
level Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdLogger(l Logger, level Level) *log.Logger {
|
||||||
|
return log.New(&stdLogger{l: l, level: level}, "" /* prefix */, 0 /* flags */)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *stdLogger) Write(p []byte) (int, error) {
|
||||||
|
p = bytes.TrimSpace(p)
|
||||||
|
sl.l.Log(sl.l.Options().Context, sl.level, string(p))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RedirectStdLogger(l Logger, level Level) func() {
|
||||||
|
flags := log.Flags()
|
||||||
|
prefix := log.Prefix()
|
||||||
|
writer := log.Writer()
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("")
|
||||||
|
log.SetOutput(&stdLogger{l: l, level: level})
|
||||||
|
return func() {
|
||||||
|
log.SetFlags(flags)
|
||||||
|
log.SetPrefix(prefix)
|
||||||
|
log.SetOutput(writer)
|
||||||
|
}
|
||||||
|
}
|
@@ -44,8 +44,8 @@ func (w *OmitLogger) Options() Options {
|
|||||||
return w.l.Options()
|
return w.l.Options()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *OmitLogger) Fields(fields map[string]interface{}) Logger {
|
func (w *OmitLogger) Fields(fields ...interface{}) Logger {
|
||||||
return w.l.Fields(fields)
|
return w.l.Fields(fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
|
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
package meter
|
package meter
|
||||||
|
|
||||||
//go:generate protoc -I./handler -I../ -I/home/vtolstov/.cache/go-path/pkg/mod/github.com/unistack-org/micro-proto@v0.0.1 --micro_out=components=micro|http|server,standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto
|
//go:generate sh -c "protoc -I./handler -I../ -I$(go list -f '{{ .Dir }}' -m github.com/unistack-org/micro-proto) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto"
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/unistack-org/micro-proto/api"
|
||||||
|
_ "github.com/unistack-org/micro-proto/openapiv2"
|
||||||
|
)
|
||||||
|
@@ -11,17 +11,17 @@ service Meter {
|
|||||||
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
|
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
option (micro.openapiv2.openapiv2_operation) = {
|
option (micro.openapiv2.openapiv2_operation) = {
|
||||||
operation_id: "Metrics";
|
operation_id: "Metrics";
|
||||||
responses: {
|
responses: {
|
||||||
key: "default";
|
response_code: {
|
||||||
|
name: "default";
|
||||||
value: {
|
value: {
|
||||||
description: "Error response";
|
json_reference: {
|
||||||
schema: {
|
description: "Error response";
|
||||||
json_schema: {
|
_ref: "micro.codec.Frame";
|
||||||
ref: "micro.codec.Frame";
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
option (micro.api.http) = { get: "/metrics"; };
|
option (micro.api.http) = { get: "/metrics"; };
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
// Code generated by protoc-gen-micro
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// protoc-gen-go-micro version: v3.4.2
|
||||||
// source: handler.proto
|
// source: handler.proto
|
||||||
|
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,15 +10,21 @@ import (
|
|||||||
codec "github.com/unistack-org/micro/v3/codec"
|
codec "github.com/unistack-org/micro/v3/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewMeterEndpoints() []*api.Endpoint {
|
var (
|
||||||
return []*api.Endpoint{
|
MeterName = "Meter"
|
||||||
&api.Endpoint{
|
|
||||||
|
MeterEndpoints = []api.Endpoint{
|
||||||
|
api.Endpoint{
|
||||||
Name: "Meter.Metrics",
|
Name: "Meter.Metrics",
|
||||||
Path: []string{"/metrics"},
|
Path: []string{"/metrics"},
|
||||||
Method: []string{"GET"},
|
Method: []string{"GET"},
|
||||||
Handler: "rpc",
|
Handler: "rpc",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMeterEndpoints() []api.Endpoint {
|
||||||
|
return MeterEndpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
type MeterServer interface {
|
type MeterServer interface {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
// Code generated by protoc-gen-micro
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// protoc-gen-go-micro version: v3.4.2
|
||||||
// source: handler.proto
|
// source: handler.proto
|
||||||
|
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -26,8 +28,8 @@ func RegisterMeterServer(s server.Server, sh MeterServer, opts ...server.Handler
|
|||||||
}
|
}
|
||||||
h := &meterServer{sh}
|
h := &meterServer{sh}
|
||||||
var nopts []server.HandlerOption
|
var nopts []server.HandlerOption
|
||||||
for _, endpoint := range NewMeterEndpoints() {
|
for _, endpoint := range MeterEndpoints {
|
||||||
nopts = append(nopts, api.WithEndpoint(endpoint))
|
nopts = append(nopts, api.WithEndpoint(&endpoint))
|
||||||
}
|
}
|
||||||
return s.Handle(s.NewHandler(&Meter{h}, append(nopts, opts...)...))
|
return s.Handle(s.NewHandler(&Meter{h}, append(nopts, opts...)...))
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
"github.com/unistack-org/micro/v3/meter"
|
"github.com/unistack-org/micro/v3/meter"
|
||||||
"github.com/unistack-org/micro/v3/network/tunnel"
|
"github.com/unistack-org/micro/v3/network/tunnel"
|
||||||
"github.com/unistack-org/micro/v3/proxy"
|
"github.com/unistack-org/micro/v3/proxy"
|
||||||
"github.com/unistack-org/micro/v3/router"
|
"github.com/unistack-org/micro/v3/router"
|
||||||
"github.com/unistack-org/micro/v3/tracer"
|
"github.com/unistack-org/micro/v3/tracer"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Option func
|
// Option func
|
||||||
@@ -119,7 +119,7 @@ func Tracer(t tracer.Tracer) Option {
|
|||||||
// NewOptions returns network default options
|
// NewOptions returns network default options
|
||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
Id: uuid.New().String(),
|
Id: id.Must(),
|
||||||
Name: "go.micro",
|
Name: "go.micro",
|
||||||
Address: ":0",
|
Address: ":0",
|
||||||
Logger: logger.DefaultLogger,
|
Logger: logger.DefaultLogger,
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/unistack-org/micro/v3/broker"
|
"github.com/unistack-org/micro/v3/broker"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
|
"github.com/unistack-org/micro/v3/metadata"
|
||||||
"github.com/unistack-org/micro/v3/network/transport"
|
"github.com/unistack-org/micro/v3/network/transport"
|
||||||
"github.com/unistack-org/micro/v3/network/tunnel"
|
"github.com/unistack-org/micro/v3/network/tunnel"
|
||||||
)
|
)
|
||||||
|
@@ -3,11 +3,11 @@ package tunnel
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
"github.com/unistack-org/micro/v3/meter"
|
"github.com/unistack-org/micro/v3/meter"
|
||||||
"github.com/unistack-org/micro/v3/network/transport"
|
"github.com/unistack-org/micro/v3/network/transport"
|
||||||
"github.com/unistack-org/micro/v3/tracer"
|
"github.com/unistack-org/micro/v3/tracer"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
|
|||||||
// NewOptions returns router default options with filled values
|
// NewOptions returns router default options with filled values
|
||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
ID: uuid.New().String(),
|
ID: id.Must(),
|
||||||
Address: DefaultAddress,
|
Address: DefaultAddress,
|
||||||
Token: DefaultToken,
|
Token: DefaultToken,
|
||||||
Logger: logger.DefaultLogger,
|
Logger: logger.DefaultLogger,
|
||||||
|
@@ -6,8 +6,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -378,13 +378,16 @@ func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Servi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
|
func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
|
||||||
|
id, err := id.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
wo := NewWatchOptions(opts...)
|
wo := NewWatchOptions(opts...)
|
||||||
|
|
||||||
// construct the watcher
|
// construct the watcher
|
||||||
w := &watcher{
|
w := &watcher{
|
||||||
exit: make(chan bool),
|
exit: make(chan bool),
|
||||||
res: make(chan *Result),
|
res: make(chan *Result),
|
||||||
id: uuid.New().String(),
|
id: id,
|
||||||
wo: wo,
|
wo: wo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,9 +3,9 @@ package router
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
"github.com/unistack-org/micro/v3/register"
|
"github.com/unistack-org/micro/v3/register"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options are router options
|
// Options are router options
|
||||||
@@ -80,7 +80,7 @@ func Name(n string) Option {
|
|||||||
// NewOptions returns router default options
|
// NewOptions returns router default options
|
||||||
func NewOptions(opts ...Option) Options {
|
func NewOptions(opts ...Option) Options {
|
||||||
options := Options{
|
options := Options{
|
||||||
Id: uuid.New().String(),
|
Id: id.Must(),
|
||||||
Network: DefaultNetwork,
|
Network: DefaultNetwork,
|
||||||
Register: register.DefaultRegister,
|
Register: register.DefaultRegister,
|
||||||
Logger: logger.DefaultLogger,
|
Logger: logger.DefaultLogger,
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
//go:generate protoc -I./health -I../ -I/home/vtolstov/.cache/go-path/pkg/mod/github.com/unistack-org/micro-proto@v0.0.1 --micro_out=components=micro|http|server,standalone=false,debug=true,paths=source_relative:./health health/health.proto
|
//go:generate sh -c "protoc -I./health -I../ -I$(go list -f '{{ .Dir }}' -m github.com/unistack-org/micro-proto) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./health health/health.proto"
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/unistack-org/micro-proto/api"
|
||||||
|
_ "github.com/unistack-org/micro-proto/openapiv2"
|
||||||
|
)
|
||||||
|
@@ -11,51 +11,51 @@ service Health {
|
|||||||
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
|
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
option (micro.openapiv2.openapiv2_operation) = {
|
option (micro.openapiv2.openapiv2_operation) = {
|
||||||
operation_id: "Live";
|
operation_id: "Live";
|
||||||
responses: {
|
responses: {
|
||||||
key: "default";
|
response_code: {
|
||||||
|
name: "default";
|
||||||
value: {
|
value: {
|
||||||
description: "Error response";
|
json_reference: {
|
||||||
schema: {
|
description: "Error response";
|
||||||
json_schema: {
|
_ref: "micro.codec.Frame";
|
||||||
ref: "micro.codec.Frame";
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
option (micro.api.http) = { get: "/live"; };
|
option (micro.api.http) = { get: "/live"; };
|
||||||
};
|
};
|
||||||
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
|
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
option (micro.openapiv2.openapiv2_operation) = {
|
option (micro.openapiv2.openapiv2_operation) = {
|
||||||
operation_id: "Ready";
|
operation_id: "Ready";
|
||||||
responses: {
|
responses: {
|
||||||
key: "default";
|
response_code: {
|
||||||
|
name: "default";
|
||||||
value: {
|
value: {
|
||||||
description: "Error response";
|
json_reference: {
|
||||||
schema: {
|
description: "Error response";
|
||||||
json_schema: {
|
_ref: "micro.codec.Frame";
|
||||||
ref: "micro.codec.Frame";
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
option (micro.api.http) = { get: "/ready"; };
|
option (micro.api.http) = { get: "/ready"; };
|
||||||
};
|
};
|
||||||
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
|
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||||
option (micro.openapiv2.openapiv2_operation) = {
|
option (micro.openapiv2.openapiv2_operation) = {
|
||||||
operation_id: "Version";
|
operation_id: "Version";
|
||||||
responses: {
|
responses: {
|
||||||
key: "default";
|
response_code: {
|
||||||
|
name: "default";
|
||||||
value: {
|
value: {
|
||||||
description: "Error response";
|
json_reference: {
|
||||||
schema: {
|
description: "Error response";
|
||||||
json_schema: {
|
_ref: "micro.codec.Frame";
|
||||||
ref: "micro.codec.Frame";
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
option (micro.api.http) = { get: "/version"; };
|
option (micro.api.http) = { get: "/version"; };
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
// Code generated by protoc-gen-micro
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// protoc-gen-go-micro version: v3.4.2
|
||||||
// source: health.proto
|
// source: health.proto
|
||||||
|
|
||||||
package health
|
package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,27 +10,33 @@ import (
|
|||||||
codec "github.com/unistack-org/micro/v3/codec"
|
codec "github.com/unistack-org/micro/v3/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHealthEndpoints() []*api.Endpoint {
|
var (
|
||||||
return []*api.Endpoint{
|
HealthName = "Health"
|
||||||
&api.Endpoint{
|
|
||||||
|
HealthEndpoints = []api.Endpoint{
|
||||||
|
api.Endpoint{
|
||||||
Name: "Health.Live",
|
Name: "Health.Live",
|
||||||
Path: []string{"/live"},
|
Path: []string{"/live"},
|
||||||
Method: []string{"GET"},
|
Method: []string{"GET"},
|
||||||
Handler: "rpc",
|
Handler: "rpc",
|
||||||
},
|
},
|
||||||
&api.Endpoint{
|
api.Endpoint{
|
||||||
Name: "Health.Ready",
|
Name: "Health.Ready",
|
||||||
Path: []string{"/ready"},
|
Path: []string{"/ready"},
|
||||||
Method: []string{"GET"},
|
Method: []string{"GET"},
|
||||||
Handler: "rpc",
|
Handler: "rpc",
|
||||||
},
|
},
|
||||||
&api.Endpoint{
|
api.Endpoint{
|
||||||
Name: "Health.Version",
|
Name: "Health.Version",
|
||||||
Path: []string{"/version"},
|
Path: []string{"/version"},
|
||||||
Method: []string{"GET"},
|
Method: []string{"GET"},
|
||||||
Handler: "rpc",
|
Handler: "rpc",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHealthEndpoints() []api.Endpoint {
|
||||||
|
return HealthEndpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthServer interface {
|
type HealthServer interface {
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
// Code generated by protoc-gen-micro
|
// Code generated by protoc-gen-go-micro. DO NOT EDIT.
|
||||||
|
// protoc-gen-go-micro version: v3.4.2
|
||||||
// source: health.proto
|
// source: health.proto
|
||||||
|
|
||||||
package health
|
package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -36,8 +38,8 @@ func RegisterHealthServer(s server.Server, sh HealthServer, opts ...server.Handl
|
|||||||
}
|
}
|
||||||
h := &healthServer{sh}
|
h := &healthServer{sh}
|
||||||
var nopts []server.HandlerOption
|
var nopts []server.HandlerOption
|
||||||
for _, endpoint := range NewHealthEndpoints() {
|
for _, endpoint := range HealthEndpoints {
|
||||||
nopts = append(nopts, api.WithEndpoint(endpoint))
|
nopts = append(nopts, api.WithEndpoint(&endpoint))
|
||||||
}
|
}
|
||||||
return s.Handle(s.NewHandler(&Health{h}, append(nopts, opts...)...))
|
return s.Handle(s.NewHandler(&Health{h}, append(nopts, opts...)...))
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// cprotorpc "github.com/unistack-org/micro-codec-protorpc"
|
|
||||||
"github.com/unistack-org/micro/v3/broker"
|
"github.com/unistack-org/micro/v3/broker"
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"github.com/unistack-org/micro/v3/codec"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
"github.com/unistack-org/micro/v3/register"
|
"github.com/unistack-org/micro/v3/register"
|
||||||
|
maddr "github.com/unistack-org/micro/v3/util/addr"
|
||||||
|
mnet "github.com/unistack-org/micro/v3/util/net"
|
||||||
|
"github.com/unistack-org/micro/v3/util/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultCodecs will be used to encode/decode
|
// DefaultCodecs will be used to encode/decode
|
||||||
@@ -73,8 +75,7 @@ func (n *noopServer) Subscribe(sb Subscriber) error {
|
|||||||
sub, ok := sb.(*subscriber)
|
sub, ok := sb.(*subscriber)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid subscriber: expected *subscriber")
|
return fmt.Errorf("invalid subscriber: expected *subscriber")
|
||||||
}
|
} else if len(sub.handlers) == 0 {
|
||||||
if len(sub.handlers) == 0 {
|
|
||||||
return fmt.Errorf("invalid subscriber: no handler functions")
|
return fmt.Errorf("invalid subscriber: no handler functions")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +108,12 @@ func (n *noopServer) Init(opts ...Option) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if n.handlers == nil {
|
if n.handlers == nil {
|
||||||
n.handlers = make(map[string]Handler)
|
n.handlers = make(map[string]Handler, 1)
|
||||||
}
|
}
|
||||||
if n.subscribers == nil {
|
if n.subscribers == nil {
|
||||||
n.subscribers = make(map[*subscriber][]broker.Subscriber)
|
n.subscribers = make(map[*subscriber][]broker.Subscriber, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.exit == nil {
|
if n.exit == nil {
|
||||||
n.exit = make(chan chan error)
|
n.exit = make(chan chan error)
|
||||||
}
|
}
|
||||||
@@ -202,26 +204,34 @@ func (n *noopServer) Register() error {
|
|||||||
|
|
||||||
cx := config.Context
|
cx := config.Context
|
||||||
|
|
||||||
for sb := range n.subscribers {
|
var sub broker.Subscriber
|
||||||
handler := n.createSubHandler(sb, config)
|
|
||||||
var opts []broker.SubscribeOption
|
|
||||||
if queue := sb.Options().Queue; len(queue) > 0 {
|
|
||||||
opts = append(opts, broker.SubscribeGroup(queue))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for sb := range n.subscribers {
|
||||||
if sb.Options().Context != nil {
|
if sb.Options().Context != nil {
|
||||||
cx = sb.Options().Context
|
cx = sb.Options().Context
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck))
|
opts := []broker.SubscribeOption{broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck)}
|
||||||
|
if queue := sb.Options().Queue; len(queue) > 0 {
|
||||||
|
opts = append(opts, broker.SubscribeGroup(queue))
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.Options().Batch {
|
||||||
|
// batch processing handler
|
||||||
|
sub, err = config.Broker.BatchSubscribe(cx, sb.Topic(), n.newBatchSubHandler(sb, config), opts...)
|
||||||
|
} else {
|
||||||
|
// single processing handler
|
||||||
|
sub, err = config.Broker.Subscribe(cx, sb.Topic(), n.newSubHandler(sb, config), opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if config.Logger.V(logger.InfoLevel) {
|
if config.Logger.V(logger.InfoLevel) {
|
||||||
config.Logger.Infof(n.opts.Context, "subscribing to topic: %s", sb.Topic())
|
config.Logger.Infof(n.opts.Context, "subscribing to topic: %s", sb.Topic())
|
||||||
}
|
}
|
||||||
sub, err := config.Broker.Subscribe(cx, sb.Topic(), handler, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n.subscribers[sb] = []broker.Subscriber{sub}
|
n.subscribers[sb] = []broker.Subscriber{sub}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,9 +313,22 @@ func (n *noopServer) Start() error {
|
|||||||
config := n.Options()
|
config := n.Options()
|
||||||
n.RUnlock()
|
n.RUnlock()
|
||||||
|
|
||||||
|
// use 127.0.0.1 to avoid scan of all network interfaces
|
||||||
|
addr, err := maddr.Extract("127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var rng rand.Rand
|
||||||
|
i := rng.Intn(20000)
|
||||||
|
// set addr with port
|
||||||
|
addr = mnet.HostPort(addr, 10000+i)
|
||||||
|
|
||||||
|
config.Address = addr
|
||||||
|
|
||||||
if config.Logger.V(logger.InfoLevel) {
|
if config.Logger.V(logger.InfoLevel) {
|
||||||
config.Logger.Infof(n.opts.Context, "server [noop] Listening on %s", config.Address)
|
config.Logger.Infof(n.opts.Context, "server [noop] Listening on %s", config.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
n.Lock()
|
n.Lock()
|
||||||
if len(config.Advertise) == 0 {
|
if len(config.Advertise) == 0 {
|
||||||
config.Advertise = config.Address
|
config.Advertise = config.Address
|
||||||
|
106
server/noop_test.go
Normal file
106
server/noop_test.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package server_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/unistack-org/micro/v3/broker"
|
||||||
|
"github.com/unistack-org/micro/v3/client"
|
||||||
|
"github.com/unistack-org/micro/v3/codec"
|
||||||
|
"github.com/unistack-org/micro/v3/metadata"
|
||||||
|
"github.com/unistack-org/micro/v3/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestHandler struct {
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestMessage struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
numMsg int = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *TestHandler) SingleSubHandler(ctx context.Context, msg *codec.Frame) error {
|
||||||
|
//fmt.Printf("msg %s\n", msg.Data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TestHandler) BatchSubHandler(ctxs []context.Context, msgs []*codec.Frame) error {
|
||||||
|
if len(msgs) != 8 {
|
||||||
|
h.t.Fatal("invalid number of messages received")
|
||||||
|
}
|
||||||
|
for idx := 0; idx < len(msgs); idx++ {
|
||||||
|
md, _ := metadata.FromIncomingContext(ctxs[idx])
|
||||||
|
_ = md
|
||||||
|
// fmt.Printf("msg md %v\n", md)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoopSub(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
b := broker.NewBroker()
|
||||||
|
|
||||||
|
if err := b.Init(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.Connect(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := server.NewServer(
|
||||||
|
server.Broker(b),
|
||||||
|
server.Codec("application/octet-stream", codec.NewCodec()),
|
||||||
|
)
|
||||||
|
if err := s.Init(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := client.NewClient(
|
||||||
|
client.Broker(b),
|
||||||
|
client.Codec("application/octet-stream", codec.NewCodec()),
|
||||||
|
client.ContentType("application/octet-stream"),
|
||||||
|
)
|
||||||
|
if err := c.Init(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h := &TestHandler{t: t}
|
||||||
|
|
||||||
|
if err := s.Subscribe(s.NewSubscriber("single_topic", h.SingleSubHandler,
|
||||||
|
server.SubscriberQueue("queue"),
|
||||||
|
)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Subscribe(s.NewSubscriber("batch_topic", h.BatchSubHandler,
|
||||||
|
server.SubscriberQueue("queue"),
|
||||||
|
server.SubscriberBatch(true),
|
||||||
|
)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := make([]client.Message, 0, 8)
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
msgs = append(msgs, c.NewMessage("batch_topic", &codec.Frame{Data: []byte(fmt.Sprintf(`{"name": "test_name %d"}`, i))}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BatchPublish(ctx, msgs); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := s.Stop(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
@@ -71,6 +71,8 @@ type Options struct {
|
|||||||
Version string
|
Version string
|
||||||
// SubWrappers holds the server subscribe wrappers
|
// SubWrappers holds the server subscribe wrappers
|
||||||
SubWrappers []SubscriberWrapper
|
SubWrappers []SubscriberWrapper
|
||||||
|
// BatchSubWrappers holds the server batch subscribe wrappers
|
||||||
|
BatchSubWrappers []BatchSubscriberWrapper
|
||||||
// HdlrWrappers holds the handler wrappers
|
// HdlrWrappers holds the handler wrappers
|
||||||
HdlrWrappers []HandlerWrapper
|
HdlrWrappers []HandlerWrapper
|
||||||
// RegisterAttempts holds the number of register attempts before error
|
// RegisterAttempts holds the number of register attempts before error
|
||||||
@@ -302,6 +304,13 @@ func WrapSubscriber(w SubscriberWrapper) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WrapBatchSubscriber adds a batch subscriber Wrapper to a list of options passed into the server
|
||||||
|
func WrapBatchSubscriber(w BatchSubscriberWrapper) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.BatchSubWrappers = append(o.BatchSubWrappers, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MaxConn specifies maximum number of max simultaneous connections to server
|
// MaxConn specifies maximum number of max simultaneous connections to server
|
||||||
func MaxConn(n int) Option {
|
func MaxConn(n int) Option {
|
||||||
return func(o *Options) {
|
return func(o *Options) {
|
||||||
@@ -354,6 +363,12 @@ type SubscriberOptions struct {
|
|||||||
AutoAck bool
|
AutoAck bool
|
||||||
// BodyOnly flag specifies that message without headers
|
// BodyOnly flag specifies that message without headers
|
||||||
BodyOnly bool
|
BodyOnly bool
|
||||||
|
// Batch flag specifies that message processed in batches
|
||||||
|
Batch bool
|
||||||
|
// BatchSize flag specifies max size of batch
|
||||||
|
BatchSize int
|
||||||
|
// BatchWait flag specifies max wait time for batch filling
|
||||||
|
BatchWait time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSubscriberOptions create new SubscriberOptions
|
// NewSubscriberOptions create new SubscriberOptions
|
||||||
@@ -413,3 +428,32 @@ func SubscriberContext(ctx context.Context) SubscriberOption {
|
|||||||
o.Context = ctx
|
o.Context = ctx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscriberAck control auto ack processing for handler
|
||||||
|
func SubscriberAck(b bool) SubscriberOption {
|
||||||
|
return func(o *SubscriberOptions) {
|
||||||
|
o.AutoAck = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscriberBatch control batch processing for handler
|
||||||
|
func SubscriberBatch(b bool) SubscriberOption {
|
||||||
|
return func(o *SubscriberOptions) {
|
||||||
|
o.Batch = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscriberBatchSize control batch filling size for handler
|
||||||
|
// Batch filling max waiting time controlled by SubscriberBatchWait
|
||||||
|
func SubscriberBatchSize(n int) SubscriberOption {
|
||||||
|
return func(o *SubscriberOptions) {
|
||||||
|
o.BatchSize = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscriberBatchWait control batch filling wait time for handler
|
||||||
|
func SubscriberBatchWait(td time.Duration) SubscriberOption {
|
||||||
|
return func(o *SubscriberOptions) {
|
||||||
|
o.BatchWait = td
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -5,10 +5,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/codec"
|
"github.com/unistack-org/micro/v3/codec"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"github.com/unistack-org/micro/v3/metadata"
|
||||||
"github.com/unistack-org/micro/v3/register"
|
"github.com/unistack-org/micro/v3/register"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultServer default server
|
// DefaultServer default server
|
||||||
@@ -22,7 +22,7 @@ var (
|
|||||||
// DefaultVersion will be used if no version passed
|
// DefaultVersion will be used if no version passed
|
||||||
DefaultVersion = "latest"
|
DefaultVersion = "latest"
|
||||||
// DefaultID will be used if no id passed
|
// DefaultID will be used if no id passed
|
||||||
DefaultID = uuid.New().String()
|
DefaultID = id.Must()
|
||||||
// DefaultRegisterCheck holds func that run before register server
|
// DefaultRegisterCheck holds func that run before register server
|
||||||
DefaultRegisterCheck = func(context.Context) error { return nil }
|
DefaultRegisterCheck = func(context.Context) error { return nil }
|
||||||
// DefaultRegisterInterval holds interval for register
|
// DefaultRegisterInterval holds interval for register
|
||||||
|
@@ -18,7 +18,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
subSig = "func(context.Context, interface{}) error"
|
subSig = "func(context.Context, interface{}) error"
|
||||||
|
batchSubSig = "func([]context.Context, []interface{}) error"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Precompute the reflect type for error. Can't use error directly
|
// Precompute the reflect type for error. Can't use error directly
|
||||||
@@ -57,26 +58,33 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
|
|||||||
return isExported(t.Name()) || t.PkgPath() == ""
|
return isExported(t.Name()) || t.PkgPath() == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateSubscriber func
|
// ValidateSubscriber func signature
|
||||||
func ValidateSubscriber(sub Subscriber) error {
|
func ValidateSubscriber(sub Subscriber) error {
|
||||||
typ := reflect.TypeOf(sub.Subscriber())
|
typ := reflect.TypeOf(sub.Subscriber())
|
||||||
var argType reflect.Type
|
var argType reflect.Type
|
||||||
|
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case reflect.Func:
|
case reflect.Func:
|
||||||
name := "Func"
|
name := "Func"
|
||||||
switch typ.NumIn() {
|
switch typ.NumIn() {
|
||||||
case 2:
|
case 2:
|
||||||
argType = typ.In(1)
|
argType = typ.In(1)
|
||||||
|
if sub.Options().Batch {
|
||||||
|
if argType.Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("subscriber %v dont have required signature %s", name, batchSubSig)
|
||||||
|
}
|
||||||
|
if strings.Compare(fmt.Sprintf("%s", argType), "[]interface{}") == 0 {
|
||||||
|
return fmt.Errorf("subscriber %v dont have required signaure %s", name, batchSubSig)
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
|
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s or %s", name, typ.NumIn(), subSig, batchSubSig)
|
||||||
}
|
}
|
||||||
if !isExportedOrBuiltinType(argType) {
|
if !isExportedOrBuiltinType(argType) {
|
||||||
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
|
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
|
||||||
}
|
}
|
||||||
if typ.NumOut() != 1 {
|
if typ.NumOut() != 1 {
|
||||||
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
|
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s or %s",
|
||||||
name, typ.NumOut(), subSig)
|
name, typ.NumOut(), subSig, batchSubSig)
|
||||||
}
|
}
|
||||||
if returnType := typ.Out(0); returnType != typeOfError {
|
if returnType := typ.Out(0); returnType != typeOfError {
|
||||||
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
|
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
|
||||||
@@ -87,13 +95,12 @@ func ValidateSubscriber(sub Subscriber) error {
|
|||||||
|
|
||||||
for m := 0; m < typ.NumMethod(); m++ {
|
for m := 0; m < typ.NumMethod(); m++ {
|
||||||
method := typ.Method(m)
|
method := typ.Method(m)
|
||||||
|
|
||||||
switch method.Type.NumIn() {
|
switch method.Type.NumIn() {
|
||||||
case 3:
|
case 3:
|
||||||
argType = method.Type.In(2)
|
argType = method.Type.In(2)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
|
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s or %s",
|
||||||
name, method.Name, method.Type.NumIn(), subSig)
|
name, method.Name, method.Type.NumIn(), subSig, batchSubSig)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isExportedOrBuiltinType(argType) {
|
if !isExportedOrBuiltinType(argType) {
|
||||||
@@ -101,8 +108,8 @@ func ValidateSubscriber(sub Subscriber) error {
|
|||||||
}
|
}
|
||||||
if method.Type.NumOut() != 1 {
|
if method.Type.NumOut() != 1 {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"subscriber %v.%v has wrong number of outs: %v require signature %s",
|
"subscriber %v.%v has wrong number of return values: %v require signature %s or %s",
|
||||||
name, method.Name, method.Type.NumOut(), subSig)
|
name, method.Name, method.Type.NumOut(), subSig, batchSubSig)
|
||||||
}
|
}
|
||||||
if returnType := method.Type.Out(0); returnType != typeOfError {
|
if returnType := method.Type.Out(0); returnType != typeOfError {
|
||||||
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
|
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
|
||||||
@@ -183,7 +190,125 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
|
func (n *noopServer) newBatchSubHandler(sb *subscriber, opts Options) broker.BatchHandler {
|
||||||
|
return func(ps broker.Events) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
n.RLock()
|
||||||
|
config := n.opts
|
||||||
|
n.RUnlock()
|
||||||
|
if config.Logger.V(logger.ErrorLevel) {
|
||||||
|
config.Logger.Error(n.opts.Context, "panic recovered: ", r)
|
||||||
|
config.Logger.Error(n.opts.Context, string(debug.Stack()))
|
||||||
|
}
|
||||||
|
err = errors.InternalServerError(n.opts.Name+".subscriber", "panic recovered: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
msgs := make([]Message, 0, len(ps))
|
||||||
|
ctxs := make([]context.Context, 0, len(ps))
|
||||||
|
for _, p := range ps {
|
||||||
|
msg := p.Message()
|
||||||
|
// if we don't have headers, create empty map
|
||||||
|
if msg.Header == nil {
|
||||||
|
msg.Header = metadata.New(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct, _ := msg.Header.Get(metadata.HeaderContentType)
|
||||||
|
if len(ct) == 0 {
|
||||||
|
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
|
||||||
|
ct = defaultContentType
|
||||||
|
}
|
||||||
|
hdr := metadata.Copy(msg.Header)
|
||||||
|
topic, _ := msg.Header.Get(metadata.HeaderTopic)
|
||||||
|
ctxs = append(ctxs, metadata.NewIncomingContext(sb.opts.Context, hdr))
|
||||||
|
msgs = append(msgs, &rpcMessage{
|
||||||
|
topic: topic,
|
||||||
|
contentType: ct,
|
||||||
|
header: msg.Header,
|
||||||
|
body: msg.Body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
results := make(chan error, len(sb.handlers))
|
||||||
|
|
||||||
|
for i := 0; i < len(sb.handlers); i++ {
|
||||||
|
handler := sb.handlers[i]
|
||||||
|
|
||||||
|
var req reflect.Value
|
||||||
|
|
||||||
|
switch handler.reqType.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
req = reflect.New(handler.reqType.Elem())
|
||||||
|
default:
|
||||||
|
req = reflect.New(handler.reqType.Elem()).Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
reqType := handler.reqType
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
cf, err := n.newCodec(msg.ContentType())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rb := reflect.New(req.Type().Elem())
|
||||||
|
if err = cf.ReadBody(bytes.NewReader(msg.Body()), rb.Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.(*rpcMessage).codec = cf
|
||||||
|
msg.(*rpcMessage).payload = rb.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := func(ctxs []context.Context, ms []Message) error {
|
||||||
|
var vals []reflect.Value
|
||||||
|
if sb.typ.Kind() != reflect.Func {
|
||||||
|
vals = append(vals, sb.rcvr)
|
||||||
|
}
|
||||||
|
if handler.ctxType != nil {
|
||||||
|
vals = append(vals, reflect.ValueOf(ctxs))
|
||||||
|
}
|
||||||
|
payloads := reflect.MakeSlice(reqType, 0, len(ms))
|
||||||
|
for _, m := range ms {
|
||||||
|
payloads = reflect.Append(payloads, reflect.ValueOf(m.Payload()))
|
||||||
|
}
|
||||||
|
vals = append(vals, payloads)
|
||||||
|
|
||||||
|
returnValues := handler.method.Call(vals)
|
||||||
|
if rerr := returnValues[0].Interface(); rerr != nil {
|
||||||
|
return rerr.(error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(opts.BatchSubWrappers); i > 0; i-- {
|
||||||
|
fn = opts.BatchSubWrappers[i-1](fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.wg != nil {
|
||||||
|
n.wg.Add(1)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if n.wg != nil {
|
||||||
|
defer n.wg.Done()
|
||||||
|
}
|
||||||
|
results <- fn(ctxs, msgs)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors []string
|
||||||
|
for i := 0; i < len(sb.handlers); i++ {
|
||||||
|
if rerr := <-results; rerr != nil {
|
||||||
|
errors = append(errors, rerr.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errors) > 0 {
|
||||||
|
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo
|
||||||
|
func (n *noopServer) newSubHandler(sb *subscriber, opts Options) broker.Handler {
|
||||||
return func(p broker.Event) (err error) {
|
return func(p broker.Event) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -201,12 +326,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
|||||||
msg := p.Message()
|
msg := p.Message()
|
||||||
// if we don't have headers, create empty map
|
// if we don't have headers, create empty map
|
||||||
if msg.Header == nil {
|
if msg.Header == nil {
|
||||||
msg.Header = make(map[string]string)
|
msg.Header = metadata.New(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
ct := msg.Header["Content-Type"]
|
ct := msg.Header["Content-Type"]
|
||||||
if len(ct) == 0 {
|
if len(ct) == 0 {
|
||||||
msg.Header["Content-Type"] = defaultContentType
|
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
|
||||||
ct = defaultContentType
|
ct = defaultContentType
|
||||||
}
|
}
|
||||||
cf, err := n.newCodec(ct)
|
cf, err := n.newCodec(ct)
|
||||||
@@ -214,12 +339,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr := make(map[string]string, len(msg.Header))
|
hdr := metadata.New(len(msg.Header))
|
||||||
for k, v := range msg.Header {
|
for k, v := range msg.Header {
|
||||||
if k == "Content-Type" {
|
if k == "Content-Type" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hdr[k] = v
|
hdr.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
|
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
|
||||||
@@ -294,7 +419,6 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
|||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,12 +14,20 @@ type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error
|
|||||||
// publication message.
|
// publication message.
|
||||||
type SubscriberFunc func(ctx context.Context, msg Message) error
|
type SubscriberFunc func(ctx context.Context, msg Message) error
|
||||||
|
|
||||||
|
// BatchSubscriberFunc represents a single method of a subscriber. It's used primarily
|
||||||
|
// for the wrappers. What's handed to the actual method is the concrete
|
||||||
|
// publication message. This func used by batch subscribers
|
||||||
|
type BatchSubscriberFunc func(ctxs []context.Context, msgs []Message) error
|
||||||
|
|
||||||
// HandlerWrapper wraps the HandlerFunc and returns the equivalent
|
// HandlerWrapper wraps the HandlerFunc and returns the equivalent
|
||||||
type HandlerWrapper func(HandlerFunc) HandlerFunc
|
type HandlerWrapper func(HandlerFunc) HandlerFunc
|
||||||
|
|
||||||
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
|
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
|
||||||
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
|
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
|
||||||
|
|
||||||
|
// BatchSubscriberWrapper wraps the SubscriberFunc and returns the equivalent
|
||||||
|
type BatchSubscriberWrapper func(BatchSubscriberFunc) BatchSubscriberFunc
|
||||||
|
|
||||||
// StreamWrapper wraps a Stream interface and returns the equivalent.
|
// StreamWrapper wraps a Stream interface and returns the equivalent.
|
||||||
// Because streams exist for the lifetime of a method invocation this
|
// Because streams exist for the lifetime of a method invocation this
|
||||||
// is a convenient way to wrap a Stream as its in use for trace, monitoring,
|
// is a convenient way to wrap a Stream as its in use for trace, monitoring,
|
||||||
|
@@ -4,9 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/auth"
|
"github.com/unistack-org/micro/v3/auth"
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
"github.com/unistack-org/micro/v3/logger"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify the auth credentials and refresh the auth token periodically
|
// Verify the auth credentials and refresh the auth token periodically
|
||||||
@@ -22,7 +22,11 @@ func Verify(a auth.Auth) error {
|
|||||||
auth.WithScopes("service"),
|
auth.WithScopes("service"),
|
||||||
}
|
}
|
||||||
|
|
||||||
acc, err := a.Generate(uuid.New().String(), opts...)
|
id, err := id.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
acc, err := a.Generate(id, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
22
util/id/LICENSE
Normal file
22
util/id/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018-2021 Matous Dzivjak <matousdzivjak@gmail.com>
|
||||||
|
Copyright (c) 2021 Unistack LLC <v.tolstov@unistack.org>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
108
util/id/id.go
Normal file
108
util/id/id.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultAlphabet is the alphabet used for ID characters by default
|
||||||
|
var DefaultAlphabet = []rune("6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz")
|
||||||
|
|
||||||
|
// DefaultSize is the size used for ID by default
|
||||||
|
var DefaultSize = 16
|
||||||
|
|
||||||
|
// getMask generates bit mask used to obtain bits from the random bytes that are used to get index of random character
|
||||||
|
// from the alphabet. Example: if the alphabet has 6 = (110)_2 characters it is sufficient to use mask 7 = (111)_2
|
||||||
|
func getMask(alphabetSize int) int {
|
||||||
|
for i := 1; i <= 8; i++ {
|
||||||
|
mask := (2 << uint(i)) - 1
|
||||||
|
if mask >= alphabetSize-1 {
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns new id or error
|
||||||
|
func New(opts ...Option) (string, error) {
|
||||||
|
options := NewOptions(opts...)
|
||||||
|
|
||||||
|
if len(options.Alphabet) == 0 || len(options.Alphabet) > 255 {
|
||||||
|
return "", errors.New("alphabet must not be empty and contain no more than 255 chars")
|
||||||
|
}
|
||||||
|
if options.Size <= 0 {
|
||||||
|
return "", errors.New("size must be positive integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
chars := options.Alphabet
|
||||||
|
|
||||||
|
mask := getMask(len(chars))
|
||||||
|
// estimate how many random bytes we will need for the ID, we might actually need more but this is tradeoff
|
||||||
|
// between average case and worst case
|
||||||
|
ceilArg := 1.6 * float64(mask*options.Size) / float64(len(options.Alphabet))
|
||||||
|
step := int(math.Ceil(ceilArg))
|
||||||
|
|
||||||
|
id := make([]rune, options.Size)
|
||||||
|
bytes := make([]byte, step)
|
||||||
|
for j := 0; ; {
|
||||||
|
_, err := rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for i := 0; i < step; i++ {
|
||||||
|
currByte := bytes[i] & byte(mask)
|
||||||
|
if currByte < byte(len(chars)) {
|
||||||
|
id[j] = chars[currByte]
|
||||||
|
j++
|
||||||
|
if j == options.Size {
|
||||||
|
return string(id[:options.Size]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must is the same as New but panics on error
|
||||||
|
func Must(opts ...Option) string {
|
||||||
|
id, err := New(opts...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options contains id deneration options
|
||||||
|
type Options struct {
|
||||||
|
Alphabet []rune
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option func signature
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
// Alphabet specifies alphabet to use
|
||||||
|
func Alphabet(alphabet string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Alphabet = []rune(alphabet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size specifies id size
|
||||||
|
func Size(size int) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Size = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOptions returns new Options struct filled by opts
|
||||||
|
func NewOptions(opts ...Option) Options {
|
||||||
|
options := Options{
|
||||||
|
Alphabet: DefaultAlphabet,
|
||||||
|
Size: DefaultSize,
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
41
util/id/id_internal_test.go
Normal file
41
util/id/id_internal_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package id
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHasNoCollisions(t *testing.T) {
|
||||||
|
tries := 100_000
|
||||||
|
used := make(map[string]bool, tries)
|
||||||
|
for i := 0; i < tries; i++ {
|
||||||
|
id := Must()
|
||||||
|
require.False(t, used[id], "shouldn't return colliding IDs")
|
||||||
|
used[id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlatDistribution(t *testing.T) {
|
||||||
|
tries := 100_000
|
||||||
|
alphabet := "abcdefghij"
|
||||||
|
size := 10
|
||||||
|
chars := make(map[rune]int)
|
||||||
|
for i := 0; i < tries; i++ {
|
||||||
|
id := Must(Alphabet(alphabet), Size(size))
|
||||||
|
for _, r := range id {
|
||||||
|
chars[r]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, count := range chars {
|
||||||
|
require.InEpsilon(t, size*tries/len(alphabet), count, .01, "should have flat distribution")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark id generator
|
||||||
|
func BenchmarkNanoid(b *testing.B) {
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
_, _ = New()
|
||||||
|
}
|
||||||
|
}
|
68
util/id/id_test.go
Normal file
68
util/id/id_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package id_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
id "github.com/unistack-org/micro/v3/util/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
t.Run("short alphabet", func(t *testing.T) {
|
||||||
|
alphabet := ""
|
||||||
|
_, err := id.New(id.Alphabet(alphabet), id.Size(32))
|
||||||
|
assert.Error(t, err, "should return error if the alphabet is too small")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("long alphabet", func(t *testing.T) {
|
||||||
|
alphabet := strings.Repeat("a", 256)
|
||||||
|
_, err := id.New(id.Alphabet(alphabet), id.Size(32))
|
||||||
|
assert.Error(t, err, "should return error if the alphabet is too long")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("negative ID length", func(t *testing.T) {
|
||||||
|
_, err := id.New(id.Alphabet("abcdef"), id.Size(-1))
|
||||||
|
assert.Error(t, err, "should return error if the requested ID length is invalid")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("happy path", func(t *testing.T) {
|
||||||
|
alphabet := "abcdef"
|
||||||
|
id, err := id.New(id.Alphabet(alphabet), id.Size(6))
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
assert.Len(t, id, 6, "should return ID of requested length")
|
||||||
|
for _, r := range id {
|
||||||
|
assert.True(t, strings.ContainsRune(alphabet, r), "should use given alphabet")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("works with unicode", func(t *testing.T) {
|
||||||
|
alphabet := "🚀💩🦄🤖"
|
||||||
|
id, err := id.New(id.Alphabet(alphabet), id.Size(6))
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
assert.Equal(t, utf8.RuneCountInString(id), 6, "should return ID of requested length")
|
||||||
|
for _, r := range id {
|
||||||
|
assert.True(t, strings.ContainsRune(alphabet, r), "should use given alphabet")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
t.Run("negative ID length", func(t *testing.T) {
|
||||||
|
_, err := id.New(id.Size(-1))
|
||||||
|
assert.Error(t, err, "should return error if the requested ID length is invalid")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("happy path", func(t *testing.T) {
|
||||||
|
nid, err := id.New()
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
assert.Len(t, nid, id.DefaultSize, "should return ID of default length")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("custom length", func(t *testing.T) {
|
||||||
|
id, err := id.New(id.Size(6))
|
||||||
|
assert.NoError(t, err, "shouldn't return error")
|
||||||
|
assert.Len(t, id, 6, "should return ID of requested length")
|
||||||
|
})
|
||||||
|
}
|
@@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/unistack-org/micro/v3/util/rand"
|
"github.com/unistack-org/micro/v3/util/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Do returns a random time to jitter with max cap specified
|
// Random returns a random time to jitter with max cap specified
|
||||||
func Do(d time.Duration) time.Duration {
|
func Random(d time.Duration) time.Duration {
|
||||||
var rng rand.Rand
|
var rng rand.Rand
|
||||||
v := rng.Float64() * float64(d.Nanoseconds())
|
v := rng.Float64() * float64(d.Nanoseconds())
|
||||||
return time.Duration(v)
|
return time.Duration(v)
|
65
util/jitter/ticker.go
Normal file
65
util/jitter/ticker.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package jitter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/unistack-org/micro/v3/util/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ticker is similar to time.Ticker but ticks at random intervals between
|
||||||
|
// the min and max duration values (stored internally as int64 nanosecond
|
||||||
|
// counts).
|
||||||
|
type Ticker struct {
|
||||||
|
C chan time.Time
|
||||||
|
done chan chan struct{}
|
||||||
|
min int64
|
||||||
|
max int64
|
||||||
|
rng rand.Rand
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTicker returns a pointer to an initialized instance of the Ticker.
|
||||||
|
// Min and max are durations of the shortest and longest allowed
|
||||||
|
// ticks. Ticker will run in a goroutine until explicitly stopped.
|
||||||
|
func NewTicker(min, max time.Duration) *Ticker {
|
||||||
|
ticker := &Ticker{
|
||||||
|
C: make(chan time.Time),
|
||||||
|
done: make(chan chan struct{}),
|
||||||
|
min: min.Nanoseconds(),
|
||||||
|
max: max.Nanoseconds(),
|
||||||
|
}
|
||||||
|
go ticker.run()
|
||||||
|
return ticker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop terminates the ticker goroutine and closes the C channel.
|
||||||
|
func (ticker *Ticker) Stop() {
|
||||||
|
c := make(chan struct{})
|
||||||
|
ticker.done <- c
|
||||||
|
<-c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ticker *Ticker) run() {
|
||||||
|
defer close(ticker.C)
|
||||||
|
t := time.NewTimer(ticker.nextInterval())
|
||||||
|
for {
|
||||||
|
// either a stop signal or a timeout
|
||||||
|
select {
|
||||||
|
case c := <-ticker.done:
|
||||||
|
t.Stop()
|
||||||
|
close(c)
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
select {
|
||||||
|
case ticker.C <- time.Now():
|
||||||
|
t.Stop()
|
||||||
|
t = time.NewTimer(ticker.nextInterval())
|
||||||
|
default:
|
||||||
|
// there could be noone receiving...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ticker *Ticker) nextInterval() time.Duration {
|
||||||
|
return time.Duration(ticker.rng.Int63n(ticker.max-ticker.min)+ticker.min) * time.Nanosecond
|
||||||
|
}
|
@@ -5,8 +5,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/network/transport"
|
"github.com/unistack-org/micro/v3/network/transport"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pool struct {
|
type pool struct {
|
||||||
@@ -87,9 +87,13 @@ func (p *pool) Get(ctx context.Context, addr string, opts ...transport.DialOptio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
id, err := id.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &poolConn{
|
return &poolConn{
|
||||||
Client: c,
|
Client: c,
|
||||||
id: uuid.New().String(),
|
id: id,
|
||||||
created: time.Now(),
|
created: time.Now(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Buffer is ring buffer
|
// Buffer is ring buffer
|
||||||
@@ -112,7 +112,7 @@ func (b *Buffer) Stream() (<-chan *Entry, chan bool) {
|
|||||||
defer b.Unlock()
|
defer b.Unlock()
|
||||||
|
|
||||||
entries := make(chan *Entry, 128)
|
entries := make(chan *Entry, 128)
|
||||||
id := uuid.New().String()
|
id := id.Must()
|
||||||
stop := make(chan bool)
|
stop := make(chan bool)
|
||||||
|
|
||||||
b.streams[id] = &Stream{
|
b.streams[id] = &Stream{
|
||||||
|
@@ -8,18 +8,18 @@ const (
|
|||||||
|
|
||||||
// Template is a compiled representation of path templates.
|
// Template is a compiled representation of path templates.
|
||||||
type Template struct {
|
type Template struct {
|
||||||
// Verb is a VERB part in the template
|
// Version is the version number of the format.
|
||||||
Verb string
|
Version int
|
||||||
// Original template (example: /v1/a_bit_of_everything)
|
// OpCodes is a sequence of operations.
|
||||||
Template string
|
|
||||||
// OpCodes is a sequence of operations
|
|
||||||
OpCodes []int
|
OpCodes []int
|
||||||
// Pool is a constant pool
|
// Pool is a constant pool
|
||||||
Pool []string
|
Pool []string
|
||||||
// Fields is a list of field paths bound in this template
|
// Verb is a VERB part in the template.
|
||||||
|
Verb string
|
||||||
|
// Fields is a list of field paths bound in this template.
|
||||||
Fields []string
|
Fields []string
|
||||||
// Version is the version number of the format
|
// Original template (example: /v1/a_bit_of_everything)
|
||||||
Version int
|
Template string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compiler compiles utilities representation of path templates into marshallable operations.
|
// Compiler compiles utilities representation of path templates into marshallable operations.
|
||||||
@@ -29,9 +29,15 @@ type Compiler interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type op struct {
|
type op struct {
|
||||||
str string
|
// code is the opcode of the operation
|
||||||
code OpCode
|
code OpCode
|
||||||
operand int
|
|
||||||
|
// str is a string operand of the code.
|
||||||
|
// num is ignored if str is not empty.
|
||||||
|
str string
|
||||||
|
|
||||||
|
// num is a numeric operand of the code.
|
||||||
|
num int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w wildcard) compile() []op {
|
func (w wildcard) compile() []op {
|
||||||
@@ -61,8 +67,8 @@ func (v variable) compile() []op {
|
|||||||
ops = append(ops, s.compile()...)
|
ops = append(ops, s.compile()...)
|
||||||
}
|
}
|
||||||
ops = append(ops, op{
|
ops = append(ops, op{
|
||||||
code: OpConcatN,
|
code: OpConcatN,
|
||||||
operand: len(v.segments),
|
num: len(v.segments),
|
||||||
}, op{
|
}, op{
|
||||||
code: OpCapture,
|
code: OpCapture,
|
||||||
str: v.path,
|
str: v.path,
|
||||||
@@ -77,7 +83,6 @@ func (t template) Compile() Template {
|
|||||||
rawOps = append(rawOps, s.compile()...)
|
rawOps = append(rawOps, s.compile()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ops := make([]int, 0, len(rawOps))
|
|
||||||
var (
|
var (
|
||||||
ops []int
|
ops []int
|
||||||
pool []string
|
pool []string
|
||||||
@@ -87,8 +92,12 @@ func (t template) Compile() Template {
|
|||||||
for _, op := range rawOps {
|
for _, op := range rawOps {
|
||||||
ops = append(ops, int(op.code))
|
ops = append(ops, int(op.code))
|
||||||
if op.str == "" {
|
if op.str == "" {
|
||||||
ops = append(ops, op.operand)
|
ops = append(ops, op.num)
|
||||||
} else {
|
} else {
|
||||||
|
// eof segment literal represents the "/" path pattern
|
||||||
|
if op.str == eof {
|
||||||
|
op.str = ""
|
||||||
|
}
|
||||||
if _, ok := consts[op.str]; !ok {
|
if _, ok := consts[op.str]; !ok {
|
||||||
consts[op.str] = len(pool)
|
consts[op.str] = len(pool)
|
||||||
pool = append(pool, op.str)
|
pool = append(pool, op.str)
|
||||||
|
@@ -21,6 +21,13 @@ func TestCompile(t *testing.T) {
|
|||||||
fields []string
|
fields []string
|
||||||
}{
|
}{
|
||||||
{},
|
{},
|
||||||
|
{
|
||||||
|
segs: []segment{
|
||||||
|
literal(eof),
|
||||||
|
},
|
||||||
|
ops: []int{int(OpLitPush), 0},
|
||||||
|
pool: []string{""},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
segs: []segment{
|
segs: []segment{
|
||||||
wildcard{},
|
wildcard{},
|
||||||
|
@@ -3,11 +3,8 @@ package router
|
|||||||
// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/parse.go
|
// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/parse.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/unistack-org/micro/v3/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// InvalidTemplateError indicates that the path template is not valid.
|
// InvalidTemplateError indicates that the path template is not valid.
|
||||||
@@ -83,8 +80,30 @@ func tokenize(path string) (tokens []string, verb string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
l := len(tokens)
|
l := len(tokens)
|
||||||
|
// See
|
||||||
|
// https://github.com/grpc-ecosystem/grpc-gateway/pull/1947#issuecomment-774523693 ;
|
||||||
|
// although normal and backwards-compat logic here is to use the last index
|
||||||
|
// of a colon, if the final segment is a variable followed by a colon, the
|
||||||
|
// part following the colon must be a verb. Hence if the previous token is
|
||||||
|
// an end var marker, we switch the index we're looking for to Index instead
|
||||||
|
// of LastIndex, so that we correctly grab the remaining part of the path as
|
||||||
|
// the verb.
|
||||||
|
var penultimateTokenIsEndVar bool
|
||||||
|
switch l {
|
||||||
|
case 0, 1:
|
||||||
|
// Not enough to be variable so skip this logic and don't result in an
|
||||||
|
// invalid index
|
||||||
|
default:
|
||||||
|
penultimateTokenIsEndVar = tokens[l-2] == "}"
|
||||||
|
}
|
||||||
t := tokens[l-1]
|
t := tokens[l-1]
|
||||||
if idx := strings.LastIndex(t, ":"); idx == 0 {
|
var idx int
|
||||||
|
if penultimateTokenIsEndVar {
|
||||||
|
idx = strings.Index(t, ":")
|
||||||
|
} else {
|
||||||
|
idx = strings.LastIndex(t, ":")
|
||||||
|
}
|
||||||
|
if idx == 0 {
|
||||||
tokens, verb = tokens[:l-1], t[1:]
|
tokens, verb = tokens[:l-1], t[1:]
|
||||||
} else if idx > 0 {
|
} else if idx > 0 {
|
||||||
tokens[l-1], verb = t[:idx], t[idx+1:]
|
tokens[l-1], verb = t[:idx], t[idx+1:]
|
||||||
@@ -101,22 +120,17 @@ type parser struct {
|
|||||||
|
|
||||||
// topLevelSegments is the target of this parser.
|
// topLevelSegments is the target of this parser.
|
||||||
func (p *parser) topLevelSegments() ([]segment, error) {
|
func (p *parser) topLevelSegments() ([]segment, error) {
|
||||||
if logger.V(logger.TraceLevel) {
|
if _, err := p.accept(typeEOF); err == nil {
|
||||||
logger.Trace(context.TODO(), "Parsing %q", p.tokens)
|
p.tokens = p.tokens[:0]
|
||||||
|
return []segment{literal(eof)}, nil
|
||||||
}
|
}
|
||||||
segs, err := p.segments()
|
segs, err := p.segments()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if logger.V(logger.TraceLevel) {
|
|
||||||
logger.Trace(context.TODO(), "accept segments: %q; %q", p.accepted, p.tokens)
|
|
||||||
}
|
|
||||||
if _, err := p.accept(typeEOF); err != nil {
|
if _, err := p.accept(typeEOF); err != nil {
|
||||||
return nil, fmt.Errorf("unexpected token %q after segments %q", p.tokens[0], strings.Join(p.accepted, ""))
|
return nil, fmt.Errorf("unexpected token %q after segments %q", p.tokens[0], strings.Join(p.accepted, ""))
|
||||||
}
|
}
|
||||||
if logger.V(logger.TraceLevel) {
|
|
||||||
logger.Trace(context.TODO(), "accept eof: %q; %q", p.accepted, p.tokens)
|
|
||||||
}
|
|
||||||
return segs, nil
|
return segs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,9 +140,6 @@ func (p *parser) segments() ([]segment, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if logger.V(logger.TraceLevel) {
|
|
||||||
logger.Trace(context.TODO(), "accept segment: %q; %q", p.accepted, p.tokens)
|
|
||||||
}
|
|
||||||
segs := []segment{s}
|
segs := []segment{s}
|
||||||
for {
|
for {
|
||||||
if _, err := p.accept("/"); err != nil {
|
if _, err := p.accept("/"); err != nil {
|
||||||
@@ -139,9 +150,6 @@ func (p *parser) segments() ([]segment, error) {
|
|||||||
return segs, err
|
return segs, err
|
||||||
}
|
}
|
||||||
segs = append(segs, s)
|
segs = append(segs, s)
|
||||||
if logger.V(logger.TraceLevel) {
|
|
||||||
logger.Trace(context.TODO(), "accept segment: %q; %q", p.accepted, p.tokens)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -16,6 +15,7 @@ func TestTokenize(t *testing.T) {
|
|||||||
for _, spec := range []struct {
|
for _, spec := range []struct {
|
||||||
src string
|
src string
|
||||||
tokens []string
|
tokens []string
|
||||||
|
verb string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
src: "",
|
src: "",
|
||||||
@@ -84,32 +84,74 @@ func TestTokenize(t *testing.T) {
|
|||||||
eof,
|
eof,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
src: "v1/a/{endpoint}:a",
|
||||||
|
tokens: []string{
|
||||||
|
"v1", "/",
|
||||||
|
"a", "/",
|
||||||
|
"{", "endpoint", "}",
|
||||||
|
eof,
|
||||||
|
},
|
||||||
|
verb: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "v1/a/{endpoint}:b:c",
|
||||||
|
tokens: []string{
|
||||||
|
"v1", "/",
|
||||||
|
"a", "/",
|
||||||
|
"{", "endpoint", "}",
|
||||||
|
eof,
|
||||||
|
},
|
||||||
|
verb: "b:c",
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
tokens, verb := tokenize(spec.src)
|
tokens, verb := tokenize(spec.src)
|
||||||
if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) {
|
if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) {
|
||||||
t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want)
|
t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want)
|
||||||
}
|
}
|
||||||
if got, want := verb, ""; got != want {
|
|
||||||
t.Errorf("tokenize(%q) = _, %q; want _, %q", spec.src, got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
src := fmt.Sprintf("%s:%s", spec.src, "LOCK")
|
switch {
|
||||||
tokens, verb = tokenize(src)
|
case spec.verb != "":
|
||||||
if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) {
|
if got, want := verb, spec.verb; !reflect.DeepEqual(got, want) {
|
||||||
t.Errorf("tokenize(%q) = %q, _; want %q, _", src, got, want)
|
t.Errorf("tokenize(%q) = %q, _; want %q, _", spec.src, got, want)
|
||||||
}
|
}
|
||||||
if got, want := verb, "LOCK"; got != want {
|
|
||||||
t.Errorf("tokenize(%q) = _, %q; want _, %q", src, got, want)
|
default:
|
||||||
|
if got, want := verb, ""; got != want {
|
||||||
|
t.Errorf("tokenize(%q) = _, %q; want _, %q", spec.src, got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
src := fmt.Sprintf("%s:%s", spec.src, "LOCK")
|
||||||
|
tokens, verb = tokenize(src)
|
||||||
|
if got, want := tokens, spec.tokens; !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("tokenize(%q) = %q, _; want %q, _", src, got, want)
|
||||||
|
}
|
||||||
|
if got, want := verb, "LOCK"; got != want {
|
||||||
|
t.Errorf("tokenize(%q) = _, %q; want _, %q", src, got, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSegments(t *testing.T) {
|
func TestParseSegments(t *testing.T) {
|
||||||
flag.Set("v", "3")
|
|
||||||
for _, spec := range []struct {
|
for _, spec := range []struct {
|
||||||
tokens []string
|
tokens []string
|
||||||
want []segment
|
want []segment
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
tokens: []string{eof},
|
||||||
|
want: []segment{
|
||||||
|
literal(eof),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Note: this case will never arise as tokenize() will never return such a sequence of tokens
|
||||||
|
// and even if it does it will be treated as [eof]
|
||||||
|
tokens: []string{eof, "v1", eof},
|
||||||
|
want: []segment{
|
||||||
|
literal(eof),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
tokens: []string{"v1", eof},
|
tokens: []string{"v1", eof},
|
||||||
want: []segment{
|
want: []segment{
|
||||||
@@ -251,7 +293,6 @@ func TestParseSegments(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSegmentsWithErrors(t *testing.T) {
|
func TestParseSegmentsWithErrors(t *testing.T) {
|
||||||
flag.Set("v", "3")
|
|
||||||
for _, spec := range []struct {
|
for _, spec := range []struct {
|
||||||
tokens []string
|
tokens []string
|
||||||
}{
|
}{
|
||||||
@@ -275,10 +316,6 @@ func TestParseSegmentsWithErrors(t *testing.T) {
|
|||||||
// invalid percent-encoding
|
// invalid percent-encoding
|
||||||
tokens: []string{"a%2z", eof},
|
tokens: []string{"a%2z", eof},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// empty segments
|
|
||||||
tokens: []string{eof},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// unterminated variable
|
// unterminated variable
|
||||||
tokens: []string{"{", "name", eof},
|
tokens: []string{"{", "name", eof},
|
||||||
|
@@ -23,9 +23,9 @@ type rop struct {
|
|||||||
operand int
|
operand int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern is a template pattern of http request paths defined in github.com/googleapis/googleapis/google/api/http.proto.
|
// Pattern is a template pattern of http request paths defined in
|
||||||
|
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
|
||||||
type Pattern struct {
|
type Pattern struct {
|
||||||
verb string
|
|
||||||
// ops is a list of operations
|
// ops is a list of operations
|
||||||
ops []rop
|
ops []rop
|
||||||
// pool is a constant pool indexed by the operands or vars
|
// pool is a constant pool indexed by the operands or vars
|
||||||
@@ -36,32 +36,16 @@ type Pattern struct {
|
|||||||
stacksize int
|
stacksize int
|
||||||
// tailLen is the length of the fixed-size segments after a deep wildcard
|
// tailLen is the length of the fixed-size segments after a deep wildcard
|
||||||
tailLen int
|
tailLen int
|
||||||
// assumeColonVerb indicates whether a path suffix after a final
|
// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
|
||||||
// colon may only be interpreted as a verb.
|
verb string
|
||||||
assumeColonVerb bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type patternOptions struct {
|
|
||||||
assumeColonVerb bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatternOpt is an option for creating Patterns.
|
|
||||||
type PatternOpt func(*patternOptions)
|
|
||||||
|
|
||||||
// NewPattern returns a new Pattern from the given definition values.
|
// NewPattern returns a new Pattern from the given definition values.
|
||||||
// "ops" is a sequence of op codes. "pool" is a constant pool.
|
// "ops" is a sequence of op codes. "pool" is a constant pool.
|
||||||
// "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
|
// "verb" is the verb part of the pattern. It is empty if the pattern does not have the part.
|
||||||
// "version" must be 1 for now.
|
// "version" must be 1 for now.
|
||||||
// It returns an error if the given definition is invalid.
|
// It returns an error if the given definition is invalid.
|
||||||
//nolint:gocyclo
|
func NewPattern(version int, ops []int, pool []string, verb string) (Pattern, error) {
|
||||||
func NewPattern(version int, ops []int, pool []string, verb string, opts ...PatternOpt) (Pattern, error) {
|
|
||||||
options := patternOptions{
|
|
||||||
assumeColonVerb: true,
|
|
||||||
}
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if version != 1 {
|
if version != 1 {
|
||||||
if logger.V(logger.TraceLevel) {
|
if logger.V(logger.TraceLevel) {
|
||||||
logger.Trace(context.TODO(), "unsupported version: %d", version)
|
logger.Trace(context.TODO(), "unsupported version: %d", version)
|
||||||
@@ -159,13 +143,12 @@ func NewPattern(version int, ops []int, pool []string, verb string, opts ...Patt
|
|||||||
typedOps = append(typedOps, op)
|
typedOps = append(typedOps, op)
|
||||||
}
|
}
|
||||||
return Pattern{
|
return Pattern{
|
||||||
ops: typedOps,
|
ops: typedOps,
|
||||||
pool: pool,
|
pool: pool,
|
||||||
vars: vars,
|
vars: vars,
|
||||||
stacksize: maxstack,
|
stacksize: maxstack,
|
||||||
tailLen: tailLen,
|
tailLen: tailLen,
|
||||||
verb: verb,
|
verb: verb,
|
||||||
assumeColonVerb: options.assumeColonVerb,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +168,7 @@ func MustPattern(p Pattern, err error) Pattern {
|
|||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
|
func (p Pattern) Match(components []string, verb string) (map[string]string, error) {
|
||||||
if p.verb != verb {
|
if p.verb != verb {
|
||||||
if p.assumeColonVerb || p.verb != "" {
|
if p.verb != "" {
|
||||||
return nil, ErrNotMatch
|
return nil, ErrNotMatch
|
||||||
}
|
}
|
||||||
if len(components) == 0 {
|
if len(components) == 0 {
|
||||||
@@ -274,11 +257,3 @@ func (p Pattern) String() string {
|
|||||||
}
|
}
|
||||||
return "/" + segs
|
return "/" + segs
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssumeColonVerbOpt indicates whether a path suffix after a final
|
|
||||||
// colon may only be interpreted as a verb.
|
|
||||||
func AssumeColonVerbOpt(val bool) PatternOpt {
|
|
||||||
return PatternOpt(func(o *patternOptions) {
|
|
||||||
o.assumeColonVerb = val
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type template struct {
|
type template struct {
|
||||||
|
segments []segment
|
||||||
verb string
|
verb string
|
||||||
template string
|
template string
|
||||||
segments []segment
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type segment interface {
|
type segment interface {
|
||||||
|
@@ -6,9 +6,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/unistack-org/micro/v3/auth"
|
"github.com/unistack-org/micro/v3/auth"
|
||||||
"github.com/unistack-org/micro/v3/store"
|
"github.com/unistack-org/micro/v3/store"
|
||||||
|
"github.com/unistack-org/micro/v3/util/id"
|
||||||
"github.com/unistack-org/micro/v3/util/token"
|
"github.com/unistack-org/micro/v3/util/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,7 +44,11 @@ func (b *Basic) Generate(acc *auth.Account, opts ...token.GenerateOption) (*toke
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write to the store
|
// write to the store
|
||||||
key := uuid.New().String()
|
key, err := id.New()
|
||||||
|
if err !=nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = b.store.Write(context.Background(), fmt.Sprintf("%v%v", StorePrefix, key), bytes, store.WriteTTL(options.Expiry))
|
err = b.store.Write(context.Background(), fmt.Sprintf("%v%v", StorePrefix, key), bytes, store.WriteTTL(options.Expiry))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/unistack-org/micro/v3/auth"
|
"github.com/unistack-org/micro/v3/auth"
|
||||||
"github.com/unistack-org/micro/v3/metadata"
|
"github.com/unistack-org/micro/v3/metadata"
|
||||||
"github.com/unistack-org/micro/v3/util/token"
|
"github.com/unistack-org/micro/v3/util/token"
|
||||||
|
Reference in New Issue
Block a user