Compare commits
104 Commits
Author | SHA1 | Date | |
---|---|---|---|
efe215cd60 | |||
b4f332bf0d | |||
f47fbb1030 | |||
1e8e57a708 | |||
|
5d0959b0a1 | ||
fa8fb3aed7 | |||
cfd2d53a79 | |||
d306f77ffc | |||
e5b0a7e20d | |||
9a5b158b4d | |||
af8d81f3c6 | |||
5c9b3dae33 | |||
9f3957d101 | |||
8fd8bdcb39 | |||
80e3d239ab | |||
419cd486cf | |||
e64269b2a8 | |||
d18429e024 | |||
675e121049 | |||
d357fb1e0d | |||
e4673bcc50 | |||
a839f75a2f | |||
a7e6d61b95 | |||
650d167313 | |||
c6ba2a91e6 | |||
7ece08896f | |||
|
57f6f23294 | ||
09e6fa2fed | |||
10a09a5c6f | |||
b4e5d9462a | |||
96aa0b6906 | |||
f54658830d | |||
1e43122660 | |||
42800fa247 | |||
5b9c810653 | |||
c3def24bf4 | |||
0d1ef31764 | |||
d49afa230f | |||
e545eb4e13 | |||
f28b107372 | |||
c592fabe2a | |||
eb107020c7 | |||
bd4d4c363e | |||
2a548634fd | |||
598dddc476 | |||
887b48f1e7 | |||
6e55d07636 | |||
919520219c | |||
60a5e737f8 | |||
34f0b209cc | |||
ba8e1889fe | |||
dae5c57a60 | |||
ea590d57df | |||
|
9aa6969836 | ||
|
c00c705c24 | ||
|
0239f795d8 | ||
|
e69b43881d | ||
3a48a613fe | |||
86626c5922 | |||
ee11f39a2f | |||
3bdfdd8fd2 | |||
6dfdff7fd8 | |||
00a4785df3 | |||
|
bae3b0ef94 | ||
|
89b0565062 | ||
1f8b0aeb61 | |||
|
5b6f849e0a | ||
|
3b416fffde | ||
3a60103aed | |||
41837a67f8 | |||
852f19598d | |||
6537b35773 | |||
b733f1316f | |||
|
840af5574c | ||
|
56e5b7001c | ||
|
11dc6fd752 | ||
a2695d8699 | |||
618421de05 | |||
|
30baaabd9f | ||
df5bce1191 | |||
|
089d0fe4df | ||
a06f535303 | |||
|
eba586a329 | ||
|
d74a8645e8 | ||
|
5a00786192 | ||
|
b3e9941634 | ||
|
a5a5904302 | ||
|
a59832e57e | ||
0e42033e7f | |||
52d8255974 | |||
9830cb48a9 | |||
92d7ab2105 | |||
d2935ef399 | |||
ce4c96ae0a | |||
|
14026d15be | ||
|
2df0c7643e | ||
e13c2c48fd | |||
8db55d2e55 | |||
ed61cad961 | |||
040fc4548f | |||
6189a1b980 | |||
eb2a450a7b | |||
|
d2a30a5da1 | ||
65889c66f6 |
19
.github/dependabot.yml
vendored
Normal file
19
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
# Maintain dependencies for Golang
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
15
.github/generate.sh
vendored
15
.github/generate.sh
vendored
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
find . -type f -name '*.pb.*.go' -o -name '*.pb.go' -a ! -name 'message.pb.go' -delete
|
||||
PROTOS=$(find . -type f -name '*.proto' | grep -v proto/google/api)
|
||||
|
||||
mkdir -p proto/google/api
|
||||
curl -s -o proto/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto
|
||||
curl -s -o proto/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto
|
||||
|
||||
for PROTO in $PROTOS; do
|
||||
echo $PROTO
|
||||
protoc -I./proto -I. -I$(dirname $PROTO) --go-grpc_out=paths=source_relative:. --go_out=paths=source_relative:. --micro_out=paths=source_relative:. $PROTO
|
||||
done
|
||||
|
||||
rm -r proto
|
20
.github/renovate.json
vendored
20
.github/renovate.json
vendored
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"postUpdateOptions": ["gomodTidy"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"groupName": "all deps",
|
||||
"separateMajorMinor": true,
|
||||
"groupSlug": "all",
|
||||
"packagePatterns": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
13
.github/stale.sh
vendored
13
.github/stale.sh
vendored
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
export PATH=$PATH:$(pwd)/bin
|
||||
export GO111MODULE=on
|
||||
export GOBIN=$(pwd)/bin
|
||||
|
||||
#go get github.com/rvflash/goup@v0.4.1
|
||||
|
||||
#goup -v ./...
|
||||
#go get github.com/psampaz/go-mod-outdated@v0.6.0
|
||||
go list -u -m -mod=mod -json all | go-mod-outdated -update -direct -ci || true
|
||||
|
||||
#go list -u -m -json all | go-mod-outdated -update
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.30
|
||||
version: v1.39
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
# Optional: golangci-lint command line arguments.
|
||||
|
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.30
|
||||
version: v1.39
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
# Optional: golangci-lint command line arguments.
|
||||
|
@@ -1,30 +1,44 @@
|
||||
run:
|
||||
concurrency: 4
|
||||
deadline: 5m
|
||||
modules-download-mode: readonly
|
||||
skip-files:
|
||||
- ".*\\.pb\\.go$"
|
||||
- ".*\\.pb\\.micro\\.go$"
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
enable:
|
||||
- fieldalignment
|
||||
|
||||
linters:
|
||||
disable-all: false
|
||||
enable-all: false
|
||||
enable:
|
||||
- megacheck
|
||||
- staticcheck
|
||||
- deadcode
|
||||
- varcheck
|
||||
- gosimple
|
||||
- unused
|
||||
- prealloc
|
||||
- scopelint
|
||||
- gocritic
|
||||
- goimports
|
||||
- unconvert
|
||||
- govet
|
||||
- nakedret
|
||||
- deadcode
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- gosec
|
||||
disable:
|
||||
- maligned
|
||||
- interfacer
|
||||
- typecheck
|
||||
- dupl
|
||||
- unused
|
||||
- varcheck
|
||||
- bodyclose
|
||||
- gci
|
||||
- goconst
|
||||
- gocritic
|
||||
- gosimple
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- golint
|
||||
- gosec
|
||||
- makezero
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- noctx
|
||||
- prealloc
|
||||
- unconvert
|
||||
- unparam
|
||||
disable-all: false
|
||||
|
@@ -149,5 +149,4 @@ func TestValidate(t *testing.T) {
|
||||
if err := Validate(epPcreInvalid); err == nil {
|
||||
t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0])
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,10 +6,8 @@ import (
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultMaxRecvSize specifies max recv size for handler
|
||||
DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
|
||||
)
|
||||
// DefaultMaxRecvSize specifies max recv size for handler
|
||||
var DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
|
||||
|
||||
// Options struct holds handler options
|
||||
type Options struct {
|
||||
|
@@ -15,12 +15,12 @@ import (
|
||||
// NewResolver creates new subdomain api resolver
|
||||
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
|
||||
options := resolver.NewOptions(opts...)
|
||||
return &subdomainResolver{options, parent}
|
||||
return &subdomainResolver{opts: options, Resolver: parent}
|
||||
}
|
||||
|
||||
type subdomainResolver struct {
|
||||
opts resolver.Options
|
||||
resolver.Resolver
|
||||
opts resolver.Options
|
||||
}
|
||||
|
||||
// Resolve resolve endpoint based on subdomain
|
||||
|
@@ -19,9 +19,7 @@ type vpathResolver struct {
|
||||
opts resolver.Options
|
||||
}
|
||||
|
||||
var (
|
||||
re = regexp.MustCompile("^v[0-9]+$")
|
||||
)
|
||||
var re = regexp.MustCompile("^v[0-9]+$")
|
||||
|
||||
// Resolve endpoint
|
||||
func (r *vpathResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
|
||||
|
@@ -7,10 +7,8 @@ import (
|
||||
"github.com/unistack-org/micro/v3/api"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRouter contains default router implementation
|
||||
DefaultRouter Router
|
||||
)
|
||||
// DefaultRouter contains default router implementation
|
||||
var DefaultRouter Router
|
||||
|
||||
// Router is used to determine an endpoint for a request
|
||||
type Router interface {
|
||||
|
@@ -24,11 +24,11 @@ func TestVerify(t *testing.T) {
|
||||
}
|
||||
|
||||
tt := []struct {
|
||||
Name string
|
||||
Rules []*Rule
|
||||
Error error
|
||||
Account *Account
|
||||
Resource *Resource
|
||||
Error error
|
||||
Name string
|
||||
Rules []*Rule
|
||||
}{
|
||||
{
|
||||
Name: "NoRules",
|
||||
|
100
broker/broker.go
100
broker/broker.go
@@ -3,48 +3,128 @@ package broker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
// DefaultBroker default memory broker
|
||||
var DefaultBroker Broker = NewBroker()
|
||||
|
||||
var (
|
||||
// DefaultBroker default broker
|
||||
DefaultBroker Broker = NewBroker()
|
||||
// ErrNotConnected returns when broker used but not connected yet
|
||||
ErrNotConnected = errors.New("broker not connected")
|
||||
// ErrDisconnected returns when broker disconnected
|
||||
ErrDisconnected = errors.New("broker disconnected")
|
||||
)
|
||||
|
||||
// Broker is an interface used for asynchronous messaging.
|
||||
type Broker interface {
|
||||
// Name returns broker instance name
|
||||
Name() string
|
||||
Init(...Option) error
|
||||
// Init initilize broker
|
||||
Init(opts ...Option) error
|
||||
// Options returns broker options
|
||||
Options() Options
|
||||
// Address return configured address
|
||||
Address() string
|
||||
Connect(context.Context) error
|
||||
Disconnect(context.Context) error
|
||||
Publish(context.Context, string, *Message, ...PublishOption) error
|
||||
Subscribe(context.Context, string, Handler, ...SubscribeOption) (Subscriber, error)
|
||||
// Connect connects to broker
|
||||
Connect(ctx context.Context) error
|
||||
// Disconnect disconnect from broker
|
||||
Disconnect(ctx context.Context) error
|
||||
// Publish message to broker topic
|
||||
Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error
|
||||
// Subscribe subscribes to topic message via handler
|
||||
Subscribe(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
|
||||
// BatchPublish messages to broker with multiple topics
|
||||
BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error
|
||||
// BatchSubscribe subscribes to topic messages via handler
|
||||
BatchSubscribe(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
|
||||
// String type of broker
|
||||
String() string
|
||||
}
|
||||
|
||||
// Handler is used to process messages via a subscription of a topic.
|
||||
type Handler func(Event) error
|
||||
|
||||
// Events contains multiple events
|
||||
type Events []Event
|
||||
|
||||
func (evs Events) Ack() error {
|
||||
var err error
|
||||
for _, ev := range evs {
|
||||
if err = ev.Ack(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (evs Events) SetError(err error) {
|
||||
for _, ev := range evs {
|
||||
ev.SetError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// BatchHandler is used to process messages in batches via a subscription of a topic.
|
||||
type BatchHandler func(Events) error
|
||||
|
||||
// Event is given to a subscription handler for processing
|
||||
type Event interface {
|
||||
// Topic returns event topic
|
||||
Topic() string
|
||||
// Message returns broker message
|
||||
Message() *Message
|
||||
// Ack acknowledge message
|
||||
Ack() error
|
||||
// Error returns message error (like decoding errors or some other)
|
||||
Error() error
|
||||
// SetError set event processing error
|
||||
SetError(err error)
|
||||
}
|
||||
|
||||
// RawMessage is a raw encoded JSON value.
|
||||
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
|
||||
type RawMessage []byte
|
||||
|
||||
// MarshalJSON returns m as the JSON encoding of m.
|
||||
func (m *RawMessage) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return *m, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *m to a copy of data.
|
||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("RawMessage UnmarshalJSON on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Message is used to transfer data
|
||||
type Message struct {
|
||||
Header metadata.Metadata // contains message metadata
|
||||
Body []byte // contains message body
|
||||
// Header contains message metadata
|
||||
Header metadata.Metadata
|
||||
// Body contains message body
|
||||
Body RawMessage
|
||||
}
|
||||
|
||||
// NewMessage create broker message with topic filled
|
||||
func NewMessage(topic string) *Message {
|
||||
m := &Message{Header: metadata.New(2)}
|
||||
m.Header.Set(metadata.HeaderTopic, topic)
|
||||
return m
|
||||
}
|
||||
|
||||
// Subscriber is a convenience return type for the Subscribe method
|
||||
type Subscriber interface {
|
||||
// Options returns subscriber options
|
||||
Options() SubscribeOptions
|
||||
// Topic returns topic for subscription
|
||||
Topic() string
|
||||
Unsubscribe(context.Context) error
|
||||
// Unsubscribe from topic
|
||||
Unsubscribe(ctx context.Context) error
|
||||
}
|
||||
|
217
broker/memory.go
217
broker/memory.go
@@ -2,38 +2,39 @@ package broker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
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"
|
||||
)
|
||||
|
||||
type memoryBroker struct {
|
||||
opts Options
|
||||
Subscribers map[string][]*memorySubscriber
|
||||
subscribers map[string][]*memorySubscriber
|
||||
addr string
|
||||
opts Options
|
||||
sync.RWMutex
|
||||
connected bool
|
||||
}
|
||||
|
||||
type memoryEvent struct {
|
||||
opts Options
|
||||
err error
|
||||
message interface{}
|
||||
topic string
|
||||
opts Options
|
||||
}
|
||||
|
||||
type memorySubscriber struct {
|
||||
opts SubscribeOptions
|
||||
ctx context.Context
|
||||
exit chan bool
|
||||
handler Handler
|
||||
id string
|
||||
topic string
|
||||
ctx context.Context
|
||||
exit chan bool
|
||||
handler Handler
|
||||
batchhandler BatchHandler
|
||||
id string
|
||||
topic string
|
||||
opts SubscribeOptions
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Options() Options {
|
||||
@@ -77,7 +78,6 @@ func (m *memoryBroker) Disconnect(ctx context.Context) error {
|
||||
}
|
||||
|
||||
m.connected = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,67 +92,190 @@ func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message,
|
||||
m.RLock()
|
||||
if !m.connected {
|
||||
m.RUnlock()
|
||||
return errors.New("not connected")
|
||||
return ErrNotConnected
|
||||
}
|
||||
|
||||
subs, ok := m.Subscribers[topic]
|
||||
m.RUnlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
if m.opts.Codec != nil {
|
||||
options := NewPublishOptions(opts...)
|
||||
vs := make([]msgWrapper, 0, 1)
|
||||
if m.opts.Codec == nil || options.BodyOnly {
|
||||
topic, _ := msg.Header.Get(metadata.HeaderTopic)
|
||||
vs = append(vs, msgWrapper{topic: topic, body: msg})
|
||||
} else {
|
||||
topic, _ := msg.Header.Get(metadata.HeaderTopic)
|
||||
buf, err := m.opts.Codec.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v = buf
|
||||
vs = append(vs, msgWrapper{topic: topic, body: buf})
|
||||
}
|
||||
|
||||
return m.publish(ctx, vs, opts...)
|
||||
}
|
||||
|
||||
type msgWrapper struct {
|
||||
topic string
|
||||
body interface{}
|
||||
}
|
||||
|
||||
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
|
||||
m.RLock()
|
||||
if !m.connected {
|
||||
m.RUnlock()
|
||||
return ErrNotConnected
|
||||
}
|
||||
m.RUnlock()
|
||||
|
||||
options := NewPublishOptions(opts...)
|
||||
vs := make([]msgWrapper, 0, len(msgs))
|
||||
if m.opts.Codec == nil || options.BodyOnly {
|
||||
for _, msg := range msgs {
|
||||
topic, _ := msg.Header.Get(metadata.HeaderTopic)
|
||||
vs = append(vs, msgWrapper{topic: topic, body: msg})
|
||||
}
|
||||
} else {
|
||||
v = msg
|
||||
for _, msg := range msgs {
|
||||
topic, _ := msg.Header.Get(metadata.HeaderTopic)
|
||||
buf, err := m.opts.Codec.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vs = append(vs, msgWrapper{topic: topic, body: buf})
|
||||
}
|
||||
}
|
||||
|
||||
p := &memoryEvent{
|
||||
topic: topic,
|
||||
message: v,
|
||||
opts: m.opts,
|
||||
return m.publish(ctx, vs, opts...)
|
||||
}
|
||||
|
||||
func (m *memoryBroker) publish(ctx context.Context, vs []msgWrapper, opts ...PublishOption) error {
|
||||
var err error
|
||||
|
||||
msgTopicMap := make(map[string]Events)
|
||||
for _, v := range vs {
|
||||
p := &memoryEvent{
|
||||
topic: v.topic,
|
||||
message: v.body,
|
||||
opts: m.opts,
|
||||
}
|
||||
msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p)
|
||||
}
|
||||
|
||||
beh := m.opts.BatchErrorHandler
|
||||
eh := m.opts.ErrorHandler
|
||||
|
||||
for _, sub := range subs {
|
||||
if err := sub.handler(p); err != nil {
|
||||
p.err = err
|
||||
if sub.opts.ErrorHandler != nil {
|
||||
eh = sub.opts.ErrorHandler
|
||||
}
|
||||
if eh != nil {
|
||||
eh(p)
|
||||
} else if m.opts.Logger.V(logger.ErrorLevel) {
|
||||
m.opts.Logger.Error(m.opts.Context, err.Error())
|
||||
}
|
||||
for t, ms := range msgTopicMap {
|
||||
m.RLock()
|
||||
subs, ok := m.subscribers[t]
|
||||
m.RUnlock()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, sub := range subs {
|
||||
// batch processing
|
||||
if sub.batchhandler != nil {
|
||||
if err = sub.batchhandler(ms); err != nil {
|
||||
ms.SetError(err)
|
||||
if sub.opts.BatchErrorHandler != nil {
|
||||
beh = sub.opts.BatchErrorHandler
|
||||
}
|
||||
if beh != nil {
|
||||
beh(ms)
|
||||
} else if m.opts.Logger.V(logger.ErrorLevel) {
|
||||
m.opts.Logger.Error(m.opts.Context, err.Error())
|
||||
}
|
||||
} else if sub.opts.AutoAck {
|
||||
if err = ms.Ack(); err != nil {
|
||||
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// single processing
|
||||
if sub.handler != nil {
|
||||
for _, p := range ms {
|
||||
if err = sub.handler(p); err != nil {
|
||||
p.SetError(err)
|
||||
if sub.opts.ErrorHandler != nil {
|
||||
eh = sub.opts.ErrorHandler
|
||||
}
|
||||
if eh != nil {
|
||||
eh(p)
|
||||
} else if m.opts.Logger.V(logger.ErrorLevel) {
|
||||
m.opts.Logger.Error(m.opts.Context, err.Error())
|
||||
}
|
||||
} else if sub.opts.AutoAck {
|
||||
if err = p.Ack(); err != nil {
|
||||
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
|
||||
func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
|
||||
m.RLock()
|
||||
if !m.connected {
|
||||
m.RUnlock()
|
||||
return nil, errors.New("not connected")
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
m.RUnlock()
|
||||
|
||||
options := NewSubscribeOptions(opts...)
|
||||
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options := NewSubscribeOptions(opts...)
|
||||
|
||||
sub := &memorySubscriber{
|
||||
exit: make(chan bool, 1),
|
||||
id: id.String(),
|
||||
topic: topic,
|
||||
batchhandler: handler,
|
||||
opts: options,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
m.subscribers[topic] = append(m.subscribers[topic], sub)
|
||||
m.Unlock()
|
||||
|
||||
go func() {
|
||||
<-sub.exit
|
||||
m.Lock()
|
||||
newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
|
||||
for _, sb := range m.subscribers[topic] {
|
||||
if sb.id == sub.id {
|
||||
continue
|
||||
}
|
||||
newSubscribers = append(newSubscribers, sb)
|
||||
}
|
||||
m.subscribers[topic] = newSubscribers
|
||||
m.Unlock()
|
||||
}()
|
||||
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
|
||||
m.RLock()
|
||||
if !m.connected {
|
||||
m.RUnlock()
|
||||
return nil, ErrNotConnected
|
||||
}
|
||||
m.RUnlock()
|
||||
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options := NewSubscribeOptions(opts...)
|
||||
|
||||
sub := &memorySubscriber{
|
||||
exit: make(chan bool, 1),
|
||||
id: id.String(),
|
||||
@@ -163,20 +286,20 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
m.Subscribers[topic] = append(m.Subscribers[topic], sub)
|
||||
m.subscribers[topic] = append(m.subscribers[topic], sub)
|
||||
m.Unlock()
|
||||
|
||||
go func() {
|
||||
<-sub.exit
|
||||
m.Lock()
|
||||
var newSubscribers []*memorySubscriber
|
||||
for _, sb := range m.Subscribers[topic] {
|
||||
newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
|
||||
for _, sb := range m.subscribers[topic] {
|
||||
if sb.id == sub.id {
|
||||
continue
|
||||
}
|
||||
newSubscribers = append(newSubscribers, sb)
|
||||
}
|
||||
m.Subscribers[topic] = newSubscribers
|
||||
m.subscribers[topic] = newSubscribers
|
||||
m.Unlock()
|
||||
}()
|
||||
|
||||
@@ -221,6 +344,10 @@ func (m *memoryEvent) Error() error {
|
||||
return m.err
|
||||
}
|
||||
|
||||
func (m *memoryEvent) SetError(err error) {
|
||||
m.err = err
|
||||
}
|
||||
|
||||
func (m *memorySubscriber) Options() SubscribeOptions {
|
||||
return m.opts
|
||||
}
|
||||
@@ -238,6 +365,6 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
|
||||
func NewBroker(opts ...Option) Broker {
|
||||
return &memoryBroker{
|
||||
opts: NewOptions(opts...),
|
||||
Subscribers: make(map[string][]*memorySubscriber),
|
||||
subscribers: make(map[string][]*memorySubscriber),
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,55 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
func TestMemoryBatchBroker(t *testing.T) {
|
||||
b := NewBroker()
|
||||
ctx := context.Background()
|
||||
|
||||
if err := b.Connect(ctx); err != nil {
|
||||
t.Fatalf("Unexpected connect error %v", err)
|
||||
}
|
||||
|
||||
topic := "test"
|
||||
count := 10
|
||||
|
||||
fn := func(evts Events) error {
|
||||
return evts.Ack()
|
||||
}
|
||||
|
||||
sub, err := b.BatchSubscribe(ctx, topic, fn)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error subscribing %v", err)
|
||||
}
|
||||
|
||||
msgs := make([]*Message, 0, 0)
|
||||
for i := 0; i < count; i++ {
|
||||
message := &Message{
|
||||
Header: map[string]string{
|
||||
metadata.HeaderTopic: topic,
|
||||
"foo": "bar",
|
||||
"id": fmt.Sprintf("%d", i),
|
||||
},
|
||||
Body: []byte(`"hello world"`),
|
||||
}
|
||||
msgs = append(msgs, message)
|
||||
}
|
||||
|
||||
if err := b.BatchPublish(ctx, msgs); err != nil {
|
||||
t.Fatalf("Unexpected error publishing %v", err)
|
||||
}
|
||||
|
||||
if err := sub.Unsubscribe(ctx); err != nil {
|
||||
t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err)
|
||||
}
|
||||
|
||||
if err := b.Disconnect(ctx); err != nil {
|
||||
t.Fatalf("Unexpected connect error %v", err)
|
||||
}
|
||||
}
|
||||
func TestMemoryBroker(t *testing.T) {
|
||||
b := NewBroker()
|
||||
ctx := context.Background()
|
||||
@@ -26,20 +73,27 @@ func TestMemoryBroker(t *testing.T) {
|
||||
t.Fatalf("Unexpected error subscribing %v", err)
|
||||
}
|
||||
|
||||
msgs := make([]*Message, 0, 0)
|
||||
for i := 0; i < count; i++ {
|
||||
message := &Message{
|
||||
Header: map[string]string{
|
||||
"foo": "bar",
|
||||
"id": fmt.Sprintf("%d", i),
|
||||
metadata.HeaderTopic: topic,
|
||||
"foo": "bar",
|
||||
"id": fmt.Sprintf("%d", i),
|
||||
},
|
||||
Body: []byte(`hello world`),
|
||||
Body: []byte(`"hello world"`),
|
||||
}
|
||||
msgs = append(msgs, message)
|
||||
|
||||
if err := b.Publish(ctx, topic, message); err != nil {
|
||||
t.Fatalf("Unexpected error publishing %d", i)
|
||||
t.Fatalf("Unexpected error publishing %d err: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.BatchPublish(ctx, msgs); err != nil {
|
||||
t.Fatalf("Unexpected error publishing %v", err)
|
||||
}
|
||||
|
||||
if err := sub.Unsubscribe(ctx); err != nil {
|
||||
t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err)
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package broker
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
@@ -29,6 +30,8 @@ type Options struct {
|
||||
TLSConfig *tls.Config
|
||||
// ErrorHandler used when broker can't unmarshal incoming message
|
||||
ErrorHandler Handler
|
||||
// BatchErrorHandler used when broker can't unmashal incoming messages
|
||||
BatchErrorHandler BatchHandler
|
||||
// Name holds the broker name
|
||||
Name string
|
||||
// Addrs holds the broker address
|
||||
@@ -71,11 +74,9 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
|
||||
options := PublishOptions{
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -85,12 +86,18 @@ type SubscribeOptions struct {
|
||||
Context context.Context
|
||||
// ErrorHandler used when broker can't unmarshal incoming message
|
||||
ErrorHandler Handler
|
||||
// BatchErrorHandler used when broker can't unmashal incoming messages
|
||||
BatchErrorHandler BatchHandler
|
||||
// Group holds consumer group
|
||||
Group string
|
||||
// AutoAck flag specifies auto ack of incoming message when no error happens
|
||||
AutoAck bool
|
||||
// BodyOnly flag specifies that message contains only body bytes without header
|
||||
BodyOnly bool
|
||||
// BatchSize flag specifies max batch size
|
||||
BatchSize int
|
||||
// BatchWait flag specifies max wait time for batch filling
|
||||
BatchWait time.Duration
|
||||
}
|
||||
|
||||
// Option func
|
||||
@@ -113,23 +120,6 @@ func PublishContext(ctx context.Context) PublishOption {
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeOption func
|
||||
type SubscribeOption func(*SubscribeOptions)
|
||||
|
||||
// NewSubscribeOptions creates new SubscribeOptions
|
||||
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
|
||||
options := SubscribeOptions{
|
||||
AutoAck: true,
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// Addrs sets the host addresses to be used by the broker
|
||||
func Addrs(addrs ...string) Option {
|
||||
return func(o *Options) {
|
||||
@@ -145,28 +135,6 @@ func Codec(c codec.Codec) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// DisableAutoAck disables auto ack
|
||||
func DisableAutoAck() SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.AutoAck = false
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeAutoAck will disable auto acking of messages
|
||||
// after they have been handled.
|
||||
func SubscribeAutoAck(b bool) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.AutoAck = b
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeBodyOnly consumes only body of the message
|
||||
func SubscribeBodyOnly(b bool) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.BodyOnly = b
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorHandler will catch all broker errors that cant be handled
|
||||
// in normal way, for example Codec errors
|
||||
func ErrorHandler(h Handler) Option {
|
||||
@@ -175,6 +143,14 @@ func ErrorHandler(h Handler) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// BatchErrorHandler will catch all broker errors that cant be handled
|
||||
// in normal way, for example Codec errors
|
||||
func BatchErrorHandler(h BatchHandler) Option {
|
||||
return func(o *Options) {
|
||||
o.BatchErrorHandler = h
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeErrorHandler will catch all broker errors that cant be handled
|
||||
// in normal way, for example Codec errors
|
||||
func SubscribeErrorHandler(h Handler) SubscribeOption {
|
||||
@@ -183,6 +159,14 @@ func SubscribeErrorHandler(h Handler) SubscribeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeBatchErrorHandler will catch all broker errors that cant be handled
|
||||
// in normal way, for example Codec errors
|
||||
func SubscribeBatchErrorHandler(h BatchHandler) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.BatchErrorHandler = h
|
||||
}
|
||||
}
|
||||
|
||||
// Queue sets the subscribers queue
|
||||
// Deprecated
|
||||
func Queue(name string) SubscribeOption {
|
||||
@@ -246,3 +230,55 @@ func SubscribeContext(ctx context.Context) SubscribeOption {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// DisableAutoAck disables auto ack
|
||||
// Deprecated
|
||||
func DisableAutoAck() SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.AutoAck = false
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeAutoAck contol auto acking of messages
|
||||
// after they have been handled.
|
||||
func SubscribeAutoAck(b bool) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.AutoAck = b
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeBodyOnly consumes only body of the message
|
||||
func SubscribeBodyOnly(b bool) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.BodyOnly = b
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeBatchSize specifies max batch size
|
||||
func SubscribeBatchSize(n int) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.BatchSize = n
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeBatchWait specifies max batch wait time
|
||||
func SubscribeBatchWait(td time.Duration) SubscribeOption {
|
||||
return func(o *SubscribeOptions) {
|
||||
o.BatchWait = td
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeOption func
|
||||
type SubscribeOption func(*SubscribeOptions)
|
||||
|
||||
// NewSubscribeOptions creates new SubscribeOptions
|
||||
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
|
||||
options := SubscribeOptions{
|
||||
AutoAck: true,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ type Client interface {
|
||||
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
|
||||
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
|
||||
Publish(ctx context.Context, msg Message, opts ...PublishOption) error
|
||||
BatchPublish(ctx context.Context, msg []Message, opts ...PublishOption) error
|
||||
String() string
|
||||
}
|
||||
|
||||
|
@@ -9,16 +9,10 @@ import (
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultCodecs will be used to encode/decode data
|
||||
DefaultCodecs = map[string]codec.Codec{
|
||||
//"application/json": cjson.NewCodec,
|
||||
//"application/json-rpc": cjsonrpc.NewCodec,
|
||||
//"application/protobuf": cproto.NewCodec,
|
||||
//"application/proto-rpc": cprotorpc.NewCodec,
|
||||
"application/octet-stream": codec.NewCodec(),
|
||||
}
|
||||
)
|
||||
// DefaultCodecs will be used to encode/decode data
|
||||
var DefaultCodecs = map[string]codec.Codec{
|
||||
"application/octet-stream": codec.NewCodec(),
|
||||
}
|
||||
|
||||
type noopClient struct {
|
||||
opts Options
|
||||
@@ -179,7 +173,7 @@ func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
@@ -187,45 +181,60 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption
|
||||
return &noopStream{}, nil
|
||||
}
|
||||
|
||||
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
|
||||
var body []byte
|
||||
func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
|
||||
return n.publish(ctx, ps, opts...)
|
||||
}
|
||||
|
||||
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
|
||||
return n.publish(ctx, []Message{p}, opts...)
|
||||
}
|
||||
|
||||
func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishOption) error {
|
||||
options := NewPublishOptions(opts...)
|
||||
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(0)
|
||||
}
|
||||
md["Content-Type"] = p.ContentType()
|
||||
md["Micro-Topic"] = p.Topic()
|
||||
msgs := make([]*broker.Message, 0, len(ps))
|
||||
|
||||
// passed in raw data
|
||||
if d, ok := p.Payload().(*codec.Frame); ok {
|
||||
body = d.Data
|
||||
} else {
|
||||
// use codec for payload
|
||||
cf, err := n.newCodec(p.ContentType())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
for _, p := range ps {
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = metadata.New(0)
|
||||
}
|
||||
md[metadata.HeaderContentType] = p.ContentType()
|
||||
|
||||
topic := p.Topic()
|
||||
|
||||
// get the exchange
|
||||
if len(options.Exchange) > 0 {
|
||||
topic = options.Exchange
|
||||
}
|
||||
|
||||
// set the body
|
||||
b, err := cf.Marshal(p.Payload())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
md[metadata.HeaderTopic] = topic
|
||||
|
||||
var body []byte
|
||||
|
||||
// passed in raw data
|
||||
if d, ok := p.Payload().(*codec.Frame); ok {
|
||||
body = d.Data
|
||||
} else {
|
||||
// use codec for payload
|
||||
cf, err := n.newCodec(p.ContentType())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
|
||||
// set the body
|
||||
b, err := cf.Marshal(p.Payload())
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
body = b
|
||||
}
|
||||
body = b
|
||||
|
||||
msgs = append(msgs, &broker.Message{Header: md, Body: body})
|
||||
}
|
||||
|
||||
topic := p.Topic()
|
||||
|
||||
// get the exchange
|
||||
if len(options.Exchange) > 0 {
|
||||
topic = options.Exchange
|
||||
}
|
||||
|
||||
return n.opts.Broker.Publish(ctx, topic, &broker.Message{
|
||||
Header: md,
|
||||
Body: body,
|
||||
}, broker.PublishContext(options.Context))
|
||||
return n.opts.Broker.BatchPublish(ctx, msgs,
|
||||
broker.PublishContext(options.Context),
|
||||
broker.PublishBodyOnly(options.BodyOnly),
|
||||
)
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/broker"
|
||||
@@ -18,8 +19,8 @@ import (
|
||||
|
||||
// Options holds client options
|
||||
type Options struct {
|
||||
// CallOptions contains default CallOptions
|
||||
CallOptions CallOptions
|
||||
// Selector used to select needed address
|
||||
Selector selector.Selector
|
||||
// Logger used to log messages
|
||||
Logger logger.Logger
|
||||
// Tracer used for tracing
|
||||
@@ -30,24 +31,26 @@ type Options struct {
|
||||
Meter meter.Meter
|
||||
// Router used to get route
|
||||
Router router.Router
|
||||
// Selector used to select needed address
|
||||
Selector selector.Selector
|
||||
// Transport used for transfer messages
|
||||
Transport transport.Transport
|
||||
// Context is used for external options
|
||||
Context context.Context
|
||||
// Codecs map
|
||||
Codecs map[string]codec.Codec
|
||||
// Lookup func used to get destination addr
|
||||
Lookup LookupFunc
|
||||
// Codecs map
|
||||
Codecs map[string]codec.Codec
|
||||
// TLSConfig specifies tls.Config for secure connection
|
||||
TLSConfig *tls.Config
|
||||
// Proxy is used for proxy requests
|
||||
Proxy string
|
||||
// ContentType is Used to select codec
|
||||
// ContentType is used to select codec
|
||||
ContentType string
|
||||
// Name is the client name
|
||||
Name string
|
||||
// Wrappers contains wrappers
|
||||
Wrappers []Wrapper
|
||||
// CallOptions contains default CallOptions
|
||||
CallOptions CallOptions
|
||||
// PoolSize connection pool size
|
||||
PoolSize int
|
||||
// PoolTTL connection pool ttl
|
||||
@@ -77,6 +80,8 @@ type CallOptions struct {
|
||||
Backoff BackoffFunc
|
||||
// Network name
|
||||
Network string
|
||||
// Content-Type
|
||||
ContentType string
|
||||
// CallWrappers call wrappers
|
||||
CallWrappers []CallWrapper
|
||||
// SelectOptions selector options
|
||||
@@ -91,8 +96,8 @@ type CallOptions struct {
|
||||
RequestTimeout time.Duration
|
||||
// DialTimeout dial timeout
|
||||
DialTimeout time.Duration
|
||||
// AuthToken flag
|
||||
AuthToken bool
|
||||
// AuthToken string
|
||||
AuthToken string
|
||||
}
|
||||
|
||||
// Context pass context to client
|
||||
@@ -113,6 +118,8 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
|
||||
|
||||
// PublishOptions holds publish options
|
||||
type PublishOptions struct {
|
||||
// BodyOnly will publish only message body
|
||||
BodyOnly bool
|
||||
// Context used for external options
|
||||
Context context.Context
|
||||
// Exchange topic exchange name
|
||||
@@ -167,14 +174,16 @@ func NewOptions(opts ...Option) Options {
|
||||
RequestTimeout: DefaultRequestTimeout,
|
||||
DialTimeout: transport.DefaultDialTimeout,
|
||||
},
|
||||
Lookup: LookupRoute,
|
||||
PoolSize: DefaultPoolSize,
|
||||
PoolTTL: DefaultPoolTTL,
|
||||
Selector: random.NewSelector(),
|
||||
Logger: logger.DefaultLogger,
|
||||
Broker: broker.DefaultBroker,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Lookup: LookupRoute,
|
||||
PoolSize: DefaultPoolSize,
|
||||
PoolTTL: DefaultPoolTTL,
|
||||
Selector: random.NewSelector(),
|
||||
Logger: logger.DefaultLogger,
|
||||
Broker: broker.DefaultBroker,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Router: router.DefaultRouter,
|
||||
Transport: transport.DefaultTransport,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
@@ -312,6 +321,22 @@ func Lookup(l LookupFunc) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfig specifies a *tls.Config
|
||||
func TLSConfig(t *tls.Config) Option {
|
||||
return func(o *Options) {
|
||||
// set the internal tls
|
||||
o.TLSConfig = t
|
||||
|
||||
// set the default transport if one is not
|
||||
// already set. Required for Init call below.
|
||||
|
||||
// set the transport tls
|
||||
o.Transport.Init(
|
||||
transport.TLSConfig(t),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Retries sets the retry count when making the request.
|
||||
func Retries(i int) Option {
|
||||
return func(o *Options) {
|
||||
@@ -348,12 +373,35 @@ func DialTimeout(d time.Duration) Option {
|
||||
}
|
||||
|
||||
// WithExchange sets the exchange to route a message through
|
||||
// Deprecated
|
||||
func WithExchange(e string) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.Exchange = e
|
||||
}
|
||||
}
|
||||
|
||||
// PublishExchange sets the exchange to route a message through
|
||||
func PublishExchange(e string) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.Exchange = e
|
||||
}
|
||||
}
|
||||
|
||||
// WithBodyOnly publish only message body
|
||||
// DERECATED
|
||||
func WithBodyOnly(b bool) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.BodyOnly = b
|
||||
}
|
||||
}
|
||||
|
||||
// PublishBodyOnly publish only message body
|
||||
func PublishBodyOnly(b bool) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
o.BodyOnly = b
|
||||
}
|
||||
}
|
||||
|
||||
// PublishContext sets the context in publish options
|
||||
func PublishContext(ctx context.Context) PublishOption {
|
||||
return func(o *PublishOptions) {
|
||||
@@ -361,6 +409,13 @@ func PublishContext(ctx context.Context) PublishOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithContentType specifies call content type
|
||||
func WithContentType(ct string) CallOption {
|
||||
return func(o *CallOptions) {
|
||||
o.ContentType = ct
|
||||
}
|
||||
}
|
||||
|
||||
// WithAddress sets the remote addresses to use rather than using service discovery
|
||||
func WithAddress(a ...string) CallOption {
|
||||
return func(o *CallOptions) {
|
||||
@@ -424,9 +479,9 @@ func WithDialTimeout(d time.Duration) CallOption {
|
||||
|
||||
// WithAuthToken is a CallOption which overrides the
|
||||
// authorization header with the services own auth token
|
||||
func WithAuthToken() CallOption {
|
||||
func WithAuthToken(t string) CallOption {
|
||||
return func(o *CallOptions) {
|
||||
o.AuthToken = true
|
||||
o.AuthToken = t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,22 +514,30 @@ func WithSelectOptions(sops ...selector.SelectOption) CallOption {
|
||||
}
|
||||
|
||||
// WithMessageContentType sets the message content type
|
||||
// Deprecated
|
||||
func WithMessageContentType(ct string) MessageOption {
|
||||
return func(o *MessageOptions) {
|
||||
o.ContentType = ct
|
||||
}
|
||||
}
|
||||
|
||||
// WithContentType specifies request content type
|
||||
func WithContentType(ct string) RequestOption {
|
||||
return func(o *RequestOptions) {
|
||||
// MessageContentType sets the message content type
|
||||
func MessageContentType(ct string) MessageOption {
|
||||
return func(o *MessageOptions) {
|
||||
o.ContentType = ct
|
||||
}
|
||||
}
|
||||
|
||||
// StreamingRequest specifies that request is streaming
|
||||
func StreamingRequest() RequestOption {
|
||||
func StreamingRequest(b bool) RequestOption {
|
||||
return func(o *RequestOptions) {
|
||||
o.Stream = true
|
||||
o.Stream = b
|
||||
}
|
||||
}
|
||||
|
||||
// RequestContentType specifies request content type
|
||||
func RequestContentType(ct string) RequestOption {
|
||||
return func(o *RequestOptions) {
|
||||
o.ContentType = ct
|
||||
}
|
||||
}
|
||||
|
@@ -5,13 +5,13 @@ import (
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
opts RequestOptions
|
||||
codec codec.Codec
|
||||
body interface{}
|
||||
service string
|
||||
method string
|
||||
endpoint string
|
||||
contentType string
|
||||
service string
|
||||
opts RequestOptions
|
||||
}
|
||||
|
||||
func (r *testRequest) ContentType() string {
|
||||
|
@@ -28,6 +28,8 @@ var (
|
||||
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb
|
||||
// DefaultCodec is the global default codec
|
||||
DefaultCodec Codec = NewCodec()
|
||||
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
|
||||
DefaultTagName = "codec"
|
||||
)
|
||||
|
||||
// MessageType specifies message type for codec
|
||||
|
@@ -5,8 +5,7 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type noopCodec struct {
|
||||
}
|
||||
type noopCodec struct{}
|
||||
|
||||
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
|
||||
return nil
|
||||
|
@@ -4,12 +4,17 @@ package config
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultConfig default config
|
||||
DefaultConfig Config = NewConfig()
|
||||
)
|
||||
// DefaultConfig default config
|
||||
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 (
|
||||
// ErrCodecMissing is returned when codec needed and not specified
|
||||
@@ -22,34 +27,38 @@ var (
|
||||
|
||||
// Config is an interface abstraction for dynamic configuration
|
||||
type Config interface {
|
||||
// Name returns name of config
|
||||
Name() string
|
||||
// Init the config
|
||||
Init(opts ...Option) error
|
||||
// Options in the config
|
||||
Options() Options
|
||||
// Load config from sources
|
||||
Load(context.Context) error
|
||||
Load(context.Context, ...LoadOption) error
|
||||
// Save config to sources
|
||||
Save(context.Context) error
|
||||
// Watch a value for changes
|
||||
// Watch(interface{}) (Watcher, error)
|
||||
Save(context.Context, ...SaveOption) error
|
||||
// Watch a config for changes
|
||||
Watch(context.Context, ...WatchOption) (Watcher, error)
|
||||
// String returns config type name
|
||||
String() string
|
||||
}
|
||||
|
||||
// Watcher is the config watcher
|
||||
//type Watcher interface {
|
||||
// Next() (, error)
|
||||
// Stop() error
|
||||
//}
|
||||
type Watcher interface {
|
||||
// Next blocks until update happens or error returned
|
||||
Next() (map[string]interface{}, error)
|
||||
// Stop stops watcher
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// Load loads config from config sources
|
||||
func Load(ctx context.Context, cs ...Config) error {
|
||||
func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
|
||||
var err error
|
||||
for _, c := range cs {
|
||||
if err = c.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.Load(ctx); err != nil {
|
||||
if err = c.Load(ctx, opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/unistack-org/micro/v3/util/jitter"
|
||||
rutil "github.com/unistack-org/micro/v3/util/reflect"
|
||||
)
|
||||
|
||||
@@ -25,18 +26,31 @@ func (c *defaultConfig) Init(opts ...Option) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *defaultConfig) Load(ctx context.Context) error {
|
||||
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
|
||||
for _, fn := range c.opts.BeforeLoad {
|
||||
if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
src, err := rutil.Zero(c.opts.Struct)
|
||||
options := NewLoadOptions(opts...)
|
||||
mopts := []func(*mergo.Config){mergo.WithTypeCheck}
|
||||
if options.Override {
|
||||
mopts = append(mopts, mergo.WithOverride)
|
||||
}
|
||||
if options.Append {
|
||||
mopts = append(mopts, mergo.WithAppendSlice)
|
||||
}
|
||||
|
||||
dst := c.opts.Struct
|
||||
if options.Struct != nil {
|
||||
dst = options.Struct
|
||||
}
|
||||
|
||||
src, err := rutil.Zero(dst)
|
||||
if err == nil {
|
||||
valueOf := reflect.ValueOf(src)
|
||||
if err = c.fillValues(ctx, valueOf); err == nil {
|
||||
err = mergo.Merge(c.opts.Struct, src, mergo.WithOverride, mergo.WithTypeCheck, mergo.WithAppendSlice)
|
||||
if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil {
|
||||
err = mergo.Merge(dst, src, mopts...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +68,7 @@ func (c *defaultConfig) Load(ctx context.Context) error {
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val string) error {
|
||||
func fillValue(value reflect.Value, val string) error {
|
||||
if !rutil.IsEmpty(value) {
|
||||
return nil
|
||||
}
|
||||
@@ -71,10 +85,10 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
|
||||
kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' })
|
||||
mkey := reflect.Indirect(reflect.New(kt))
|
||||
mval := reflect.Indirect(reflect.New(et))
|
||||
if err := c.fillValue(ctx, mkey, kv[0]); err != nil {
|
||||
if err := fillValue(mkey, kv[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.fillValue(ctx, mval, kv[1]); err != nil {
|
||||
if err := fillValue(mval, kv[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
value.SetMapIndex(mkey, mval)
|
||||
@@ -84,7 +98,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
|
||||
value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals)))
|
||||
for idx, nval := range nvals {
|
||||
nvalue := reflect.Indirect(reflect.New(value.Type().Elem()))
|
||||
if err := c.fillValue(ctx, nvalue, nval); err != nil {
|
||||
if err := fillValue(nvalue, nval); err != nil {
|
||||
return err
|
||||
}
|
||||
value.Index(idx).Set(nvalue)
|
||||
@@ -173,7 +187,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) error {
|
||||
func fillValues(valueOf reflect.Value, tname string) error {
|
||||
var values reflect.Value
|
||||
|
||||
if valueOf.Kind() == reflect.Ptr {
|
||||
@@ -200,7 +214,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
|
||||
switch value.Kind() {
|
||||
case reflect.Struct:
|
||||
value.Set(reflect.Indirect(reflect.New(value.Type())))
|
||||
if err := c.fillValues(ctx, value); err != nil {
|
||||
if err := fillValues(value, tname); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
@@ -214,17 +228,17 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
}
|
||||
value = value.Elem()
|
||||
if err := c.fillValues(ctx, value); err != nil {
|
||||
if err := fillValues(value, tname); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
tag, ok := field.Tag.Lookup(c.opts.StructTag)
|
||||
tag, ok := field.Tag.Lookup(tname)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := c.fillValue(ctx, value, tag); err != nil {
|
||||
if err := fillValue(value, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -232,7 +246,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *defaultConfig) Save(ctx context.Context) error {
|
||||
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
|
||||
for _, fn := range c.opts.BeforeSave {
|
||||
if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
|
||||
return err
|
||||
@@ -256,6 +270,20 @@ func (c *defaultConfig) Name() string {
|
||||
return c.opts.Name
|
||||
}
|
||||
|
||||
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
|
||||
w := &defaultWatcher{
|
||||
opts: c.opts,
|
||||
wopts: NewWatchOptions(opts...),
|
||||
done: make(chan struct{}),
|
||||
vchan: make(chan map[string]interface{}),
|
||||
echan: make(chan error),
|
||||
}
|
||||
|
||||
go w.run()
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// NewConfig returns new default config source
|
||||
func NewConfig(opts ...Option) Config {
|
||||
options := NewOptions(opts...)
|
||||
@@ -264,3 +292,76 @@ func NewConfig(opts ...Option) Config {
|
||||
}
|
||||
return &defaultConfig{opts: options}
|
||||
}
|
||||
|
||||
type defaultWatcher struct {
|
||||
opts Options
|
||||
wopts WatchOptions
|
||||
done chan struct{}
|
||||
vchan chan map[string]interface{}
|
||||
echan chan error
|
||||
}
|
||||
|
||||
func (w *defaultWatcher) run() {
|
||||
ticker := jitter.NewTicker(w.wopts.MinInterval, w.wopts.MaxInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
src := w.opts.Struct
|
||||
if w.wopts.Struct != nil {
|
||||
src = w.wopts.Struct
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-w.done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
dst, err := rutil.Zero(src)
|
||||
if err == nil {
|
||||
err = fillValues(reflect.ValueOf(dst), w.opts.StructTag)
|
||||
}
|
||||
if err != nil {
|
||||
w.echan <- err
|
||||
return
|
||||
}
|
||||
srcmp, err := rutil.StructFieldsMap(src)
|
||||
if err != nil {
|
||||
w.echan <- err
|
||||
return
|
||||
}
|
||||
dstmp, err := rutil.StructFieldsMap(dst)
|
||||
if err != nil {
|
||||
w.echan <- err
|
||||
return
|
||||
}
|
||||
for sk, sv := range srcmp {
|
||||
if reflect.DeepEqual(dstmp[sk], sv) {
|
||||
delete(dstmp, sk)
|
||||
}
|
||||
}
|
||||
if len(dstmp) > 0 {
|
||||
w.vchan <- dstmp
|
||||
src = dst
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *defaultWatcher) Next() (map[string]interface{}, error) {
|
||||
select {
|
||||
case <-w.done:
|
||||
break
|
||||
case err := <-w.echan:
|
||||
return nil, err
|
||||
case v, ok := <-w.vchan:
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
return nil, ErrWatcherStopped
|
||||
}
|
||||
|
||||
func (w *defaultWatcher) Stop() error {
|
||||
close(w.done)
|
||||
return nil
|
||||
}
|
||||
|
@@ -4,36 +4,88 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/config"
|
||||
)
|
||||
|
||||
type Cfg struct {
|
||||
StringValue string `default:"string_value"`
|
||||
IntValue int `default:"99"`
|
||||
IgnoreValue string `json:"-"`
|
||||
StructValue struct {
|
||||
StringValue string `default:"string_value"`
|
||||
}
|
||||
IntValue int `default:"99"`
|
||||
}
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
conf := &Cfg{IntValue: 10}
|
||||
|
||||
cfg := config.NewConfig(config.Struct(conf))
|
||||
if err := cfg.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cfg.Load(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
w, err := cfg.Watch(ctx, config.WatchInterval(200*time.Millisecond, 500*time.Millisecond))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = w.Stop()
|
||||
}()
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
mp, err := w.Next()
|
||||
if err != nil && err != config.ErrWatcherStopped {
|
||||
t.Fatal(err)
|
||||
} else if err == config.ErrWatcherStopped {
|
||||
return
|
||||
}
|
||||
if len(mp) != 1 {
|
||||
t.Fatal(fmt.Errorf("default watcher err: %v", mp))
|
||||
}
|
||||
|
||||
v, ok := mp["IntValue"]
|
||||
if !ok {
|
||||
t.Fatal(fmt.Errorf("default watcher err: %v", v))
|
||||
}
|
||||
if nv, ok := v.(int); !ok || nv != 99 {
|
||||
t.Fatal(fmt.Errorf("default watcher err: %v", v))
|
||||
}
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conf := &Cfg{IntValue: 10}
|
||||
blfn := func(ctx context.Context, cfg config.Config) error {
|
||||
conf, ok := cfg.Options().Struct.(*Cfg)
|
||||
nconf, ok := cfg.Options().Struct.(*Cfg)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
|
||||
}
|
||||
conf.StringValue = "before_load"
|
||||
nconf.StringValue = "before_load"
|
||||
return nil
|
||||
}
|
||||
alfn := func(ctx context.Context, cfg config.Config) error {
|
||||
conf, ok := cfg.Options().Struct.(*Cfg)
|
||||
nconf, ok := cfg.Options().Struct.(*Cfg)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
|
||||
}
|
||||
conf.StringValue = "after_load"
|
||||
nconf.StringValue = "after_load"
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,6 +99,6 @@ func TestDefault(t *testing.T) {
|
||||
if conf.StringValue != "after_load" {
|
||||
t.Fatal("AfterLoad option not working")
|
||||
}
|
||||
|
||||
t.Logf("%#+v\n", conf)
|
||||
_ = conf
|
||||
//t.Logf("%#+v\n", conf)
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
@@ -57,6 +58,69 @@ func NewOptions(opts ...Option) Options {
|
||||
return options
|
||||
}
|
||||
|
||||
// LoadOption function signature
|
||||
type LoadOption func(o *LoadOptions)
|
||||
|
||||
// LoadOptions struct
|
||||
type LoadOptions struct {
|
||||
Struct interface{}
|
||||
Override bool
|
||||
Append bool
|
||||
}
|
||||
|
||||
func NewLoadOptions(opts ...LoadOption) LoadOptions {
|
||||
options := LoadOptions{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// LoadOverride override values when load
|
||||
func LoadOverride(b bool) LoadOption {
|
||||
return func(o *LoadOptions) {
|
||||
o.Override = b
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAppend override values when load
|
||||
func LoadAppend(b bool) LoadOption {
|
||||
return func(o *LoadOptions) {
|
||||
o.Append = b
|
||||
}
|
||||
}
|
||||
|
||||
// LoadStruct override struct for loading
|
||||
func LoadStruct(src interface{}) LoadOption {
|
||||
return func(o *LoadOptions) {
|
||||
o.Struct = src
|
||||
}
|
||||
}
|
||||
|
||||
// SaveOption function signature
|
||||
type SaveOption func(o *SaveOptions)
|
||||
|
||||
// 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 {
|
||||
options := SaveOptions{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// AllowFail allows config source to fail
|
||||
func AllowFail(b bool) Option {
|
||||
return func(o *Options) {
|
||||
@@ -140,3 +204,60 @@ func Name(n string) Option {
|
||||
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 {
|
||||
Value string
|
||||
SubConfig *SubConfig
|
||||
Config *Config
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@@ -17,7 +17,6 @@ func TestFromError(t *testing.T) {
|
||||
if merr.Id != "go.micro.test" || merr.Code != 404 {
|
||||
t.Fatalf("invalid conversation %v != %v", err, merr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
@@ -32,7 +31,6 @@ func TestEqual(t *testing.T) {
|
||||
if Equal(err1, err3) {
|
||||
t.Fatal("errors must be not equal")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
|
34
flow/context.go
Normal file
34
flow/context.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package flow
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type flowKey struct{}
|
||||
|
||||
// FromContext returns Flow from context
|
||||
func FromContext(ctx context.Context) (Flow, bool) {
|
||||
if ctx == nil {
|
||||
return nil, false
|
||||
}
|
||||
c, ok := ctx.Value(flowKey{}).(Flow)
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// NewContext stores Flow to context
|
||||
func NewContext(ctx context.Context, f Flow) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, flowKey{}, f)
|
||||
}
|
||||
|
||||
// SetOption returns a function to setup a context with given value
|
||||
func SetOption(k, v interface{}) Option {
|
||||
return func(o *Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, k, v)
|
||||
}
|
||||
}
|
@@ -44,10 +44,10 @@ func TestDag(t *testing.T) {
|
||||
var steps [][]string
|
||||
fn := func(n dag.Vertex, idx int) error {
|
||||
if idx == 0 {
|
||||
steps = make([][]string, 1, 1)
|
||||
steps = make([][]string, 1)
|
||||
steps[0] = make([]string, 0, 1)
|
||||
} else if idx >= len(steps) {
|
||||
tsteps := make([][]string, idx+1, idx+1)
|
||||
tsteps := make([][]string, idx+1)
|
||||
copy(tsteps, steps)
|
||||
steps = tsteps
|
||||
steps[idx] = make([]string, 0, 1)
|
||||
|
588
flow/default.go
Normal file
588
flow/default.go
Normal file
@@ -0,0 +1,588 @@
|
||||
package flow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/silas/dag"
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
"github.com/unistack-org/micro/v3/store"
|
||||
)
|
||||
|
||||
type microFlow struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
type microWorkflow struct {
|
||||
id string
|
||||
g *dag.AcyclicGraph
|
||||
init bool
|
||||
sync.RWMutex
|
||||
opts Options
|
||||
steps map[string]Step
|
||||
status Status
|
||||
}
|
||||
|
||||
func (w *microWorkflow) ID() string {
|
||||
return w.id
|
||||
}
|
||||
|
||||
func (w *microWorkflow) Steps() ([][]Step, error) {
|
||||
return w.getSteps("", false)
|
||||
}
|
||||
|
||||
func (w *microWorkflow) Status() Status {
|
||||
return w.status
|
||||
}
|
||||
|
||||
func (w *microWorkflow) AppendSteps(steps ...Step) error {
|
||||
w.Lock()
|
||||
|
||||
for _, s := range steps {
|
||||
w.steps[s.String()] = s
|
||||
w.g.Add(s)
|
||||
}
|
||||
|
||||
for _, dst := range steps {
|
||||
for _, req := range dst.Requires() {
|
||||
src, ok := w.steps[req]
|
||||
if !ok {
|
||||
return ErrStepNotExists
|
||||
}
|
||||
w.g.Connect(dag.BasicEdge(src, dst))
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.g.Validate(); err != nil {
|
||||
w.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
w.g.TransitiveReduction()
|
||||
|
||||
w.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *microWorkflow) RemoveSteps(steps ...Step) error {
|
||||
// TODO: handle case when some step requires or required by removed step
|
||||
|
||||
w.Lock()
|
||||
|
||||
for _, s := range steps {
|
||||
delete(w.steps, s.String())
|
||||
w.g.Remove(s)
|
||||
}
|
||||
|
||||
for _, dst := range steps {
|
||||
for _, req := range dst.Requires() {
|
||||
src, ok := w.steps[req]
|
||||
if !ok {
|
||||
return ErrStepNotExists
|
||||
}
|
||||
w.g.Connect(dag.BasicEdge(src, dst))
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.g.Validate(); err != nil {
|
||||
w.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
w.g.TransitiveReduction()
|
||||
|
||||
w.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) {
|
||||
var steps [][]Step
|
||||
var root dag.Vertex
|
||||
var err error
|
||||
|
||||
fn := func(n dag.Vertex, idx int) error {
|
||||
if idx == 0 {
|
||||
steps = make([][]Step, 1)
|
||||
steps[0] = make([]Step, 0, 1)
|
||||
} else if idx >= len(steps) {
|
||||
tsteps := make([][]Step, idx+1)
|
||||
copy(tsteps, steps)
|
||||
steps = tsteps
|
||||
steps[idx] = make([]Step, 0, 1)
|
||||
}
|
||||
steps[idx] = append(steps[idx], n.(Step))
|
||||
return nil
|
||||
}
|
||||
|
||||
if start != "" {
|
||||
var ok bool
|
||||
w.RLock()
|
||||
root, ok = w.steps[start]
|
||||
w.RUnlock()
|
||||
if !ok {
|
||||
return nil, ErrStepNotExists
|
||||
}
|
||||
} else {
|
||||
root, err = w.g.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if reverse {
|
||||
err = w.g.SortedReverseDepthFirstWalk([]dag.Vertex{root}, fn)
|
||||
} else {
|
||||
err = w.g.SortedDepthFirstWalk([]dag.Vertex{root}, fn)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return steps, nil
|
||||
}
|
||||
|
||||
func (w *microWorkflow) Abort(ctx context.Context, eid string) error {
|
||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
||||
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
|
||||
}
|
||||
|
||||
func (w *microWorkflow) Suspend(ctx context.Context, eid string) error {
|
||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
||||
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
|
||||
}
|
||||
|
||||
func (w *microWorkflow) Resume(ctx context.Context, eid string) error {
|
||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
||||
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
|
||||
}
|
||||
|
||||
func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error) {
|
||||
w.Lock()
|
||||
if !w.init {
|
||||
if err := w.g.Validate(); err != nil {
|
||||
w.Unlock()
|
||||
return "", err
|
||||
}
|
||||
w.g.TransitiveReduction()
|
||||
w.init = true
|
||||
}
|
||||
w.Unlock()
|
||||
|
||||
uid, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
eid := uid.String()
|
||||
|
||||
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid))
|
||||
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
|
||||
|
||||
options := NewExecuteOptions(opts...)
|
||||
|
||||
steps, err := w.getSteps(options.Start, options.Reverse)
|
||||
if err != nil {
|
||||
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
|
||||
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
cherr := make(chan error, 1)
|
||||
chstatus := make(chan Status, 1)
|
||||
|
||||
nctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
nopts := make([]ExecuteOption, 0, len(opts)+5)
|
||||
|
||||
nopts = append(nopts,
|
||||
ExecuteClient(w.opts.Client),
|
||||
ExecuteTracer(w.opts.Tracer),
|
||||
ExecuteLogger(w.opts.Logger),
|
||||
ExecuteMeter(w.opts.Meter),
|
||||
)
|
||||
nopts = append(nopts, opts...)
|
||||
done := make(chan struct{})
|
||||
|
||||
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
|
||||
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
|
||||
return eid, werr
|
||||
}
|
||||
for idx := range steps {
|
||||
for nidx := range steps[idx] {
|
||||
cstep := steps[idx][nidx]
|
||||
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
|
||||
return eid, werr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
for idx := range steps {
|
||||
for nidx := range steps[idx] {
|
||||
wStatus := &codec.Frame{}
|
||||
if werr := workflowStore.Read(w.opts.Context, "status", wStatus); werr != nil {
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
if status := StringStatus[string(wStatus.Data)]; status != StatusRunning {
|
||||
chstatus <- status
|
||||
return
|
||||
}
|
||||
if w.opts.Logger.V(logger.TraceLevel) {
|
||||
w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx])
|
||||
}
|
||||
cstep := steps[idx][nidx]
|
||||
if len(cstep.Requires()) == 0 {
|
||||
wg.Add(1)
|
||||
go func(step Step) {
|
||||
defer wg.Done()
|
||||
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "req"), req); werr != nil {
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
rsp, serr := step.Execute(nctx, req, nopts...)
|
||||
if serr != nil {
|
||||
step.SetStatus(StatusFailure)
|
||||
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
|
||||
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
|
||||
}
|
||||
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
|
||||
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
|
||||
}
|
||||
cherr <- serr
|
||||
return
|
||||
} else {
|
||||
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), rsp); werr != nil {
|
||||
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
|
||||
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
}
|
||||
}(cstep)
|
||||
wg.Wait()
|
||||
} else {
|
||||
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "req"), req); werr != nil {
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
rsp, serr := cstep.Execute(nctx, req, nopts...)
|
||||
if serr != nil {
|
||||
cstep.SetStatus(StatusFailure)
|
||||
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
|
||||
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
|
||||
}
|
||||
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
|
||||
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
|
||||
}
|
||||
cherr <- serr
|
||||
return
|
||||
} else {
|
||||
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), rsp); werr != nil {
|
||||
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
|
||||
cherr <- werr
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}()
|
||||
|
||||
if options.Async {
|
||||
return eid, nil
|
||||
}
|
||||
|
||||
logger.Tracef(ctx, "wait for finish or error")
|
||||
select {
|
||||
case <-nctx.Done():
|
||||
err = nctx.Err()
|
||||
case cerr := <-cherr:
|
||||
err = cerr
|
||||
case <-done:
|
||||
close(cherr)
|
||||
case <-chstatus:
|
||||
close(chstatus)
|
||||
return uid.String(), nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case nctx.Err() != nil:
|
||||
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
|
||||
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
|
||||
}
|
||||
break
|
||||
case err == nil:
|
||||
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
|
||||
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
|
||||
}
|
||||
break
|
||||
case err != nil:
|
||||
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
|
||||
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return uid.String(), err
|
||||
}
|
||||
|
||||
func NewFlow(opts ...Option) Flow {
|
||||
options := NewOptions(opts...)
|
||||
return µFlow{opts: options}
|
||||
}
|
||||
|
||||
func (f *microFlow) Options() Options {
|
||||
return f.opts
|
||||
}
|
||||
|
||||
func (f *microFlow) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&f.opts)
|
||||
}
|
||||
if err := f.opts.Client.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.opts.Tracer.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.opts.Logger.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.opts.Meter.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.opts.Store.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *microFlow) WorkflowList(ctx context.Context) ([]Workflow, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *microFlow) WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error) {
|
||||
w := µWorkflow{opts: f.opts, id: id, g: &dag.AcyclicGraph{}, steps: make(map[string]Step, len(steps))}
|
||||
|
||||
for _, s := range steps {
|
||||
w.steps[s.String()] = s
|
||||
w.g.Add(s)
|
||||
}
|
||||
|
||||
for _, dst := range steps {
|
||||
for _, req := range dst.Requires() {
|
||||
src, ok := w.steps[req]
|
||||
if !ok {
|
||||
return nil, ErrStepNotExists
|
||||
}
|
||||
w.g.Connect(dag.BasicEdge(src, dst))
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.g.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.g.TransitiveReduction()
|
||||
|
||||
w.init = true
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (f *microFlow) WorkflowRemove(ctx context.Context, id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *microFlow) WorkflowSave(ctx context.Context, w Workflow) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *microFlow) WorkflowLoad(ctx context.Context, id string) (Workflow, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type microCallStep struct {
|
||||
opts StepOptions
|
||||
service string
|
||||
method string
|
||||
rsp *Message
|
||||
req *Message
|
||||
status Status
|
||||
}
|
||||
|
||||
func (s *microCallStep) Request() *Message {
|
||||
return s.req
|
||||
}
|
||||
|
||||
func (s *microCallStep) Response() *Message {
|
||||
return s.rsp
|
||||
}
|
||||
|
||||
func (s *microCallStep) ID() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s *microCallStep) Options() StepOptions {
|
||||
return s.opts
|
||||
}
|
||||
|
||||
func (s *microCallStep) Endpoint() string {
|
||||
return s.method
|
||||
}
|
||||
|
||||
func (s *microCallStep) Requires() []string {
|
||||
return s.opts.Requires
|
||||
}
|
||||
|
||||
func (s *microCallStep) Require(steps ...Step) error {
|
||||
for _, step := range steps {
|
||||
s.opts.Requires = append(s.opts.Requires, step.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *microCallStep) String() string {
|
||||
if s.opts.ID != "" {
|
||||
return s.opts.ID
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", s.service, s.method)
|
||||
}
|
||||
|
||||
func (s *microCallStep) Name() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s *microCallStep) Hashcode() interface{} {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s *microCallStep) GetStatus() Status {
|
||||
return s.status
|
||||
}
|
||||
|
||||
func (s *microCallStep) SetStatus(status Status) {
|
||||
s.status = status
|
||||
}
|
||||
|
||||
func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
|
||||
options := NewExecuteOptions(opts...)
|
||||
if options.Client == nil {
|
||||
return nil, ErrMissingClient
|
||||
}
|
||||
rsp := &codec.Frame{}
|
||||
copts := []client.CallOption{client.WithRetries(0)}
|
||||
if options.Timeout > 0 {
|
||||
copts = append(copts, client.WithRequestTimeout(options.Timeout), client.WithDialTimeout(options.Timeout))
|
||||
}
|
||||
nctx := metadata.NewOutgoingContext(ctx, req.Header)
|
||||
err := options.Client.Call(nctx, options.Client.NewRequest(s.service, s.method, &codec.Frame{Data: req.Body}), rsp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
md, _ := metadata.FromOutgoingContext(nctx)
|
||||
return &Message{Header: md, Body: rsp.Data}, err
|
||||
}
|
||||
|
||||
type microPublishStep struct {
|
||||
opts StepOptions
|
||||
topic string
|
||||
req *Message
|
||||
rsp *Message
|
||||
status Status
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Request() *Message {
|
||||
return s.req
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Response() *Message {
|
||||
return s.rsp
|
||||
}
|
||||
|
||||
func (s *microPublishStep) ID() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Options() StepOptions {
|
||||
return s.opts
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Endpoint() string {
|
||||
return s.topic
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Requires() []string {
|
||||
return s.opts.Requires
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Require(steps ...Step) error {
|
||||
for _, step := range steps {
|
||||
s.opts.Requires = append(s.opts.Requires, step.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *microPublishStep) String() string {
|
||||
if s.opts.ID != "" {
|
||||
return s.opts.ID
|
||||
}
|
||||
return fmt.Sprintf("%s", s.topic)
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Name() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Hashcode() interface{} {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s *microPublishStep) GetStatus() Status {
|
||||
return s.status
|
||||
}
|
||||
|
||||
func (s *microPublishStep) SetStatus(status Status) {
|
||||
s.status = status
|
||||
}
|
||||
|
||||
func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewCallStep(service string, name string, method string, opts ...StepOption) Step {
|
||||
options := NewStepOptions(opts...)
|
||||
return µCallStep{service: service, method: name + "." + method, opts: options}
|
||||
}
|
||||
|
||||
func NewPublishStep(topic string, opts ...StepOption) Step {
|
||||
options := NewStepOptions(opts...)
|
||||
return µPublishStep{topic: topic, opts: options}
|
||||
}
|
153
flow/flow.go
153
flow/flow.go
@@ -1,7 +1,152 @@
|
||||
// Package flow is an interface used for saga pattern messaging
|
||||
// Package flow is an interface used for saga pattern microservice workflow
|
||||
package flow
|
||||
|
||||
type Step interface {
|
||||
// Endpoint returns service_name.service_method
|
||||
Endpoint() string
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrStepNotExists = errors.New("step not exists")
|
||||
ErrMissingClient = errors.New("client not set")
|
||||
)
|
||||
|
||||
// RawMessage is a raw encoded JSON value.
|
||||
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
|
||||
type RawMessage []byte
|
||||
|
||||
// MarshalJSON returns m as the JSON encoding of m.
|
||||
func (m *RawMessage) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return *m, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON sets *m to a copy of data.
|
||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||
if m == nil {
|
||||
return errors.New("RawMessage UnmarshalJSON on nil pointer")
|
||||
}
|
||||
*m = append((*m)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Header metadata.Metadata
|
||||
Body RawMessage
|
||||
}
|
||||
|
||||
// Step represents dedicated workflow step
|
||||
type Step interface {
|
||||
// ID returns step id
|
||||
ID() string
|
||||
// Endpoint returns rpc endpoint service_name.service_method or broker topic
|
||||
Endpoint() string
|
||||
// Execute step run
|
||||
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error)
|
||||
// Requires returns dependent steps
|
||||
Requires() []string
|
||||
// Options returns step options
|
||||
Options() StepOptions
|
||||
// Require add required steps
|
||||
Require(steps ...Step) error
|
||||
// String
|
||||
String() string
|
||||
// GetStatus returns step status
|
||||
GetStatus() Status
|
||||
// SetStatus sets the step status
|
||||
SetStatus(Status)
|
||||
// Request returns step request message
|
||||
Request() *Message
|
||||
// Response returns step response message
|
||||
Response() *Message
|
||||
}
|
||||
|
||||
type Status int
|
||||
|
||||
func (status Status) String() string {
|
||||
return StatusString[status]
|
||||
}
|
||||
|
||||
const (
|
||||
StatusPending Status = iota
|
||||
StatusRunning
|
||||
StatusFailure
|
||||
StatusSuccess
|
||||
StatusAborted
|
||||
StatusSuspend
|
||||
)
|
||||
|
||||
var (
|
||||
StatusString = map[Status]string{
|
||||
StatusPending: "StatusPending",
|
||||
StatusRunning: "StatusRunning",
|
||||
StatusFailure: "StatusFailure",
|
||||
StatusSuccess: "StatusSuccess",
|
||||
StatusAborted: "StatusAborted",
|
||||
StatusSuspend: "StatusSuspend",
|
||||
}
|
||||
StringStatus = map[string]Status{
|
||||
"StatusPending": StatusPending,
|
||||
"StatusRunning": StatusRunning,
|
||||
"StatusFailure": StatusFailure,
|
||||
"StatusSuccess": StatusSuccess,
|
||||
"StatusAborted": StatusAborted,
|
||||
"StatusSuspend": StatusSuspend,
|
||||
}
|
||||
)
|
||||
|
||||
// Workflow contains all steps to execute
|
||||
type Workflow interface {
|
||||
// ID returns id of the workflow
|
||||
ID() string
|
||||
// Execute workflow with args, return execution id and error
|
||||
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error)
|
||||
// RemoveSteps remove steps from workflow
|
||||
RemoveSteps(steps ...Step) error
|
||||
// AppendSteps append steps to workflow
|
||||
AppendSteps(steps ...Step) error
|
||||
// Status returns workflow status
|
||||
Status() Status
|
||||
// Steps returns steps slice where parallel steps returned on the same level
|
||||
Steps() ([][]Step, error)
|
||||
// Suspend suspends execution
|
||||
Suspend(ctx context.Context, eid string) error
|
||||
// Resume resumes execution
|
||||
Resume(ctx context.Context, eid string) error
|
||||
// Abort abort execution
|
||||
Abort(ctx context.Context, eid string) error
|
||||
}
|
||||
|
||||
// Flow the base interface to interact with workflows
|
||||
type Flow interface {
|
||||
// Options returns options
|
||||
Options() Options
|
||||
// Init initialize
|
||||
Init(...Option) error
|
||||
// WorkflowCreate creates new workflow with specific id and steps
|
||||
WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error)
|
||||
// WorkflowSave saves workflow
|
||||
WorkflowSave(ctx context.Context, w Workflow) error
|
||||
// WorkflowLoad loads workflow with specific id
|
||||
WorkflowLoad(ctx context.Context, id string) (Workflow, error)
|
||||
// WorkflowList lists all workflows
|
||||
WorkflowList(ctx context.Context) ([]Workflow, error)
|
||||
}
|
||||
|
||||
var (
|
||||
flowMu sync.Mutex
|
||||
atomicSteps atomic.Value
|
||||
)
|
||||
|
||||
func RegisterStep(step Step) {
|
||||
flowMu.Lock()
|
||||
steps, _ := atomicSteps.Load().([]Step)
|
||||
atomicSteps.Store(append(steps, step))
|
||||
flowMu.Unlock()
|
||||
}
|
||||
|
230
flow/options.go
Normal file
230
flow/options.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package flow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
"github.com/unistack-org/micro/v3/store"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
)
|
||||
|
||||
// Option func
|
||||
type Option func(*Options)
|
||||
|
||||
// Options server struct
|
||||
type Options struct {
|
||||
// Context holds the external options and can be used for flow shutdown
|
||||
Context context.Context
|
||||
// Client holds the client.Client
|
||||
Client client.Client
|
||||
// Tracer holds the tracer
|
||||
Tracer tracer.Tracer
|
||||
// Logger holds the logger
|
||||
Logger logger.Logger
|
||||
// Meter holds the meter
|
||||
Meter meter.Meter
|
||||
// Store used for intermediate results
|
||||
Store store.Store
|
||||
}
|
||||
|
||||
// NewOptions returns new options struct with default or passed values
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Context: context.Background(),
|
||||
Logger: logger.DefaultLogger,
|
||||
Meter: meter.DefaultMeter,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Client: client.DefaultClient,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// Logger sets the logger option
|
||||
func Logger(l logger.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
// Meter sets the meter option
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
// Client to use for sync/async communication
|
||||
func Client(c client.Client) Option {
|
||||
return func(o *Options) {
|
||||
o.Client = c
|
||||
}
|
||||
}
|
||||
|
||||
// Context specifies a context for the service.
|
||||
// Can be used to signal shutdown of the flow
|
||||
// Can be used for extra option values.
|
||||
func Context(ctx context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer mechanism for distributed tracking
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
// Store used for intermediate results
|
||||
func Store(s store.Store) Option {
|
||||
return func(o *Options) {
|
||||
o.Store = s
|
||||
}
|
||||
}
|
||||
|
||||
// WorflowOption signature
|
||||
type WorkflowOption func(*WorkflowOptions)
|
||||
|
||||
// WorkflowOptions holds workflow options
|
||||
type WorkflowOptions struct {
|
||||
ID string
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// WorkflowID set workflow id
|
||||
func WorkflowID(id string) WorkflowOption {
|
||||
return func(o *WorkflowOptions) {
|
||||
o.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
type ExecuteOptions struct {
|
||||
// Client holds the client.Client
|
||||
Client client.Client
|
||||
// Tracer holds the tracer
|
||||
Tracer tracer.Tracer
|
||||
// Logger holds the logger
|
||||
Logger logger.Logger
|
||||
// Meter holds the meter
|
||||
Meter meter.Meter
|
||||
// Context can be used to abort execution or pass additional opts
|
||||
Context context.Context
|
||||
// Start step
|
||||
Start string
|
||||
// Reverse execution
|
||||
Reverse bool
|
||||
// Timeout for execution
|
||||
Timeout time.Duration
|
||||
// Async enables async execution
|
||||
Async bool
|
||||
}
|
||||
|
||||
type ExecuteOption func(*ExecuteOptions)
|
||||
|
||||
func ExecuteClient(c client.Client) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Client = c
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteTracer(t tracer.Tracer) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Tracer = t
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteLogger(l logger.Logger) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Logger = l
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteMeter(m meter.Meter) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteContext(ctx context.Context) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteReverse(b bool) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Reverse = b
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteTimeout(td time.Duration) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Timeout = td
|
||||
}
|
||||
}
|
||||
|
||||
func ExecuteAsync(b bool) ExecuteOption {
|
||||
return func(o *ExecuteOptions) {
|
||||
o.Async = b
|
||||
}
|
||||
}
|
||||
|
||||
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
|
||||
options := ExecuteOptions{
|
||||
Client: client.DefaultClient,
|
||||
Logger: logger.DefaultLogger,
|
||||
Tracer: tracer.DefaultTracer,
|
||||
Meter: meter.DefaultMeter,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
type StepOptions struct {
|
||||
ID string
|
||||
Context context.Context
|
||||
Requires []string
|
||||
Fallback string
|
||||
}
|
||||
|
||||
type StepOption func(*StepOptions)
|
||||
|
||||
func NewStepOptions(opts ...StepOption) StepOptions {
|
||||
options := StepOptions{
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func StepID(id string) StepOption {
|
||||
return func(o *StepOptions) {
|
||||
o.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
func StepRequires(steps ...string) StepOption {
|
||||
return func(o *StepOptions) {
|
||||
o.Requires = steps
|
||||
}
|
||||
}
|
||||
|
||||
func StepFallback(step string) StepOption {
|
||||
return func(o *StepOptions) {
|
||||
o.Fallback = step
|
||||
}
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
package micro
|
||||
|
||||
//go:generate ./.github/generate.sh
|
6
go.mod
6
go.mod
@@ -3,11 +3,11 @@ module github.com/unistack-org/micro/v3
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/ef-ds/deque v1.0.4
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed
|
||||
)
|
||||
|
16
go.sum
16
go.sum
@@ -1,21 +1,21 @@
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
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/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
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/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
|
@@ -21,9 +21,11 @@ func init() {
|
||||
}
|
||||
|
||||
type defaultLogger struct {
|
||||
opts Options
|
||||
enc *json.Encoder
|
||||
opts Options
|
||||
sync.RWMutex
|
||||
logFunc LogFunc
|
||||
logfFunc LogfFunc
|
||||
}
|
||||
|
||||
// Init(opts...) should only overwrite provided options
|
||||
@@ -33,6 +35,12 @@ func (l *defaultLogger) Init(opts ...Option) error {
|
||||
o(&l.opts)
|
||||
}
|
||||
l.enc = json.NewEncoder(l.opts.Out)
|
||||
// wrap the Log func
|
||||
for i := len(l.opts.Wrappers); i > 0; i-- {
|
||||
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
|
||||
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
|
||||
}
|
||||
|
||||
l.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -121,27 +129,27 @@ func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
|
||||
l.Logf(ctx, InfoLevel, msg, args...)
|
||||
l.logfFunc(ctx, InfoLevel, msg, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
|
||||
l.Logf(ctx, ErrorLevel, msg, args...)
|
||||
l.logfFunc(ctx, ErrorLevel, msg, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
|
||||
l.Logf(ctx, DebugLevel, msg, args...)
|
||||
l.logfFunc(ctx, DebugLevel, msg, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
|
||||
l.Logf(ctx, WarnLevel, msg, args...)
|
||||
l.logfFunc(ctx, WarnLevel, msg, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
|
||||
l.Logf(ctx, TraceLevel, msg, args...)
|
||||
l.logfFunc(ctx, TraceLevel, msg, args...)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
|
||||
l.Logf(ctx, FatalLevel, msg, args...)
|
||||
l.logfFunc(ctx, FatalLevel, msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -207,7 +215,11 @@ func (l *defaultLogger) Options() Options {
|
||||
|
||||
// NewLogger builds a new logger based on options
|
||||
func NewLogger(opts ...Option) Logger {
|
||||
l := &defaultLogger{opts: NewOptions(opts...)}
|
||||
l := &defaultLogger{
|
||||
opts: NewOptions(opts...),
|
||||
}
|
||||
l.logFunc = l.Log
|
||||
l.logfFunc = l.Logf
|
||||
l.enc = json.NewEncoder(l.opts.Out)
|
||||
return l
|
||||
}
|
@@ -8,6 +8,8 @@ var (
|
||||
DefaultLogger Logger = NewLogger()
|
||||
// DefaultLevel used by logger
|
||||
DefaultLevel Level = InfoLevel
|
||||
// DefaultCallerSkipCount used by logger
|
||||
DefaultCallerSkipCount = 2
|
||||
)
|
||||
|
||||
// Logger is a generic logging interface
|
||||
|
@@ -1,13 +1,15 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
l := NewLogger(WithLevel(TraceLevel))
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
|
||||
if err := l.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -15,4 +17,52 @@ func TestLogger(t *testing.T) {
|
||||
l.Warn(ctx, "warn_msg1")
|
||||
l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message")
|
||||
l.Warn(ctx, "first", " ", "second")
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
|
||||
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||
}
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
|
||||
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||
}
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
|
||||
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||
}
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
|
||||
t.Fatalf("logger error, buf %s", buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerWrapper(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
|
||||
if err := l.Init(WrapLogger(NewOmitWrapper())); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
type secret struct {
|
||||
Name string
|
||||
Passw string `logger:"omit"`
|
||||
}
|
||||
s := &secret{Name: "name", Passw: "secret"}
|
||||
l.Errorf(ctx, "test %#+v", s)
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
|
||||
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOmitLoggerWrapper(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
l := NewOmitLogger(NewLogger(WithLevel(TraceLevel), WithOutput(buf)))
|
||||
if err := l.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
type secret struct {
|
||||
Name string
|
||||
Passw string `logger:"omit"`
|
||||
}
|
||||
s := &secret{Name: "name", Passw: "secret"}
|
||||
l.Errorf(ctx, "test %#+v", s)
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
|
||||
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,8 @@ type Options struct {
|
||||
CallerSkipCount int
|
||||
// The logging level the logger should log
|
||||
Level Level
|
||||
// Wrappers logger wrapper that called before actual Log/Logf function
|
||||
Wrappers []Wrapper
|
||||
}
|
||||
|
||||
// NewOptions creates new options struct
|
||||
@@ -31,7 +33,7 @@ func NewOptions(opts ...Option) Options {
|
||||
Level: DefaultLevel,
|
||||
Fields: make(map[string]interface{}),
|
||||
Out: os.Stderr,
|
||||
CallerSkipCount: 0,
|
||||
CallerSkipCount: DefaultCallerSkipCount,
|
||||
Context: context.Background(),
|
||||
}
|
||||
for _, o := range opts {
|
||||
@@ -76,8 +78,15 @@ func WithContext(ctx context.Context) Option {
|
||||
}
|
||||
|
||||
// WithName sets the name
|
||||
func withName(n string) Option {
|
||||
func WithName(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// WrapLogger adds a logger Wrapper to a list of options passed into the logger
|
||||
func WrapLogger(w Wrapper) Option {
|
||||
return func(o *Options) {
|
||||
o.Wrappers = append(o.Wrappers, w)
|
||||
}
|
||||
}
|
||||
|
154
logger/wrapper.go
Normal file
154
logger/wrapper.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
rutil "github.com/unistack-org/micro/v3/util/reflect"
|
||||
)
|
||||
|
||||
// LogFunc function used for Log method
|
||||
type LogFunc func(ctx context.Context, level Level, args ...interface{})
|
||||
|
||||
// LogfFunc function used for Logf method
|
||||
type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
|
||||
|
||||
type Wrapper interface {
|
||||
// Log logs message with needed level
|
||||
Log(LogFunc) LogFunc
|
||||
// Logf logs message with needed level
|
||||
Logf(LogfFunc) LogfFunc
|
||||
}
|
||||
|
||||
var (
|
||||
_ Logger = &OmitLogger{}
|
||||
)
|
||||
|
||||
type OmitLogger struct {
|
||||
l Logger
|
||||
}
|
||||
|
||||
func NewOmitLogger(l Logger) Logger {
|
||||
return &OmitLogger{l: l}
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Init(opts ...Option) error {
|
||||
return w.l.Init(append(opts, WrapLogger(NewOmitWrapper()))...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) V(level Level) bool {
|
||||
return w.l.V(level)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Options() Options {
|
||||
return w.l.Options()
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Fields(fields map[string]interface{}) Logger {
|
||||
return w.l.Fields(fields)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
|
||||
w.l.Info(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Trace(ctx context.Context, args ...interface{}) {
|
||||
w.l.Trace(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Debug(ctx context.Context, args ...interface{}) {
|
||||
w.l.Debug(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Warn(ctx context.Context, args ...interface{}) {
|
||||
w.l.Warn(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Error(ctx context.Context, args ...interface{}) {
|
||||
w.l.Error(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Fatal(ctx context.Context, args ...interface{}) {
|
||||
w.l.Fatal(ctx, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Infof(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Tracef(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Debugf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Warnf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Errorf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
|
||||
w.l.Fatalf(ctx, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
|
||||
w.l.Log(ctx, level, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
|
||||
w.l.Logf(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
func (w *OmitLogger) String() string {
|
||||
return w.l.String()
|
||||
}
|
||||
|
||||
type OmitWrapper struct{}
|
||||
|
||||
func NewOmitWrapper() Wrapper {
|
||||
return &OmitWrapper{}
|
||||
}
|
||||
|
||||
func getArgs(args []interface{}) []interface{} {
|
||||
nargs := make([]interface{}, 0, len(args))
|
||||
var err error
|
||||
for _, arg := range args {
|
||||
val := reflect.ValueOf(arg)
|
||||
switch val.Kind() {
|
||||
case reflect.Ptr:
|
||||
val = val.Elem()
|
||||
}
|
||||
narg := arg
|
||||
if val.Kind() == reflect.Struct {
|
||||
if narg, err = rutil.Zero(arg); err == nil {
|
||||
rutil.CopyDefaults(narg, arg)
|
||||
if flds, ferr := rutil.StructFields(narg); ferr == nil {
|
||||
for _, fld := range flds {
|
||||
if tv, ok := fld.Field.Tag.Lookup("logger"); ok && tv == "omit" {
|
||||
fld.Value.Set(reflect.Zero(fld.Value.Type()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nargs = append(nargs, narg)
|
||||
}
|
||||
return nargs
|
||||
}
|
||||
|
||||
func (w *OmitWrapper) Log(fn LogFunc) LogFunc {
|
||||
return func(ctx context.Context, level Level, args ...interface{}) {
|
||||
fn(ctx, level, getArgs(args)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
|
||||
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
|
||||
fn(ctx, level, msg, getArgs(args)...)
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ package wrapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
@@ -57,6 +58,8 @@ var (
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
DefaultSkipEndpoints = []string{"Meter.Metrics"}
|
||||
)
|
||||
|
||||
type lWrapper struct {
|
||||
@@ -67,21 +70,23 @@ type lWrapper struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
type ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
|
||||
type ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
|
||||
type ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
|
||||
type ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
|
||||
type ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
|
||||
type ServerSubscriberObserver func(context.Context, server.Message, error) []string
|
||||
type (
|
||||
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
|
||||
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
|
||||
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
|
||||
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
|
||||
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
|
||||
ServerSubscriberObserver func(context.Context, server.Message, error) []string
|
||||
)
|
||||
|
||||
// Options struct for wrapper
|
||||
type Options struct {
|
||||
// Logger that used for log
|
||||
Logger logger.Logger
|
||||
// Level for logger
|
||||
Level logger.Level
|
||||
// Enabled flag
|
||||
Enabled bool
|
||||
// ServerHandlerObservers funcs
|
||||
ServerHandlerObservers []ServerHandlerObserver
|
||||
// ServerSubscriberObservers funcs
|
||||
ServerSubscriberObservers []ServerSubscriberObserver
|
||||
// ClientCallObservers funcs
|
||||
ClientCallObservers []ClientCallObserver
|
||||
// ClientStreamObservers funcs
|
||||
@@ -90,10 +95,12 @@ type Options struct {
|
||||
ClientPublishObservers []ClientPublishObserver
|
||||
// ClientCallFuncObservers funcs
|
||||
ClientCallFuncObservers []ClientCallFuncObserver
|
||||
// ServerHandlerObservers funcs
|
||||
ServerHandlerObservers []ServerHandlerObserver
|
||||
// ServerSubscriberObservers funcs
|
||||
ServerSubscriberObservers []ServerSubscriberObserver
|
||||
// SkipEndpoints
|
||||
SkipEndpoints []string
|
||||
// Level for logger
|
||||
Level logger.Level
|
||||
// Enabled flag
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// Option func signature
|
||||
@@ -110,6 +117,7 @@ func NewOptions(opts ...Option) Options {
|
||||
ClientCallFuncObservers: []ClientCallFuncObserver{DefaultClientCallFuncObserver},
|
||||
ServerHandlerObservers: []ServerHandlerObserver{DefaultServerHandlerObserver},
|
||||
ServerSubscriberObservers: []ServerSubscriberObserver{DefaultServerSubscriberObserver},
|
||||
SkipEndpoints: DefaultSkipEndpoints,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
@@ -182,9 +190,23 @@ func WithServerSubscriberObservers(ob ...ServerSubscriberObserver) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// SkipEndpoins
|
||||
func SkipEndpoints(eps ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
|
||||
err := l.Client.Call(ctx, req, rsp, opts...)
|
||||
|
||||
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
|
||||
for _, ep := range l.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !l.opts.Enabled {
|
||||
return err
|
||||
}
|
||||
@@ -193,7 +215,7 @@ func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}
|
||||
for _, o := range l.opts.ClientCallObservers {
|
||||
labels = append(labels, o(ctx, req, rsp, opts, err)...)
|
||||
}
|
||||
fields := make(map[string]interface{}, int(len(labels)/2))
|
||||
fields := make(map[string]interface{}, len(labels)/2)
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
fields[labels[i]] = labels[i+1]
|
||||
}
|
||||
@@ -205,6 +227,13 @@ func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}
|
||||
func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
|
||||
stream, err := l.Client.Stream(ctx, req, opts...)
|
||||
|
||||
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
|
||||
for _, ep := range l.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return stream, err
|
||||
}
|
||||
}
|
||||
|
||||
if !l.opts.Enabled {
|
||||
return stream, err
|
||||
}
|
||||
@@ -213,7 +242,7 @@ func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...clien
|
||||
for _, o := range l.opts.ClientStreamObservers {
|
||||
labels = append(labels, o(ctx, req, opts, stream, err)...)
|
||||
}
|
||||
fields := make(map[string]interface{}, int(len(labels)/2))
|
||||
fields := make(map[string]interface{}, len(labels)/2)
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
fields[labels[i]] = labels[i+1]
|
||||
}
|
||||
@@ -225,6 +254,13 @@ func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...clien
|
||||
func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
|
||||
err := l.Client.Publish(ctx, msg, opts...)
|
||||
|
||||
endpoint := msg.Topic()
|
||||
for _, ep := range l.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !l.opts.Enabled {
|
||||
return err
|
||||
}
|
||||
@@ -233,7 +269,7 @@ func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...clie
|
||||
for _, o := range l.opts.ClientPublishObservers {
|
||||
labels = append(labels, o(ctx, msg, opts, err)...)
|
||||
}
|
||||
fields := make(map[string]interface{}, int(len(labels)/2))
|
||||
fields := make(map[string]interface{}, len(labels)/2)
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
fields[labels[i]] = labels[i+1]
|
||||
}
|
||||
@@ -245,6 +281,13 @@ func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...clie
|
||||
func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
err := l.serverHandler(ctx, req, rsp)
|
||||
|
||||
endpoint := req.Endpoint()
|
||||
for _, ep := range l.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !l.opts.Enabled {
|
||||
return err
|
||||
}
|
||||
@@ -253,7 +296,7 @@ func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp in
|
||||
for _, o := range l.opts.ServerHandlerObservers {
|
||||
labels = append(labels, o(ctx, req, rsp, err)...)
|
||||
}
|
||||
fields := make(map[string]interface{}, int(len(labels)/2))
|
||||
fields := make(map[string]interface{}, len(labels)/2)
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
fields[labels[i]] = labels[i+1]
|
||||
}
|
||||
@@ -265,6 +308,13 @@ func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp in
|
||||
func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) error {
|
||||
err := l.serverSubscriber(ctx, msg)
|
||||
|
||||
endpoint := msg.Topic()
|
||||
for _, ep := range l.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !l.opts.Enabled {
|
||||
return err
|
||||
}
|
||||
@@ -273,7 +323,7 @@ func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) err
|
||||
for _, o := range l.opts.ServerSubscriberObservers {
|
||||
labels = append(labels, o(ctx, msg, err)...)
|
||||
}
|
||||
fields := make(map[string]interface{}, int(len(labels)/2))
|
||||
fields := make(map[string]interface{}, len(labels)/2)
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
fields[labels[i]] = labels[i+1]
|
||||
}
|
||||
@@ -309,6 +359,13 @@ func NewClientCallWrapper(opts ...Option) client.CallWrapper {
|
||||
func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
err := l.clientCallFunc(ctx, addr, req, rsp, opts)
|
||||
|
||||
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
|
||||
for _, ep := range l.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !l.opts.Enabled {
|
||||
return err
|
||||
}
|
||||
@@ -317,7 +374,7 @@ func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.R
|
||||
for _, o := range l.opts.ClientCallFuncObservers {
|
||||
labels = append(labels, o(ctx, addr, req, rsp, opts, err)...)
|
||||
}
|
||||
fields := make(map[string]interface{}, int(len(labels)/2))
|
||||
fields := make(map[string]interface{}, len(labels)/2)
|
||||
for i := 0; i < len(labels); i += 2 {
|
||||
fields[labels[i]] = labels[i+1]
|
||||
}
|
||||
|
@@ -5,9 +5,11 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type mdIncomingKey struct{}
|
||||
type mdOutgoingKey struct{}
|
||||
type mdKey struct{}
|
||||
type (
|
||||
mdIncomingKey struct{}
|
||||
mdOutgoingKey struct{}
|
||||
mdKey struct{}
|
||||
)
|
||||
|
||||
// FromIncomingContext returns metadata from incoming ctx
|
||||
// returned metadata shoud not be modified or race condition happens
|
||||
|
@@ -7,8 +7,18 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// HeaderPrefix for all headers passed
|
||||
HeaderPrefix = "Micro-"
|
||||
// HeaderTopic is the header name that contains topic name
|
||||
HeaderTopic = "Micro-Topic"
|
||||
// HeaderContentType specifies content type of message
|
||||
HeaderContentType = "Content-Type"
|
||||
// HeaderEndpoint specifies endpoint in service
|
||||
HeaderEndpoint = "Micro-Endpoint"
|
||||
// HeaderService specifies service
|
||||
HeaderService = "Micro-Service"
|
||||
// HeaderTimeout specifies timeout of operation
|
||||
HeaderTimeout = "Micro-Timeout"
|
||||
// HeaderAuthorization specifies Authorization header
|
||||
HeaderAuthorization = "Authorization"
|
||||
)
|
||||
|
||||
// Metadata is our way of representing request headers internally.
|
||||
@@ -20,10 +30,8 @@ type rawMetadata struct {
|
||||
md Metadata
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultMetadataSize used when need to init new Metadata
|
||||
defaultMetadataSize = 2
|
||||
)
|
||||
// defaultMetadataSize used when need to init new Metadata
|
||||
var defaultMetadataSize = 2
|
||||
|
||||
// Iterator used to iterate over metadata with order
|
||||
type Iterator struct {
|
||||
|
@@ -76,7 +76,7 @@ func TestIterator(t *testing.T) {
|
||||
var k, v string
|
||||
|
||||
for iter.Next(&k, &v) {
|
||||
//fmt.Printf("k: %s, v: %s\n", k, v)
|
||||
// fmt.Printf("k: %s, v: %s\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ func TestMedataCanonicalKey(t *testing.T) {
|
||||
} else if v != "12345" {
|
||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMetadataSet(t *testing.T) {
|
||||
@@ -130,7 +129,6 @@ func TestMetadataDelete(t *testing.T) {
|
||||
if ok {
|
||||
t.Fatal("key Baz not deleted")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNilContext(t *testing.T) {
|
||||
|
@@ -1,33 +1,64 @@
|
||||
package pb
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/errors"
|
||||
"github.com/unistack-org/micro/v3/meter"
|
||||
)
|
||||
|
||||
var (
|
||||
// guard to fail early
|
||||
_ MeterServer = &handler{}
|
||||
)
|
||||
// guard to fail early
|
||||
var _ MeterServer = &Handler{}
|
||||
|
||||
type Empty struct{}
|
||||
|
||||
type handler struct {
|
||||
meter meter.Meter
|
||||
opts []meter.Option
|
||||
type Handler struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
func NewHandler(meter meter.Meter, opts ...meter.Option) *handler {
|
||||
return &handler{meter: meter, opts: opts}
|
||||
type Option func(*Options)
|
||||
|
||||
type Options struct {
|
||||
Meter meter.Meter
|
||||
Name string
|
||||
MeterOptions []meter.Option
|
||||
}
|
||||
|
||||
func (h *handler) Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error {
|
||||
func Meter(m meter.Meter) Option {
|
||||
return func(o *Options) {
|
||||
o.Meter = m
|
||||
}
|
||||
}
|
||||
|
||||
func Name(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
func MeterOptions(opts ...meter.Option) Option {
|
||||
return func(o *Options) {
|
||||
o.MeterOptions = append(o.MeterOptions, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{Meter: meter.DefaultMeter}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func NewHandler(opts ...Option) *Handler {
|
||||
options := NewOptions(opts...)
|
||||
return &Handler{opts: options}
|
||||
}
|
||||
|
||||
func (h *Handler) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := h.meter.Write(buf, h.opts...); err != nil {
|
||||
return err
|
||||
if err := h.opts.Meter.Write(buf, h.opts.MeterOptions...); err != nil {
|
||||
return errors.InternalServerError(h.opts.Name, "%v", err)
|
||||
}
|
||||
|
||||
rsp.Data = buf.Bytes()
|
||||
|
@@ -1,14 +1,14 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meter;
|
||||
option go_package = "github.com/unistack-org/micro/v3/meter/handler;pb";
|
||||
package micro.meter.handler;
|
||||
option go_package = "github.com/unistack-org/micro/v3/meter/handler;handler";
|
||||
|
||||
import "api/annotations.proto";
|
||||
import "openapiv2/annotations.proto";
|
||||
import "codec/frame.proto";
|
||||
|
||||
service Meter {
|
||||
rpc Metrics(Empty) returns (micro.codec.Frame) {
|
||||
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv2.openapiv2_operation) = {
|
||||
operation_id: "Metrics";
|
||||
responses: {
|
||||
@@ -17,7 +17,7 @@ service Meter {
|
||||
description: "Error response";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "Empty";
|
||||
ref: "micro.codec.Frame";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,5 +26,3 @@ service Meter {
|
||||
option (micro.api.http) = { get: "/metrics"; };
|
||||
};
|
||||
};
|
||||
|
||||
message Empty{};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-micro
|
||||
// source: handler.proto
|
||||
package pb
|
||||
package handler
|
||||
|
||||
import (
|
||||
context "context"
|
||||
@@ -20,5 +20,5 @@ func NewMeterEndpoints() []*api.Endpoint {
|
||||
}
|
||||
|
||||
type MeterServer interface {
|
||||
Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error
|
||||
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-micro
|
||||
// source: handler.proto
|
||||
package pb
|
||||
package handler
|
||||
|
||||
import (
|
||||
context "context"
|
||||
@@ -13,20 +13,21 @@ type meterServer struct {
|
||||
MeterServer
|
||||
}
|
||||
|
||||
func (h *meterServer) Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error {
|
||||
func (h *meterServer) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.MeterServer.Metrics(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func RegisterMeterServer(s server.Server, sh MeterServer, opts ...server.HandlerOption) error {
|
||||
type meter interface {
|
||||
Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error
|
||||
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
||||
type Meter struct {
|
||||
meter
|
||||
}
|
||||
h := &meterServer{sh}
|
||||
var nopts []server.HandlerOption
|
||||
for _, endpoint := range NewMeterEndpoints() {
|
||||
opts = append(opts, api.WithEndpoint(endpoint))
|
||||
nopts = append(nopts, api.WithEndpoint(endpoint))
|
||||
}
|
||||
return s.Handle(s.NewHandler(&Meter{h}, opts...))
|
||||
return s.Handle(s.NewHandler(&Meter{h}, append(nopts, opts...)...))
|
||||
}
|
||||
|
@@ -3,8 +3,9 @@ package meter
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -29,13 +30,13 @@ var (
|
||||
type Meter interface {
|
||||
Name() string
|
||||
Init(opts ...Option) error
|
||||
Counter(name string, opts ...Option) Counter
|
||||
FloatCounter(name string, opts ...Option) FloatCounter
|
||||
Gauge(name string, fn func() float64, opts ...Option) Gauge
|
||||
Counter(name string, labels ...string) Counter
|
||||
FloatCounter(name string, labels ...string) FloatCounter
|
||||
Gauge(name string, fn func() float64, labels ...string) Gauge
|
||||
Set(opts ...Option) Meter
|
||||
Histogram(name string, opts ...Option) Histogram
|
||||
Summary(name string, opts ...Option) Summary
|
||||
SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary
|
||||
Histogram(name string, labels ...string) Histogram
|
||||
Summary(name string, labels ...string) Summary
|
||||
SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
|
||||
Write(w io.Writer, opts ...Option) error
|
||||
Options() Options
|
||||
String() string
|
||||
@@ -77,36 +78,62 @@ type Summary interface {
|
||||
UpdateDuration(time.Time)
|
||||
}
|
||||
|
||||
// sort labels alphabeticaly by label name
|
||||
type byKey []string
|
||||
|
||||
func (k byKey) Len() int { return len(k) / 2 }
|
||||
func (k byKey) Less(i, j int) bool { return k[i*2] < k[j*2] }
|
||||
func (k byKey) Swap(i, j int) {
|
||||
k[i*2], k[i*2+1], k[j*2], k[j*2+1] = k[j*2], k[j*2+1], k[i*2], k[i*2+1]
|
||||
k[i*2], k[j*2] = k[j*2], k[i*2]
|
||||
k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1]
|
||||
}
|
||||
|
||||
func Sort(slice *[]string) {
|
||||
bk := byKey(*slice)
|
||||
if bk.Len() <= 1 {
|
||||
return
|
||||
// BuildLables used to sort labels and delete duplicates.
|
||||
// Last value wins in case of duplicate label keys.
|
||||
func BuildLabels(labels ...string) []string {
|
||||
if len(labels)%2 == 1 {
|
||||
labels = labels[:len(labels)-1]
|
||||
}
|
||||
sort.Sort(bk)
|
||||
v := reflect.ValueOf(slice).Elem()
|
||||
cnt := 0
|
||||
key := 0
|
||||
val := 1
|
||||
for key < v.Len() {
|
||||
if len(bk) > key+2 && bk[key] == bk[key+2] {
|
||||
key += 2
|
||||
val += 2
|
||||
continue
|
||||
}
|
||||
v.Index(cnt).Set(v.Index(key))
|
||||
cnt++
|
||||
v.Index(cnt).Set(v.Index(val))
|
||||
cnt++
|
||||
key += 2
|
||||
val += 2
|
||||
}
|
||||
v.SetLen(cnt)
|
||||
sort.Sort(byKey(labels))
|
||||
return labels
|
||||
}
|
||||
|
||||
// BuildName used to combine metric with labels.
|
||||
// If labels count is odd, drop last element
|
||||
func BuildName(name string, labels ...string) string {
|
||||
if len(labels)%2 == 1 {
|
||||
labels = labels[:len(labels)-1]
|
||||
}
|
||||
|
||||
if len(labels) > 2 {
|
||||
sort.Sort(byKey(labels))
|
||||
|
||||
idx := 0
|
||||
for {
|
||||
if labels[idx] == labels[idx+2] {
|
||||
copy(labels[idx:], labels[idx+2:])
|
||||
labels = labels[:len(labels)-2]
|
||||
} else {
|
||||
idx += 2
|
||||
}
|
||||
if idx+2 >= len(labels) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
_, _ = b.WriteString(name)
|
||||
_, _ = b.WriteRune('{')
|
||||
for idx := 0; idx < len(labels); idx += 2 {
|
||||
if idx > 0 {
|
||||
_, _ = b.WriteRune(',')
|
||||
}
|
||||
_, _ = b.WriteString(labels[idx])
|
||||
_, _ = b.WriteString(`=`)
|
||||
_, _ = b.WriteString(strconv.Quote(labels[idx+1]))
|
||||
}
|
||||
_, _ = b.WriteRune('}')
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
@@ -10,15 +10,61 @@ func TestNoopMeter(t *testing.T) {
|
||||
t.Fatalf("invalid options parsing: %v", m.Options())
|
||||
}
|
||||
|
||||
cnt := m.Counter("counter", Labels("server", "noop"))
|
||||
cnt := m.Counter("counter", "server", "noop")
|
||||
cnt.Inc()
|
||||
}
|
||||
|
||||
func TestLabelsSort(t *testing.T) {
|
||||
ls := []string{"server", "http", "register", "mdns", "broker", "broker1", "broker", "broker2", "server", "tcp"}
|
||||
Sort(&ls)
|
||||
func testEq(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if ls[0] != "broker" || ls[1] != "broker2" {
|
||||
t.Fatalf("sort error: %v", ls)
|
||||
func TestBuildLabels(t *testing.T) {
|
||||
type testData struct {
|
||||
src []string
|
||||
dst []string
|
||||
}
|
||||
|
||||
data := []testData{
|
||||
testData{
|
||||
src: []string{"zerolabel", "value3", "firstlabel", "value2"},
|
||||
dst: []string{"firstlabel", "value2", "zerolabel", "value3"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, d := range data {
|
||||
if !testEq(d.dst, BuildLabels(d.src...)) {
|
||||
t.Fatalf("slices not properly sorted: %v %v", d.dst, d.src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildName(t *testing.T) {
|
||||
data := map[string][]string{
|
||||
`my_metric{firstlabel="value2",zerolabel="value3"}`: []string{
|
||||
"my_metric",
|
||||
"zerolabel", "value3", "firstlabel", "value2",
|
||||
},
|
||||
`my_metric{broker="broker2",register="mdns",server="tcp"}`: []string{
|
||||
"my_metric",
|
||||
"broker", "broker1", "broker", "broker2", "server", "http", "server", "tcp", "register", "mdns",
|
||||
},
|
||||
`my_metric{aaa="aaa"}`: []string{
|
||||
"my_metric",
|
||||
"aaa", "aaa",
|
||||
},
|
||||
}
|
||||
|
||||
for e, d := range data {
|
||||
if x := BuildName(d[0], d[1:]...); x != e {
|
||||
t.Fatalf("expect: %s, result: %s", e, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -28,57 +28,33 @@ func (r *noopMeter) Init(opts ...Option) error {
|
||||
}
|
||||
|
||||
// Counter implements the Meter interface
|
||||
func (r *noopMeter) Counter(name string, opts ...Option) Counter {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &noopCounter{labels: options.Labels}
|
||||
func (r *noopMeter) Counter(name string, labels ...string) Counter {
|
||||
return &noopCounter{labels: labels}
|
||||
}
|
||||
|
||||
// FloatCounter implements the Meter interface
|
||||
func (r *noopMeter) FloatCounter(name string, opts ...Option) FloatCounter {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &noopFloatCounter{labels: options.Labels}
|
||||
func (r *noopMeter) FloatCounter(name string, labels ...string) FloatCounter {
|
||||
return &noopFloatCounter{labels: labels}
|
||||
}
|
||||
|
||||
// Gauge implements the Meter interface
|
||||
func (r *noopMeter) Gauge(name string, f func() float64, opts ...Option) Gauge {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &noopGauge{labels: options.Labels}
|
||||
func (r *noopMeter) Gauge(name string, f func() float64, labels ...string) Gauge {
|
||||
return &noopGauge{labels: labels}
|
||||
}
|
||||
|
||||
// Summary implements the Meter interface
|
||||
func (r *noopMeter) Summary(name string, opts ...Option) Summary {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &noopSummary{labels: options.Labels}
|
||||
func (r *noopMeter) Summary(name string, labels ...string) Summary {
|
||||
return &noopSummary{labels: labels}
|
||||
}
|
||||
|
||||
// SummaryExt implements the Meter interface
|
||||
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &noopSummary{labels: options.Labels}
|
||||
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary {
|
||||
return &noopSummary{labels: labels}
|
||||
}
|
||||
|
||||
// Histogram implements the Meter interface
|
||||
func (r *noopMeter) Histogram(name string, opts ...Option) Histogram {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &noopHistogram{labels: options.Labels}
|
||||
func (r *noopMeter) Histogram(name string, labels ...string) Histogram {
|
||||
return &noopHistogram{labels: labels}
|
||||
}
|
||||
|
||||
// Set implements the Meter interface
|
||||
@@ -111,11 +87,9 @@ type noopCounter struct {
|
||||
}
|
||||
|
||||
func (r *noopCounter) Add(int) {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopCounter) Dec() {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopCounter) Get() uint64 {
|
||||
@@ -123,11 +97,9 @@ func (r *noopCounter) Get() uint64 {
|
||||
}
|
||||
|
||||
func (r *noopCounter) Inc() {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopCounter) Set(uint64) {
|
||||
|
||||
}
|
||||
|
||||
type noopFloatCounter struct {
|
||||
@@ -135,7 +107,6 @@ type noopFloatCounter struct {
|
||||
}
|
||||
|
||||
func (r *noopFloatCounter) Add(float64) {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopFloatCounter) Get() float64 {
|
||||
@@ -143,11 +114,9 @@ func (r *noopFloatCounter) Get() float64 {
|
||||
}
|
||||
|
||||
func (r *noopFloatCounter) Set(float64) {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopFloatCounter) Sub(float64) {
|
||||
|
||||
}
|
||||
|
||||
type noopGauge struct {
|
||||
@@ -163,11 +132,9 @@ type noopSummary struct {
|
||||
}
|
||||
|
||||
func (r *noopSummary) Update(float64) {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopSummary) UpdateDuration(time.Time) {
|
||||
|
||||
}
|
||||
|
||||
type noopHistogram struct {
|
||||
@@ -175,15 +142,12 @@ type noopHistogram struct {
|
||||
}
|
||||
|
||||
func (r *noopHistogram) Reset() {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopHistogram) Update(float64) {
|
||||
|
||||
}
|
||||
|
||||
func (r *noopHistogram) UpdateDuration(time.Time) {
|
||||
|
||||
}
|
||||
|
||||
//func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {}
|
||||
// func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {}
|
||||
|
@@ -90,7 +90,7 @@ func Logger(l logger.Logger) Option {
|
||||
|
||||
func Labels(ls ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.Labels = ls
|
||||
o.Labels = append(o.Labels, ls...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,36 +11,48 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ClientRequestDurationSeconds = "client_request_duration_seconds"
|
||||
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
|
||||
ClientRequestTotal = "client_request_total"
|
||||
ServerRequestDurationSeconds = "server_request_duration_seconds"
|
||||
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
|
||||
ServerRequestTotal = "server_request_total"
|
||||
PublishMessageDurationSeconds = "publish_message_duration_seconds"
|
||||
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
|
||||
PublishMessageTotal = "publish_message_total"
|
||||
ClientRequestDurationSeconds = "client_request_duration_seconds"
|
||||
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
|
||||
ClientRequestTotal = "client_request_total"
|
||||
ClientRequestInflight = "client_request_inflight"
|
||||
|
||||
ServerRequestDurationSeconds = "server_request_duration_seconds"
|
||||
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
|
||||
ServerRequestTotal = "server_request_total"
|
||||
ServerRequestInflight = "server_request_inflight"
|
||||
|
||||
PublishMessageDurationSeconds = "publish_message_duration_seconds"
|
||||
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
|
||||
PublishMessageTotal = "publish_message_total"
|
||||
PublishMessageInflight = "publish_message_inflight"
|
||||
|
||||
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
|
||||
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
|
||||
SubscribeMessageTotal = "subscribe_message_total"
|
||||
SubscribeMessageInflight = "subscribe_message_inflight"
|
||||
|
||||
labelSuccess = "success"
|
||||
labelFailure = "failure"
|
||||
labelStatus = "status"
|
||||
labelEndpoint = "endpoint"
|
||||
|
||||
// DefaultSkipEndpoints contains list of endpoints that not evaluted by wrapper
|
||||
DefaultSkipEndpoints = []string{"Meter.Metrics"}
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Meter meter.Meter
|
||||
lopts []meter.Option
|
||||
Meter meter.Meter
|
||||
lopts []meter.Option
|
||||
SkipEndpoints []string
|
||||
}
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Meter: meter.DefaultMeter,
|
||||
lopts: make([]meter.Option, 0, 5),
|
||||
Meter: meter.DefaultMeter,
|
||||
lopts: make([]meter.Option, 0, 5),
|
||||
SkipEndpoints: DefaultSkipEndpoints,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
@@ -72,6 +84,12 @@ func Meter(m meter.Meter) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func SkipEndoints(eps ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
|
||||
}
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
client.Client
|
||||
callFunc client.CallFunc
|
||||
@@ -100,69 +118,90 @@ func NewCallWrapper(opts ...Option) client.CallWrapper {
|
||||
|
||||
func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
|
||||
for _, ep := range w.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return w.callFunc(ctx, addr, req, rsp, opts)
|
||||
}
|
||||
}
|
||||
|
||||
labels := make([]string, 0, 4)
|
||||
labels = append(labels, labelEndpoint, endpoint)
|
||||
|
||||
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
|
||||
ts := time.Now()
|
||||
err := w.callFunc(ctx, addr, req, rsp, opts)
|
||||
te := time.Since(ts)
|
||||
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
|
||||
|
||||
lopts := w.opts.lopts
|
||||
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
|
||||
|
||||
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
|
||||
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
|
||||
|
||||
if err == nil {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
|
||||
labels = append(labels, labelStatus, labelSuccess)
|
||||
} else {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
|
||||
labels = append(labels, labelStatus, labelFailure)
|
||||
}
|
||||
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
|
||||
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
|
||||
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
|
||||
for _, ep := range w.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return w.Client.Call(ctx, req, rsp, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
labels := make([]string, 0, 4)
|
||||
labels = append(labels, labelEndpoint, endpoint)
|
||||
|
||||
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
|
||||
ts := time.Now()
|
||||
err := w.Client.Call(ctx, req, rsp, opts...)
|
||||
te := time.Since(ts)
|
||||
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
|
||||
|
||||
lopts := w.opts.lopts
|
||||
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
|
||||
|
||||
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
|
||||
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
|
||||
|
||||
if err == nil {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
|
||||
labels = append(labels, labelStatus, labelSuccess)
|
||||
} else {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
|
||||
labels = append(labels, labelStatus, labelFailure)
|
||||
}
|
||||
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
|
||||
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
|
||||
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
|
||||
for _, ep := range w.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return w.Client.Stream(ctx, req, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
labels := make([]string, 0, 4)
|
||||
labels = append(labels, labelEndpoint, endpoint)
|
||||
|
||||
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
|
||||
ts := time.Now()
|
||||
stream, err := w.Client.Stream(ctx, req, opts...)
|
||||
te := time.Since(ts)
|
||||
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
|
||||
|
||||
lopts := w.opts.lopts
|
||||
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
|
||||
|
||||
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
|
||||
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
|
||||
|
||||
if err == nil {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
|
||||
labels = append(labels, labelStatus, labelSuccess)
|
||||
} else {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
|
||||
labels = append(labels, labelStatus, labelFailure)
|
||||
}
|
||||
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
|
||||
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
|
||||
|
||||
return stream, err
|
||||
}
|
||||
@@ -170,22 +209,24 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client
|
||||
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
|
||||
endpoint := p.Topic()
|
||||
|
||||
labels := make([]string, 0, 4)
|
||||
labels = append(labels, labelEndpoint, endpoint)
|
||||
|
||||
w.opts.Meter.Counter(PublishMessageInflight, labels...).Inc()
|
||||
ts := time.Now()
|
||||
err := w.Client.Publish(ctx, p, opts...)
|
||||
te := time.Since(ts)
|
||||
w.opts.Meter.Counter(PublishMessageInflight, labels...).Dec()
|
||||
|
||||
lopts := w.opts.lopts
|
||||
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
|
||||
|
||||
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Histogram(PublishMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, labels...).Update(te.Seconds())
|
||||
w.opts.Meter.Histogram(PublishMessageDurationSeconds, labels...).Update(te.Seconds())
|
||||
|
||||
if err == nil {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
|
||||
labels = append(labels, labelStatus, labelSuccess)
|
||||
} else {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
|
||||
labels = append(labels, labelStatus, labelFailure)
|
||||
}
|
||||
w.opts.Meter.Counter(PublishMessageTotal, lopts...).Inc()
|
||||
w.opts.Meter.Counter(PublishMessageTotal, labels...).Inc()
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -200,23 +241,30 @@ func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
|
||||
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
|
||||
return func(ctx context.Context, req server.Request, rsp interface{}) error {
|
||||
endpoint := req.Endpoint()
|
||||
for _, ep := range w.opts.SkipEndpoints {
|
||||
if ep == endpoint {
|
||||
return fn(ctx, req, rsp)
|
||||
}
|
||||
}
|
||||
|
||||
labels := make([]string, 0, 4)
|
||||
labels = append(labels, labelEndpoint, endpoint)
|
||||
|
||||
w.opts.Meter.Counter(ServerRequestInflight, labels...).Inc()
|
||||
ts := time.Now()
|
||||
err := fn(ctx, req, rsp)
|
||||
te := time.Since(ts)
|
||||
w.opts.Meter.Counter(ServerRequestInflight, labels...).Dec()
|
||||
|
||||
lopts := w.opts.lopts
|
||||
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
|
||||
|
||||
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Histogram(ServerRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
|
||||
w.opts.Meter.Histogram(ServerRequestDurationSeconds, labels...).Update(te.Seconds())
|
||||
|
||||
if err == nil {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
|
||||
labels = append(labels, labelStatus, labelSuccess)
|
||||
} else {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
|
||||
labels = append(labels, labelStatus, labelFailure)
|
||||
}
|
||||
w.opts.Meter.Counter(ServerRequestTotal, lopts...).Inc()
|
||||
w.opts.Meter.Counter(ServerRequestTotal, labels...).Inc()
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -233,22 +281,24 @@ func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc
|
||||
return func(ctx context.Context, msg server.Message) error {
|
||||
endpoint := msg.Topic()
|
||||
|
||||
labels := make([]string, 0, 4)
|
||||
labels = append(labels, labelEndpoint, endpoint)
|
||||
|
||||
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Inc()
|
||||
ts := time.Now()
|
||||
err := fn(ctx, msg)
|
||||
te := time.Since(ts)
|
||||
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Dec()
|
||||
|
||||
lopts := w.opts.lopts
|
||||
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
|
||||
|
||||
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
|
||||
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, labels...).Update(te.Seconds())
|
||||
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, labels...).Update(te.Seconds())
|
||||
|
||||
if err == nil {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
|
||||
labels = append(labels, labelStatus, labelSuccess)
|
||||
} else {
|
||||
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
|
||||
labels = append(labels, labelStatus, labelFailure)
|
||||
}
|
||||
w.opts.Meter.Counter(SubscribeMessageTotal, lopts...).Inc()
|
||||
w.opts.Meter.Counter(SubscribeMessageTotal, labels...).Inc()
|
||||
|
||||
return err
|
||||
}
|
||||
|
@@ -31,18 +31,18 @@ type memoryClient struct {
|
||||
}
|
||||
|
||||
type memoryListener struct {
|
||||
topts Options
|
||||
ctx context.Context
|
||||
lopts ListenOptions
|
||||
ctx context.Context
|
||||
exit chan bool
|
||||
conn chan *memorySocket
|
||||
addr string
|
||||
topts Options
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type memoryTransport struct {
|
||||
opts Options
|
||||
listeners map[string]*memoryListener
|
||||
opts Options
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
|
@@ -27,9 +27,9 @@ func TestMemoryTransport(t *testing.T) {
|
||||
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
|
||||
t.Logf("Server Received %s", string(m.Body))
|
||||
}
|
||||
if err := sock.Send(&Message{
|
||||
if cerr := sock.Send(&Message{
|
||||
Body: []byte(`pong`),
|
||||
}); err != nil {
|
||||
}); cerr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,6 @@ func TestMemoryTransport(t *testing.T) {
|
||||
t.Logf("Client Received %s", string(m.Body))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestListener(t *testing.T) {
|
||||
|
@@ -12,26 +12,37 @@ import (
|
||||
)
|
||||
|
||||
type tunBroker struct {
|
||||
opts broker.Options
|
||||
tunnel tunnel.Tunnel
|
||||
opts broker.Options
|
||||
}
|
||||
|
||||
type tunSubscriber struct {
|
||||
opts broker.SubscribeOptions
|
||||
listener tunnel.Listener
|
||||
handler broker.Handler
|
||||
closed chan bool
|
||||
topic string
|
||||
opts broker.SubscribeOptions
|
||||
}
|
||||
|
||||
type tunBatchSubscriber struct {
|
||||
listener tunnel.Listener
|
||||
handler broker.BatchHandler
|
||||
closed chan bool
|
||||
topic string
|
||||
opts broker.SubscribeOptions
|
||||
}
|
||||
|
||||
type tunEvent struct {
|
||||
message *broker.Message
|
||||
topic string
|
||||
err error
|
||||
}
|
||||
|
||||
// used to access tunnel from options context
|
||||
type tunnelKey struct{}
|
||||
type tunnelAddr struct{}
|
||||
type (
|
||||
tunnelKey struct{}
|
||||
tunnelAddr struct{}
|
||||
)
|
||||
|
||||
func (t *tunBroker) Init(opts ...broker.Option) error {
|
||||
for _, o := range opts {
|
||||
@@ -60,6 +71,36 @@ func (t *tunBroker) Disconnect(ctx context.Context) error {
|
||||
return t.tunnel.Close(ctx)
|
||||
}
|
||||
|
||||
func (t *tunBroker) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
|
||||
// TODO: this is probably inefficient, we might want to just maintain an open connection
|
||||
// it may be easier to add broadcast to the tunnel
|
||||
topicMap := make(map[string]tunnel.Session)
|
||||
|
||||
var err error
|
||||
for _, msg := range msgs {
|
||||
topic, _ := msg.Header.Get(metadata.HeaderTopic)
|
||||
c, ok := topicMap[topic]
|
||||
if !ok {
|
||||
c, err := t.tunnel.Dial(ctx, topic, tunnel.DialMode(tunnel.Multicast))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
topicMap[topic] = c
|
||||
}
|
||||
|
||||
if err = c.Send(&transport.Message{
|
||||
Header: msg.Header,
|
||||
Body: msg.Body,
|
||||
}); err != nil {
|
||||
// msg.SetError(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message, opts ...broker.PublishOption) error {
|
||||
// TODO: this is probably inefficient, we might want to just maintain an open connection
|
||||
// it may be easier to add broadcast to the tunnel
|
||||
@@ -75,6 +116,26 @@ func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message
|
||||
})
|
||||
}
|
||||
|
||||
func (t *tunBroker) BatchSubscribe(ctx context.Context, topic string, h broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
|
||||
l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tunSub := &tunBatchSubscriber{
|
||||
topic: topic,
|
||||
handler: h,
|
||||
opts: broker.NewSubscribeOptions(opts...),
|
||||
closed: make(chan bool),
|
||||
listener: l,
|
||||
}
|
||||
|
||||
// start processing
|
||||
go tunSub.run()
|
||||
|
||||
return tunSub, nil
|
||||
}
|
||||
|
||||
func (t *tunBroker) Subscribe(ctx context.Context, topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
|
||||
l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
|
||||
if err != nil {
|
||||
@@ -99,6 +160,49 @@ func (t *tunBroker) String() string {
|
||||
return "tunnel"
|
||||
}
|
||||
|
||||
func (t *tunBatchSubscriber) run() {
|
||||
for {
|
||||
// accept a new connection
|
||||
c, err := t.listener.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-t.closed:
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// receive message
|
||||
m := new(transport.Message)
|
||||
if err := c.Recv(m); err != nil {
|
||||
if logger.V(logger.ErrorLevel) {
|
||||
logger.Error(t.opts.Context, err.Error())
|
||||
}
|
||||
if err = c.Close(); err != nil {
|
||||
if logger.V(logger.ErrorLevel) {
|
||||
logger.Error(t.opts.Context, err.Error())
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// close the connection
|
||||
c.Close()
|
||||
|
||||
evts := broker.Events{&tunEvent{
|
||||
topic: t.topic,
|
||||
message: &broker.Message{
|
||||
Header: m.Header,
|
||||
Body: m.Body,
|
||||
},
|
||||
}}
|
||||
// handle the message
|
||||
go t.handler(evts)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunSubscriber) run() {
|
||||
for {
|
||||
// accept a new connection
|
||||
@@ -140,6 +244,24 @@ func (t *tunSubscriber) run() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunBatchSubscriber) Options() broker.SubscribeOptions {
|
||||
return t.opts
|
||||
}
|
||||
|
||||
func (t *tunBatchSubscriber) Topic() string {
|
||||
return t.topic
|
||||
}
|
||||
|
||||
func (t *tunBatchSubscriber) Unsubscribe(ctx context.Context) error {
|
||||
select {
|
||||
case <-t.closed:
|
||||
return nil
|
||||
default:
|
||||
close(t.closed)
|
||||
return t.listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunSubscriber) Options() broker.SubscribeOptions {
|
||||
return t.opts
|
||||
}
|
||||
@@ -171,7 +293,11 @@ func (t *tunEvent) Ack() error {
|
||||
}
|
||||
|
||||
func (t *tunEvent) Error() error {
|
||||
return nil
|
||||
return t.err
|
||||
}
|
||||
|
||||
func (t *tunEvent) SetError(err error) {
|
||||
t.err = err
|
||||
}
|
||||
|
||||
// NewBroker returns new tunnel broker
|
||||
|
@@ -34,8 +34,8 @@ type Options struct {
|
||||
Token string
|
||||
// Name holds the tunnel name
|
||||
Name string
|
||||
// Id holds the tunnel id
|
||||
Id string
|
||||
// ID holds the tunnel id
|
||||
ID string
|
||||
// Address holds the tunnel address
|
||||
Address string
|
||||
// Nodes holds the tunnel nodes
|
||||
@@ -68,10 +68,10 @@ type ListenOptions struct {
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Id sets the tunnel id
|
||||
func Id(id string) Option {
|
||||
// ID sets the tunnel id
|
||||
func ID(id string) Option {
|
||||
return func(o *Options) {
|
||||
o.Id = id
|
||||
o.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
|
||||
// NewOptions returns router default options with filled values
|
||||
func NewOptions(opts ...Option) Options {
|
||||
options := Options{
|
||||
Id: uuid.New().String(),
|
||||
ID: uuid.New().String(),
|
||||
Address: DefaultAddress,
|
||||
Token: DefaultToken,
|
||||
Logger: logger.DefaultLogger,
|
||||
|
@@ -10,9 +10,8 @@ import (
|
||||
)
|
||||
|
||||
type tunTransport struct {
|
||||
tunnel tunnel.Tunnel
|
||||
options transport.Options
|
||||
|
||||
tunnel tunnel.Tunnel
|
||||
}
|
||||
|
||||
type tunnelKey struct{}
|
||||
@@ -88,7 +87,7 @@ func NewTransport(opts ...transport.Option) transport.Transport {
|
||||
}
|
||||
|
||||
// initialise
|
||||
//t.Init(opts...)
|
||||
// t.Init(opts...)
|
||||
|
||||
return t
|
||||
}
|
||||
|
@@ -9,10 +9,8 @@ import (
|
||||
"github.com/unistack-org/micro/v3/network/transport"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTunnel contains default tunnel implementation
|
||||
DefaultTunnel Tunnel
|
||||
)
|
||||
// DefaultTunnel contains default tunnel implementation
|
||||
var DefaultTunnel Tunnel
|
||||
|
||||
const (
|
||||
// Unicast send over one link
|
||||
|
@@ -17,8 +17,6 @@ import (
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
"github.com/unistack-org/micro/v3/store"
|
||||
"github.com/unistack-org/micro/v3/tracer"
|
||||
// "github.com/unistack-org/micro/v3/profiler"
|
||||
// "github.com/unistack-org/micro/v3/runtime"
|
||||
)
|
||||
|
||||
// Options for micro service
|
||||
@@ -78,8 +76,8 @@ func NewOptions(opts ...Option) Options {
|
||||
Meters: []meter.Meter{meter.DefaultMeter},
|
||||
Configs: []config.Config{config.DefaultConfig},
|
||||
Stores: []store.Store{store.DefaultStore},
|
||||
//Runtime runtime.Runtime
|
||||
//Profile profile.Profile
|
||||
// Runtime runtime.Runtime
|
||||
// Profile profile.Profile
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
|
@@ -16,10 +16,8 @@ type httpProfile struct {
|
||||
running bool
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultAddress for http profiler
|
||||
DefaultAddress = ":6060"
|
||||
)
|
||||
// DefaultAddress for http profiler
|
||||
var DefaultAddress = ":6060"
|
||||
|
||||
// Start the profiler
|
||||
func (h *httpProfile) Start() error {
|
||||
|
@@ -11,10 +11,8 @@ type Profiler interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultProfiler holds the default profiler
|
||||
DefaultProfiler Profiler = NewProfiler()
|
||||
)
|
||||
// DefaultProfiler holds the default profiler
|
||||
var DefaultProfiler Profiler = NewProfiler()
|
||||
|
||||
// Options holds the options for profiler
|
||||
type Options struct {
|
||||
|
@@ -7,10 +7,8 @@ import (
|
||||
"github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultEndpoint holds default proxy address
|
||||
DefaultEndpoint = "localhost:9090"
|
||||
)
|
||||
// DefaultEndpoint holds default proxy address
|
||||
var DefaultEndpoint = "localhost:9090"
|
||||
|
||||
// Proxy can be used as a proxy server for micro services
|
||||
type Proxy interface {
|
||||
|
@@ -3,7 +3,6 @@ package register
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
@@ -11,12 +10,12 @@ import (
|
||||
)
|
||||
|
||||
// ExtractValue from reflect.Type from specified depth
|
||||
func ExtractValue(v reflect.Type, d int) *Value {
|
||||
func ExtractValue(v reflect.Type, d int) string {
|
||||
if d == 3 {
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
if v == nil {
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
@@ -25,7 +24,7 @@ func ExtractValue(v reflect.Type, d int) *Value {
|
||||
|
||||
// slices and maps don't have a defined name
|
||||
if (v.Kind() == reflect.Slice || v.Kind() == reflect.Map) || len(v.Name()) == 0 {
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
// get the rune character
|
||||
@@ -33,58 +32,10 @@ func ExtractValue(v reflect.Type, d int) *Value {
|
||||
|
||||
// crude check for is unexported field
|
||||
if unicode.IsLower(a) {
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
arg := &Value{
|
||||
Name: v.Name(),
|
||||
Type: v.Name(),
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := v.Field(i)
|
||||
val := ExtractValue(f.Type, d+1)
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// if we can find a json tag use it
|
||||
if tags := f.Tag.Get("json"); len(tags) > 0 {
|
||||
parts := strings.Split(tags, ",")
|
||||
if parts[0] == "-" || parts[0] == "omitempty" {
|
||||
continue
|
||||
}
|
||||
val.Name = parts[0]
|
||||
}
|
||||
|
||||
// if there's no name default it
|
||||
if len(val.Name) == 0 {
|
||||
val.Name = v.Field(i).Name
|
||||
}
|
||||
|
||||
arg.Values = append(arg.Values, val)
|
||||
}
|
||||
case reflect.Slice:
|
||||
p := v.Elem()
|
||||
if p.Kind() == reflect.Ptr {
|
||||
p = p.Elem()
|
||||
}
|
||||
arg.Type = "[]" + p.Name()
|
||||
case reflect.Map:
|
||||
p := v.Elem()
|
||||
if p.Kind() == reflect.Ptr {
|
||||
p = p.Elem()
|
||||
}
|
||||
key := v.Key()
|
||||
if key.Kind() == reflect.Ptr {
|
||||
key = key.Elem()
|
||||
}
|
||||
arg.Type = fmt.Sprintf("map[%s]%s", key.Name(), p.Name())
|
||||
}
|
||||
|
||||
return arg
|
||||
return v.Name()
|
||||
}
|
||||
|
||||
// ExtractEndpoint extract *Endpoint from reflect.Method
|
||||
@@ -116,7 +67,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
|
||||
|
||||
request := ExtractValue(reqType, 0)
|
||||
response := ExtractValue(rspType, 0)
|
||||
if request == nil || response == nil {
|
||||
if request == "" || response == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -135,7 +86,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
|
||||
}
|
||||
|
||||
// ExtractSubValue exctact *Value from reflect.Type
|
||||
func ExtractSubValue(typ reflect.Type) *Value {
|
||||
func ExtractSubValue(typ reflect.Type) string {
|
||||
var reqType reflect.Type
|
||||
switch typ.NumIn() {
|
||||
case 1:
|
||||
@@ -145,7 +96,7 @@ func ExtractSubValue(typ reflect.Type) *Value {
|
||||
case 3:
|
||||
reqType = typ.In(2)
|
||||
default:
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
return ExtractValue(reqType, 0)
|
||||
}
|
||||
|
@@ -36,28 +36,21 @@ func TestExtractEndpoint(t *testing.T) {
|
||||
t.Fatalf("Expected handler Test, got %s", endpoints[0].Name)
|
||||
}
|
||||
|
||||
if endpoints[0].Request == nil {
|
||||
if endpoints[0].Request == "" {
|
||||
t.Fatal("Expected non nil Request")
|
||||
}
|
||||
|
||||
if endpoints[0].Response == nil {
|
||||
if endpoints[0].Response == "" {
|
||||
t.Fatal("Expected non nil Request")
|
||||
}
|
||||
|
||||
if endpoints[0].Request.Name != "TestRequest" {
|
||||
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request.Name)
|
||||
if endpoints[0].Request != "TestRequest" {
|
||||
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request)
|
||||
}
|
||||
|
||||
if endpoints[0].Response.Name != "TestResponse" {
|
||||
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response.Name)
|
||||
}
|
||||
|
||||
if endpoints[0].Request.Type != "TestRequest" {
|
||||
t.Fatalf("Expected TestRequest type got %s", endpoints[0].Request.Type)
|
||||
}
|
||||
|
||||
if endpoints[0].Response.Type != "TestResponse" {
|
||||
t.Fatalf("Expected TestResponse type got %s", endpoints[0].Response.Type)
|
||||
if endpoints[0].Response != "TestResponse" {
|
||||
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response)
|
||||
}
|
||||
|
||||
t.Logf("XXX %#+v\n", endpoints[0])
|
||||
}
|
||||
|
@@ -30,10 +30,9 @@ type record struct {
|
||||
}
|
||||
|
||||
type memory struct {
|
||||
opts Options
|
||||
// records is a KV map with domain name as the key and a services map as the value
|
||||
records map[string]services
|
||||
watchers map[string]*watcher
|
||||
opts Options
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -65,7 +64,7 @@ func (m *memory) ttlPrune() {
|
||||
for id, n := range record.Nodes {
|
||||
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.Id, service)
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.ID, service)
|
||||
}
|
||||
delete(m.records[domain][service][version].Nodes, id)
|
||||
}
|
||||
@@ -162,7 +161,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
// check if already exists
|
||||
if _, ok := srvs[s.Name][s.Version].Nodes[n.Id]; ok {
|
||||
if _, ok := srvs[s.Name][s.Version].Nodes[n.ID]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -177,9 +176,9 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
|
||||
metadata["domain"] = options.Domain
|
||||
|
||||
// add the node
|
||||
srvs[s.Name][s.Version].Nodes[n.Id] = &node{
|
||||
srvs[s.Name][s.Version].Nodes[n.ID] = &node{
|
||||
Node: &Node{
|
||||
Id: n.Id,
|
||||
ID: n.ID,
|
||||
Address: n.Address,
|
||||
Metadata: metadata,
|
||||
},
|
||||
@@ -201,8 +200,8 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Updated registration for service: %s, version: %s", s.Name, s.Version)
|
||||
}
|
||||
srvs[s.Name][s.Version].Nodes[n.Id].TTL = options.TTL
|
||||
srvs[s.Name][s.Version].Nodes[n.Id].LastSeen = time.Now()
|
||||
srvs[s.Name][s.Version].Nodes[n.ID].TTL = options.TTL
|
||||
srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,11 +241,11 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
|
||||
|
||||
// deregister all of the service nodes from this version
|
||||
for _, n := range s.Nodes {
|
||||
if _, ok := version.Nodes[n.Id]; ok {
|
||||
if _, ok := version.Nodes[n.ID]; ok {
|
||||
if m.opts.Logger.V(logger.DebugLevel) {
|
||||
m.opts.Logger.Debugf(m.opts.Context, "Register removed node from service: %s, version: %s", s.Name, s.Version)
|
||||
}
|
||||
delete(version.Nodes, n.Id)
|
||||
delete(version.Nodes, n.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +457,7 @@ func serviceToRecord(s *Service, ttl time.Duration) *record {
|
||||
|
||||
nodes := make(map[string]*node, len(s.Nodes))
|
||||
for _, n := range s.Nodes {
|
||||
nodes[n.Id] = &node{
|
||||
nodes[n.ID] = &node{
|
||||
Node: n,
|
||||
TTL: ttl,
|
||||
LastSeen: time.Now(),
|
||||
@@ -490,40 +489,31 @@ func recordToService(r *record, domain string) *Service {
|
||||
|
||||
endpoints := make([]*Endpoint, len(r.Endpoints))
|
||||
for i, e := range r.Endpoints {
|
||||
request := new(Value)
|
||||
if e.Request != nil {
|
||||
*request = *e.Request
|
||||
}
|
||||
response := new(Value)
|
||||
if e.Response != nil {
|
||||
*response = *e.Response
|
||||
}
|
||||
|
||||
metadata := make(map[string]string, len(e.Metadata))
|
||||
md := make(map[string]string, len(e.Metadata))
|
||||
for k, v := range e.Metadata {
|
||||
metadata[k] = v
|
||||
md[k] = v
|
||||
}
|
||||
|
||||
endpoints[i] = &Endpoint{
|
||||
Name: e.Name,
|
||||
Request: request,
|
||||
Response: response,
|
||||
Metadata: metadata,
|
||||
Request: e.Request,
|
||||
Response: e.Response,
|
||||
Metadata: md,
|
||||
}
|
||||
}
|
||||
|
||||
nodes := make([]*Node, len(r.Nodes))
|
||||
i := 0
|
||||
for _, n := range r.Nodes {
|
||||
metadata := make(map[string]string, len(n.Metadata))
|
||||
md := make(map[string]string, len(n.Metadata))
|
||||
for k, v := range n.Metadata {
|
||||
metadata[k] = v
|
||||
md[k] = v
|
||||
}
|
||||
|
||||
nodes[i] = &Node{
|
||||
Id: n.Id,
|
||||
ID: n.ID,
|
||||
Address: n.Address,
|
||||
Metadata: metadata,
|
||||
Metadata: md,
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
@@ -8,72 +8,70 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
testData = map[string][]*Service{
|
||||
"foo": {
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
Id: "foo-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
{
|
||||
Id: "foo-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
var testData = map[string][]*Service{
|
||||
"foo": {
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.0",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
ID: "foo-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
Id: "foo-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
Id: "foo-1.0.3-345",
|
||||
Address: "localhost:8888",
|
||||
},
|
||||
{
|
||||
ID: "foo-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
{
|
||||
Name: "bar",
|
||||
Version: "default",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
Id: "bar-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
{
|
||||
Id: "bar-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
Version: "latest",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
Id: "bar-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
ID: "foo-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
{
|
||||
Name: "foo",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
ID: "foo-1.0.3-345",
|
||||
Address: "localhost:8888",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
{
|
||||
Name: "bar",
|
||||
Version: "default",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
ID: "bar-1.0.0-123",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
{
|
||||
ID: "bar-1.0.0-321",
|
||||
Address: "localhost:9999",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
Version: "latest",
|
||||
Nodes: []*Node{
|
||||
{
|
||||
ID: "bar-1.0.1-321",
|
||||
Address: "localhost:6666",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func TestMemoryRegistry(t *testing.T) {
|
||||
|
@@ -44,6 +44,7 @@ func NewOptions(opts ...Option) Options {
|
||||
return options
|
||||
}
|
||||
|
||||
// nolint: golint
|
||||
// RegisterOptions holds options for register method
|
||||
type RegisterOptions struct {
|
||||
Context context.Context
|
||||
@@ -196,6 +197,7 @@ func TLSConfig(t *tls.Config) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: golint
|
||||
// RegisterAttempts specifies register atempts count
|
||||
func RegisterAttempts(t int) RegisterOption {
|
||||
return func(o *RegisterOptions) {
|
||||
@@ -203,6 +205,7 @@ func RegisterAttempts(t int) RegisterOption {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: golint
|
||||
// RegisterTTL specifies register ttl
|
||||
func RegisterTTL(t time.Duration) RegisterOption {
|
||||
return func(o *RegisterOptions) {
|
||||
@@ -210,6 +213,7 @@ func RegisterTTL(t time.Duration) RegisterOption {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: golint
|
||||
// RegisterContext sets the register context
|
||||
func RegisterContext(ctx context.Context) RegisterOption {
|
||||
return func(o *RegisterOptions) {
|
||||
@@ -217,6 +221,7 @@ func RegisterContext(ctx context.Context) RegisterOption {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: golint
|
||||
// RegisterDomain secifies register domain
|
||||
func RegisterDomain(d string) RegisterOption {
|
||||
return func(o *RegisterOptions) {
|
||||
|
@@ -53,29 +53,23 @@ type Service struct {
|
||||
// Node holds node register info
|
||||
type Node struct {
|
||||
Metadata metadata.Metadata `json:"metadata"`
|
||||
Id string `json:"id"`
|
||||
ID string `json:"id"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// Endpoint holds endpoint register info
|
||||
type Endpoint struct {
|
||||
Request *Value `json:"request"`
|
||||
Response *Value `json:"response"`
|
||||
Request string `json:"request"`
|
||||
Response string `json:"response"`
|
||||
Metadata metadata.Metadata `json:"metadata"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Value holds additional kv stuff
|
||||
type Value struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Values []*Value `json:"values"`
|
||||
}
|
||||
|
||||
// Option func signature
|
||||
type Option func(*Options)
|
||||
|
||||
// RegisterOption option is used to register service
|
||||
// nolint: golint
|
||||
type RegisterOption func(*RegisterOptions)
|
||||
|
||||
// WatchOption option is used to watch service changes
|
||||
|
@@ -52,8 +52,8 @@ type Event struct {
|
||||
Timestamp time.Time
|
||||
// Service is register service
|
||||
Service *Service
|
||||
// Id is register id
|
||||
Id string
|
||||
// ID is register id
|
||||
ID string
|
||||
// Type defines type of event
|
||||
Type EventType
|
||||
}
|
||||
|
91
router/dns.go
Normal file
91
router/dns.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// NewRouter returns an initialized dns router
|
||||
func NewRouter(opts ...Option) Router {
|
||||
options := NewOptions(opts...)
|
||||
return &dns{options}
|
||||
}
|
||||
|
||||
type dns struct {
|
||||
options Options
|
||||
}
|
||||
|
||||
func (d *dns) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&d.options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dns) Options() Options {
|
||||
return d.options
|
||||
}
|
||||
|
||||
func (d *dns) Table() Table {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dns) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dns) Lookup(opts ...QueryOption) ([]Route, error) {
|
||||
options := NewQuery(opts...)
|
||||
// check to see if we have the port provided in the service, e.g. go-micro-srv-foo:8000
|
||||
host, port, err := net.SplitHostPort(options.Service)
|
||||
if err == nil {
|
||||
// lookup the service using A records
|
||||
ips, err := net.LookupHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, _ := strconv.Atoi(port)
|
||||
|
||||
// convert the ip addresses to routes
|
||||
result := make([]Route, len(ips))
|
||||
for i, ip := range ips {
|
||||
result[i] = Route{
|
||||
Service: options.Service,
|
||||
Address: fmt.Sprintf("%s:%d", ip, uint16(p)),
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// we didn't get the port so we'll lookup the service using SRV records. If we can't lookup the
|
||||
// service using the SRV record, we return the error.
|
||||
_, nodes, err := net.LookupSRV(options.Service, "tcp", d.options.Network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// convert the nodes (net services) to routes
|
||||
result := make([]Route, len(nodes))
|
||||
for i, n := range nodes {
|
||||
result[i] = Route{
|
||||
Service: options.Service,
|
||||
Address: fmt.Sprintf("%s:%d", n.Target, n.Port),
|
||||
Network: d.options.Network,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *dns) Watch(opts ...WatchOption) (Watcher, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *dns) Name() string {
|
||||
return d.options.Name
|
||||
}
|
||||
|
||||
func (d *dns) String() string {
|
||||
return "dns"
|
||||
}
|
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
var (
|
||||
// DefaultRouter is the global default router
|
||||
DefaultRouter Router
|
||||
DefaultRouter Router = NewRouter()
|
||||
// DefaultNetwork is default micro network
|
||||
DefaultNetwork = "micro"
|
||||
// ErrRouteNotFound is returned when no route was found in the routing table
|
||||
|
@@ -5,10 +5,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrWatcherStopped is returned when routing table watcher has been stopped
|
||||
ErrWatcherStopped = errors.New("watcher stopped")
|
||||
)
|
||||
// ErrWatcherStopped is returned when routing table watcher has been stopped
|
||||
var ErrWatcherStopped = errors.New("watcher stopped")
|
||||
|
||||
// EventType defines routing table event
|
||||
type EventType int
|
||||
@@ -38,12 +36,12 @@ func (t EventType) String() string {
|
||||
|
||||
// Event is returned by a call to Next on the watcher.
|
||||
type Event struct {
|
||||
// Route is table route
|
||||
Route Route
|
||||
// Timestamp is event timestamp
|
||||
Timestamp time.Time
|
||||
// Id of the event
|
||||
Id string
|
||||
// Route is table route
|
||||
Route Route
|
||||
// Type defines type of event
|
||||
Type EventType
|
||||
}
|
||||
|
@@ -8,10 +8,8 @@ import (
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAlreadyExists error
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
)
|
||||
// ErrAlreadyExists error
|
||||
var ErrAlreadyExists = errors.New("already exists")
|
||||
|
||||
// Runtime is a service runtime manager
|
||||
type Runtime interface {
|
||||
|
@@ -5,10 +5,8 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoneAvailable is returned by select when no routes were provided to select from
|
||||
ErrNoneAvailable = errors.New("none available")
|
||||
)
|
||||
// ErrNoneAvailable is returned by select when no routes were provided to select from
|
||||
var ErrNoneAvailable = errors.New("none available")
|
||||
|
||||
// Selector selects a route from a pool
|
||||
type Selector interface {
|
||||
|
3
server/generate.go
Normal file
3
server/generate.go
Normal file
@@ -0,0 +1,3 @@
|
||||
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
|
@@ -13,7 +13,7 @@ type rpcHandler struct {
|
||||
endpoints []*register.Endpoint
|
||||
}
|
||||
|
||||
func newRpcHandler(handler interface{}, opts ...HandlerOption) Handler {
|
||||
func newRPCHandler(handler interface{}, opts ...HandlerOption) Handler {
|
||||
options := NewHandlerOptions(opts...)
|
||||
|
||||
typ := reflect.TypeOf(handler)
|
||||
|
82
server/health/health.go
Normal file
82
server/health/health.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/errors"
|
||||
)
|
||||
|
||||
var _ HealthServer = &Handler{}
|
||||
|
||||
type Handler struct {
|
||||
opts Options
|
||||
}
|
||||
|
||||
type CheckFunc func(context.Context) error
|
||||
|
||||
type Option func(*Options)
|
||||
|
||||
type Options struct {
|
||||
Version string
|
||||
Name string
|
||||
LiveChecks []CheckFunc
|
||||
ReadyChecks []CheckFunc
|
||||
}
|
||||
|
||||
func LiveChecks(fns ...CheckFunc) Option {
|
||||
return func(o *Options) {
|
||||
o.LiveChecks = append(o.LiveChecks, fns...)
|
||||
}
|
||||
}
|
||||
|
||||
func ReadyChecks(fns ...CheckFunc) Option {
|
||||
return func(o *Options) {
|
||||
o.ReadyChecks = append(o.ReadyChecks, fns...)
|
||||
}
|
||||
}
|
||||
|
||||
func Name(name string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
func Version(version string) Option {
|
||||
return func(o *Options) {
|
||||
o.Version = version
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandler(opts ...Option) *Handler {
|
||||
options := Options{}
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
return &Handler{opts: options}
|
||||
}
|
||||
|
||||
func (h *Handler) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
var err error
|
||||
for _, fn := range h.opts.LiveChecks {
|
||||
if err = fn(ctx); err != nil {
|
||||
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
var err error
|
||||
for _, fn := range h.opts.ReadyChecks {
|
||||
if err = fn(ctx); err != nil {
|
||||
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
rsp.Data = []byte(h.opts.Version)
|
||||
return nil
|
||||
}
|
62
server/health/health.proto
Normal file
62
server/health/health.proto
Normal file
@@ -0,0 +1,62 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package micro.server.health;
|
||||
option go_package = "github.com/unistack-org/micro/v3/server/health;health";
|
||||
|
||||
import "api/annotations.proto";
|
||||
import "openapiv2/annotations.proto";
|
||||
import "codec/frame.proto";
|
||||
|
||||
service Health {
|
||||
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv2.openapiv2_operation) = {
|
||||
operation_id: "Live";
|
||||
responses: {
|
||||
key: "default";
|
||||
value: {
|
||||
description: "Error response";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "micro.codec.Frame";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
option (micro.api.http) = { get: "/live"; };
|
||||
};
|
||||
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv2.openapiv2_operation) = {
|
||||
operation_id: "Ready";
|
||||
responses: {
|
||||
key: "default";
|
||||
value: {
|
||||
description: "Error response";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "micro.codec.Frame";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
option (micro.api.http) = { get: "/ready"; };
|
||||
};
|
||||
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
|
||||
option (micro.openapiv2.openapiv2_operation) = {
|
||||
operation_id: "Version";
|
||||
responses: {
|
||||
key: "default";
|
||||
value: {
|
||||
description: "Error response";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "micro.codec.Frame";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
option (micro.api.http) = { get: "/version"; };
|
||||
};
|
||||
};
|
38
server/health/health_micro.pb.go
Normal file
38
server/health/health_micro.pb.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Code generated by protoc-gen-micro
|
||||
// source: health.proto
|
||||
package health
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/unistack-org/micro/v3/api"
|
||||
codec "github.com/unistack-org/micro/v3/codec"
|
||||
)
|
||||
|
||||
func NewHealthEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{
|
||||
&api.Endpoint{
|
||||
Name: "Health.Live",
|
||||
Path: []string{"/live"},
|
||||
Method: []string{"GET"},
|
||||
Handler: "rpc",
|
||||
},
|
||||
&api.Endpoint{
|
||||
Name: "Health.Ready",
|
||||
Path: []string{"/ready"},
|
||||
Method: []string{"GET"},
|
||||
Handler: "rpc",
|
||||
},
|
||||
&api.Endpoint{
|
||||
Name: "Health.Version",
|
||||
Path: []string{"/version"},
|
||||
Method: []string{"GET"},
|
||||
Handler: "rpc",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type HealthServer interface {
|
||||
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
43
server/health/health_micro_http.pb.go
Normal file
43
server/health/health_micro_http.pb.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Code generated by protoc-gen-micro
|
||||
// source: health.proto
|
||||
package health
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/unistack-org/micro/v3/api"
|
||||
codec "github.com/unistack-org/micro/v3/codec"
|
||||
server "github.com/unistack-org/micro/v3/server"
|
||||
)
|
||||
|
||||
type healthServer struct {
|
||||
HealthServer
|
||||
}
|
||||
|
||||
func (h *healthServer) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.HealthServer.Live(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func (h *healthServer) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.HealthServer.Ready(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func (h *healthServer) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
|
||||
return h.HealthServer.Version(ctx, req, rsp)
|
||||
}
|
||||
|
||||
func RegisterHealthServer(s server.Server, sh HealthServer, opts ...server.HandlerOption) error {
|
||||
type health interface {
|
||||
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
|
||||
}
|
||||
type Health struct {
|
||||
health
|
||||
}
|
||||
h := &healthServer{sh}
|
||||
var nopts []server.HandlerOption
|
||||
for _, endpoint := range NewHealthEndpoints() {
|
||||
nopts = append(nopts, api.WithEndpoint(endpoint))
|
||||
}
|
||||
return s.Handle(s.NewHandler(&Health{h}, append(nopts, opts...)...))
|
||||
}
|
113
server/noop.go
113
server/noop.go
@@ -6,39 +6,32 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// cjson "github.com/unistack-org/micro-codec-json"
|
||||
// cjsonrpc "github.com/unistack-org/micro-codec-jsonrpc"
|
||||
// cproto "github.com/unistack-org/micro-codec-proto"
|
||||
// cprotorpc "github.com/unistack-org/micro-codec-protorpc"
|
||||
"github.com/unistack-org/micro/v3/broker"
|
||||
"github.com/unistack-org/micro/v3/codec"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultCodecs will be used to encode/decode
|
||||
DefaultCodecs = map[string]codec.Codec{
|
||||
//"application/json": cjson.NewCodec,
|
||||
//"application/json-rpc": cjsonrpc.NewCodec,
|
||||
//"application/protobuf": cproto.NewCodec,
|
||||
//"application/proto-rpc": cprotorpc.NewCodec,
|
||||
"application/octet-stream": codec.NewCodec(),
|
||||
}
|
||||
)
|
||||
// DefaultCodecs will be used to encode/decode
|
||||
var DefaultCodecs = map[string]codec.Codec{
|
||||
"application/octet-stream": codec.NewCodec(),
|
||||
}
|
||||
|
||||
const (
|
||||
defaultContentType = "application/json"
|
||||
)
|
||||
|
||||
type noopServer struct {
|
||||
opts Options
|
||||
h Handler
|
||||
wg *sync.WaitGroup
|
||||
rsvc *register.Service
|
||||
handlers map[string]Handler
|
||||
subscribers map[*subscriber][]broker.Subscriber
|
||||
exit chan chan error
|
||||
wg *sync.WaitGroup
|
||||
opts Options
|
||||
sync.RWMutex
|
||||
registered bool
|
||||
started bool
|
||||
@@ -82,8 +75,7 @@ func (n *noopServer) Subscribe(sb Subscriber) error {
|
||||
sub, ok := sb.(*subscriber)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid subscriber: expected *subscriber")
|
||||
}
|
||||
if len(sub.handlers) == 0 {
|
||||
} else if len(sub.handlers) == 0 {
|
||||
return fmt.Errorf("invalid subscriber: no handler functions")
|
||||
}
|
||||
|
||||
@@ -103,7 +95,7 @@ func (n *noopServer) Subscribe(sb Subscriber) error {
|
||||
}
|
||||
|
||||
func (n *noopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
|
||||
return newRpcHandler(h, opts...)
|
||||
return newRPCHandler(h, opts...)
|
||||
}
|
||||
|
||||
func (n *noopServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
|
||||
@@ -116,11 +108,12 @@ func (n *noopServer) Init(opts ...Option) error {
|
||||
}
|
||||
|
||||
if n.handlers == nil {
|
||||
n.handlers = make(map[string]Handler)
|
||||
n.handlers = make(map[string]Handler, 1)
|
||||
}
|
||||
if n.subscribers == nil {
|
||||
n.subscribers = make(map[*subscriber][]broker.Subscriber)
|
||||
n.subscribers = make(map[*subscriber][]broker.Subscriber, 1)
|
||||
}
|
||||
|
||||
if n.exit == nil {
|
||||
n.exit = make(chan chan error)
|
||||
}
|
||||
@@ -158,23 +151,16 @@ func (n *noopServer) Register() error {
|
||||
}
|
||||
|
||||
n.RLock()
|
||||
// Maps are ordered randomly, sort the keys for consistency
|
||||
var handlerList []string
|
||||
for n, e := range n.handlers {
|
||||
// Only advertise non internal handlers
|
||||
if !e.Options().Internal {
|
||||
handlerList = append(handlerList, n)
|
||||
}
|
||||
handlerList := make([]string, 0, len(n.handlers))
|
||||
for n := range n.handlers {
|
||||
handlerList = append(handlerList, n)
|
||||
}
|
||||
|
||||
sort.Strings(handlerList)
|
||||
|
||||
var subscriberList []*subscriber
|
||||
subscriberList := make([]*subscriber, 0, len(n.subscribers))
|
||||
for e := range n.subscribers {
|
||||
// Only advertise non internal subscribers
|
||||
if !e.Options().Internal {
|
||||
subscriberList = append(subscriberList, e)
|
||||
}
|
||||
subscriberList = append(subscriberList, e)
|
||||
}
|
||||
sort.Slice(subscriberList, func(i, j int) bool {
|
||||
return subscriberList[i].topic > subscriberList[j].topic
|
||||
@@ -190,7 +176,7 @@ func (n *noopServer) Register() error {
|
||||
n.RUnlock()
|
||||
|
||||
service.Nodes[0].Metadata["protocol"] = "noop"
|
||||
service.Nodes[0].Metadata["transport"] = "noop"
|
||||
service.Nodes[0].Metadata["transport"] = service.Nodes[0].Metadata["protocol"]
|
||||
service.Endpoints = endpoints
|
||||
|
||||
n.RLock()
|
||||
@@ -199,7 +185,7 @@ func (n *noopServer) Register() error {
|
||||
|
||||
if !registered {
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].Id)
|
||||
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,26 +204,34 @@ func (n *noopServer) Register() error {
|
||||
|
||||
cx := config.Context
|
||||
|
||||
for sb := range n.subscribers {
|
||||
handler := n.createSubHandler(sb, config)
|
||||
var opts []broker.SubscribeOption
|
||||
if queue := sb.Options().Queue; len(queue) > 0 {
|
||||
opts = append(opts, broker.SubscribeGroup(queue))
|
||||
}
|
||||
var sub broker.Subscriber
|
||||
|
||||
for sb := range n.subscribers {
|
||||
if sb.Options().Context != nil {
|
||||
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) {
|
||||
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}
|
||||
}
|
||||
|
||||
@@ -262,7 +256,7 @@ func (n *noopServer) Deregister() error {
|
||||
}
|
||||
|
||||
if config.Logger.V(logger.InfoLevel) {
|
||||
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].Id)
|
||||
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].ID)
|
||||
}
|
||||
|
||||
if err := DefaultDeregisterFunc(service, config); err != nil {
|
||||
@@ -319,9 +313,22 @@ func (n *noopServer) Start() error {
|
||||
config := n.Options()
|
||||
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) {
|
||||
config.Logger.Infof(n.opts.Context, "server [noop] Listening on %s", config.Address)
|
||||
}
|
||||
|
||||
n.Lock()
|
||||
if len(config.Advertise) == 0 {
|
||||
config.Advertise = config.Address
|
||||
@@ -344,9 +351,10 @@ func (n *noopServer) Start() error {
|
||||
}
|
||||
|
||||
// use RegisterCheck func before register
|
||||
// nolint: nestif
|
||||
if err := config.RegisterCheck(config.Context); err != nil {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, err)
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, err)
|
||||
}
|
||||
} else {
|
||||
// announce self to the world
|
||||
@@ -378,25 +386,26 @@ func (n *noopServer) Start() error {
|
||||
registered := n.registered
|
||||
n.RUnlock()
|
||||
rerr := config.RegisterCheck(config.Context)
|
||||
// nolint: nestif
|
||||
if rerr != nil && registered {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.ID, rerr)
|
||||
}
|
||||
// deregister self in case of error
|
||||
if err := n.Deregister(); err != nil {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.Id, err)
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.ID, err)
|
||||
}
|
||||
}
|
||||
} else if rerr != nil && !registered {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, rerr)
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, rerr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := n.Register(); err != nil {
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.Id, err)
|
||||
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.ID, err)
|
||||
}
|
||||
}
|
||||
// wait for exit
|
||||
|
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)
|
||||
}
|
||||
}()
|
||||
}
|
@@ -57,8 +57,8 @@ type Options struct {
|
||||
RegisterCheck func(context.Context) error
|
||||
// Codecs map to handle content-type
|
||||
Codecs map[string]codec.Codec
|
||||
// Id holds the id of the server
|
||||
Id string
|
||||
// ID holds the id of the server
|
||||
ID string
|
||||
// Namespace for te server
|
||||
Namespace string
|
||||
// Name holds the server name
|
||||
@@ -71,6 +71,8 @@ type Options struct {
|
||||
Version string
|
||||
// SubWrappers holds the server subscribe wrappers
|
||||
SubWrappers []SubscriberWrapper
|
||||
// BatchSubWrappers holds the server batch subscribe wrappers
|
||||
BatchSubWrappers []BatchSubscriberWrapper
|
||||
// HdlrWrappers holds the handler wrappers
|
||||
HdlrWrappers []HandlerWrapper
|
||||
// RegisterAttempts holds the number of register attempts before error
|
||||
@@ -104,7 +106,7 @@ func NewOptions(opts ...Option) Options {
|
||||
Address: DefaultAddress,
|
||||
Name: DefaultName,
|
||||
Version: DefaultVersion,
|
||||
Id: DefaultId,
|
||||
ID: DefaultID,
|
||||
Namespace: DefaultNamespace,
|
||||
}
|
||||
|
||||
@@ -143,10 +145,10 @@ func Meter(m meter.Meter) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Id unique server id
|
||||
func Id(id string) Option {
|
||||
// ID unique server id
|
||||
func ID(id string) Option {
|
||||
return func(o *Options) {
|
||||
o.Id = id
|
||||
o.ID = id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
func MaxConn(n int) Option {
|
||||
return func(o *Options) {
|
||||
@@ -325,8 +334,6 @@ type HandlerOptions struct {
|
||||
Context context.Context
|
||||
// Metadata for hondler
|
||||
Metadata map[string]metadata.Metadata
|
||||
// Internal flag limits exporting to other nodes via register
|
||||
Internal bool
|
||||
}
|
||||
|
||||
// NewHandlerOptions creates new HandlerOptions
|
||||
@@ -354,10 +361,14 @@ type SubscriberOptions struct {
|
||||
Queue string
|
||||
// AutoAck flag for auto ack messages after processing
|
||||
AutoAck bool
|
||||
// Internal flag limit exporting info via register
|
||||
Internal bool
|
||||
// BodyOnly flag specifies that message without headers
|
||||
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
|
||||
@@ -382,23 +393,6 @@ func EndpointMetadata(name string, md metadata.Metadata) HandlerOption {
|
||||
}
|
||||
}
|
||||
|
||||
// InternalHandler options specifies that a handler is not advertised
|
||||
// to the discovery system. In the future this may also limit request
|
||||
// to the internal network or authorised user.
|
||||
func InternalHandler(b bool) HandlerOption {
|
||||
return func(o *HandlerOptions) {
|
||||
o.Internal = b
|
||||
}
|
||||
}
|
||||
|
||||
// InternalSubscriber options specifies that a subscriber is not advertised
|
||||
// to the discovery system.
|
||||
func InternalSubscriber(b bool) SubscriberOption {
|
||||
return func(o *SubscriberOptions) {
|
||||
o.Internal = b
|
||||
}
|
||||
}
|
||||
|
||||
// DisableAutoAck will disable auto acking of messages
|
||||
// after they have been handled.
|
||||
func DisableAutoAck() SubscriberOption {
|
||||
@@ -434,3 +428,32 @@ func SubscriberContext(ctx context.Context) SubscriberOption {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@@ -72,7 +72,7 @@ func NewRegisterService(s Server) (*register.Service, error) {
|
||||
}
|
||||
|
||||
node := ®ister.Node{
|
||||
Id: opts.Name + "-" + opts.Id,
|
||||
ID: opts.Name + "-" + opts.ID,
|
||||
Address: net.JoinHostPort(addr, port),
|
||||
}
|
||||
node.Metadata = metadata.Copy(opts.Metadata)
|
||||
|
@@ -11,20 +11,18 @@ import (
|
||||
"github.com/unistack-org/micro/v3/register"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultServer default server
|
||||
DefaultServer Server = NewServer()
|
||||
)
|
||||
// DefaultServer default server
|
||||
var DefaultServer Server = NewServer()
|
||||
|
||||
var (
|
||||
// DefaultAddress will be used if no address passed
|
||||
DefaultAddress = ":0"
|
||||
// DefaultAddress will be used if no address passed, use secure localhost
|
||||
DefaultAddress = "127.0.0.1:0"
|
||||
// DefaultName will be used if no name passed
|
||||
DefaultName = "server"
|
||||
// DefaultVersion will be used if no version passed
|
||||
DefaultVersion = "latest"
|
||||
// DefaultId will be used if no id passed
|
||||
DefaultId = uuid.New().String()
|
||||
// DefaultID will be used if no id passed
|
||||
DefaultID = uuid.New().String()
|
||||
// DefaultRegisterCheck holds func that run before register server
|
||||
DefaultRegisterCheck = func(context.Context) error { return nil }
|
||||
// DefaultRegisterInterval holds interval for register
|
||||
|
@@ -18,14 +18,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
subSig = "func(context.Context, interface{}) error"
|
||||
subSig = "func(context.Context, interface{}) error"
|
||||
batchSubSig = "func([]context.Context, []interface{}) error"
|
||||
)
|
||||
|
||||
var (
|
||||
// Precompute the reflect type for error. Can't use error directly
|
||||
// because Typeof takes an empty interface value. This is annoying.
|
||||
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
// Precompute the reflect type for error. Can't use error directly
|
||||
// because Typeof takes an empty interface value. This is annoying.
|
||||
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
type handler struct {
|
||||
reqType reflect.Type
|
||||
@@ -59,42 +58,49 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
|
||||
return isExported(t.Name()) || t.PkgPath() == ""
|
||||
}
|
||||
|
||||
// ValidateSubscriber func
|
||||
// ValidateSubscriber func signature
|
||||
func ValidateSubscriber(sub Subscriber) error {
|
||||
typ := reflect.TypeOf(sub.Subscriber())
|
||||
var argType reflect.Type
|
||||
|
||||
if typ.Kind() == reflect.Func {
|
||||
switch typ.Kind() {
|
||||
case reflect.Func:
|
||||
name := "Func"
|
||||
switch typ.NumIn() {
|
||||
case 2:
|
||||
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:
|
||||
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) {
|
||||
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
|
||||
}
|
||||
if typ.NumOut() != 1 {
|
||||
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
|
||||
name, typ.NumOut(), subSig)
|
||||
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s or %s",
|
||||
name, typ.NumOut(), subSig, batchSubSig)
|
||||
}
|
||||
if returnType := typ.Out(0); returnType != typeOfError {
|
||||
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
hdlr := reflect.ValueOf(sub.Subscriber())
|
||||
name := reflect.Indirect(hdlr).Type().Name()
|
||||
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
method := typ.Method(m)
|
||||
|
||||
switch method.Type.NumIn() {
|
||||
case 3:
|
||||
argType = method.Type.In(2)
|
||||
default:
|
||||
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
|
||||
name, method.Name, method.Type.NumIn(), subSig)
|
||||
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s or %s",
|
||||
name, method.Name, method.Type.NumIn(), subSig, batchSubSig)
|
||||
}
|
||||
|
||||
if !isExportedOrBuiltinType(argType) {
|
||||
@@ -102,8 +108,8 @@ func ValidateSubscriber(sub Subscriber) error {
|
||||
}
|
||||
if method.Type.NumOut() != 1 {
|
||||
return fmt.Errorf(
|
||||
"subscriber %v.%v has wrong number of outs: %v require signature %s",
|
||||
name, method.Name, method.Type.NumOut(), subSig)
|
||||
"subscriber %v.%v has wrong number of return values: %v require signature %s or %s",
|
||||
name, method.Name, method.Type.NumOut(), subSig, batchSubSig)
|
||||
}
|
||||
if returnType := method.Type.Out(0); returnType != typeOfError {
|
||||
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
|
||||
@@ -184,7 +190,125 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
||||
}
|
||||
|
||||
//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) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -202,12 +326,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
msg := p.Message()
|
||||
// if we don't have headers, create empty map
|
||||
if msg.Header == nil {
|
||||
msg.Header = make(map[string]string)
|
||||
msg.Header = metadata.New(2)
|
||||
}
|
||||
|
||||
ct := msg.Header["Content-Type"]
|
||||
if len(ct) == 0 {
|
||||
msg.Header["Content-Type"] = defaultContentType
|
||||
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
|
||||
ct = defaultContentType
|
||||
}
|
||||
cf, err := n.newCodec(ct)
|
||||
@@ -215,12 +339,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
return err
|
||||
}
|
||||
|
||||
hdr := make(map[string]string, len(msg.Header))
|
||||
hdr := metadata.New(len(msg.Header))
|
||||
for k, v := range msg.Header {
|
||||
if k == "Content-Type" {
|
||||
continue
|
||||
}
|
||||
hdr[k] = v
|
||||
hdr.Set(k, v)
|
||||
}
|
||||
|
||||
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
|
||||
@@ -276,14 +400,14 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
if n.wg != nil {
|
||||
defer n.wg.Done()
|
||||
}
|
||||
err := fn(ctx, &rpcMessage{
|
||||
cerr := fn(ctx, &rpcMessage{
|
||||
topic: sb.topic,
|
||||
contentType: ct,
|
||||
payload: req.Interface(),
|
||||
header: msg.Header,
|
||||
body: msg.Body,
|
||||
})
|
||||
results <- err
|
||||
results <- cerr
|
||||
}()
|
||||
}
|
||||
var errors []string
|
||||
@@ -295,7 +419,6 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
if len(errors) > 0 {
|
||||
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@@ -14,12 +14,20 @@ type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error
|
||||
// publication message.
|
||||
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
|
||||
type HandlerWrapper func(HandlerFunc) HandlerFunc
|
||||
|
||||
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
|
||||
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.
|
||||
// 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,
|
||||
|
17
service.go
17
service.go
@@ -103,48 +103,48 @@ func (s *service) Init(opts ...Option) error {
|
||||
// skip config as the struct not passed
|
||||
continue
|
||||
}
|
||||
if err = cfg.Init(config.Context(s.opts.Context)); err != nil {
|
||||
if err = cfg.Init(config.Context(cfg.Options().Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = cfg.Load(s.opts.Context); err != nil {
|
||||
if err = cfg.Load(cfg.Options().Context); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, log := range s.opts.Loggers {
|
||||
if err = log.Init(logger.WithContext(s.opts.Context)); err != nil {
|
||||
if err = log.Init(logger.WithContext(log.Options().Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, reg := range s.opts.Registers {
|
||||
if err = reg.Init(register.Context(s.opts.Context)); err != nil {
|
||||
if err = reg.Init(register.Context(reg.Options().Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, brk := range s.opts.Brokers {
|
||||
if err = brk.Init(broker.Context(s.opts.Context)); err != nil {
|
||||
if err = brk.Init(broker.Context(brk.Options().Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, str := range s.opts.Stores {
|
||||
if err = str.Init(store.Context(s.opts.Context)); err != nil {
|
||||
if err = str.Init(store.Context(str.Options().Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, srv := range s.opts.Servers {
|
||||
if err = srv.Init(server.Context(s.opts.Context)); err != nil {
|
||||
if err = srv.Init(server.Context(srv.Options().Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, cli := range s.opts.Clients {
|
||||
if err = cli.Init(client.Context(s.opts.Context)); err != nil {
|
||||
if err = cli.Init(client.Context(cli.Options().Context)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,6 @@ func (s *service) Broker(names ...string) broker.Broker {
|
||||
idx = getNameIndex(names[0], s.opts.Brokers)
|
||||
}
|
||||
return s.opts.Brokers[idx]
|
||||
|
||||
}
|
||||
|
||||
func (s *service) Tracer(names ...string) tracer.Tracer {
|
||||
|
@@ -28,24 +28,14 @@ func (m *memoryStore) Disconnect(ctx context.Context) error {
|
||||
}
|
||||
|
||||
type memoryStore struct {
|
||||
opts Options
|
||||
store *cache.Cache
|
||||
opts Options
|
||||
}
|
||||
|
||||
func (m *memoryStore) key(prefix, key string) string {
|
||||
return filepath.Join(prefix, key)
|
||||
}
|
||||
|
||||
func (m *memoryStore) prefix(database, table string) string {
|
||||
if len(database) == 0 {
|
||||
database = m.opts.Database
|
||||
}
|
||||
if len(table) == 0 {
|
||||
table = m.opts.Table
|
||||
}
|
||||
return filepath.Join(database, table)
|
||||
}
|
||||
|
||||
func (m *memoryStore) exists(prefix, key string) error {
|
||||
key = m.key(prefix, key)
|
||||
|
||||
@@ -80,15 +70,17 @@ func (m *memoryStore) delete(prefix, key string) {
|
||||
|
||||
func (m *memoryStore) list(prefix string, limit, offset uint) []string {
|
||||
allItems := m.store.Items()
|
||||
allKeys := make([]string, len(allItems))
|
||||
i := 0
|
||||
allKeys := make([]string, 0, len(allItems))
|
||||
|
||||
for k := range allItems {
|
||||
if !strings.HasPrefix(k, prefix+"/") {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
continue
|
||||
}
|
||||
allKeys[i] = strings.TrimPrefix(k, prefix+"/")
|
||||
i++
|
||||
k = strings.TrimPrefix(k, prefix)
|
||||
if k[0] == '/' {
|
||||
k = k[1:]
|
||||
}
|
||||
allKeys = append(allKeys, k)
|
||||
}
|
||||
|
||||
if limit != 0 || offset != 0 {
|
||||
@@ -107,7 +99,6 @@ func (m *memoryStore) list(prefix string, limit, offset uint) []string {
|
||||
}
|
||||
return allKeys[offset:end]
|
||||
}
|
||||
|
||||
return allKeys
|
||||
}
|
||||
|
||||
@@ -127,37 +118,48 @@ func (m *memoryStore) Name() string {
|
||||
}
|
||||
|
||||
func (m *memoryStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
|
||||
prefix := m.prefix(m.opts.Database, m.opts.Table)
|
||||
return m.exists(prefix, key)
|
||||
options := NewExistsOptions(opts...)
|
||||
if options.Namespace == "" {
|
||||
options.Namespace = m.opts.Namespace
|
||||
}
|
||||
return m.exists(options.Namespace, key)
|
||||
}
|
||||
|
||||
func (m *memoryStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
|
||||
readOpts := NewReadOptions(opts...)
|
||||
prefix := m.prefix(readOpts.Database, readOpts.Table)
|
||||
return m.get(prefix, key, val)
|
||||
options := NewReadOptions(opts...)
|
||||
if options.Namespace == "" {
|
||||
options.Namespace = m.opts.Namespace
|
||||
}
|
||||
return m.get(options.Namespace, key, val)
|
||||
}
|
||||
|
||||
func (m *memoryStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
|
||||
writeOpts := NewWriteOptions(opts...)
|
||||
options := NewWriteOptions(opts...)
|
||||
if options.Namespace == "" {
|
||||
options.Namespace = m.opts.Namespace
|
||||
}
|
||||
if options.TTL == 0 {
|
||||
options.TTL = cache.NoExpiration
|
||||
}
|
||||
|
||||
prefix := m.prefix(writeOpts.Database, writeOpts.Table)
|
||||
|
||||
key = m.key(prefix, key)
|
||||
key = m.key(options.Namespace, key)
|
||||
|
||||
buf, err := m.opts.Codec.Marshal(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.store.Set(key, buf, writeOpts.TTL)
|
||||
m.store.Set(key, buf, options.TTL)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
|
||||
deleteOptions := NewDeleteOptions(opts...)
|
||||
options := NewDeleteOptions(opts...)
|
||||
if options.Namespace == "" {
|
||||
options.Namespace = m.opts.Namespace
|
||||
}
|
||||
|
||||
prefix := m.prefix(deleteOptions.Database, deleteOptions.Table)
|
||||
m.delete(prefix, key)
|
||||
m.delete(options.Namespace, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -166,25 +168,27 @@ func (m *memoryStore) Options() Options {
|
||||
}
|
||||
|
||||
func (m *memoryStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
|
||||
listOptions := NewListOptions(opts...)
|
||||
options := NewListOptions(opts...)
|
||||
if options.Namespace == "" {
|
||||
options.Namespace = m.opts.Namespace
|
||||
}
|
||||
|
||||
prefix := m.prefix(listOptions.Database, listOptions.Table)
|
||||
keys := m.list(prefix, listOptions.Limit, listOptions.Offset)
|
||||
keys := m.list(options.Namespace, options.Limit, options.Offset)
|
||||
|
||||
if len(listOptions.Prefix) > 0 {
|
||||
if len(options.Prefix) > 0 {
|
||||
var prefixKeys []string
|
||||
for _, k := range keys {
|
||||
if strings.HasPrefix(k, listOptions.Prefix) {
|
||||
if strings.HasPrefix(k, options.Prefix) {
|
||||
prefixKeys = append(prefixKeys, k)
|
||||
}
|
||||
}
|
||||
keys = prefixKeys
|
||||
}
|
||||
|
||||
if len(listOptions.Suffix) > 0 {
|
||||
if len(options.Suffix) > 0 {
|
||||
var suffixKeys []string
|
||||
for _, k := range keys {
|
||||
if strings.HasSuffix(k, listOptions.Suffix) {
|
||||
if strings.HasSuffix(k, options.Suffix) {
|
||||
suffixKeys = append(suffixKeys, k)
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,11 @@ import (
|
||||
)
|
||||
|
||||
func TestMemoryReInit(t *testing.T) {
|
||||
s := store.NewStore(store.Table("aaa"))
|
||||
if err := s.Init(store.Table("")); err != nil {
|
||||
s := store.NewStore(store.Namespace("aaa"))
|
||||
if err := s.Init(store.Namespace("")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(s.Options().Table) > 0 {
|
||||
if len(s.Options().Namespace) > 0 {
|
||||
t.Error("Init didn't reinitialise the store")
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func TestMemoryBasic(t *testing.T) {
|
||||
|
||||
func TestMemoryPrefix(t *testing.T) {
|
||||
s := store.NewStore()
|
||||
if err := s.Init(store.Table("some-prefix")); err != nil {
|
||||
if err := s.Init(store.Namespace("some-prefix")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
basictest(s, t)
|
||||
@@ -36,7 +36,7 @@ func TestMemoryPrefix(t *testing.T) {
|
||||
|
||||
func TestMemoryNamespace(t *testing.T) {
|
||||
s := store.NewStore()
|
||||
if err := s.Init(store.Database("some-namespace")); err != nil {
|
||||
if err := s.Init(store.Namespace("some-namespace")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
basictest(s, t)
|
||||
@@ -44,7 +44,7 @@ func TestMemoryNamespace(t *testing.T) {
|
||||
|
||||
func TestMemoryNamespacePrefix(t *testing.T) {
|
||||
s := store.NewStore()
|
||||
if err := s.Init(store.Table("some-prefix"), store.Database("some-namespace")); err != nil {
|
||||
if err := s.Init(store.Namespace("some-namespace")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
basictest(s, t)
|
||||
|
261
store/options.go
261
store/options.go
@@ -28,13 +28,12 @@ type Options struct {
|
||||
TLSConfig *tls.Config
|
||||
// Name specifies store name
|
||||
Name string
|
||||
// Database specifies store database
|
||||
Database string
|
||||
// Table specifies store table
|
||||
Table string
|
||||
// Nodes contains store address
|
||||
// TODO: replace with Addrs
|
||||
Nodes []string
|
||||
// Namespace of the records
|
||||
Namespace string
|
||||
// Addrs contains store address
|
||||
Addrs []string
|
||||
//Wrappers store wrapper that called before actual functions
|
||||
//Wrappers []Wrapper
|
||||
}
|
||||
|
||||
// NewOptions creates options struct
|
||||
@@ -90,13 +89,20 @@ func Meter(m meter.Meter) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Name the name
|
||||
// Name the name of the store
|
||||
func Name(n string) Option {
|
||||
return func(o *Options) {
|
||||
o.Name = n
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace sets namespace of the store
|
||||
func Namespace(ns string) Option {
|
||||
return func(o *Options) {
|
||||
o.Namespace = ns
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer sets the tracer
|
||||
func Tracer(t tracer.Tracer) Option {
|
||||
return func(o *Options) {
|
||||
@@ -104,27 +110,21 @@ func Tracer(t tracer.Tracer) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes contains the addresses or other connection information of the backing storage.
|
||||
// Addrs contains the addresses or other connection information of the backing storage.
|
||||
// For example, an etcd implementation would contain the nodes of the cluster.
|
||||
// A SQL implementation could contain one or more connection strings.
|
||||
func Nodes(a ...string) Option {
|
||||
func Addrs(addrs ...string) Option {
|
||||
return func(o *Options) {
|
||||
o.Nodes = a
|
||||
o.Addrs = addrs
|
||||
}
|
||||
}
|
||||
|
||||
// Database allows multiple isolated stores to be kept in one backend, if supported.
|
||||
func Database(db string) Option {
|
||||
return func(o *Options) {
|
||||
o.Database = db
|
||||
}
|
||||
}
|
||||
|
||||
// Table is analag for a table in database backends or a key prefix in KV backends
|
||||
func Table(t string) Option {
|
||||
return func(o *Options) {
|
||||
o.Table = t
|
||||
}
|
||||
// ReadOptions configures an individual Read operation
|
||||
type ReadOptions struct {
|
||||
// Context holds external options
|
||||
Context context.Context
|
||||
// Namespace holds namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// NewReadOptions fills ReadOptions struct with opts slice
|
||||
@@ -136,29 +136,35 @@ func NewReadOptions(opts ...ReadOption) ReadOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// ReadOptions configures an individual Read operation
|
||||
type ReadOptions struct {
|
||||
// Context holds external options
|
||||
Context context.Context
|
||||
// Database holds the database name
|
||||
Database string
|
||||
// Table holds table name
|
||||
Table string
|
||||
// Namespace holds namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// ReadOption sets values in ReadOptions
|
||||
type ReadOption func(r *ReadOptions)
|
||||
|
||||
// ReadFrom the database and table
|
||||
func ReadFrom(database, table string) ReadOption {
|
||||
return func(r *ReadOptions) {
|
||||
r.Database = database
|
||||
r.Table = table
|
||||
// ReadContext pass context.Context to ReadOptions
|
||||
func ReadContext(ctx context.Context) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// ReadNamespace pass namespace to ReadOptions
|
||||
func ReadNamespace(ns string) ReadOption {
|
||||
return func(o *ReadOptions) {
|
||||
o.Namespace = ns
|
||||
}
|
||||
}
|
||||
|
||||
// WriteOptions configures an individual Write operation
|
||||
type WriteOptions struct {
|
||||
// Context holds external options
|
||||
Context context.Context
|
||||
// Metadata contains additional metadata
|
||||
Metadata metadata.Metadata
|
||||
// Namespace holds namespace
|
||||
Namespace string
|
||||
// TTL specifies key TTL
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
// NewWriteOptions fills WriteOptions struct with opts slice
|
||||
func NewWriteOptions(opts ...WriteOption) WriteOptions {
|
||||
options := WriteOptions{}
|
||||
@@ -168,47 +174,45 @@ func NewWriteOptions(opts ...WriteOption) WriteOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// WriteOptions configures an individual Write operation
|
||||
type WriteOptions struct {
|
||||
// Context holds external options
|
||||
Context context.Context
|
||||
// Metadata contains additional metadata
|
||||
Metadata metadata.Metadata
|
||||
// Database holds database name
|
||||
Database string
|
||||
// Table holds table name
|
||||
Table string
|
||||
// Namespace holds namespace
|
||||
Namespace string
|
||||
// TTL specifies key TTL
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
// WriteOption sets values in WriteOptions
|
||||
type WriteOption func(w *WriteOptions)
|
||||
|
||||
// WriteTo the database and table
|
||||
func WriteTo(database, table string) WriteOption {
|
||||
return func(w *WriteOptions) {
|
||||
w.Database = database
|
||||
w.Table = table
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTTL is the time the record expires
|
||||
func WriteTTL(d time.Duration) WriteOption {
|
||||
return func(w *WriteOptions) {
|
||||
w.TTL = d
|
||||
// WriteContext pass context.Context to wirte options
|
||||
func WriteContext(ctx context.Context) WriteOption {
|
||||
return func(o *WriteOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// WriteMetadata add metadata.Metadata
|
||||
func WriteMetadata(md metadata.Metadata) WriteOption {
|
||||
return func(w *WriteOptions) {
|
||||
w.Metadata = metadata.Copy(md)
|
||||
return func(o *WriteOptions) {
|
||||
o.Metadata = metadata.Copy(md)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTTL is the time the record expires
|
||||
func WriteTTL(d time.Duration) WriteOption {
|
||||
return func(o *WriteOptions) {
|
||||
o.TTL = d
|
||||
}
|
||||
}
|
||||
|
||||
// WriteNamespace pass namespace to write options
|
||||
func WriteNamespace(ns string) WriteOption {
|
||||
return func(o *WriteOptions) {
|
||||
o.Namespace = ns
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteOptions configures an individual Delete operation
|
||||
type DeleteOptions struct {
|
||||
// Context holds external options
|
||||
Context context.Context
|
||||
// Namespace holds namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// NewDeleteOptions fills DeleteOptions struct with opts slice
|
||||
func NewDeleteOptions(opts ...DeleteOption) DeleteOptions {
|
||||
options := DeleteOptions{}
|
||||
@@ -218,29 +222,33 @@ func NewDeleteOptions(opts ...DeleteOption) DeleteOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// DeleteOptions configures an individual Delete operation
|
||||
type DeleteOptions struct {
|
||||
// Context holds external options
|
||||
Context context.Context
|
||||
// Database holds database name
|
||||
Database string
|
||||
// Table holds table name
|
||||
Table string
|
||||
// Namespace holds namespace
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// DeleteOption sets values in DeleteOptions
|
||||
type DeleteOption func(d *DeleteOptions)
|
||||
|
||||
// DeleteFrom the database and table
|
||||
func DeleteFrom(database, table string) DeleteOption {
|
||||
return func(d *DeleteOptions) {
|
||||
d.Database = database
|
||||
d.Table = table
|
||||
// DeleteContext pass context.Context to delete options
|
||||
func DeleteContext(ctx context.Context) DeleteOption {
|
||||
return func(o *DeleteOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteNamespace pass namespace to delete options
|
||||
func DeleteNamespace(ns string) DeleteOption {
|
||||
return func(o *DeleteOptions) {
|
||||
o.Namespace = ns
|
||||
}
|
||||
}
|
||||
|
||||
// ListOptions configures an individual List operation
|
||||
type ListOptions struct {
|
||||
Context context.Context
|
||||
Prefix string
|
||||
Suffix string
|
||||
Namespace string
|
||||
Limit uint
|
||||
Offset uint
|
||||
}
|
||||
|
||||
// NewListOptions fills ListOptions struct with opts slice
|
||||
func NewListOptions(opts ...ListOption) ListOptions {
|
||||
options := ListOptions{}
|
||||
@@ -250,59 +258,50 @@ func NewListOptions(opts ...ListOption) ListOptions {
|
||||
return options
|
||||
}
|
||||
|
||||
// ListOptions configures an individual List operation
|
||||
type ListOptions struct {
|
||||
Context context.Context
|
||||
Database string
|
||||
Prefix string
|
||||
Suffix string
|
||||
Namespace string
|
||||
Table string
|
||||
Limit uint
|
||||
Offset uint
|
||||
}
|
||||
|
||||
// ListOption sets values in ListOptions
|
||||
type ListOption func(l *ListOptions)
|
||||
|
||||
// ListFrom the database and table
|
||||
func ListFrom(database, table string) ListOption {
|
||||
return func(l *ListOptions) {
|
||||
l.Database = database
|
||||
l.Table = table
|
||||
// ListContext pass context.Context to list options
|
||||
func ListContext(ctx context.Context) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// ListPrefix returns all keys that are prefixed with key
|
||||
func ListPrefix(p string) ListOption {
|
||||
return func(l *ListOptions) {
|
||||
l.Prefix = p
|
||||
func ListPrefix(s string) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
o.Prefix = s
|
||||
}
|
||||
}
|
||||
|
||||
// ListSuffix returns all keys that end with key
|
||||
func ListSuffix(s string) ListOption {
|
||||
return func(l *ListOptions) {
|
||||
l.Suffix = s
|
||||
return func(o *ListOptions) {
|
||||
o.Suffix = s
|
||||
}
|
||||
}
|
||||
|
||||
// ListLimit limits the number of returned keys to l
|
||||
func ListLimit(l uint) ListOption {
|
||||
return func(lo *ListOptions) {
|
||||
lo.Limit = l
|
||||
// ListLimit limits the number of returned keys
|
||||
func ListLimit(n uint) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
o.Limit = n
|
||||
}
|
||||
}
|
||||
|
||||
// ListOffset starts returning responses from o. Use in conjunction with Limit for pagination.
|
||||
func ListOffset(o uint) ListOption {
|
||||
return func(l *ListOptions) {
|
||||
l.Offset = o
|
||||
// ListOffset use with Limit for pagination
|
||||
func ListOffset(n uint) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
o.Offset = n
|
||||
}
|
||||
}
|
||||
|
||||
// ExistsOption specifies Exists call options
|
||||
type ExistsOption func(*ExistsOptions)
|
||||
// ListNamespace pass namespace to list options
|
||||
func ListNamespace(ns string) ListOption {
|
||||
return func(o *ListOptions) {
|
||||
o.Namespace = ns
|
||||
}
|
||||
}
|
||||
|
||||
// ExistsOptions holds options for Exists method
|
||||
type ExistsOptions struct {
|
||||
@@ -312,6 +311,9 @@ type ExistsOptions struct {
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// ExistsOption specifies Exists call options
|
||||
type ExistsOption func(*ExistsOptions)
|
||||
|
||||
// NewExistsOptions helper for Exists method
|
||||
func NewExistsOptions(opts ...ExistsOption) ExistsOptions {
|
||||
options := ExistsOptions{
|
||||
@@ -322,3 +324,24 @@ func NewExistsOptions(opts ...ExistsOption) ExistsOptions {
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// ExistsContext pass context.Context to exist options
|
||||
func ExistsContext(ctx context.Context) ExistsOption {
|
||||
return func(o *ExistsOptions) {
|
||||
o.Context = ctx
|
||||
}
|
||||
}
|
||||
|
||||
// ExistsNamespace pass namespace to exist options
|
||||
func ExistsNamespace(ns string) ExistsOption {
|
||||
return func(o *ExistsOptions) {
|
||||
o.Namespace = ns
|
||||
}
|
||||
}
|
||||
|
||||
// WrapStore adds a store Wrapper to a list of options passed into the store
|
||||
//func WrapStore(w Wrapper) Option {
|
||||
// return func(o *Options) {
|
||||
// o.Wrappers = append(o.Wrappers, w)
|
||||
// }
|
||||
//}
|
||||
|
@@ -5,8 +5,6 @@ package store
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -42,12 +40,3 @@ type Store interface {
|
||||
// String returns the name of the implementation.
|
||||
String() string
|
||||
}
|
||||
|
||||
// Value is an item stored or retrieved from a Store
|
||||
// may be used in store implementations to provide metadata
|
||||
type Value struct {
|
||||
// Data holds underline struct
|
||||
Data interface{} `json:"data"`
|
||||
// Metadata associated with data for indexing
|
||||
Metadata metadata.Metadata `json:"metadata"`
|
||||
}
|
||||
|
84
store/wrapper.go
Normal file
84
store/wrapper.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// LogfFunc function used for Logf method
|
||||
//type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
|
||||
|
||||
//type Wrapper interface {
|
||||
// Logf logs message with needed level
|
||||
//Logf(LogfFunc) LogfFunc
|
||||
//}
|
||||
|
||||
type NamespaceStore struct {
|
||||
s Store
|
||||
ns string
|
||||
}
|
||||
|
||||
var (
|
||||
_ Store = &NamespaceStore{}
|
||||
)
|
||||
|
||||
func NewNamespaceStore(s Store, ns string) Store {
|
||||
return &NamespaceStore{s: s, ns: ns}
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Init(opts ...Option) error {
|
||||
return w.s.Init(opts...)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Connect(ctx context.Context) error {
|
||||
return w.s.Connect(ctx)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Disconnect(ctx context.Context) error {
|
||||
return w.s.Disconnect(ctx)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
|
||||
return w.s.Read(ctx, key, val, append(opts, ReadNamespace(w.ns))...)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
|
||||
return w.s.Write(ctx, key, val, append(opts, WriteNamespace(w.ns))...)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
|
||||
return w.s.Delete(ctx, key, append(opts, DeleteNamespace(w.ns))...)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
|
||||
return w.s.Exists(ctx, key, append(opts, ExistsNamespace(w.ns))...)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
|
||||
return w.s.List(ctx, append(opts, ListNamespace(w.ns))...)
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Options() Options {
|
||||
return w.s.Options()
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) Name() string {
|
||||
return w.s.Name()
|
||||
}
|
||||
|
||||
func (w *NamespaceStore) String() string {
|
||||
return w.s.String()
|
||||
}
|
||||
|
||||
//type NamespaceWrapper struct{}
|
||||
|
||||
//func NewNamespaceWrapper() Wrapper {
|
||||
// return &NamespaceWrapper{}
|
||||
//}
|
||||
|
||||
/*
|
||||
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
|
||||
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
|
||||
fn(ctx, level, msg, getArgs(args)...)
|
||||
}
|
||||
}
|
||||
*/
|
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
type memorySync struct {
|
||||
options Options
|
||||
locks map[string]*memoryLock
|
||||
options Options
|
||||
mtx gosync.RWMutex
|
||||
}
|
||||
|
||||
|
@@ -5,10 +5,8 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrLockTimeout error
|
||||
ErrLockTimeout = errors.New("lock timeout")
|
||||
)
|
||||
// ErrLockTimeout error
|
||||
var ErrLockTimeout = errors.New("lock timeout")
|
||||
|
||||
// Sync is an interface for distributed synchronization
|
||||
type Sync interface {
|
||||
|
@@ -38,7 +38,6 @@ type noopSpan struct {
|
||||
}
|
||||
|
||||
func (s *noopSpan) Finish(opts ...SpanOption) {
|
||||
|
||||
}
|
||||
|
||||
func (s *noopSpan) Context() context.Context {
|
||||
@@ -50,7 +49,6 @@ func (s *noopSpan) Tracer() Tracer {
|
||||
}
|
||||
|
||||
func (s *noopSpan) AddEvent(name string, opts ...EventOption) {
|
||||
|
||||
}
|
||||
|
||||
func (s *noopSpan) SetName(name string) {
|
||||
@@ -58,7 +56,6 @@ func (s *noopSpan) SetName(name string) {
|
||||
}
|
||||
|
||||
func (s *noopSpan) SetLabels(labels ...Label) {
|
||||
|
||||
}
|
||||
|
||||
// NewTracer returns new memory tracer
|
||||
|
@@ -2,13 +2,11 @@ package tracer
|
||||
|
||||
import "github.com/unistack-org/micro/v3/logger"
|
||||
|
||||
type SpanOptions struct {
|
||||
}
|
||||
type SpanOptions struct{}
|
||||
|
||||
type SpanOption func(o *SpanOptions)
|
||||
|
||||
type EventOptions struct {
|
||||
}
|
||||
type EventOptions struct{}
|
||||
|
||||
type EventOption func(o *EventOptions)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user